appy.pod: improved html2odt conversion; appy.gen: added CSS tooltips and bugfix while querying several content type at once.

This commit is contained in:
Gaetan Delannay 2011-02-14 16:04:30 +01:00
parent 39d68f6490
commit 1bb4dbf20a
12 changed files with 712 additions and 645 deletions

View file

@ -1 +1 @@
0.6.2 0.6.3

View file

@ -911,6 +911,8 @@ class ToolMixin(BaseMixin):
def getResultPodFields(self, contentType): def getResultPodFields(self, contentType):
'''Finds, among fields defined on p_contentType, which ones are Pod '''Finds, among fields defined on p_contentType, which ones are Pod
fields that need to be shown on a page displaying query results.''' fields that need to be shown on a page displaying query results.'''
# Skip this if we are searching multiple content types.
if ',' in contentType: return ()
return [f.__dict__ for f in self.getAllAppyTypes(contentType) \ return [f.__dict__ for f in self.getAllAppyTypes(contentType) \
if (f.type == 'Pod') and (f.show == 'result')] if (f.type == 'Pod') and (f.show == 'result')]
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -109,6 +109,22 @@ label { font-weight: bold; font-style: italic; line-height: 1.4em;}
/* With fields layout in columns, standard error frame is too large */ /* With fields layout in columns, standard error frame is too large */
.odd { background-color: white; } .odd { background-color: white; }
/* Tooltip */
a.tooltip span {
display:none;
padding:2px 3px;
margin-top: 25px;
}
a.rtip span { margin-left:3px; }
a.ltip span { margin-left:-150px }
a.tooltip:hover span {
display: inline;
position: absolute;
border: 1px solid grey;
background-color: white;
color: #dd;
}
/* Table styles */ /* Table styles */
.no-style-table { .no-style-table {
border: 0 !important; border: 0 !important;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
xhtmlInput = '<div class="document">\n<p>Hallo?</p>\n</div>\n'

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -18,7 +18,7 @@ from appy.pod import *
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',
'p':'p', 'b':'span', 'i':'span', 'strong':'span', 'strike':'span', 'p':'p', 'b':'span', 'i':'span', 'strong':'span', 'strike':'span',
'u':'span', 'em': 'span', 'sub': 'span', 'sup': 'span', 'u':'span', 'em': 'span', 'sub': 'span', 'sup': 'span',
'br': 'line-break', 'div': 'span'} 'br': 'line-break', 'div': 'span', 'podxhtml':'p'}
DEFAULT_ODT_STYLES = {'b': 'podBold', 'strong':'podBold', 'i': 'podItalic', DEFAULT_ODT_STYLES = {'b': 'podBold', 'strong':'podBold', 'i': 'podItalic',
'u': 'podUnderline', 'strike': 'podStrike', 'u': 'podUnderline', 'strike': 'podStrike',
'em': 'podItalic', 'sup': 'podSup', 'sub':'podSub', 'em': 'podItalic', 'sup': 'podSup', 'sub':'podSub',
@ -28,6 +28,7 @@ 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',)
NOT_INSIDE_LIST = ('table',) NOT_INSIDE_LIST = ('table',)
IGNORABLE_TAGS = ('meta', 'title', 'style') IGNORABLE_TAGS = ('meta', 'title', 'style')
@ -36,8 +37,8 @@ class HtmlElement:
'''Every time an HTML element is encountered during the SAX parsing, '''Every time an HTML element is encountered during the SAX parsing,
an instance of this class is pushed on the stack of currently parsed an instance of this class is pushed on the stack of currently parsed
elements.''' elements.'''
elemTypes = {'p':'para', 'li':'para','ol':'list','ul':'list'} elemTypes = {'p':'para', 'li':'para','ol':'list','ul':'list',
odfElems = {'p':'p', 'li':'list-item', 'ol': 'list', 'ul': 'list'} 'podxhtml': 'para'}
def __init__(self, elem, attrs): def __init__(self, elem, attrs):
self.elem = elem self.elem = elem
# Keep "class" attribute (useful for finding the corresponding ODT # Keep "class" attribute (useful for finding the corresponding ODT
@ -58,10 +59,40 @@ class HtmlElement:
self.elemType = self.elem self.elemType = self.elem
if self.elemTypes.has_key(self.elem): if self.elemTypes.has_key(self.elem):
self.elemType = self.elemTypes[self.elem] self.elemType = self.elemTypes[self.elem]
# If a conflict occurs on this element, we will note it.
self.isConflictual = False
def setConflictual(self):
'''Note p_self as conflictual.'''
self.isConflictual = True
return self
def getOdfTag(self, env): def getOdfTag(self, env):
'''Gets the ODF tag that corresponds to me.''' '''Gets the raw ODF tag that corresponds to me.'''
return '%s:%s' % (env.textNs, self.odfElems[self.elem]) res = ''
if HTML_2_ODT.has_key(self.elem):
res += '%s:%s' % (env.textNs, HTML_2_ODT[self.elem])
elif self.elem == 'a':
res += '%s:a' % env.textNs
elif self.elem in XHTML_LISTS:
res += '%s:list' % env.textNs
elif self.elem == 'li':
res += '%s:list-item' % env.textNs
elif self.elem == 'table':
res += '%s:table' % env.tableNs
elif self.elem == 'thead':
res += '%s:table-header-rows' % env.tableNs
elif self.elem == 'tr':
res += '%s:table-row' % env.tableNs
elif self.elem in TABLE_CELL_TAGS:
res += '%s:table-cell' % env.tableNs
return res
def getOdfTags(self, env):
'''Gets the start and end tags corresponding to p_self.'''
tag = self.getOdfTag(env)
if not tag: return (None, None)
return ('<%s>' % tag, '</%s>' % tag)
def getConflictualElements(self, env): def getConflictualElements(self, env):
'''self was just parsed. In some cases, this element can't be dumped '''self was just parsed. In some cases, this element can't be dumped
@ -72,15 +103,37 @@ class HtmlElement:
if env.currentElements: if env.currentElements:
parentElem = env.currentElements[-1] parentElem = env.currentElements[-1]
# Check elements that can't be found within a paragraph # Check elements that can't be found within a paragraph
if (parentElem.elemType=='para') and (self.elem in NOT_INSIDE_P): if (parentElem.elemType == 'para') and \
return (parentElem,) (self.elem in NOT_INSIDE_P_OR_P):
if (parentElem.elem in OUTER_TAGS) and parentElem.tagsToClose and \ # Oups, li->p wrongly considered as a conflict.
(parentElem.tagsToClose[-1].elem == 'p') and \ if (parentElem.elem == 'li') and (self.elem == 'p'): return ()
return (parentElem.setConflictual(),)
# Check inner paragraphs
if (parentElem.elem in INNER_TAGS) and (self.elemType == 'para'):
res = [parentElem.setConflictual()]
if len(env.currentElements) > 1:
i = 2
visitParents = True
while visitParents:
try:
nextParent = env.currentElements[-i]
res.insert(0, nextParent.setConflictual())
if nextParent.elemType == 'para':
visitParents = False
except IndexError:
visitParents = False
return res
if parentElem.tagsToClose and \
(parentElem.tagsToClose[-1].elemType == 'para') and \
(self.elem in NOT_INSIDE_P): (self.elem in NOT_INSIDE_P):
return (parentElem.tagsToClose[-1],) return (parentElem.tagsToClose[-1].setConflictual(),)
#if (parentElem.elem in OUTER_TAGS) and parentElem.tagsToClose and \
# (parentElem.tagsToClose[-1].elem == 'p') and \
# (self.elem in NOT_INSIDE_P):
# return (parentElem.tagsToClose[-1].setConflictual(),)
# Check elements that can't be found within a list # Check elements that can't be found within a list
if (parentElem.elemType=='list') and (self.elem in NOT_INSIDE_LIST): if (parentElem.elemType=='list') and (self.elem in NOT_INSIDE_LIST):
return (parentElem,) return (parentElem.setConflictual(),)
return () return ()
def addInnerParagraph(self, env): def addInnerParagraph(self, env):
@ -102,7 +155,7 @@ class HtmlElement:
self.tagsToClose.append(HtmlElement('p',{})) self.tagsToClose.append(HtmlElement('p',{}))
def dump(self, start, env): def dump(self, start, env):
'''Dumps the start or stop (depending onp_start) tag of this HTML '''Dumps the start or stop (depending on p_start) tag of this HTML
element. We must take care of potential innerTags.''' element. We must take care of potential innerTags.'''
# Compute the tag in itself # Compute the tag in itself
tag = '' tag = ''
@ -204,27 +257,6 @@ class XhtmlEnvironment(XmlEnvironment):
res = True res = True
return res return res
def dumpRootParagraph(self, currentElem=None):
'''Dumps content that is outside any tag (directly within the root
"podXhtml" tag.'''
mustStartParagraph = True
mustEndParagraph = True
if self.creatingRootParagraph:
mustStartParagraph = False
if currentElem and (currentElem not in NOT_INSIDE_P+('p',)):
mustEndParagraph = False
if mustStartParagraph and mustEndParagraph and \
not self.currentContent.strip():
# In this case I would dump an empty paragraph. So I do nothing.
return
if mustStartParagraph:
self.dumpStyledElement('p', {})
self.creatingRootParagraph = True
self.dumpCurrentContent()
if mustEndParagraph:
self.dumpString('</%s:p>' % self.textNs)
self.creatingRootParagraph = False
def dumpCurrentContent(self): def dumpCurrentContent(self):
'''Dumps content that was temporarily stored in self.currentContent '''Dumps content that was temporarily stored in self.currentContent
into the result.''' into the result.'''
@ -243,10 +275,10 @@ class XhtmlEnvironment(XmlEnvironment):
self.dumpString(c) self.dumpString(c)
self.currentContent = u'' self.currentContent = u''
def dumpStyledElement(self, elem, attrs): def dumpStyledElement(self, elem, odfTag, attrs):
'''Dumps an element that potentially has associated style '''Dumps an element that potentially has associated style
information.''' information.'''
self.dumpString('<%s:%s' % (self.textNs, HTML_2_ODT[elem])) self.dumpString('<' + odfTag)
odtStyle = self.parser.caller.findStyle(elem, attrs) odtStyle = self.parser.caller.findStyle(elem, attrs)
styleName = None styleName = None
if odtStyle: if odtStyle:
@ -260,15 +292,27 @@ class XhtmlEnvironment(XmlEnvironment):
self.textNs, odtStyle.outlineLevel)) self.textNs, odtStyle.outlineLevel))
self.dumpString('>') self.dumpString('>')
def dumpTags(self, elems, start=True): def getTags(self, elems, start=True):
'''Dumps a series of start or end tags (depending on p_start) of '''This method returns a series of start or end tags (depending on
HtmlElement instances in p_elems.''' p_start) that correspond to HtmlElement instances in p_elems.'''
tags = '' res = ''
for elem in elems: for elem in elems:
tag = elem.dump(start, self) tag = elem.dump(start, self)
if start: tags += tag if start: res += tag
else: tags = tag + tags else: res = tag + res
self.dumpString(tags) return res
def closeConflictualElements(self, conflictElems):
'''This method dumps end tags for p_conflictElems, excepted if those
tags would be empty. In this latter case, tags are purely removed
from the result.'''
startTags = self.getTags(conflictElems, start=True)
if self.res.endswith(startTags):
# In this case I would dump an empty (series of) tag(s). Instead, I
# will remove those tags.
self.res = self.res[:-len(startTags)]
else:
self.dumpString(self.getTags(conflictElems, start=False))
def dumpString(self, s): def dumpString(self, s):
'''Dumps arbitrary content p_s. '''Dumps arbitrary content p_s.
@ -294,17 +338,14 @@ class XhtmlEnvironment(XmlEnvironment):
def onElementStart(self, elem, attrs): def onElementStart(self, elem, attrs):
previousElem = self.getCurrentElement() previousElem = self.getCurrentElement()
if previousElem and (previousElem.elem == 'podxhtml'): self.dumpCurrentContent()
self.dumpRootParagraph(elem)
else:
self.dumpCurrentContent()
currentElem = HtmlElement(elem, attrs) currentElem = HtmlElement(elem, attrs)
# Manage conflictual elements # Manage conflictual elements
conflictElems = currentElem.getConflictualElements(self) conflictElems = currentElem.getConflictualElements(self)
if conflictElems: if conflictElems:
# We must close the conflictual elements, and once the currentElem # We must close the conflictual elements, and once the currentElem
# will be dumped, we will re-open the conflictual elements. # will be dumped, we will re-open the conflictual elements.
self.dumpTags(conflictElems, start=False) self.closeConflictualElements(conflictElems)
currentElem.tagsToReopen = self.getTagsToReopen(conflictElems) currentElem.tagsToReopen = self.getTagsToReopen(conflictElems)
# Manage missing elements # Manage missing elements
if self.anElementIsMissing(previousElem, currentElem): if self.anElementIsMissing(previousElem, currentElem):
@ -325,13 +366,11 @@ class XhtmlEnvironment(XmlEnvironment):
if attrs.has_key('colspan'): if attrs.has_key('colspan'):
nbOfCols = int(attrs['colspan']) nbOfCols = int(attrs['colspan'])
currentTable.nbOfColumns += nbOfCols currentTable.nbOfColumns += nbOfCols
return currentElem
def onElementEnd(self, elem): def onElementEnd(self, elem):
res = None res = None
if elem == 'podxhtml': self.dumpCurrentContent()
self.dumpRootParagraph()
else:
self.dumpCurrentContent()
currentElem = self.currentElements.pop() currentElem = self.currentElements.pop()
if elem in XHTML_LISTS: if elem in XHTML_LISTS:
self.currentLists.pop() self.currentLists.pop()
@ -350,10 +389,10 @@ class XhtmlEnvironment(XmlEnvironment):
lastTable.res += lastTable.tempRes lastTable.res += lastTable.tempRes
lastTable.tempRes = u'' lastTable.tempRes = u''
if currentElem.tagsToClose: if currentElem.tagsToClose:
self.dumpTags(currentElem.tagsToClose, start=False) self.closeConflictualElements(currentElem.tagsToClose)
if currentElem.tagsToReopen: if currentElem.tagsToReopen:
res = currentElem.tagsToReopen res = currentElem.tagsToReopen
return res return currentElem, res
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class XhtmlParser(XmlParser): class XhtmlParser(XmlParser):
@ -375,11 +414,13 @@ class XhtmlParser(XmlParser):
def startElement(self, elem, attrs): def startElement(self, elem, attrs):
elem, attrs = self.lowerizeInput(elem, attrs) elem, attrs = self.lowerizeInput(elem, attrs)
e = XmlParser.startElement(self, elem, attrs) e = XmlParser.startElement(self, elem, attrs)
e.onElementStart(elem, attrs) currentElem = e.onElementStart(elem, attrs)
odfTag = currentElem.getOdfTag(e)
if HTML_2_ODT.has_key(elem): if HTML_2_ODT.has_key(elem):
e.dumpStyledElement(elem, attrs) e.dumpStyledElement(elem, odfTag, attrs)
elif elem == 'a': elif elem == 'a':
e.dumpString('<%s:a %s:type="simple"' % (e.textNs, e.linkNs)) e.dumpString('<%s %s:type="simple"' % (odfTag, e.linkNs))
if attrs.has_key('href'): if attrs.has_key('href'):
e.dumpString(' %s:href="%s"' % (e.linkNs, attrs['href'])) e.dumpString(' %s:href="%s"' % (e.linkNs, attrs['href']))
e.dumpString('>') e.dumpString('>')
@ -389,24 +430,19 @@ class XhtmlParser(XmlParser):
# It is a list into another list. In this case the inner list # It is a list into another list. In this case the inner list
# must be surrounded by a list-item element. # must be surrounded by a list-item element.
prologue = '<%s:list-item>' % e.textNs prologue = '<%s:list-item>' % e.textNs
e.dumpString('%s<%s:list %s:style-name="%s">' % ( e.dumpString('%s<%s %s:style-name="%s">' % (
prologue, e.textNs, e.textNs, e.listStyles[elem])) prologue, odfTag, e.textNs, e.listStyles[elem]))
elif elem == 'li': elif elem in ('li', 'thead', 'tr'):
e.dumpString('<%s:list-item>' % e.textNs) e.dumpString('<%s>' % odfTag)
elif elem == 'table': elif elem == 'table':
# Here we must call "dumpString" only once # Here we must call "dumpString" only once
e.dumpString('<%s:table %s:style-name="podTable">' % (e.tableNs, e.dumpString('<%s %s:style-name="podTable">' % (odfTag, e.tableNs))
e.tableNs))
elif elem == 'thead':
e.dumpString('<%s:table-header-rows>' % e.tableNs)
elif elem == 'tr':
e.dumpString('<%s:table-row>' % e.tableNs)
elif elem in TABLE_CELL_TAGS: elif elem in TABLE_CELL_TAGS:
e.dumpString('<%s:table-cell %s:style-name="%s"' % \ e.dumpString('<%s %s:style-name="%s"' % \
(e.tableNs, e.tableNs, DEFAULT_ODT_STYLES[elem])) (odfTag, e.tableNs, DEFAULT_ODT_STYLES[elem]))
if attrs.has_key('colspan'): if attrs.has_key('colspan'):
e.dumpString(' %s:number-columns-spanned="%s"' % \ e.dumpString(' %s:number-columns-spanned="%s"' % \
(e.tableNs, attrs['colspan'])) (e.tableNs, attrs['colspan']))
e.dumpString('>') e.dumpString('>')
elif elem in IGNORABLE_TAGS: elif elem in IGNORABLE_TAGS:
e.ignore = True e.ignore = True
@ -414,35 +450,28 @@ class XhtmlParser(XmlParser):
def endElement(self, elem): def endElement(self, elem):
elem = self.lowerizeInput(elem) elem = self.lowerizeInput(elem)
e = XmlParser.endElement(self, elem) e = XmlParser.endElement(self, elem)
elemsToReopen = e.onElementEnd(elem) currentElem, elemsToReopen = e.onElementEnd(elem)
if HTML_2_ODT.has_key(elem): # Determine the tag to dump
# For "div" elements, we put append a carriage return. startTag, endTag = currentElem.getOdfTags(e)
if currentElem.isConflictual and e.res.endswith(startTag):
# We will not dump it, it would constitute a silly empty tag.
e.res = e.res[:-len(startTag)]
else:
# Dump the end tag. But dump some additional stuff if required.
if elem == 'div': if elem == 'div':
e.dumpString('<%s:line-break/>' % e.textNs) # For "div" elements, we append a carriage return.
e.dumpString('</%s:%s>' % (e.textNs, HTML_2_ODT[elem])) endTag = '<%s:line-break/>%s' % (e.textNs, endTag)
elif elem == 'a': elif elem in XHTML_LISTS:
e.dumpString('</%s:a>' % e.textNs) if len(e.currentLists) >= 1:
elif elem in XHTML_LISTS: # We were in an inner list. So we must close the list-item
epilogue = '' # tag that surrounds it.
if len(e.currentLists) >= 1: endTag = '%s</%s:list-item>' % (endTag, e.textNs)
# We were in an inner list. So we must close the list-item tag if endTag:
# that surrounds it. e.dumpString(endTag)
epilogue = '</%s:list-item>' % e.textNs if elem in IGNORABLE_TAGS:
e.dumpString('</%s:list>%s' % (e.textNs, epilogue))
elif elem == 'li':
e.dumpString('</%s:list-item>' % e.textNs)
elif elem == 'table':
e.dumpString('</%s:table>' % e.tableNs)
elif elem == 'thead':
e.dumpString('</%s:table-header-rows>' % e.tableNs)
elif elem == 'tr':
e.dumpString('</%s:table-row>' % e.tableNs)
elif elem in TABLE_CELL_TAGS:
e.dumpString('</%s:table-cell>' % e.tableNs)
elif elem in IGNORABLE_TAGS:
e.ignore = False e.ignore = False
if elemsToReopen: if elemsToReopen:
e.dumpTags(elemsToReopen) e.dumpString(e.getTags(elemsToReopen, start=True))
def characters(self, content): def characters(self, content):
e = XmlParser.characters(self, content) e = XmlParser.characters(self, content)