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:
parent
040cdafb8c
commit
8e1760842e
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(',');
|
||||||
|
@ -247,17 +247,17 @@ function getMasterValues(master) {
|
||||||
function getSlaves(master) {
|
function getSlaves(master) {
|
||||||
// Gets all the slaves of master.
|
// Gets all the slaves of master.
|
||||||
allSlaves = document.getElementsByName('slave');
|
allSlaves = document.getElementsByName('slave');
|
||||||
res = [];
|
res = [];
|
||||||
masterName = master.attributes['name'].value;
|
masterName = master.attributes['name'].value;
|
||||||
if (master.type == 'checkbox') {
|
if (master.type == 'checkbox') {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
exec 'msg = self.%sMsg' % type
|
# What message wiill it show in its 'title' attribute?
|
||||||
|
if not msg:
|
||||||
|
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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
Loading…
Reference in a new issue