diff --git a/gen/__init__.py b/gen/__init__.py
index b57e552..02b954c 100644
--- a/gen/__init__.py
+++ b/gen/__init__.py
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
import re, time, copy, sys, types, os, os.path, mimetypes, string, StringIO
+from appy import Object
from appy.gen.layout import Table
from appy.gen.layout import defaultFieldLayouts
from appy.gen.po import PoMessage
from appy.gen.utils import sequenceTypes, GroupDescr, Keywords, FileWrapper, \
- getClassName, SomeObjects, AppyObject
+ getClassName, SomeObjects
import appy.pod
from appy.pod.renderer import Renderer
from appy.shared.data import countries
@@ -16,7 +17,7 @@ r, w, d = ('read', 'write', 'delete')
digit = re.compile('[0-9]')
alpha = re.compile('[a-zA-Z0-9]')
letter = re.compile('[a-zA-Z]')
-nullValues = (None, '', ' ')
+nullValues = (None, '', [])
validatorTypes = (types.FunctionType, types.UnboundMethodType,
type(re.compile('')))
emptyTuple = ()
@@ -729,7 +730,7 @@ class Type:
def getValue(self, obj):
'''Gets, on_obj, the value conforming to self's type definition.'''
value = getattr(obj.aq_base, self.name, None)
- if (value == None):
+ if self.isEmptyValue(value):
# If there is no value, get the default value if any
if not self.editDefault:
# Return self.default, of self.default() if it is a method
@@ -2243,10 +2244,6 @@ class List(Type):
for n, field in self.fields:
if n == name: return field
- def isEmptyValue(self, value, obj=None):
- '''Returns True if the p_value must be considered as an empty value.'''
- return not value
-
def getRequestValue(self, request):
'''Concatenates the list from distinct form elements in the request.'''
prefix = self.name + '*' + self.fields[0][0] + '*'
@@ -2254,7 +2251,7 @@ class List(Type):
for key in request.keys():
if not key.startswith(prefix): continue
# I have found a row. Gets its index
- row = AppyObject()
+ row = Object()
rowIndex = int(key.split('*')[-1])
if rowIndex == -1: continue # Ignore the template row.
for name, field in self.fields:
@@ -2271,10 +2268,6 @@ class List(Type):
keys = res.keys()
keys.sort()
res = [res[key] for key in keys]
- print 'REQUEST VALUE FOR LIST (%d)' % len(res)
- for value in res:
- for k, v in value.__dict__.iteritems():
- print k, '=', v
# I store in the request this computed value. This way, when individual
# subFields will need to get their value, they will take it from here,
# instead of taking it from the specific request key. Indeed, specific
@@ -2290,14 +2283,13 @@ class List(Type):
setattr(v, name, field.getStorableValue(getattr(v, name)))
return value
- def getInnerValue(self, obj, name, i):
+ def getInnerValue(self, outerValue, name, i):
'''Returns the value of inner field named p_name in row number p_i
- with the list of values from this field on p_obj.'''
+ within the whole list of values p_outerValue.'''
if i == -1: return ''
- value = getattr(obj, self.name, None)
- if not value: return ''
- if i >= len(value): return ''
- return getattr(value[i], name, '')
+ if not outerValue: return ''
+ if i >= len(outerValue): return ''
+ return getattr(outerValue[i], name, '')
# Workflow-specific types and default workflows --------------------------------
appyToZopePermissions = {
diff --git a/gen/plone25/mixins/__init__.py b/gen/plone25/mixins/__init__.py
index 1d9fdbc..f653043 100644
--- a/gen/plone25/mixins/__init__.py
+++ b/gen/plone25/mixins/__init__.py
@@ -4,6 +4,7 @@
# ------------------------------------------------------------------------------
import os, os.path, sys, types, mimetypes, urllib, cgi
+from appy import Object
import appy.gen
from appy.gen import Type, String, Selection, Role, No, WorkflowAnonymous, \
Transition, Permission
@@ -206,9 +207,9 @@ class BaseMixin:
return self.goto(urlBack)
# Object for storing validation errors
- errors = AppyObject()
+ errors = Object()
# Object for storing the (converted) values from the request
- values = AppyObject()
+ values = Object()
# Trigger field-specific validation
self.intraFieldValidation(errors, values)
@@ -435,7 +436,8 @@ class BaseMixin:
# broken on returned object.
return getattr(self, methodName, None)
- def getFieldValue(self, name, onlyIfSync=False, layoutType=None):
+ def getFieldValue(self, name, onlyIfSync=False, layoutType=None,
+ outerValue=None):
'''Returns the database value of field named p_name for p_self.
If p_onlyIfSync is True, it returns the value only if appyType can be
retrieved in synchronous mode.'''
@@ -445,7 +447,8 @@ class BaseMixin:
if '*' not in name: return appyType.getValue(self)
# The field is an inner field from a List.
listName, name, i = name.split('*')
- return self.getAppyType(listName).getInnerValue(self, name, int(i))
+ listType = self.getAppyType(listName)
+ return listType.getInnerValue(outerValue, name, int(i))
def getFormattedFieldValue(self, name, value):
'''Gets a nice, string representation of p_value which is a value from
diff --git a/gen/plone25/skin/widgets/ref.pt b/gen/plone25/skin/widgets/ref.pt
index caf0c31..93e72ae 100644
--- a/gen/plone25/skin/widgets/ref.pt
+++ b/gen/plone25/skin/widgets/ref.pt
@@ -145,7 +145,7 @@
If there is an object...
-
+
|
@@ -161,7 +161,7 @@
()
The search icon if field is queryable
-
diff --git a/gen/plone25/skin/widgets/show.pt b/gen/plone25/skin/widgets/show.pt
index 93157df..971a7a0 100644
--- a/gen/plone25/skin/widgets/show.pt
+++ b/gen/plone25/skin/widgets/show.pt
@@ -59,7 +59,8 @@
layout python: widget['layouts'][layoutType];
name widgetName| widget/name;
sync python: widget['sync'][layoutType];
- rawValue python: contextObj.getFieldValue(name, onlyIfSync=True, layoutType=layoutType);
+ outerValue value|nothing;
+ rawValue python: contextObj.getFieldValue(name,onlyIfSync=True,layoutType=layoutType,outerValue=outerValue);
value python: contextObj.getFormattedFieldValue(name, rawValue);
requestValue python: contextObj.getRequestFieldValue(name);
inRequest python: request.has_key(name);
diff --git a/gen/utils.py b/gen/utils.py
index bb5e413..42d18ad 100644
--- a/gen/utils.py
+++ b/gen/utils.py
@@ -158,9 +158,6 @@ def produceNiceMessage(msg):
res += c
return res
-# ------------------------------------------------------------------------------
-class AppyObject: pass
-
# ------------------------------------------------------------------------------
class SomeObjects:
'''Represents a bunch of objects retrieved from a reference or a query in
diff --git a/pod/parts.py b/pod/parts.py
new file mode 100644
index 0000000..e3349b5
--- /dev/null
+++ b/pod/parts.py
@@ -0,0 +1,64 @@
+# ------------------------------------------------------------------------------
+class OdtTable:
+ '''This class allows to construct an ODT table programmatically.'''
+ # Some namespace definitions
+ tns = 'table:'
+ txns = 'text:'
+
+ def __init__(self, name, paraStyle, cellStyle, nbOfCols,
+ paraHeaderStyle=None, cellHeaderStyle=None):
+ # An ODT table must have a name.
+ self.name = name
+ # The default style of every paragraph within cells
+ self.paraStyle = paraStyle
+ # The default style of every cell
+ self.cellStyle = cellStyle
+ # The total number of columns
+ self.nbOfCols = nbOfCols
+ # The default style of every paragraph within a header cell
+ self.paraHeaderStyle = paraHeaderStyle or paraStyle
+ # The default style of every header cell
+ self.cellHeaderStyle = cellHeaderStyle or cellStyle
+ # The buffer where the resulting table will be rendered
+ self.res = ''
+
+ def dumpCell(self, content, span=1, header=False,
+ paraStyle=None, cellStyle=None):
+ '''Dumps a cell in the table. If no specific p_paraStyle (p_cellStyle)
+ is given, self.paraStyle (self.cellStyle) is used, excepted if
+ p_header is True: in that case, self.paraHeaderStyle
+ (self.cellHeaderStyle) is used.'''
+ if not paraStyle:
+ if header: paraStyle = self.paraHeaderStyle
+ else: paraStyle = self.paraStyle
+ if not cellStyle:
+ if header: cellStyle = self.cellHeaderStyle
+ else: cellStyle = self.cellStyle
+ self.res += '<%stable-cell %sstyle-name="%s" ' \
+ '%snumber-columns-spanned="%d">' % \
+ (self.tns, self.tns, cellStyle, self.tns, span)
+ self.res += '<%sp %sstyle-name="%s">%s%sp>' % \
+ (self.txns, self.txns, paraStyle, content, self.txns)
+ self.res += '%stable-cell>' % self.tns
+
+ def startRow(self):
+ self.res += '<%stable-row>' % self.tns
+
+ def endRow(self):
+ self.res += '%stable-row>' % self.tns
+
+ def startTable(self):
+ self.res += '<%stable %sname="%s">' % (self.tns, self.tns, self.name)
+ self.res += '<%stable-column %snumber-columns-repeated="%d"/>' % \
+ (self.tns, self.tns, self.nbOfCols)
+
+ def endTable(self):
+ self.res += '%stable>' % self.tns
+
+ def dumpFloat(self, number):
+ return str(round(number, 2))
+
+ def get(self):
+ '''Returns the whole table.'''
+ return self.res.decode('utf-8')
+# ------------------------------------------------------------------------------
diff --git a/shared/__init__.py b/shared/__init__.py
index 9a3dff2..8997905 100644
--- a/shared/__init__.py
+++ b/shared/__init__.py
@@ -44,7 +44,4 @@ class UnicodeBuffer:
self.buffer.append(unicode(s))
def getValue(self):
return u''.join(self.buffer)
-
-# ------------------------------------------------------------------------------
-class Dummy: pass
# ------------------------------------------------------------------------------
diff --git a/shared/diff.py b/shared/diff.py
index 02e70ea..baf8ce6 100644
--- a/shared/diff.py
+++ b/shared/diff.py
@@ -212,26 +212,25 @@ class HtmlDiff:
ratio = difflib.SequenceMatcher(a=s1.lower(), b=s2.lower()).ratio()
return ratio > self.diffRatio
- def isEmpty(self, l):
- '''Is list p_l empty ?'''
- return not l or ( (len(l) == 1) and (l[0] in ('', '\r')))
-
- def getTagContent(self, line):
- '''p_lines is a XHTML tag with content. This method returns the content
- of the tag, removing start and end tags.'''
- return line[line.find('>')+1:line.rfind('<')]
+ def splitTagAndContent(self, line):
+ '''p_line is a XHTML tag with content. This method returns a tuple
+ (startTag, content), where p_startTag is the isolated start tag and
+ content is the tag content.'''
+ i = line.find('>')+1
+ return line[0:i], line[i:line.rfind('<')]
def getLineAndType(self, line):
'''p_line is a string that can already have been surrounded by an
"insert" or "delete" tag. This is what we try to determine here.
- This method returns a tuple (type, line, innerDiffs), where "type"
- can be:
+ This method returns a tuple (type, line, innerDiffs, outerTag),
+ where "type" can be:
* "insert" if it has already been flagged as inserted;
* "delete" if it has already been flagged as deleted;
* None else;
"line" holds the original parameter p_line, excepted:
* if type="insert". In that case, the surrounding insert tag has been
- removed;
+ removed and placed into "outerTag" (the outer start tag to be more
+ precise);
* if inner diff tags (insert or delete) are found. In that case,
- if inner "insert" tags are found, they are removed but their
content is kept;
@@ -240,10 +239,15 @@ class HtmlDiff:
- "innerDiffs" holds the list of re.MatchObjects instances
representing the found inner tags.
'''
- if line.startswith(self.divDeletePrefix): return ('delete', line, None)
+ if line.startswith(self.divDeletePrefix):
+ return ('delete', line, None, None)
if line.startswith(self.divInsertPrefix):
# Return the line without the surrounding tag.
- return ('insert', self.getTagContent(line), None)
+ action = 'insert'
+ outerTag, line = self.splitTagAndContent(line)
+ else:
+ action = None
+ outerTag = None
# Replace found inner inserts with their content.
innerDiffs = []
while True:
@@ -256,7 +260,7 @@ class HtmlDiff:
if match.group(1) == 'insert':
content = match.group(2)
line = line[:match.start()] + content + line[match.end():]
- return (None, line, innerDiffs)
+ return (action, line, innerDiffs, outerTag)
def getSeqDiff(self, seqA, seqB):
'''p_seqA and p_seqB are lists of strings. Here we will try to identify
@@ -278,7 +282,7 @@ class HtmlDiff:
# Scan every string from p_seqA and try to find a similar string in
# p_seqB.
while i < len(seqA):
- pastAction, lineSeqA, innerDiffs = self.getLineAndType(seqA[i])
+ pastAction, lineA, innerDiffs, outerTag=self.getLineAndType(seqA[i])
if pastAction == 'delete':
# We will consider this line as "equal" because it already has
# been noted as deleted in a previous diff.
@@ -293,10 +297,10 @@ class HtmlDiff:
# be found in seqB.
res.append( ('equal', seqA[i]) )
else:
- # Try to find a line in seqB which is similar to lineSeqA.
+ # Try to find a line in seqB which is similar to lineA.
similarFound = False
for j in range(k, len(seqB)):
- if self.isSimilar(lineSeqA, seqB[j]):
+ if self.isSimilar(lineA, seqB[j]):
similarFound = True
# Strings between indices k and j in p_seqB must be
# considered as inserted, because no similar line exists
@@ -304,16 +308,15 @@ class HtmlDiff:
if k < j:
for line in seqB[k:j]: res.append(('insert', line))
# Similar strings are appended in a 'replace' entry,
- # excepted if lineSeqA is already an insert from a
+ # excepted if lineA is already an insert from a
# previous diff: in this case, we keep the "old"
# version: the new one is the same, but for which we
# don't remember who updated it.
- if (pastAction == 'insert') and (lineSeqA == seqB[j]):
+ if (pastAction == 'insert') and (lineA == seqB[j]):
res.append( ('equal', seqA[i]) )
- # TODO: manage lineSeqA != seqB[j]
else:
- res.append(('replace', (lineSeqA, seqB[j],
- innerDiffs)))
+ res.append(('replace', (lineA, seqB[j],
+ innerDiffs, outerTag)))
k = j+1
break
if not similarFound: res.append( ('delete', seqA[i]) )
@@ -337,6 +340,16 @@ class HtmlDiff:
if trailSpace: res[-1] = res[-1] + sep
return res
+ garbage = ('', '\r')
+ def removeGarbage(self, l):
+ '''Removes from list p_l elements that have no interest, like blank
+ strings or considered as is.'''
+ i = len(l)-1
+ while i >= 0:
+ if l[i] in self.garbage: del l[i]
+ i -= 1
+ return l
+
def getHtmlDiff(self, old, new, sep):
'''Returns the differences between p_old and p_new. Result is a string
containing the comparison in HTML format. p_sep is used for turning
@@ -352,29 +365,27 @@ class HtmlDiff:
matcher = difflib.SequenceMatcher()
matcher.set_seqs(a,b)
for action, i1, i2, j1, j2 in matcher.get_opcodes():
- chunkA = a[i1:i2]
- chunkB = b[j1:j2]
- aIsEmpty = self.isEmpty(chunkA)
- bIsEmpty = self.isEmpty(chunkB)
+ chunkA = self.removeGarbage(a[i1:i2])
+ chunkB = self.removeGarbage(b[j1:j2])
toAdd = None
if action == 'equal':
- if not aIsEmpty: toAdd = sep.join(chunkA)
+ if chunkA: toAdd = sep.join(chunkA)
elif action == 'insert':
- if not bIsEmpty:
+ if chunkB:
toAdd = self.getModifiedChunk(chunkB, action, sep)
elif action == 'delete':
- if not aIsEmpty:
+ if chunkA:
toAdd = self.getModifiedChunk(chunkA, action, sep)
elif action == 'replace':
- if aIsEmpty and bIsEmpty:
+ if not chunkA and not chunkB:
toAdd = ''
- elif aIsEmpty:
+ elif not chunkA:
# Was an addition, not a replacement
toAdd = self.getModifiedChunk(chunkB, 'insert', sep)
- elif bIsEmpty:
+ elif not chunkB:
# Was a deletion, not a replacement
toAdd = self.getModifiedChunk(chunkA, 'delete', sep)
- else: # At least, a true replacement (grr difflib)
+ else: # At least, a true replacement
if sep == '\n':
# We know that some lines have been replaced from a to
# b. By identifying similarities between those lines,
@@ -387,7 +398,7 @@ class HtmlDiff:
elif sAction == 'equal':
toAdd += line
elif sAction == 'replace':
- lineA, lineB, previousDiffsA = line
+ lineA, lineB, previousDiffsA, outerTag = line
# Investigate further here and explore
# differences at the *word* level between lineA
# and lineB. As a preamble, and in order to
@@ -405,6 +416,10 @@ class HtmlDiff:
if previousDiffsA:
merger= Merger(lineA, toAdd, previousDiffsA)
toAdd = merger.merge()
+ # Rewrap line into outerTag if lineA was a line
+ # tagged as previously inserted.
+ if outerTag:
+ toAdd = outerTag + toAdd + ''
else:
toAdd = self.getModifiedChunk(chunkA, 'delete', sep)
toAdd += self.getModifiedChunk(chunkB, 'insert', sep)
diff --git a/shared/xml_parser.py b/shared/xml_parser.py
index 8563349..4f3f327 100644
--- a/shared/xml_parser.py
+++ b/shared/xml_parser.py
@@ -448,7 +448,8 @@ class XmlMarshaller:
self.rootElementName = rootTag
# The namespaces that will be defined at the root of the XML message.
# It is a dict whose keys are namespace prefixes and whose values are
- # namespace URLs.
+ # namespace URLs. If you want to specify a default namespace, specify an
+ # entry with an empty string as a key.
self.namespaces = namespaces
# The following dict will tell which XML tags will get which namespace
# prefix ({s_tagName: s_prefix}). Special optional dict entry
@@ -475,8 +476,12 @@ class XmlMarshaller:
res.write('<'); res.write(tagName)
# Dumps namespace definitions if any
for prefix, url in self.namespaces.iteritems():
- res.write(' xmlns:%s="%s"' % (prefix, url))
- # Dumps Appy- or Plone-specific attributed
+ if not prefix:
+ pre = 'xmlns' # The default namespace
+ else:
+ pre = 'xmlns:%s' % prefix
+ res.write(' %s="%s"' % (pre, url))
+ # Dumps Appy- or Plone-specific attribute
if self.objectType != 'popo':
res.write(' type="object" id="%s"' % instance.UID())
res.write('>')
@@ -572,6 +577,11 @@ class XmlMarshaller:
def dumpField(self, res, fieldName, fieldValue, fieldType='basic'):
'''Dumps in p_res, the value of the p_field for p_instance.'''
+ # As a preamble, manage special case of p_fieldName == "_any". In that
+ # case, p_fieldValue corresponds to a previously marshalled string that
+ # must be included as is here, without dumping the tag name.
+ if fieldName == '_any': self.dumpValue(res, fieldValue, None)
+ # Now, dump "normal" fields.
fieldTag = self.getTagName(fieldName)
res.write('<'); res.write(fieldTag)
# Dump the type of the field as an XML attribute