[pod,px] Complete tracebacks when errors occurs within for loops. [pod] xhtml2odt: removal of unnecessary spaces. [pod] xhtml2odt: bugfix in the automatic computation of table column widths.

This commit is contained in:
Gaetan Delannay 2015-03-13 17:38:05 +01:00
parent 8168306b57
commit 5beb60f145
17 changed files with 1916 additions and 1871 deletions

View file

@ -59,10 +59,10 @@ class Hack:
"_base_<initial_method_name>_". In the patched method, one may use
Hack.base to call the base method. If p_method is static, you must
specify its class in p_klass.'''
# Get the class on which the surgery will take place.
# Get the class on which the surgery will take place
isStatic = klass
klass = klass or method.im_class
# On this class, store m_method under its "base" name.
# On this class, store m_method under its "base" name
name = isStatic and method.func_name or method.im_func.__name__
baseName = '_base_%s_' % name
setattr(klass, baseName, method)

View file

@ -254,10 +254,10 @@ class Calendar(Field):
var="monthsInfos=field.getTimelineMonths(grid, zobj)">
<colgroup> <!-- Column specifiers -->
<!-- Names of calendars -->
<col></col>
<col/>
<col for="date in grid"
style=":field.getColumnStyle(zobj, date, render, today)"></col>
<col></col>
style=":field.getColumnStyle(zobj, date, render, today)"/>
<col/>
</colgroup>
<tbody>
<!-- Header rows (months and days) -->

View file

@ -1105,7 +1105,7 @@ class ToolMixin(BaseMixin):
return '%s%s%s' % (name, timestamp, randomNumber)
def manageError(self, error):
'''Manages an error.'''
'''Manages an error'''
tb = sys.exc_info()
if error.type.__name__ == 'Unauthorized':
siteUrl = self.getSiteUrl()

View file

@ -84,6 +84,6 @@ class PodError(Exception):
buffer.write('</%s>' % subTag.elem)
buffer.write('</%s>' % withinElement.OD.elem)
# XXX To remove, present for backward compatibility only.
# XXX To remove, present for backward compatibility only
convertToXhtml = escapeXhtml
# ------------------------------------------------------------------------------

View file

@ -56,7 +56,7 @@ class BufferAction:
class, message and line number.'''
return '%s: %s' % (e.__class__.__name__, str(e))
def manageError(self, result, context, errorMessage, dumpTb=True):
def manageError(self, result, context, errorMessage):
'''Manage the encountered error: dump it into the buffer or raise an
exception.'''
if self.buffer.env.raiseOnError:
@ -69,8 +69,8 @@ class BufferAction:
if col == None: col = ''
else: col = ', column %d' % col
errorMessage += ' (line %s%s)' % (locator.getLineNumber(), col)
# Integrate the traceback if requested
if dumpTb: errorMessage += '\n' + Traceback.get(5)
# Integrate the traceback (at least, it last lines)
errorMessage += '\n' + Traceback.get(4)
raise Exception(errorMessage)
# Create a temporary buffer to dump the error. If I reuse this buffer to
# dump the error (what I did before), and we are, at some depth, in a
@ -78,8 +78,7 @@ class BufferAction:
# content to repeat anymore. It means that this error will also show up
# for every subsequent iteration.
tempBuffer = self.buffer.clone()
PodError.dump(tempBuffer, errorMessage, withinElement=self.elem,
dumpTb=dumpTb)
PodError.dump(tempBuffer, errorMessage, withinElement=self.elem)
tempBuffer.evaluate(result, context)
def _evalExpr(self, expr, context):
@ -137,7 +136,7 @@ class BufferAction:
feRes = eval(self.fromExpr, context)
except Exception, e:
msg = FROM_EVAL_ERROR% (self.fromExpr, self.getExceptionLine(e))
self.manageError(result, context, msg, dumpTb=False)
self.manageError(result, context, msg)
error = True
if not error:
result.write(feRes)

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,19 +1,17 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
# Appy is a framework for building applications in the Python language.
# Copyright (C) 2007-2011 Gaetan Delannay
#
# Copyright (c) 2007-2015 Gaetan Delannay
# Distributed under the GNU General Public License.
#
# Thanks to Fabio Marcuzzi and Gauthier Bastien for management of strike and
# underline.
# Contributors: Gauthier Bastien, Fabio Marcuzzi, IMIO.
# ------------------------------------------------------------------------------
import xml.sax, time, random
from appy.shared.xml_parser import XmlEnvironment, XmlParser, escapeXml
from appy.pod import *
from appy.pod.odf_parser import OdfEnvironment
from appy.pod.styles_manager import Style
from appy.pod import *
from appy.shared.xml_parser import XmlEnvironment, XmlParser, escapeXml
from appy.shared.utils import WhitespaceCruncher
# To which ODT tags do HTML tags correspond ?
HTML_2_ODT = {'h1':'h', 'h2':'h', 'h3':'h', 'h4':'h', 'h5':'h', 'h6':'h',
@ -24,14 +22,15 @@ DEFAULT_ODT_STYLES = {'b': 'podBold', 'strong':'podBold', 'i': 'podItalic',
'u': 'podUnderline', 'strike': 'podStrike', 's': 'podStrike',
'em': 'podItalic', 'sup': 'podSup', 'sub':'podSub', 'td': 'podCell',
'th': 'podHeaderCell'}
INNER_TAGS = ('b', 'strong', 'i', 'u', 'em', 'sup', 'sub', 'span')
INNER_TAGS = ('b', 'i', 'strong', 'strike', 's', 'u', 'em', 'sub', 'sup', 'br',
'span', 'acronym', 'a')
TABLE_CELL_TAGS = ('td', 'th')
OUTER_TAGS = TABLE_CELL_TAGS + ('li',)
# The following elements can't be rendered inside paragraphs
NOT_INSIDE_P = XHTML_HEADINGS + XHTML_LISTS + ('table',)
NOT_INSIDE_P_OR_P = NOT_INSIDE_P + ('p', 'div')
NOT_INSIDE_LIST = ('table',)
IGNORABLE_TAGS = ('meta', 'title', 'style')
IGNORABLE_TAGS = ('meta', 'title', 'style', 'script')
# ------------------------------------------------------------------------------
class HtmlElement:
@ -216,7 +215,7 @@ class HtmlTable:
elems = str(time.time()).split('.')
self.name= 'AppyTable%s%s%d' % (elems[0],elems[1],random.randint(1,100))
self.styleNs = env.ns[OdfEnvironment.NS_STYLE]
self.res = u'' # The sub-buffer.
self.res = u'' # The sub-buffer
self.tempRes = u'' # The temporary sub-buffer, into which we will
# dump all table sub-elements, until we encounter the end of the first
# row. Then, we will know how much columns are defined in the table;
@ -299,6 +298,7 @@ class XhtmlEnvironment(XmlEnvironment):
self.currentElements = [] # Stack of currently walked elements
self.currentLists = [] # Stack of currently walked lists (ul or ol)
self.currentTables = [] # Stack of currently walked tables
self.lastElem = None # Last walked element before the current one
self.textNs = self.ns[OdfEnvironment.NS_TEXT]
self.linkNs = self.ns[OdfEnvironment.NS_XLINK]
self.tableNs = self.ns[OdfEnvironment.NS_TABLE]
@ -326,25 +326,31 @@ class XhtmlEnvironment(XmlEnvironment):
res = True
return res
def dumpCurrentContent(self):
def dumpCurrentContent(self, place, elem):
'''Dumps content that was temporarily stored in self.currentContent
into the result.'''
contentSize = 0
if self.currentContent.strip(' \n\r\t'): # NBSP must not be in this list
# Remove the trailing whitespace if needed
if place == 'start':
if self.currentContent.endswith(' ') and \
((elem not in INNER_TAGS) or (elem == 'br')):
self.currentContent = self.currentContent[:-1]
# Remove the leading whitespace if needed
if self.currentContent.startswith(' '):
if not self.lastElem or \
((self.lastElem not in INNER_TAGS) or (self.lastElem == 'br')):
self.currentContent = self.currentContent[1:]
if self.currentContent:
# Manage missing elements
currentElem = self.getCurrentElement()
if self.anElementIsMissing(currentElem, None):
currentElem.addInnerParagraph(self)
# Dump and reinitialize the current content
content = self.currentContent.strip('\n\t')
# We remove leading and trailing carriage returns, but not
# whitespace because whitespace may be part of the text to dump.
contentSize = len(content)
# We do not escape carriage returns, because, in XHTML, carriage
# returns are just ignorable white space.
self.dumpString(escapeXml(content))
contentSize = len(self.currentContent)
self.dumpString(escapeXml(self.currentContent))
self.currentContent = u''
# If we are within a table cell, update the total size of cell content.
# If we are within a table cell, update the total size of cell content
if not contentSize: return
if self.currentTables and self.currentTables[-1].inCell:
for table in self.currentTables:
table.cellContentSize += contentSize
@ -419,8 +425,8 @@ class XhtmlEnvironment(XmlEnvironment):
return conflictElems
def onElementStart(self, elem, attrs):
self.dumpCurrentContent('start', elem)
previousElem = self.getCurrentElement()
self.dumpCurrentContent()
currentElem = HtmlElement(elem, attrs)
# Manage conflictual elements
conflictElems = currentElem.getConflictualElements(self)
@ -472,7 +478,7 @@ class XhtmlEnvironment(XmlEnvironment):
def onElementEnd(self, elem):
res = None
self.dumpCurrentContent()
self.dumpCurrentContent('end', elem)
currentElem = self.currentElements.pop()
if elem in XHTML_LISTS:
self.currentLists.pop()
@ -482,7 +488,7 @@ class XhtmlEnvironment(XmlEnvironment):
table.computeColumnStyles(self.parser.caller.renderer)
# Dumps the content of the last parsed table into the parent buffer
self.dumpString(table.res)
# Remove cell-paragraph from local styles mapping if it was added.
# Remove cell-paragraph from local styles mapping if it was added
map = self.parser.caller.localStylesMapping
if not self.currentTables and ('p' in map):
mapValue = map['p']
@ -519,6 +525,7 @@ class XhtmlEnvironment(XmlEnvironment):
self.closeConflictualElements(currentElem.tagsToClose)
if currentElem.tagsToReopen:
res = currentElem.tagsToReopen
self.lastElem = currentElem.elem
return currentElem, res
# ------------------------------------------------------------------------------
@ -614,8 +621,8 @@ class XhtmlParser(XmlParser):
def characters(self, content):
e = XmlParser.characters(self, content)
if not e.ignore:
e.currentContent += content
if e.ignore: return
e.currentContent += WhitespaceCruncher.crunch(content, e.currentContent)
# -------------------------------------------------------------------------------
class Xhtml2OdtConverter:

View file

@ -653,12 +653,12 @@ class FileWrapper:
return filePath
def copy(self):
'''Returns a copy of this file.'''
'''Returns a copy of this file'''
return FileWrapper(self._zopeFile._getCopy(self._zopeFile))
# ------------------------------------------------------------------------------
def getMimeType(fileName):
'''Tries to guess mime type from p_fileName.'''
'''Tries to guess mime type from p_fileName'''
res, encoding = mimetypes.guess_type(fileName)
if not res:
if fileName.endswith('.po'):
@ -667,4 +667,37 @@ def getMimeType(fileName):
if not res: return ''
if not encoding: return res
return '%s;;charset=%s' % (res, encoding)
# ------------------------------------------------------------------------------
class WhitespaceCruncher:
'''Takes care of removing unnecessary whitespace in several contexts'''
whitechars = u' \r\t\n' # Chars considered as whitespace
allWhitechars = whitechars + u' ' # nbsp
@staticmethod
def crunch(s, previous=None):
'''Return a version of p_s (expected to be a unicode string) where all
"whitechars" are:
* converted to real whitespace;
* reduced in such a way that there cannot be 2 consecutive
whitespace chars.
If p_previous is given, those rules must also apply globally to
previous+s.'''
res = ''
# Initialise the previous char
if previous:
previousChar = previous[-1]
else:
previousChar = u''
for char in s:
if char in WhitespaceCruncher.whitechars:
# Include the current whitechar in the result if the previous
# char is not a whitespace or nbsp.
if not previousChar or \
(previousChar not in WhitespaceCruncher.allWhitechars):
res += u' '
else: res += char
previousChar = char
# "res" can be a single whitespace. It is up to the caller method to
# identify when this single whitespace must be kept or crunched.
return res
# ------------------------------------------------------------------------------