|
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) |