[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 "_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 Hack.base to call the base method. If p_method is static, you must
specify its class in p_klass.''' 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 isStatic = klass
klass = klass or method.im_class 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__ name = isStatic and method.func_name or method.im_func.__name__
baseName = '_base_%s_' % name baseName = '_base_%s_' % name
setattr(klass, baseName, method) setattr(klass, baseName, method)

View file

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

View file

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

View file

@ -84,6 +84,6 @@ class PodError(Exception):
buffer.write('</%s>' % subTag.elem) buffer.write('</%s>' % subTag.elem)
buffer.write('</%s>' % withinElement.OD.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 convertToXhtml = escapeXhtml
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -56,7 +56,7 @@ class BufferAction:
class, message and line number.''' class, message and line number.'''
return '%s: %s' % (e.__class__.__name__, str(e)) 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 '''Manage the encountered error: dump it into the buffer or raise an
exception.''' exception.'''
if self.buffer.env.raiseOnError: if self.buffer.env.raiseOnError:
@ -69,8 +69,8 @@ class BufferAction:
if col == None: col = '' if col == None: col = ''
else: col = ', column %d' % col else: col = ', column %d' % col
errorMessage += ' (line %s%s)' % (locator.getLineNumber(), col) errorMessage += ' (line %s%s)' % (locator.getLineNumber(), col)
# Integrate the traceback if requested # Integrate the traceback (at least, it last lines)
if dumpTb: errorMessage += '\n' + Traceback.get(5) errorMessage += '\n' + Traceback.get(4)
raise Exception(errorMessage) raise Exception(errorMessage)
# Create a temporary buffer to dump the error. If I reuse this buffer to # 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 # 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 # content to repeat anymore. It means that this error will also show up
# for every subsequent iteration. # for every subsequent iteration.
tempBuffer = self.buffer.clone() tempBuffer = self.buffer.clone()
PodError.dump(tempBuffer, errorMessage, withinElement=self.elem, PodError.dump(tempBuffer, errorMessage, withinElement=self.elem)
dumpTb=dumpTb)
tempBuffer.evaluate(result, context) tempBuffer.evaluate(result, context)
def _evalExpr(self, expr, context): def _evalExpr(self, expr, context):
@ -137,7 +136,7 @@ class BufferAction:
feRes = eval(self.fromExpr, context) feRes = eval(self.fromExpr, context)
except Exception, e: except Exception, e:
msg = FROM_EVAL_ERROR% (self.fromExpr, self.getExceptionLine(e)) msg = FROM_EVAL_ERROR% (self.fromExpr, self.getExceptionLine(e))
self.manageError(result, context, msg, dumpTb=False) self.manageError(result, context, msg)
error = True error = True
if not error: if not error:
result.write(feRes) 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 -*- # -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Appy is a framework for building applications in the Python language. # 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. # Distributed under the GNU General Public License.
# # Contributors: Gauthier Bastien, Fabio Marcuzzi, IMIO.
# Thanks to Fabio Marcuzzi and Gauthier Bastien for management of strike and
# underline.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import xml.sax, time, random 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.odf_parser import OdfEnvironment
from appy.pod.styles_manager import Style 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 ? # To which ODT tags do HTML tags correspond ?
HTML_2_ODT = {'h1':'h', 'h2':'h', 'h3':'h', 'h4':'h', 'h5':'h', 'h6':'h', 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', 'u': 'podUnderline', 'strike': 'podStrike', 's': 'podStrike',
'em': 'podItalic', 'sup': 'podSup', 'sub':'podSub', 'td': 'podCell', 'em': 'podItalic', 'sup': 'podSup', 'sub':'podSub', 'td': 'podCell',
'th': 'podHeaderCell'} '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') TABLE_CELL_TAGS = ('td', 'th')
OUTER_TAGS = TABLE_CELL_TAGS + ('li',) OUTER_TAGS = TABLE_CELL_TAGS + ('li',)
# The following elements can't be rendered inside paragraphs # The following elements can't be rendered inside paragraphs
NOT_INSIDE_P = XHTML_HEADINGS + XHTML_LISTS + ('table',) NOT_INSIDE_P = XHTML_HEADINGS + XHTML_LISTS + ('table',)
NOT_INSIDE_P_OR_P = NOT_INSIDE_P + ('p', 'div') NOT_INSIDE_P_OR_P = NOT_INSIDE_P + ('p', 'div')
NOT_INSIDE_LIST = ('table',) NOT_INSIDE_LIST = ('table',)
IGNORABLE_TAGS = ('meta', 'title', 'style') IGNORABLE_TAGS = ('meta', 'title', 'style', 'script')
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class HtmlElement: class HtmlElement:
@ -216,7 +215,7 @@ class HtmlTable:
elems = str(time.time()).split('.') elems = str(time.time()).split('.')
self.name= 'AppyTable%s%s%d' % (elems[0],elems[1],random.randint(1,100)) self.name= 'AppyTable%s%s%d' % (elems[0],elems[1],random.randint(1,100))
self.styleNs = env.ns[OdfEnvironment.NS_STYLE] 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 self.tempRes = u'' # The temporary sub-buffer, into which we will
# dump all table sub-elements, until we encounter the end of the first # 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; # 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.currentElements = [] # Stack of currently walked elements
self.currentLists = [] # Stack of currently walked lists (ul or ol) self.currentLists = [] # Stack of currently walked lists (ul or ol)
self.currentTables = [] # Stack of currently walked tables 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.textNs = self.ns[OdfEnvironment.NS_TEXT]
self.linkNs = self.ns[OdfEnvironment.NS_XLINK] self.linkNs = self.ns[OdfEnvironment.NS_XLINK]
self.tableNs = self.ns[OdfEnvironment.NS_TABLE] self.tableNs = self.ns[OdfEnvironment.NS_TABLE]
@ -326,25 +326,31 @@ class XhtmlEnvironment(XmlEnvironment):
res = True res = True
return res return res
def dumpCurrentContent(self): def dumpCurrentContent(self, place, elem):
'''Dumps content that was temporarily stored in self.currentContent '''Dumps content that was temporarily stored in self.currentContent
into the result.''' into the result.'''
contentSize = 0 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 # Manage missing elements
currentElem = self.getCurrentElement() currentElem = self.getCurrentElement()
if self.anElementIsMissing(currentElem, None): if self.anElementIsMissing(currentElem, None):
currentElem.addInnerParagraph(self) currentElem.addInnerParagraph(self)
# Dump and reinitialize the current content # Dump and reinitialize the current content
content = self.currentContent.strip('\n\t') contentSize = len(self.currentContent)
# We remove leading and trailing carriage returns, but not self.dumpString(escapeXml(self.currentContent))
# 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))
self.currentContent = u'' 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: if self.currentTables and self.currentTables[-1].inCell:
for table in self.currentTables: for table in self.currentTables:
table.cellContentSize += contentSize table.cellContentSize += contentSize
@ -419,8 +425,8 @@ class XhtmlEnvironment(XmlEnvironment):
return conflictElems return conflictElems
def onElementStart(self, elem, attrs): def onElementStart(self, elem, attrs):
self.dumpCurrentContent('start', elem)
previousElem = self.getCurrentElement() previousElem = self.getCurrentElement()
self.dumpCurrentContent()
currentElem = HtmlElement(elem, attrs) currentElem = HtmlElement(elem, attrs)
# Manage conflictual elements # Manage conflictual elements
conflictElems = currentElem.getConflictualElements(self) conflictElems = currentElem.getConflictualElements(self)
@ -472,7 +478,7 @@ class XhtmlEnvironment(XmlEnvironment):
def onElementEnd(self, elem): def onElementEnd(self, elem):
res = None res = None
self.dumpCurrentContent() self.dumpCurrentContent('end', elem)
currentElem = self.currentElements.pop() currentElem = self.currentElements.pop()
if elem in XHTML_LISTS: if elem in XHTML_LISTS:
self.currentLists.pop() self.currentLists.pop()
@ -482,7 +488,7 @@ class XhtmlEnvironment(XmlEnvironment):
table.computeColumnStyles(self.parser.caller.renderer) table.computeColumnStyles(self.parser.caller.renderer)
# Dumps the content of the last parsed table into the parent buffer # Dumps the content of the last parsed table into the parent buffer
self.dumpString(table.res) 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 map = self.parser.caller.localStylesMapping
if not self.currentTables and ('p' in map): if not self.currentTables and ('p' in map):
mapValue = map['p'] mapValue = map['p']
@ -519,6 +525,7 @@ class XhtmlEnvironment(XmlEnvironment):
self.closeConflictualElements(currentElem.tagsToClose) self.closeConflictualElements(currentElem.tagsToClose)
if currentElem.tagsToReopen: if currentElem.tagsToReopen:
res = currentElem.tagsToReopen res = currentElem.tagsToReopen
self.lastElem = currentElem.elem
return currentElem, res return currentElem, res
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -614,8 +621,8 @@ class XhtmlParser(XmlParser):
def characters(self, content): def characters(self, content):
e = XmlParser.characters(self, content) e = XmlParser.characters(self, content)
if not e.ignore: if e.ignore: return
e.currentContent += content e.currentContent += WhitespaceCruncher.crunch(content, e.currentContent)
# ------------------------------------------------------------------------------- # -------------------------------------------------------------------------------
class Xhtml2OdtConverter: class Xhtml2OdtConverter:

View file

@ -653,12 +653,12 @@ class FileWrapper:
return filePath return filePath
def copy(self): def copy(self):
'''Returns a copy of this file.''' '''Returns a copy of this file'''
return FileWrapper(self._zopeFile._getCopy(self._zopeFile)) return FileWrapper(self._zopeFile._getCopy(self._zopeFile))
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
def getMimeType(fileName): 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) res, encoding = mimetypes.guess_type(fileName)
if not res: if not res:
if fileName.endswith('.po'): if fileName.endswith('.po'):
@ -667,4 +667,37 @@ def getMimeType(fileName):
if not res: return '' if not res: return ''
if not encoding: return res if not encoding: return res
return '%s;;charset=%s' % (res, encoding) 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
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------