summaryrefslogtreecommitdiff
path: root/lib/html_word_diff.rb
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-13 23:33:11 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-13 23:33:11 +0200
commit8eab68f2c5a3c126e2fec2ecdfa7ace87ce0937b (patch)
treef446ebc26a7707c7b64a937aa51a155df146c80a /lib/html_word_diff.rb
parent42714c697273a7117c6b355fab26c8c35e336ad1 (diff)
parentcdf5d9941ca866d437612d2f863eac6eb0b3db12 (diff)
Merge branch 'erdgeist-revive-events'
Diffstat (limited to 'lib/html_word_diff.rb')
-rw-r--r--lib/html_word_diff.rb74
1 files changed, 74 insertions, 0 deletions
diff --git a/lib/html_word_diff.rb b/lib/html_word_diff.rb
new file mode 100644
index 0000000..aa0cb86
--- /dev/null
+++ b/lib/html_word_diff.rb
@@ -0,0 +1,74 @@
1require 'diff/lcs'
2require 'cgi'
3
4module HtmlWordDiff
5 TOKEN_REGEXP = /<[^>]*>|[[:space:]]+|[[:alnum:]]+|[^[:space:][:alnum:]<>]/
6
7 def self.inline(old_html, new_html)
8 html = +''
9 each_change(old_html, new_html) do |action, old_token, new_token|
10 case action
11 when '='
12 html << new_token
13 when '-'
14 html << wrap_removed(old_token)
15 when '+'
16 html << wrap_inserted(new_token)
17 when '!'
18 html << wrap_removed(old_token)
19 html << wrap_inserted(new_token)
20 end
21 end
22 html
23 end
24
25 def self.side_by_side(old_html, new_html)
26 old_out = +''
27 new_out = +''
28 each_change(old_html, new_html) do |action, old_token, new_token|
29 case action
30 when '='
31 old_out << old_token
32 new_out << new_token
33 when '-'
34 old_out << wrap_removed(old_token)
35 when '+'
36 new_out << wrap_inserted(new_token)
37 when '!'
38 old_out << wrap_removed(old_token)
39 new_out << wrap_inserted(new_token)
40 end
41 end
42 [old_out, new_out]
43 end
44
45 def self.each_change(old_html, new_html)
46 Diff::LCS.sdiff(tokenize(old_html), tokenize(new_html)).each do |change|
47 yield change.action, change.old_element, change.new_element
48 end
49 end
50
51 def self.tokenize(html)
52 html.to_s.scan(TOKEN_REGEXP)
53 end
54
55 def self.tag_token?(token)
56 token.to_s.match?(/\A<[^>]*>\z/)
57 end
58
59 def self.wrap_removed(token)
60 if tag_token?(token)
61 %(<del class="diff_structural" title="removed: #{CGI.escapeHTML(token)}">\u00b6</del>)
62 else
63 "<del>#{token}</del>"
64 end
65 end
66
67 def self.wrap_inserted(token)
68 if tag_token?(token)
69 %(<ins class="diff_structural" title="added: #{CGI.escapeHTML(token)}">\u00b6</ins>)
70 else
71 "<ins>#{token}</ins>"
72 end
73 end
74end