appy.gen: Type 'float': added the possibility to define a separator for thousands; bugfixes in master/slave relationships; permission-related bugfix while creating objects through AbstractWrapper.create; appy.shared.diff: more improvements (still ongoing work).

This commit is contained in:
Gaetan Delannay 2011-11-10 21:59:02 +01:00
parent 040cdafb8c
commit 8e1760842e
12 changed files with 139 additions and 55 deletions

View file

@ -25,7 +25,7 @@ labelTypes = ('label', 'descr', 'help')
def initMasterValue(v): def initMasterValue(v):
'''Standardizes p_v as a list of strings.''' '''Standardizes p_v as a list of strings.'''
if not v: res = [] if not isinstance(v, bool) and not v: res = []
elif type(v) not in sequenceTypes: res = [v] elif type(v) not in sequenceTypes: res = [v]
else: res = v else: res = v
return [str(v) for v in res] return [str(v) for v in res]
@ -959,6 +959,7 @@ class Integer(Type):
class Float(Type): class Float(Type):
allowedDecimalSeps = (',', '.') allowedDecimalSeps = (',', '.')
allowedThousandsSeps = (' ', '')
def __init__(self, validator=None, multiplicity=(0,1), index=None, def __init__(self, validator=None, multiplicity=(0,1), index=None,
default=None, optional=False, editDefault=False, show=True, default=None, optional=False, editDefault=False, show=True,
page='main', group=None, layouts=None, move=0, indexed=False, page='main', group=None, layouts=None, move=0, indexed=False,
@ -966,7 +967,7 @@ class Float(Type):
specificWritePermission=False, width=6, height=None, specificWritePermission=False, width=6, height=None,
maxChars=13, colspan=1, master=None, masterValue=None, maxChars=13, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None, focus=False, historized=False, mapping=None, label=None,
precision=None, sep=(',', '.')): precision=None, sep=(',', '.'), tsep=' '):
# The precision is the number of decimal digits. This number is used # The precision is the number of decimal digits. This number is used
# for rendering the float, but the internal float representation is not # for rendering the float, but the internal float representation is not
# rounded. # rounded.
@ -981,6 +982,7 @@ class Float(Type):
for sep in self.sep: for sep in self.sep:
if sep not in Float.allowedDecimalSeps: if sep not in Float.allowedDecimalSeps:
raise 'Char "%s" is not allowed as decimal separator.' % sep raise 'Char "%s" is not allowed as decimal separator.' % sep
self.tsep = tsep
Type.__init__(self, validator, multiplicity, index, default, optional, Type.__init__(self, validator, multiplicity, index, default, optional,
editDefault, show, page, group, layouts, move, indexed, editDefault, show, page, group, layouts, move, indexed,
False, specificReadPermission, specificWritePermission, False, specificReadPermission, specificWritePermission,
@ -989,11 +991,13 @@ class Float(Type):
self.pythonType = float self.pythonType = float
def getFormattedValue(self, obj, value): def getFormattedValue(self, obj, value):
return formatNumber(value, sep=self.sep[0], precision=self.precision) return formatNumber(value, sep=self.sep[0], precision=self.precision,
tsep=self.tsep)
def validateValue(self, obj, value): def validateValue(self, obj, value):
# Replace used separator with the Python separator '.' # Replace used separator with the Python separator '.'
for sep in self.sep: value = value.replace(sep, '.') for sep in self.sep: value = value.replace(sep, '.')
value = value.replace(self.tsep, '')
try: try:
value = self.pythonType(value) value = self.pythonType(value)
except ValueError: except ValueError:
@ -1002,6 +1006,7 @@ class Float(Type):
def getStorableValue(self, value): def getStorableValue(self, value):
if not self.isEmptyValue(value): if not self.isEmptyValue(value):
for sep in self.sep: value = value.replace(sep, '.') for sep in self.sep: value = value.replace(sep, '.')
value = value.replace(self.tsep, '')
return self.pythonType(value) return self.pythonType(value)
class String(Type): class String(Type):
@ -1752,7 +1757,7 @@ class Ref(Type):
if type == 'uids': if type == 'uids':
ref = uids[i] ref = uids[i]
else: else:
ref = obj.portal_catalog(UID=uids[i])[0].getObject() ref = obj.uid_catalog(UID=uids[i])[0].getObject()
if type == 'objects': if type == 'objects':
ref = ref.appy() ref = ref.appy()
res.objects.append(ref) res.objects.append(ref)

View file

@ -452,7 +452,6 @@ class ZopeInstaller:
self.config.listTypes(self.productName) self.config.listTypes(self.productName)
contentTypes, constructors, ftis = self.config.process_types( contentTypes, constructors, ftis = self.config.process_types(
self.config.listTypes(self.productName), self.productName) self.config.listTypes(self.productName), self.productName)
self.config.cmfutils.ContentInit(self.productName + ' Content', self.config.cmfutils.ContentInit(self.productName + ' Content',
content_types = contentTypes, content_types = contentTypes,
permission = self.defaultAddContentPermission, permission = self.defaultAddContentPermission,

View file

@ -217,8 +217,8 @@ function getSlaveInfo(slave, infoType) {
function getMasterValues(master) { function getMasterValues(master) {
// Returns the list of values that p_master currently has. // Returns the list of values that p_master currently has.
if (master.tagName == 'SPAN') { if ((master.tagName == 'INPUT') && (master.type != 'checkbox')) {
res = master.attributes['value'].value; res = master.value;
if ((res[0] == '(') || (res[0] == '[')) { if ((res[0] == '(') || (res[0] == '[')) {
// There are multiple values, split it // There are multiple values, split it
values = res.substring(1, res.length-1).split(','); values = res.substring(1, res.length-1).split(',');
@ -253,11 +253,11 @@ function getSlaves(master) {
masterName = masterName.substr(0, masterName.length-8); masterName = masterName.substr(0, masterName.length-8);
} }
slavePrefix = 'slave_' + masterName + '_'; slavePrefix = 'slave_' + masterName + '_';
for (var i=0; i < slaves.length; i++){ for (var i=0; i < allSlaves.length; i++){
cssClasses = slaves[i].className.split(' '); cssClasses = allSlaves[i].className.split(' ');
for (var j=0; j < cssClasses.length; j++) { for (var j=0; j < cssClasses.length; j++) {
if (cssClasses[j].indexOf(slavePrefix) == 0) { if (cssClasses[j].indexOf(slavePrefix) == 0) {
res.push(slaves[i]); res.push(allSlaves[i]);
break; break;
} }
} }
@ -265,9 +265,13 @@ function getSlaves(master) {
return res; return res;
} }
function updateSlaves(master) { function updateSlaves(master, slave) {
// Given the value(s) in a master field, we must update slave's visibility. // Given the value(s) in a master field, we must update slave's visibility.
slaves = getSlaves(master); // If p_slave is given, it updates only this slave. Else, it updates all
// slaves of p_master.
var slaves = null;
if (slave) { slaves = [slave]; }
else { slaves = getSlaves(master); }
masterValues = getMasterValues(master); masterValues = getMasterValues(master);
for (var i=0; i < slaves.length; i++) { for (var i=0; i < slaves.length; i++) {
showSlave = false; showSlave = false;
@ -286,13 +290,12 @@ function initSlaves() {
// When the current page is loaded, we must set the correct state for all // When the current page is loaded, we must set the correct state for all
// slave fields. // slave fields.
slaves = document.getElementsByName('slave'); slaves = document.getElementsByName('slave');
walkedMasters = {}; // Remember the already walked masters. i = slaves.length -1;
for (var i=0; i < slaves.length; i++) { while (i >= 0) {
masterName = getSlaveInfo(slaves[i], 'masterName'); masterName = getSlaveInfo(slaves[i], 'masterName');
if (masterName in walkedMasters) continue;
master = document.getElementById(masterName); master = document.getElementById(masterName);
updateSlaves(master); updateSlaves(master, slaves[i]);
walkedMasters[masterName] = 'walked'; i -= 1;
} }
} }

View file

@ -1,7 +1,8 @@
<tal:comment replace="nothing">View macro for a Boolean.</tal:comment> <tal:comment replace="nothing">View macro for a Boolean.</tal:comment>
<metal:view define-macro="view"> <metal:view define-macro="view">
<span tal:attributes="class masterCss; value rawValue; name name; id name" <span tal:replace="value"></span>
tal:content="value"></span> <input type="hidden" tal:condition="masterCss"
tal:attributes="class masterCss; value rawValue; name name; id name"/>
</metal:view> </metal:view>
<tal:comment replace="nothing">Edit macro for an Boolean.</tal:comment> <tal:comment replace="nothing">Edit macro for an Boolean.</tal:comment>

View file

@ -1,7 +1,8 @@
<tal:comment replace="nothing">View macro for a Float.</tal:comment> <tal:comment replace="nothing">View macro for a Float.</tal:comment>
<metal:view define-macro="view"> <metal:view define-macro="view">
<span tal:content="value" <span tal:replace="value"></span>
tal:attributes="value value; class masterCss; name name; id name"></span> <input type="hidden" tal:condition="masterCss"
tal:attributes="class masterCss; value value; name name; id name"/>
</metal:view> </metal:view>
<tal:comment replace="nothing">Edit macro for an Float.</tal:comment> <tal:comment replace="nothing">Edit macro for an Float.</tal:comment>

View file

@ -1,7 +1,8 @@
<tal:comment replace="nothing">View macro for an Integer.</tal:comment> <tal:comment replace="nothing">View macro for an Integer.</tal:comment>
<metal:view define-macro="view"> <metal:view define-macro="view">
<span tal:content="value" <span tal:replace="value"></span>
tal:attributes="value value; class masterCss; name name; id name"></span> <input type="hidden" tal:condition="masterCss"
tal:attributes="class masterCss; value value; name name; id name"/>
</metal:view> </metal:view>
<tal:comment replace="nothing">Edit macro for an Integer.</tal:comment> <tal:comment replace="nothing">Edit macro for an Integer.</tal:comment>

View file

@ -130,7 +130,6 @@
The definition of "atMostOneRef" above may sound strange: we shouldn't check the actual number The definition of "atMostOneRef" above may sound strange: we shouldn't check the actual number
of referenced objects. But for back references people often forget to specify multiplicities. of referenced objects. But for back references people often forget to specify multiplicities.
So concretely, multiplicities (0,None) are coded as (0,1).</tal:comment> So concretely, multiplicities (0,None) are coded as (0,1).</tal:comment>
<tal:atMostOneReference condition="atMostOneRef"> <tal:atMostOneReference condition="atMostOneRef">
<tal:comment replace="nothing">Display a simplified widget if maximum number of <tal:comment replace="nothing">Display a simplified widget if maximum number of
referenced objects is 1.</tal:comment> referenced objects is 1.</tal:comment>

View file

@ -1,8 +1,7 @@
<tal:comment replace="nothing">View macro for a String.</tal:comment> <tal:comment replace="nothing">View macro for a String.</tal:comment>
<metal:view define-macro="view" <metal:view define-macro="view"
tal:define="fmt widget/format"> tal:define="fmt widget/format">
<span tal:condition="python: fmt in (0, 3)" <span tal:condition="python: fmt in (0, 3)">
tal:attributes="class masterCss; value rawValue; name name; id name">
<ul tal:condition="python: value and isMultiple"> <ul tal:condition="python: value and isMultiple">
<li tal:repeat="sv value"><i tal:content="structure sv"></i></li> <li tal:repeat="sv value"><i tal:content="structure sv"></i></li>
</ul> </ul>
@ -16,6 +15,8 @@
tal:replace="structure python: contextObj.formatText(value, format='html')"/> tal:replace="structure python: contextObj.formatText(value, format='html')"/>
<span tal:condition="python: value and (fmt == 2)" tal:replace="structure value"/> <span tal:condition="python: value and (fmt == 2)" tal:replace="structure value"/>
</tal:formattedString> </tal:formattedString>
<input type="hidden" tal:condition="masterCss"
tal:attributes="class masterCss; value rawValue; name name; id name"/>
</metal:view> </metal:view>
<tal:comment replace="nothing">Edit macro for a String.</tal:comment> <tal:comment replace="nothing">Edit macro for a String.</tal:comment>

View file

@ -148,6 +148,14 @@ class AbstractWrapper(object):
else: else:
folder = self.o.getParentNode() folder = self.o.getParentNode()
# Create the object # Create the object
# -------------------- Try to replace invokeFactory --------------------
#folder._objects = folder._objects + ({'id':id,'meta_type':portalType},)
#folder._setOb(id, ob)
#ploneObj = self._getOb(id)
#ob._setPortalTypeName(self.getId())
#ob.notifyWorkflowCreated()
# + Check what's done in Archetypes/ClassGen.py in m_genCtor
# ------------------------------ Try end -------------------------------
folder.invokeFactory(portalType, objId) folder.invokeFactory(portalType, objId)
ploneObj = getattr(folder, objId) ploneObj = getattr(folder, objId)
appyObj = ploneObj.appy() appyObj = ploneObj.appy()
@ -157,6 +165,7 @@ class AbstractWrapper(object):
if isField: if isField:
# Link the object to this one # Link the object to this one
appyType.linkObject(self.o, ploneObj) appyType.linkObject(self.o, ploneObj)
ploneObj._appy_managePermissions()
# Call custom initialization # Call custom initialization
if externalData: param = externalData if externalData: param = externalData
else: param = True else: param = True

View file

@ -237,6 +237,7 @@ class Renderer:
ns = self.currentParser.env.namespaces ns = self.currentParser.env.namespaces
# xhtmlString can only be a chunk of XHTML. So we must surround it a # xhtmlString can only be a chunk of XHTML. So we must surround it a
# tag in order to get a XML-compliant file (we need a root tag). # tag in order to get a XML-compliant file (we need a root tag).
if xhtmlString == None: xhtmlString = ''
xhtmlContent = '<p>%s</p>' % xhtmlString xhtmlContent = '<p>%s</p>' % xhtmlString
return Xhtml2OdtConverter(xhtmlContent, encoding, self.stylesManager, return Xhtml2OdtConverter(xhtmlContent, encoding, self.stylesManager,
stylesMapping, ns).run() stylesMapping, ns).run()
@ -244,6 +245,7 @@ class Renderer:
def renderText(self, text, encoding='utf-8', stylesMapping={}): def renderText(self, text, encoding='utf-8', stylesMapping={}):
'''Method that can be used (under the name 'text') into a pod template '''Method that can be used (under the name 'text') into a pod template
for inserting a text containing carriage returns.''' for inserting a text containing carriage returns.'''
if text == None: text = ''
text = cgi.escape(text).replace('\r\n', '<br/>').replace('\n', '<br/>') text = cgi.escape(text).replace('\r\n', '<br/>').replace('\n', '<br/>')
return self.renderXhtml(text, encoding, stylesMapping) return self.renderXhtml(text, encoding, stylesMapping)

View file

@ -2,13 +2,14 @@
import re, difflib import re, difflib
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
innerDiff = re.compile('<span name="(insert|delete)".*?>(.*?)</span>') innerDiff = re.compile('<span name="(insert|delete)".*? title="(.*?)">' \
'(.*?)</span>')
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class Merger: class Merger:
'''This class allows to merge 2 lines of text, each containing inserts and '''This class allows to merge 2 lines of text, each containing inserts and
deletions.''' deletions.'''
def __init__(self, lineA, lineB, previousDiffs): def __init__(self, lineA, lineB, previousDiffs, differ):
# lineA comes "naked": any diff previously found on it was removed from # lineA comes "naked": any diff previously found on it was removed from
# it (ie, deleted text has been completely removed, while inserted text # it (ie, deleted text has been completely removed, while inserted text
# has been included, but without its surrounding tag). Info about # has been included, but without its surrounding tag). Info about
@ -25,6 +26,11 @@ class Merger:
self.i = 0 self.i = 0
# The delta index that must be applied on previous diffs # The delta index that must be applied on previous diffs
self.deltaPrevious = 0 self.deltaPrevious = 0
# A link to the caller HtmlDiff class.
self.differ = differ
# While "consuming" diffs (see m_getNextDiff), keep here every message
# from every diff.
self.messages = [self.differ.complexMsg]
def computeNewDiffs(self): def computeNewDiffs(self):
'''lineB may include inner "insert" and/or tags. This function '''lineB may include inner "insert" and/or tags. This function
@ -68,10 +74,31 @@ class Merger:
del self.newDiffs[0] del self.newDiffs[0]
return newDiff, newDiffIndex, False return newDiff, newDiffIndex, False
def manageOverlaps(self):
'''We have detected that changes between lineA and lineB include
overlapping inserts and deletions. Our solution: to remember names
of editors and return the whole line in a distinct colour, where
we (unfortunately) can't distinguish editors's specific updates.'''
# First, get a "naked" version of self.lineB, without the latest
# updates.
res = self.lineB
for diff in self.newDiffs:
res = self.differ.applyDiff(res, diff)
# Construct the message explaining the series of updates.
# self.messages already contains messages from the "consumed" diffs
# (see m_getNextDiff).
for type in ('previous', 'new'):
exec 'diffs = self.%sDiffs' % type
for diff in diffs:
self.messages.append(diff.group(2))
msg = ' -=- '.join(self.messages)
return self.differ.getModifiedChunk(res, 'complex', '\n', msg=msg)
def merge(self): def merge(self):
'''Merges self.previousDiffs into self.lineB.''' '''Merges self.previousDiffs into self.lineB.'''
res = '' res = ''
diff, diffStart, isPrevious = self.getNextDiff() diff, diffStart, isPrevious = self.getNextDiff()
if diff: self.messages.append(diff.group(2))
while diff: while diff:
# Dump the part of lineB between self.i and diffStart # Dump the part of lineB between self.i and diffStart
res += self.lineB[self.i:diffStart] res += self.lineB[self.i:diffStart]
@ -80,7 +107,14 @@ class Merger:
res += diff.group(0) res += diff.group(0)
if isPrevious: if isPrevious:
if diff.group(1) == 'insert': if diff.group(1) == 'insert':
self.i += len(diff.group(2)) # Check if the inserted text is still present in lineB
if self.lineB[self.i:].startswith(diff.group(3)):
# Yes. Go ahead within lineB
self.i += len(diff.group(3))
else:
# The inserted text can't be found as is in lineB.
# Must have been (partly) re-edited or removed.
return self.manageOverlaps()
else: else:
# Update self.i # Update self.i
self.i += len(diff.group(0)) self.i += len(diff.group(0))
@ -92,9 +126,10 @@ class Merger:
# The indexes in lineA do not take the deleted text into # The indexes in lineA do not take the deleted text into
# account, because it wasn't deleted at this time. So remove # account, because it wasn't deleted at this time. So remove
# from self.deltaPrevious the length of removed text. # from self.deltaPrevious the length of removed text.
self.deltaPrevious -= len(diff.group(2)) self.deltaPrevious -= len(diff.group(3))
# Load next diff # Load next diff
diff, diffStart, isPrevious = self.getNextDiff() diff, diffStart, isPrevious = self.getNextDiff()
if diff: self.messages.append(diff.group(2))
# Dump the end of self.lineB if not completely consumed # Dump the end of self.lineB if not completely consumed
if self.i < len(self.lineB): if self.i < len(self.lineB):
res += self.lineB[self.i:] res += self.lineB[self.i:]
@ -106,11 +141,14 @@ class HtmlDiff:
HTML chunk.''' HTML chunk.'''
insertStyle = 'color: blue; cursor: help' insertStyle = 'color: blue; cursor: help'
deleteStyle = 'color: red; text-decoration: line-through; cursor: help' deleteStyle = 'color: red; text-decoration: line-through; cursor: help'
complexStyle = 'color: purple; cursor: help'
def __init__(self, old, new, def __init__(self, old, new,
insertMsg='Inserted text', deleteMsg='Deleted text', insertMsg='Inserted text', deleteMsg='Deleted text',
insertCss=None, deleteCss=None, insertName='insert', complexMsg='Multiple inserts and/or deletions',
deleteName='delete', diffRatio=0.7): insertCss=None, deleteCss=None, complexCss=None,
insertName='insert', deleteName='delete',
complexName='complex', diffRatio=0.7):
# p_old and p_new are strings containing chunks of HTML. # p_old and p_new are strings containing chunks of HTML.
self.old = old.strip() self.old = old.strip()
self.new = new.strip() self.new = new.strip()
@ -121,15 +159,18 @@ class HtmlDiff:
# (who made it and at what time, for example). # (who made it and at what time, for example).
self.insertMsg = insertMsg self.insertMsg = insertMsg
self.deleteMsg = deleteMsg self.deleteMsg = deleteMsg
self.complexMsg = complexMsg
# This tag will get a CSS class p_insertCss or p_deleteCss for # This tag will get a CSS class p_insertCss or p_deleteCss for
# highlighting the change. If no class is provided, default styles will # highlighting the change. If no class is provided, default styles will
# be used (see HtmlDiff.insertStyle and HtmlDiff.deleteStyle). # be used (see HtmlDiff.insertStyle and HtmlDiff.deleteStyle).
self.insertCss = insertCss self.insertCss = insertCss
self.deleteCss = deleteCss self.deleteCss = deleteCss
self.complexCss = complexCss
# This tag will get a "name" attribute whose content will be # This tag will get a "name" attribute whose content will be
# p_insertName or p_deleteName # p_insertName or p_deleteName
self.insertName = insertName self.insertName = insertName
self.deleteName = deleteName self.deleteName = deleteName
self.complexName = complexName
# The diff algorithm of this class will need to identify similarities # The diff algorithm of this class will need to identify similarities
# between strings. Similarity ratios will be computed by using method # between strings. Similarity ratios will be computed by using method
# difflib.SequenceMatcher.ratio (see m_isSimilar below). Strings whose # difflib.SequenceMatcher.ratio (see m_isSimilar below). Strings whose
@ -138,27 +179,33 @@ class HtmlDiff:
self.diffRatio = diffRatio self.diffRatio = diffRatio
# Some computed values # Some computed values
for tag in ('div', 'span'): for tag in ('div', 'span'):
setattr(self, '%sInsertPrefix' % tag, for type in ('insert', 'delete', 'complex'):
'<%s name="%s"' % (tag, self.insertName)) setattr(self, '%s%sPrefix' % (tag, type.capitalize()),
setattr(self, '%sDeletePrefix' % tag, '<%s name="%s"' % (tag, getattr(self, '%sName' % type)))
'<%s name="%s"' % (tag, self.deleteName))
def getModifiedChunk(self, seq, type, sep): def getModifiedChunk(self, seq, type, sep, msg=None):
'''p_sep.join(p_seq) (if p_seq is a list) or p_seq (if p_seq is a '''p_sep.join(p_seq) (if p_seq is a list) or p_seq (if p_seq is a
string) is a chunk that was either inserted (p_type='insert') or string) is a chunk that was either inserted (p_type='insert') or
deleted (p_type='delete'). This method will surround this part with deleted (p_type='delete'). It can also be a complex, partially
a div or span tag that will get some CSS class allowing to highlight managed combination of inserts/deletions (p_type='insert').
the difference.''' This method will surround this part with a div or span tag that will
# Prepare parts of the surrounding tag. get some CSS class allowing to highlight the update. If p_msg is
given, it will be used instead of the default p_type-related message
stored on p_self.'''
# Will the surrouding tag be a div or a span?
if sep == '\n': tag = 'div' if sep == '\n': tag = 'div'
else: tag = 'span' else: tag = 'span'
# What message wiill it show in its 'title' attribute?
if not msg:
exec 'msg = self.%sMsg' % type exec 'msg = self.%sMsg' % type
# What CSS class (or, if none, tag-specific style) will be used ?
exec 'cssClass = self.%sCss' % type exec 'cssClass = self.%sCss' % type
if cssClass: if cssClass:
style = 'class="%s"' % cssClass style = 'class="%s"' % cssClass
else: else:
exec 'style = self.%sStyle' % type exec 'style = self.%sStyle' % type
style = 'style="%s"' % style style = 'style="%s"' % style
# the 'name' attribute of the tag indicates the type of the update.
exec 'tagName = self.%sName' % type exec 'tagName = self.%sName' % type
# The idea is: if there are several lines, every line must be surrounded # The idea is: if there are several lines, every line must be surrounded
# by a tag. this way, we know that a surrounding tag can't span several # by a tag. this way, we know that a surrounding tag can't span several
@ -176,6 +223,16 @@ class HtmlDiff:
(sep, tag, tagName, style, msg, line, tag, sep) (sep, tag, tagName, style, msg, line, tag, sep)
return res return res
def applyDiff(self, line, diff):
'''p_diff is a regex containing an insert or delete that was found within
line. This function applies the diff, removing or inserting the diff
into p_line.'''
# Keep content only for "insert" tags.
content = ''
if diff.group(1) == 'insert':
content = diff.group(3)
return line[:diff.start()] + content + line[diff.end():]
def getStringDiff(self, old, new): def getStringDiff(self, old, new):
'''Identifies the differences between strings p_old and p_new by '''Identifies the differences between strings p_old and p_new by
computing: computing:
@ -255,11 +312,7 @@ class HtmlDiff:
if not match: break if not match: break
# I found one. # I found one.
innerDiffs.append(match) innerDiffs.append(match)
# Keep content only for "insert" tags. line = self.applyDiff(line, match)
content = ''
if match.group(1) == 'insert':
content = match.group(2)
line = line[:match.start()] + content + line[match.end():]
return (action, line, innerDiffs, outerTag) return (action, line, innerDiffs, outerTag)
def getSeqDiff(self, seqA, seqB): def getSeqDiff(self, seqA, seqB):
@ -414,7 +467,8 @@ class HtmlDiff:
# Merge potential previous inner diff tags that # Merge potential previous inner diff tags that
# were found (but extracted from) lineA. # were found (but extracted from) lineA.
if previousDiffsA: if previousDiffsA:
merger= Merger(lineA, toAdd, previousDiffsA) merger = Merger(lineA, toAdd,
previousDiffsA, self)
toAdd = merger.merge() toAdd = merger.merge()
# Rewrap line into outerTag if lineA was a line # Rewrap line into outerTag if lineA was a line
# tagged as previously inserted. # tagged as previously inserted.

View file

@ -209,18 +209,27 @@ def formatNumber(n, sep=',', precision=2, tsep=' '):
res = format % n res = format % n
# Use the correct decimal separator # Use the correct decimal separator
res = res.replace('.', sep) res = res.replace('.', sep)
# Format the integer part with tsep: TODO. # Insert p_tsep every 3 chars in the integer part of the number
splitted = res.split(sep) splitted = res.split(sep)
# Remove the decimal part if = 0 res = ''
i = len(splitted[0])-1
j = 0
while i >= 0:
j += 1
res = splitted[0][i] + res
if (j % 3) == 0:
res = tsep + res
i -= 1
# Add the decimal part if not 0
if len(splitted) > 1: if len(splitted) > 1:
try: try:
decPart = int(splitted[1]) decPart = int(splitted[1])
if decPart == 0: if decPart != 0:
res = splitted[0] res += sep + str(decPart)
except ValueError: except ValueError:
# This exception may occur when the float value has an "exp" # This exception may occur when the float value has an "exp"
# part, like in this example: 4.345e-05. # part, like in this example: 4.345e-05
pass res += sep + splitted[1]
return res return res
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------