|
149
|
1 |
import difflib, string |
|
|
2 |
from django.utils.translation import ugettext as _ |
|
|
3 |
|
|
|
4 |
def isatag(x): return x.startswith("<") and x.endswith(">") |
|
|
5 |
|
|
|
6 |
def text_diff(a, b): |
|
|
7 |
res = [] |
|
|
8 |
a, b = createlist(a), createlist(b) |
|
|
9 |
s = difflib.SequenceMatcher(isatag, a, b) |
|
|
10 |
for e in s.get_opcodes(): |
|
|
11 |
if e[0] == "replace": |
|
|
12 |
res.append('<del class="diff modified">'+''.join(a[e[1]:e[2]]) + '</del><ins class="diff modified">'+''.join(b[e[3]:e[4]])+"</ins>") |
|
|
13 |
elif e[0] == "delete": |
|
|
14 |
res.append('<del class="diff">'+ ''.join(a[e[1]:e[2]]) + "</del>") |
|
|
15 |
elif e[0] == "insert": |
|
|
16 |
res.append('<ins class="diff">'+''.join(b[e[3]:e[4]]) + "</ins>") |
|
|
17 |
elif e[0] == "equal": |
|
|
18 |
res.append(''.join(b[e[3]:e[4]])) |
|
|
19 |
else: |
|
|
20 |
raise "error parsing %s" %(e[0]) |
|
|
21 |
return ''.join(res) |
|
|
22 |
|
|
|
23 |
COLORS = [ |
|
|
24 |
'#FF0000', |
|
|
25 |
'#EE0000', |
|
|
26 |
'#DD0000', |
|
|
27 |
'#CC0000', |
|
|
28 |
'#BB0000', |
|
|
29 |
'#AA0000', |
|
|
30 |
'#990000', |
|
|
31 |
'#880000', |
|
|
32 |
'#770000', |
|
|
33 |
] |
|
|
34 |
|
|
|
35 |
from colorsys import hls_to_rgb, hsv_to_rgb |
|
|
36 |
|
|
|
37 |
def generatePastelColors(n): |
|
|
38 |
s = .4 |
|
|
39 |
v = .99 |
|
|
40 |
return ['#'+''.join((hex(int(r*255))[2:],hex(int(g*255))[2:],hex(int(b*255))[2:])) for r,g,b in [hsv_to_rgb(h/200.,s,v) for h in range(1,100,100/n)]] |
|
|
41 |
|
|
|
42 |
|
|
|
43 |
DEFAULT_COLOR = '#D9D9D9' |
|
|
44 |
def get_colors(authors, last_white = False): |
|
|
45 |
""" |
|
|
46 |
returns a dict for authors's colors (with last one white if 'last_white') |
|
|
47 |
""" |
|
|
48 |
authors = set(authors) |
|
|
49 |
#new_colors = list(tuple(COLORS)) |
|
|
50 |
new_colors = generatePastelColors(len(set(authors))) |
|
|
51 |
res = [] |
|
|
52 |
if None in authors or '' in authors: |
|
|
53 |
res = [(_(u'unknown'),DEFAULT_COLOR) ] |
|
|
54 |
res.extend([(author,new_colors.pop()) for author in authors if author]) |
|
|
55 |
#if authors[-1]: |
|
|
56 |
# res[authors[-1]] = '#FFFFFF' |
|
|
57 |
return res |
|
|
58 |
|
|
|
59 |
|
|
|
60 |
def text_history(versions, authors): |
|
|
61 |
res = versions[0] |
|
|
62 |
colors = get_colors(authors) |
|
|
63 |
for ind in range(len(versions)-1): |
|
|
64 |
author = authors[ind] |
|
|
65 |
color = colors.get(author,DEFAULT_COLOR) |
|
|
66 |
v_2 = versions[ind + 1] |
|
|
67 |
res = text_diff_add(v_2, res, color) |
|
|
68 |
return res,colors.items() |
|
|
69 |
|
|
|
70 |
from cm.utils.html import surrond_text_node |
|
|
71 |
|
|
|
72 |
def text_diff_add(a,b, color): |
|
|
73 |
res = [] |
|
|
74 |
a, b = createlist(a), createlist(b) |
|
|
75 |
s = difflib.SequenceMatcher(isatag, a, b) |
|
|
76 |
for e in s.get_opcodes(): |
|
|
77 |
if e[0] == "replace": |
|
|
78 |
html_chunk = ''.join(b[e[3]:e[4]]) |
|
|
79 |
new_html_chunk = surrond_text_node(html_chunk,'<span style="background: %s;">' %color,'</span>') |
|
|
80 |
res.append(new_html_chunk) |
|
|
81 |
#res.append('<font style="background: %s;" class="diff modified">' %color+''.join(b[e[3]:e[4]])+"</font>") |
|
|
82 |
elif e[0] == "delete": |
|
|
83 |
pass |
|
|
84 |
elif e[0] == "insert": |
|
|
85 |
html_chunk = ''.join(b[e[3]:e[4]]) |
|
|
86 |
new_html_chunk = surrond_text_node(html_chunk,'<span style="background: %s;">' %color,'</span>') |
|
|
87 |
res.append(new_html_chunk) |
|
|
88 |
#res.append('<font style="background: %s;" class="diff">' %color+''.join(b[e[3]:e[4]]) + "</font>") |
|
|
89 |
elif e[0] == "equal": |
|
|
90 |
res.append(''.join(b[e[3]:e[4]])) |
|
|
91 |
else: |
|
|
92 |
raise "error parsing %s" %(e[0]) |
|
|
93 |
return ''.join(res) |
|
|
94 |
|
|
|
95 |
def createlist(x, b=0): |
|
|
96 |
mode = 'char' |
|
|
97 |
cur = '' |
|
|
98 |
out = [] |
|
|
99 |
for c in x: |
|
|
100 |
if mode == 'tag': |
|
|
101 |
if c == '>': |
|
|
102 |
if b: cur += ']' |
|
|
103 |
else: cur += c |
|
|
104 |
out.append(cur); cur = ''; mode = 'char' |
|
|
105 |
else: cur += c |
|
|
106 |
elif mode == 'char': |
|
|
107 |
if c == '<': |
|
|
108 |
out.append(cur) |
|
|
109 |
if b: cur = '[' |
|
|
110 |
else: cur = c |
|
|
111 |
mode = 'tag' |
|
|
112 |
elif c in string.whitespace: out.append(cur+c); cur = '' |
|
|
113 |
else: cur += c |
|
|
114 |
out.append(cur) |
|
|
115 |
return filter(lambda x: x is not '', out) |
|
250
|
116 |
|
|
|
117 |
# |
|
|
118 |
|
|
251
|
119 |
from cm.ext.diff_match_patch import diff_match_patch |
|
250
|
120 |
|
|
|
121 |
class diff_match_patch2(diff_match_patch): |
|
|
122 |
def diff_prettyHtml_one_way(self, diffs, way=False, mode='red'): |
|
|
123 |
"""Convert a diff array into a pretty HTML report. |
|
|
124 |
|
|
|
125 |
Args: |
|
|
126 |
diffs: Array of diff tuples. |
|
|
127 |
way: [None, 1, 2] |
|
|
128 |
mode: ['ins_del', 'red'] |
|
|
129 |
|
|
|
130 |
Returns: |
|
|
131 |
HTML representation. |
|
|
132 |
""" |
|
|
133 |
html = [] |
|
|
134 |
i = 0 |
|
|
135 |
for (op, data) in diffs: |
|
|
136 |
text = data #(data.replace("&", "&").replace("<", "<") |
|
|
137 |
#.replace(">", ">").replace("\n", "¶<BR>")) |
|
|
138 |
if op == self.DIFF_INSERT and (not way or way==1): |
|
|
139 |
if mode=='red': |
|
|
140 |
html.append('<span class="diffchange-ins">%s</span>' % (text)) |
|
|
141 |
else: |
|
|
142 |
html.append('<ins>%s</ins>' % (text)) |
|
|
143 |
|
|
|
144 |
elif op == self.DIFF_DELETE and (not way or way==2): |
|
|
145 |
if mode=='red': |
|
|
146 |
html.append('<span class="diffchange-del">%s</span>'% (text)) |
|
|
147 |
else: |
|
|
148 |
html.append('<del>%s</del>'% (text)) |
|
|
149 |
elif op == self.DIFF_EQUAL: |
|
|
150 |
html.append("<SPAN>%s</SPAN>" % (text)) |
|
|
151 |
if op != self.DIFF_DELETE: |
|
|
152 |
i += len(data) |
|
|
153 |
return "".join(html) |