[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:
parent
8168306b57
commit
5beb60f145
|
@ -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)
|
||||||
|
|
|
@ -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) -->
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -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)
|
||||||
|
|
3668
pod/test/Tests.rtf
3668
pod/test/Tests.rtf
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
Loading…
Reference in a new issue