appy.gen: generalized use of appy.Object; existence of a field value (that triggers search for a default value) is now based on Type.isEmptyValue, and not '==None'; bugfix with default values for List fields; prevent search icon to be shown for a Ref when there is no linked object; appy.pod: added class appy.pod.parts.OdtTable that allows to create a complex (ie, with a dynamic number of columns) table programmatically (to be imported with a statement 'do ... from'); appy.shared.diff: improvements in the multiple XHTML diff; appy.shared.xml_parser.XmlMarshaller: support for default namespaces and 'any' tags.

This commit is contained in:
Gaetan Delannay 2011-10-26 10:21:09 +02:00
parent 1ebcbb7b34
commit 3ab6cec7d6
9 changed files with 147 additions and 68 deletions

View file

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import re, time, copy, sys, types, os, os.path, mimetypes, string, StringIO 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 Table
from appy.gen.layout import defaultFieldLayouts from appy.gen.layout import defaultFieldLayouts
from appy.gen.po import PoMessage from appy.gen.po import PoMessage
from appy.gen.utils import sequenceTypes, GroupDescr, Keywords, FileWrapper, \ from appy.gen.utils import sequenceTypes, GroupDescr, Keywords, FileWrapper, \
getClassName, SomeObjects, AppyObject getClassName, SomeObjects
import appy.pod import appy.pod
from appy.pod.renderer import Renderer from appy.pod.renderer import Renderer
from appy.shared.data import countries from appy.shared.data import countries
@ -16,7 +17,7 @@ r, w, d = ('read', 'write', 'delete')
digit = re.compile('[0-9]') digit = re.compile('[0-9]')
alpha = re.compile('[a-zA-Z0-9]') alpha = re.compile('[a-zA-Z0-9]')
letter = re.compile('[a-zA-Z]') letter = re.compile('[a-zA-Z]')
nullValues = (None, '', ' ') nullValues = (None, '', [])
validatorTypes = (types.FunctionType, types.UnboundMethodType, validatorTypes = (types.FunctionType, types.UnboundMethodType,
type(re.compile(''))) type(re.compile('')))
emptyTuple = () emptyTuple = ()
@ -729,7 +730,7 @@ class Type:
def getValue(self, obj): def getValue(self, obj):
'''Gets, on_obj, the value conforming to self's type definition.''' '''Gets, on_obj, the value conforming to self's type definition.'''
value = getattr(obj.aq_base, self.name, None) 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 there is no value, get the default value if any
if not self.editDefault: if not self.editDefault:
# Return self.default, of self.default() if it is a method # Return self.default, of self.default() if it is a method
@ -2243,10 +2244,6 @@ class List(Type):
for n, field in self.fields: for n, field in self.fields:
if n == name: return field 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): def getRequestValue(self, request):
'''Concatenates the list from distinct form elements in the request.''' '''Concatenates the list from distinct form elements in the request.'''
prefix = self.name + '*' + self.fields[0][0] + '*' prefix = self.name + '*' + self.fields[0][0] + '*'
@ -2254,7 +2251,7 @@ class List(Type):
for key in request.keys(): for key in request.keys():
if not key.startswith(prefix): continue if not key.startswith(prefix): continue
# I have found a row. Gets its index # I have found a row. Gets its index
row = AppyObject() row = Object()
rowIndex = int(key.split('*')[-1]) rowIndex = int(key.split('*')[-1])
if rowIndex == -1: continue # Ignore the template row. if rowIndex == -1: continue # Ignore the template row.
for name, field in self.fields: for name, field in self.fields:
@ -2271,10 +2268,6 @@ class List(Type):
keys = res.keys() keys = res.keys()
keys.sort() keys.sort()
res = [res[key] for key in keys] 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 # 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, # subFields will need to get their value, they will take it from here,
# instead of taking it from the specific request key. Indeed, specific # 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))) setattr(v, name, field.getStorableValue(getattr(v, name)))
return value 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 '''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 '' if i == -1: return ''
value = getattr(obj, self.name, None) if not outerValue: return ''
if not value: return '' if i >= len(outerValue): return ''
if i >= len(value): return '' return getattr(outerValue[i], name, '')
return getattr(value[i], name, '')
# Workflow-specific types and default workflows -------------------------------- # Workflow-specific types and default workflows --------------------------------
appyToZopePermissions = { appyToZopePermissions = {

View file

@ -4,6 +4,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os, os.path, sys, types, mimetypes, urllib, cgi import os, os.path, sys, types, mimetypes, urllib, cgi
from appy import Object
import appy.gen import appy.gen
from appy.gen import Type, String, Selection, Role, No, WorkflowAnonymous, \ from appy.gen import Type, String, Selection, Role, No, WorkflowAnonymous, \
Transition, Permission Transition, Permission
@ -206,9 +207,9 @@ class BaseMixin:
return self.goto(urlBack) return self.goto(urlBack)
# Object for storing validation errors # Object for storing validation errors
errors = AppyObject() errors = Object()
# Object for storing the (converted) values from the request # Object for storing the (converted) values from the request
values = AppyObject() values = Object()
# Trigger field-specific validation # Trigger field-specific validation
self.intraFieldValidation(errors, values) self.intraFieldValidation(errors, values)
@ -435,7 +436,8 @@ class BaseMixin:
# broken on returned object. # broken on returned object.
return getattr(self, methodName, None) 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. '''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 If p_onlyIfSync is True, it returns the value only if appyType can be
retrieved in synchronous mode.''' retrieved in synchronous mode.'''
@ -445,7 +447,8 @@ class BaseMixin:
if '*' not in name: return appyType.getValue(self) if '*' not in name: return appyType.getValue(self)
# The field is an inner field from a List. # The field is an inner field from a List.
listName, name, i = name.split('*') 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): def getFormattedFieldValue(self, name, value):
'''Gets a nice, string representation of p_value which is a value from '''Gets a nice, string representation of p_value which is a value from

View file

@ -145,7 +145,7 @@
</tal:noObject> </tal:noObject>
<tal:comment replace="nothing">If there is an object...</tal:comment> <tal:comment replace="nothing">If there is an object...</tal:comment>
<tal:objectIsPresent condition="objs"> <tal:objectIsPresent condition="python: objs">
<tal:obj repeat="obj objs"> <tal:obj repeat="obj objs">
<td tal:define="includeShownInfo python:True"><metal:showObjectTitle use-macro="app/skyn/widgets/ref/macros/objectTitle" /></td> <td tal:define="includeShownInfo python:True"><metal:showObjectTitle use-macro="app/skyn/widgets/ref/macros/objectTitle" /></td>
</tal:obj> </tal:obj>
@ -161,7 +161,7 @@
(<span tal:replace="totalNumber"/>) (<span tal:replace="totalNumber"/>)
<metal:plusIcon use-macro="app/skyn/widgets/ref/macros/plusIcon"/> <metal:plusIcon use-macro="app/skyn/widgets/ref/macros/plusIcon"/>
<tal:comment replace="nothing">The search icon if field is queryable</tal:comment> <tal:comment replace="nothing">The search icon if field is queryable</tal:comment>
<a tal:condition="appyType/queryable" <a tal:condition="python: objs and appyType['queryable']"
tal:attributes="href python: '%s/skyn/search?type_name=%s&ref=%s:%s' % (tool.absolute_url(), linkedPortalType, contextObj.UID(), appyType['name'])"> tal:attributes="href python: '%s/skyn/search?type_name=%s&ref=%s:%s' % (tool.absolute_url(), linkedPortalType, contextObj.UID(), appyType['name'])">
<img src="search.gif" tal:attributes="title python: _('search_objects')"/></a> <img src="search.gif" tal:attributes="title python: _('search_objects')"/></a>
</legend> </legend>

View file

@ -59,7 +59,8 @@
layout python: widget['layouts'][layoutType]; layout python: widget['layouts'][layoutType];
name widgetName| widget/name; name widgetName| widget/name;
sync python: widget['sync'][layoutType]; 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); value python: contextObj.getFormattedFieldValue(name, rawValue);
requestValue python: contextObj.getRequestFieldValue(name); requestValue python: contextObj.getRequestFieldValue(name);
inRequest python: request.has_key(name); inRequest python: request.has_key(name);

View file

@ -158,9 +158,6 @@ def produceNiceMessage(msg):
res += c res += c
return res return res
# ------------------------------------------------------------------------------
class AppyObject: pass
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class SomeObjects: class SomeObjects:
'''Represents a bunch of objects retrieved from a reference or a query in '''Represents a bunch of objects retrieved from a reference or a query in

64
pod/parts.py Normal file
View file

@ -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')
# ------------------------------------------------------------------------------

View file

@ -44,7 +44,4 @@ class UnicodeBuffer:
self.buffer.append(unicode(s)) self.buffer.append(unicode(s))
def getValue(self): def getValue(self):
return u''.join(self.buffer) return u''.join(self.buffer)
# ------------------------------------------------------------------------------
class Dummy: pass
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -212,26 +212,25 @@ class HtmlDiff:
ratio = difflib.SequenceMatcher(a=s1.lower(), b=s2.lower()).ratio() ratio = difflib.SequenceMatcher(a=s1.lower(), b=s2.lower()).ratio()
return ratio > self.diffRatio return ratio > self.diffRatio
def isEmpty(self, l): def splitTagAndContent(self, line):
'''Is list p_l empty ?''' '''p_line is a XHTML tag with content. This method returns a tuple
return not l or ( (len(l) == 1) and (l[0] in ('', '\r'))) (startTag, content), where p_startTag is the isolated start tag and
content is the tag content.'''
def getTagContent(self, line): i = line.find('>')+1
'''p_lines is a XHTML tag with content. This method returns the content return line[0:i], line[i:line.rfind('<')]
of the tag, removing start and end tags.'''
return line[line.find('>')+1:line.rfind('<')]
def getLineAndType(self, line): def getLineAndType(self, line):
'''p_line is a string that can already have been surrounded by an '''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. "insert" or "delete" tag. This is what we try to determine here.
This method returns a tuple (type, line, innerDiffs), where "type" This method returns a tuple (type, line, innerDiffs, outerTag),
can be: where "type" can be:
* "insert" if it has already been flagged as inserted; * "insert" if it has already been flagged as inserted;
* "delete" if it has already been flagged as deleted; * "delete" if it has already been flagged as deleted;
* None else; * None else;
"line" holds the original parameter p_line, excepted: "line" holds the original parameter p_line, excepted:
* if type="insert". In that case, the surrounding insert tag has been * 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 diff tags (insert or delete) are found. In that case,
- if inner "insert" tags are found, they are removed but their - if inner "insert" tags are found, they are removed but their
content is kept; content is kept;
@ -240,10 +239,15 @@ class HtmlDiff:
- "innerDiffs" holds the list of re.MatchObjects instances - "innerDiffs" holds the list of re.MatchObjects instances
representing the found inner tags. 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): if line.startswith(self.divInsertPrefix):
# Return the line without the surrounding tag. # 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. # Replace found inner inserts with their content.
innerDiffs = [] innerDiffs = []
while True: while True:
@ -256,7 +260,7 @@ class HtmlDiff:
if match.group(1) == 'insert': if match.group(1) == 'insert':
content = match.group(2) content = match.group(2)
line = line[:match.start()] + content + line[match.end():] line = line[:match.start()] + content + line[match.end():]
return (None, line, innerDiffs) return (action, line, innerDiffs, outerTag)
def getSeqDiff(self, seqA, seqB): def getSeqDiff(self, seqA, seqB):
'''p_seqA and p_seqB are lists of strings. Here we will try to identify '''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 # Scan every string from p_seqA and try to find a similar string in
# p_seqB. # p_seqB.
while i < len(seqA): while i < len(seqA):
pastAction, lineSeqA, innerDiffs = self.getLineAndType(seqA[i]) pastAction, lineA, innerDiffs, outerTag=self.getLineAndType(seqA[i])
if pastAction == 'delete': if pastAction == 'delete':
# We will consider this line as "equal" because it already has # We will consider this line as "equal" because it already has
# been noted as deleted in a previous diff. # been noted as deleted in a previous diff.
@ -293,10 +297,10 @@ class HtmlDiff:
# be found in seqB. # be found in seqB.
res.append( ('equal', seqA[i]) ) res.append( ('equal', seqA[i]) )
else: 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 similarFound = False
for j in range(k, len(seqB)): for j in range(k, len(seqB)):
if self.isSimilar(lineSeqA, seqB[j]): if self.isSimilar(lineA, seqB[j]):
similarFound = True similarFound = True
# Strings between indices k and j in p_seqB must be # Strings between indices k and j in p_seqB must be
# considered as inserted, because no similar line exists # considered as inserted, because no similar line exists
@ -304,16 +308,15 @@ class HtmlDiff:
if k < j: if k < j:
for line in seqB[k:j]: res.append(('insert', line)) for line in seqB[k:j]: res.append(('insert', line))
# Similar strings are appended in a 'replace' entry, # 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" # previous diff: in this case, we keep the "old"
# version: the new one is the same, but for which we # version: the new one is the same, but for which we
# don't remember who updated it. # 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]) ) res.append( ('equal', seqA[i]) )
# TODO: manage lineSeqA != seqB[j]
else: else:
res.append(('replace', (lineSeqA, seqB[j], res.append(('replace', (lineA, seqB[j],
innerDiffs))) innerDiffs, outerTag)))
k = j+1 k = j+1
break break
if not similarFound: res.append( ('delete', seqA[i]) ) if not similarFound: res.append( ('delete', seqA[i]) )
@ -337,6 +340,16 @@ class HtmlDiff:
if trailSpace: res[-1] = res[-1] + sep if trailSpace: res[-1] = res[-1] + sep
return res 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): def getHtmlDiff(self, old, new, sep):
'''Returns the differences between p_old and p_new. Result is a string '''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 containing the comparison in HTML format. p_sep is used for turning
@ -352,29 +365,27 @@ class HtmlDiff:
matcher = difflib.SequenceMatcher() matcher = difflib.SequenceMatcher()
matcher.set_seqs(a,b) matcher.set_seqs(a,b)
for action, i1, i2, j1, j2 in matcher.get_opcodes(): for action, i1, i2, j1, j2 in matcher.get_opcodes():
chunkA = a[i1:i2] chunkA = self.removeGarbage(a[i1:i2])
chunkB = b[j1:j2] chunkB = self.removeGarbage(b[j1:j2])
aIsEmpty = self.isEmpty(chunkA)
bIsEmpty = self.isEmpty(chunkB)
toAdd = None toAdd = None
if action == 'equal': if action == 'equal':
if not aIsEmpty: toAdd = sep.join(chunkA) if chunkA: toAdd = sep.join(chunkA)
elif action == 'insert': elif action == 'insert':
if not bIsEmpty: if chunkB:
toAdd = self.getModifiedChunk(chunkB, action, sep) toAdd = self.getModifiedChunk(chunkB, action, sep)
elif action == 'delete': elif action == 'delete':
if not aIsEmpty: if chunkA:
toAdd = self.getModifiedChunk(chunkA, action, sep) toAdd = self.getModifiedChunk(chunkA, action, sep)
elif action == 'replace': elif action == 'replace':
if aIsEmpty and bIsEmpty: if not chunkA and not chunkB:
toAdd = '' toAdd = ''
elif aIsEmpty: elif not chunkA:
# Was an addition, not a replacement # Was an addition, not a replacement
toAdd = self.getModifiedChunk(chunkB, 'insert', sep) toAdd = self.getModifiedChunk(chunkB, 'insert', sep)
elif bIsEmpty: elif not chunkB:
# Was a deletion, not a replacement # Was a deletion, not a replacement
toAdd = self.getModifiedChunk(chunkA, 'delete', sep) toAdd = self.getModifiedChunk(chunkA, 'delete', sep)
else: # At least, a true replacement (grr difflib) else: # At least, a true replacement
if sep == '\n': if sep == '\n':
# We know that some lines have been replaced from a to # We know that some lines have been replaced from a to
# b. By identifying similarities between those lines, # b. By identifying similarities between those lines,
@ -387,7 +398,7 @@ class HtmlDiff:
elif sAction == 'equal': elif sAction == 'equal':
toAdd += line toAdd += line
elif sAction == 'replace': elif sAction == 'replace':
lineA, lineB, previousDiffsA = line lineA, lineB, previousDiffsA, outerTag = line
# Investigate further here and explore # Investigate further here and explore
# differences at the *word* level between lineA # differences at the *word* level between lineA
# and lineB. As a preamble, and in order to # and lineB. As a preamble, and in order to
@ -405,6 +416,10 @@ class HtmlDiff:
if previousDiffsA: if previousDiffsA:
merger= Merger(lineA, toAdd, previousDiffsA) merger= Merger(lineA, toAdd, previousDiffsA)
toAdd = merger.merge() toAdd = merger.merge()
# Rewrap line into outerTag if lineA was a line
# tagged as previously inserted.
if outerTag:
toAdd = outerTag + toAdd + '</div>'
else: else:
toAdd = self.getModifiedChunk(chunkA, 'delete', sep) toAdd = self.getModifiedChunk(chunkA, 'delete', sep)
toAdd += self.getModifiedChunk(chunkB, 'insert', sep) toAdd += self.getModifiedChunk(chunkB, 'insert', sep)

View file

@ -448,7 +448,8 @@ class XmlMarshaller:
self.rootElementName = rootTag self.rootElementName = rootTag
# The namespaces that will be defined at the root of the XML message. # 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 # 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 self.namespaces = namespaces
# The following dict will tell which XML tags will get which namespace # The following dict will tell which XML tags will get which namespace
# prefix ({s_tagName: s_prefix}). Special optional dict entry # prefix ({s_tagName: s_prefix}). Special optional dict entry
@ -475,8 +476,12 @@ class XmlMarshaller:
res.write('<'); res.write(tagName) res.write('<'); res.write(tagName)
# Dumps namespace definitions if any # Dumps namespace definitions if any
for prefix, url in self.namespaces.iteritems(): for prefix, url in self.namespaces.iteritems():
res.write(' xmlns:%s="%s"' % (prefix, url)) if not prefix:
# Dumps Appy- or Plone-specific attributed 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': if self.objectType != 'popo':
res.write(' type="object" id="%s"' % instance.UID()) res.write(' type="object" id="%s"' % instance.UID())
res.write('>') res.write('>')
@ -572,6 +577,11 @@ class XmlMarshaller:
def dumpField(self, res, fieldName, fieldValue, fieldType='basic'): def dumpField(self, res, fieldName, fieldValue, fieldType='basic'):
'''Dumps in p_res, the value of the p_field for p_instance.''' '''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) fieldTag = self.getTagName(fieldName)
res.write('<'); res.write(fieldTag) res.write('<'); res.write(fieldTag)
# Dump the type of the field as an XML attribute # Dump the type of the field as an XML attribute