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
					
				
					 12 changed files with 139 additions and 55 deletions
				
			
		|  | @ -2,13 +2,14 @@ | |||
| import re, difflib | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| innerDiff = re.compile('<span name="(insert|delete)".*?>(.*?)</span>') | ||||
| innerDiff = re.compile('<span name="(insert|delete)".*? title="(.*?)">' \ | ||||
|                        '(.*?)</span>') | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| class Merger: | ||||
|     '''This class allows to merge 2 lines of text, each containing inserts and | ||||
|        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 | ||||
|         # it (ie, deleted text has been completely removed, while inserted text | ||||
|         # has been included, but without its surrounding tag). Info about | ||||
|  | @ -25,6 +26,11 @@ class Merger: | |||
|         self.i = 0 | ||||
|         # The delta index that must be applied on previous diffs | ||||
|         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): | ||||
|         '''lineB may include inner "insert" and/or tags. This function | ||||
|  | @ -68,10 +74,31 @@ class Merger: | |||
|             del self.newDiffs[0] | ||||
|             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): | ||||
|         '''Merges self.previousDiffs into self.lineB.''' | ||||
|         res = '' | ||||
|         diff, diffStart, isPrevious = self.getNextDiff() | ||||
|         if diff: self.messages.append(diff.group(2)) | ||||
|         while diff: | ||||
|             # Dump the part of lineB between self.i and diffStart | ||||
|             res += self.lineB[self.i:diffStart] | ||||
|  | @ -80,7 +107,14 @@ class Merger: | |||
|             res += diff.group(0) | ||||
|             if isPrevious: | ||||
|                 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: | ||||
|                 # Update self.i | ||||
|                 self.i += len(diff.group(0)) | ||||
|  | @ -92,9 +126,10 @@ class Merger: | |||
|                     # The indexes in lineA do not take the deleted text into | ||||
|                     # account, because it wasn't deleted at this time. So remove | ||||
|                     # from self.deltaPrevious the length of removed text. | ||||
|                     self.deltaPrevious -= len(diff.group(2)) | ||||
|                     self.deltaPrevious -= len(diff.group(3)) | ||||
|             # Load next diff | ||||
|             diff, diffStart, isPrevious = self.getNextDiff() | ||||
|             if diff: self.messages.append(diff.group(2)) | ||||
|         # Dump the end of self.lineB if not completely consumed | ||||
|         if self.i < len(self.lineB): | ||||
|             res += self.lineB[self.i:] | ||||
|  | @ -106,11 +141,14 @@ class HtmlDiff: | |||
|        HTML chunk.''' | ||||
|     insertStyle = 'color: blue; cursor: help' | ||||
|     deleteStyle = 'color: red; text-decoration: line-through; cursor: help' | ||||
|     complexStyle = 'color: purple; cursor: help' | ||||
| 
 | ||||
|     def __init__(self, old, new, | ||||
|                  insertMsg='Inserted text', deleteMsg='Deleted text', | ||||
|                  insertCss=None, deleteCss=None, insertName='insert', | ||||
|                  deleteName='delete', diffRatio=0.7): | ||||
|                  complexMsg='Multiple inserts and/or deletions', | ||||
|                  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. | ||||
|         self.old = old.strip() | ||||
|         self.new = new.strip() | ||||
|  | @ -121,15 +159,18 @@ class HtmlDiff: | |||
|         # (who made it and at what time, for example). | ||||
|         self.insertMsg = insertMsg | ||||
|         self.deleteMsg = deleteMsg | ||||
|         self.complexMsg = complexMsg | ||||
|         # This tag will get a CSS class p_insertCss or p_deleteCss for | ||||
|         # highlighting the change. If no class is provided, default styles will | ||||
|         # be used (see HtmlDiff.insertStyle and HtmlDiff.deleteStyle). | ||||
|         self.insertCss = insertCss | ||||
|         self.deleteCss = deleteCss | ||||
|         self.complexCss = complexCss | ||||
|         # This tag will get a "name" attribute whose content will be | ||||
|         # p_insertName or p_deleteName | ||||
|         self.insertName = insertName | ||||
|         self.deleteName = deleteName | ||||
|         self.complexName = complexName | ||||
|         # The diff algorithm of this class will need to identify similarities | ||||
|         # between strings. Similarity ratios will be computed by using method | ||||
|         # difflib.SequenceMatcher.ratio (see m_isSimilar below). Strings whose | ||||
|  | @ -138,27 +179,33 @@ class HtmlDiff: | |||
|         self.diffRatio = diffRatio | ||||
|         # Some computed values | ||||
|         for tag in ('div', 'span'): | ||||
|             setattr(self, '%sInsertPrefix' % tag, | ||||
|                     '<%s name="%s"' % (tag, self.insertName)) | ||||
|             setattr(self, '%sDeletePrefix' % tag, | ||||
|                     '<%s name="%s"' % (tag, self.deleteName)) | ||||
|             for type in ('insert', 'delete', 'complex'): | ||||
|                 setattr(self, '%s%sPrefix' % (tag, type.capitalize()), | ||||
|                         '<%s name="%s"' % (tag, getattr(self, '%sName' % type))) | ||||
| 
 | ||||
|     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 | ||||
|            string) is a chunk that was either inserted (p_type='insert') or | ||||
|            deleted (p_type='delete'). This method will surround this part with | ||||
|            a div or span tag that will get some CSS class allowing to highlight | ||||
|            the difference.''' | ||||
|         # Prepare parts of the surrounding tag. | ||||
|            deleted (p_type='delete'). It can also be a complex, partially | ||||
|            managed combination of inserts/deletions (p_type='insert'). | ||||
|            This method will surround this part with a div or span tag that will | ||||
|            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' | ||||
|         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 | ||||
|         if cssClass: | ||||
|             style = 'class="%s"' % cssClass | ||||
|         else: | ||||
|             exec 'style = self.%sStyle' % type | ||||
|             style = 'style="%s"' % style | ||||
|         # the 'name' attribute of the tag indicates the type of the update. | ||||
|         exec 'tagName = self.%sName' % type | ||||
|         # 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 | ||||
|  | @ -176,6 +223,16 @@ class HtmlDiff: | |||
|                        (sep, tag, tagName, style, msg, line, tag, sep) | ||||
|             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): | ||||
|         '''Identifies the differences between strings p_old and p_new by | ||||
|            computing: | ||||
|  | @ -255,11 +312,7 @@ class HtmlDiff: | |||
|             if not match: break | ||||
|             # I found one. | ||||
|             innerDiffs.append(match) | ||||
|             # Keep content only for "insert" tags. | ||||
|             content = '' | ||||
|             if match.group(1) == 'insert': | ||||
|                 content = match.group(2) | ||||
|             line = line[:match.start()] + content + line[match.end():] | ||||
|             line = self.applyDiff(line, match) | ||||
|         return (action, line, innerDiffs, outerTag) | ||||
| 
 | ||||
|     def getSeqDiff(self, seqA, seqB): | ||||
|  | @ -414,7 +467,8 @@ class HtmlDiff: | |||
|                                 # Merge potential previous inner diff tags that | ||||
|                                 # were found (but extracted from) lineA. | ||||
|                                 if previousDiffsA: | ||||
|                                     merger= Merger(lineA, toAdd, previousDiffsA) | ||||
|                                     merger = Merger(lineA, toAdd, | ||||
|                                                     previousDiffsA, self) | ||||
|                                     toAdd = merger.merge() | ||||
|                                 # Rewrap line into outerTag if lineA was a line | ||||
|                                 # tagged as previously inserted. | ||||
|  |  | |||
|  | @ -209,18 +209,27 @@ def formatNumber(n, sep=',', precision=2, tsep=' '): | |||
|         res = format % n | ||||
|     # Use the correct decimal separator | ||||
|     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) | ||||
|     # 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: | ||||
|         try: | ||||
|             decPart = int(splitted[1]) | ||||
|             if decPart == 0: | ||||
|                 res = splitted[0] | ||||
|             if decPart != 0: | ||||
|                 res += sep + str(decPart) | ||||
|         except ValueError: | ||||
|             # This exception may occur when the float value has an "exp" | ||||
|             # part, like in this example: 4.345e-05. | ||||
|             pass | ||||
|             # part, like in this example: 4.345e-05 | ||||
|             res += sep + splitted[1] | ||||
|     return res | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Gaetan Delannay
						Gaetan Delannay