nokodiff 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +111 -1
- data/lib/nokodiff/differ.rb +3 -3
- data/lib/nokodiff/html_fragment.rb +40 -0
- data/lib/nokodiff/version.rb +1 -1
- data/lib/nokodiff.rb +24 -26
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e12bce0257af1ebff2c6de595dce5d68cefc73feb5ae4a419a98a81d9dbac212
|
|
4
|
+
data.tar.gz: 9479aa3c4ae7f3481a0a96baf002735fb02a4848530a9fc38565c72118e4a2eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 864f165ec8332dbcc0a61cffda45f6649ed00d5483e969ad7d55dc9af2e4acdbe202f54686424ff387f6f36ecd160b3834e89f4d1633a8bf311534b6c09eb0b3
|
|
7
|
+
data.tar.gz: f182809b76cf85e8ce23002575fb5c2708e527cd3659628d0ef248fdce79e1581ed6a551f99ca41a16dff7bacf9d67db87579185761052136e3f491d227f8bf6
|
data/README.md
CHANGED
|
@@ -50,6 +50,116 @@ In your application.scss file include:
|
|
|
50
50
|
|
|
51
51
|
This will include the styling for `<del>`, `<ins>` and `<strong>` tags to allow colour coding, highlighting and underlining of changes.
|
|
52
52
|
|
|
53
|
+
### More complex diffing with `data-diff-key`
|
|
54
|
+
|
|
55
|
+
For more complex HTML structures, a standard diff can produce surprising or misleading results when elements are added, removed, or reordered. To get around this, add a `data-diff-key` attribute to elements you want compared in isolation.
|
|
56
|
+
|
|
57
|
+
Each element with a unique `data-diff-key` value is diffed independently against its counterpart in the other HTML fragment. This prevents unrelated changes from affecting each other and produces more accurate, contextually meaningful output.
|
|
58
|
+
|
|
59
|
+
The key value can be any unique string — it just needs to match between your `before` and `after` HTML for the elements you want paired together.
|
|
60
|
+
|
|
61
|
+
#### Adding a new element
|
|
62
|
+
|
|
63
|
+
When a `data-diff-key` element is present in `after` but not in `before`, its content is wrapped in an `<ins>` tag:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
before = <<~HTML
|
|
67
|
+
<div>
|
|
68
|
+
<div data-diff-key="ixn4">
|
|
69
|
+
<p>First paragraph</p>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
HTML
|
|
73
|
+
|
|
74
|
+
after = <<~HTML
|
|
75
|
+
<div>
|
|
76
|
+
<div data-diff-key="ixn4">
|
|
77
|
+
<p>First paragraph</p>
|
|
78
|
+
</div>
|
|
79
|
+
<div data-diff-key="zm7q">
|
|
80
|
+
<p>Second paragraph</p>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
HTML
|
|
84
|
+
|
|
85
|
+
Nokodiff.diff(before, after)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Output:
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<div>
|
|
92
|
+
<div data-diff-key="ixn4">
|
|
93
|
+
<p>First paragraph</p>
|
|
94
|
+
</div>
|
|
95
|
+
<div data-diff-key="zm7q">
|
|
96
|
+
<div class="diff">
|
|
97
|
+
<ins aria-label="added content"><p>Second paragraph</p></ins>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+

|
|
104
|
+
|
|
105
|
+
#### Modifying an existing element
|
|
106
|
+
|
|
107
|
+
When a `data-diff-key` element exists in both fragments but its content has changed, the element is diffed in isolation. Character-level changes are highlighted within `<del>` and `<ins>` tags:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
before = <<~HTML
|
|
111
|
+
<div>
|
|
112
|
+
<div data-diff-key="ixn4">
|
|
113
|
+
<p>First paragraph</p>
|
|
114
|
+
</div>
|
|
115
|
+
<div data-diff-key="zm7q">
|
|
116
|
+
<p>Second paragraph</p>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
HTML
|
|
120
|
+
|
|
121
|
+
after = <<~HTML
|
|
122
|
+
<div>
|
|
123
|
+
<div data-diff-key="ixn4">
|
|
124
|
+
<p>First paragraph</p>
|
|
125
|
+
</div>
|
|
126
|
+
<div data-diff-key="zm7q">
|
|
127
|
+
<p>New paragraph</p>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
HTML
|
|
131
|
+
|
|
132
|
+
Nokodiff.diff(before, after)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Output:
|
|
136
|
+
|
|
137
|
+
```html
|
|
138
|
+
<div>
|
|
139
|
+
<div data-diff-key="ixn4"><p>First paragraph</p></div>
|
|
140
|
+
<div data-diff-key="zm7q">
|
|
141
|
+
<div class="diff">
|
|
142
|
+
<del aria-label="removed content"><p><strong>S</strong>e<strong>cond</strong> paragraph</p></del>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="diff">
|
|
145
|
+
<ins aria-label="added content"><p><strong>N</strong>e<strong>w</strong> paragraph</p></ins>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+

|
|
152
|
+
|
|
153
|
+
#### Unchanged elements
|
|
154
|
+
|
|
155
|
+
If a `data-diff-key` element's content is identical in both fragments, no diff markup is added and the element is returned as-is.
|
|
156
|
+
|
|
157
|
+
#### Limitations
|
|
158
|
+
|
|
159
|
+
Currently, the gem does not support highlighting removed `data-diff-key` elements. If a keyed element is present in `before` but absent from `after`, it will not be marked as deleted in the output.
|
|
160
|
+
|
|
161
|
+
Thanks to [HTML Diff](https://html-diff.lix.dev/index.html) for inspiring this approach!
|
|
162
|
+
|
|
53
163
|
## Licence
|
|
54
164
|
|
|
55
|
-
The gem is available as open source under the terms of the [MIT License.](https://opensource.org/license/MIT)
|
|
165
|
+
The gem is available as open source under the terms of the [MIT License.](https://opensource.org/license/MIT)
|
data/lib/nokodiff/differ.rb
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
|
|
3
|
+
module Nokodiff
|
|
4
|
+
class HTMLFragment
|
|
5
|
+
extend Forwardable
|
|
6
|
+
|
|
7
|
+
class InvalidHTMLError < StandardError; end
|
|
8
|
+
|
|
9
|
+
def initialize(html)
|
|
10
|
+
@fragment = Nokogiri::HTML.fragment(html)
|
|
11
|
+
validate!
|
|
12
|
+
remove_blank_nodes!
|
|
13
|
+
remove_comments!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def_delegators :@fragment, :children, :css, :at, :to_html
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def validate!
|
|
21
|
+
invalid_text_nodes = @fragment.children.reject do |node|
|
|
22
|
+
node.element? || node.comment? || (node.text? && node.text.strip.empty?)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
unless invalid_text_nodes.empty?
|
|
26
|
+
raise InvalidHTMLError, "Invalid HTML input: #{@fragment.to_html}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def remove_blank_nodes!
|
|
31
|
+
@fragment.traverse do |node|
|
|
32
|
+
node.remove if node.blank?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def remove_comments!
|
|
37
|
+
@fragment.css("comment()").remove
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/nokodiff/version.rb
CHANGED
data/lib/nokodiff.rb
CHANGED
|
@@ -9,13 +9,17 @@ require_relative "nokodiff/differ"
|
|
|
9
9
|
require_relative "nokodiff/engine"
|
|
10
10
|
require_relative "nokodiff/text_node_diffs"
|
|
11
11
|
require_relative "nokodiff/changes_in_fragments"
|
|
12
|
+
require_relative "nokodiff/html_fragment"
|
|
12
13
|
|
|
13
14
|
module Nokodiff
|
|
14
15
|
def self.diff(before_html, after_html)
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
before = Nokodiff::HTMLFragment.new(before_html)
|
|
17
|
+
after = Nokodiff::HTMLFragment.new(after_html)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
before_nodes, after_nodes = nodes(before, after)
|
|
20
|
+
keys = (before_nodes.keys + after_nodes.keys).uniq
|
|
21
|
+
|
|
22
|
+
html = keys.any? ? diff_by_keys(after, keys, before_nodes, after_nodes) : Differ.new(before, after).to_html
|
|
19
23
|
safe_html(html)
|
|
20
24
|
end
|
|
21
25
|
|
|
@@ -23,31 +27,25 @@ module Nokodiff
|
|
|
23
27
|
html.respond_to?(:html_safe) ? html.html_safe : html
|
|
24
28
|
end
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
document = Nokogiri::HTML::DocumentFragment.parse(html)
|
|
33
|
-
|
|
34
|
-
invalid_text_nodes = document.children.select do |node|
|
|
35
|
-
if node.element?
|
|
36
|
-
false
|
|
37
|
-
elsif node.comment?
|
|
38
|
-
false
|
|
39
|
-
elsif node.text?
|
|
40
|
-
!node.text.strip.empty?
|
|
41
|
-
else
|
|
42
|
-
true
|
|
43
|
-
end
|
|
44
|
-
end
|
|
30
|
+
private_class_method def self.nodes(before, after)
|
|
31
|
+
[
|
|
32
|
+
fetch_diff_nodes(before),
|
|
33
|
+
fetch_diff_nodes(after),
|
|
34
|
+
]
|
|
35
|
+
end
|
|
45
36
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
private_class_method def self.fetch_diff_nodes(fragment)
|
|
38
|
+
fragment.css("[data-diff-key]").map { |node| [node["data-diff-key"], node] }.to_h
|
|
39
|
+
end
|
|
49
40
|
|
|
50
|
-
|
|
41
|
+
private_class_method def self.diff_by_keys(after, keys, before_nodes, after_nodes)
|
|
42
|
+
keys.each do |key|
|
|
43
|
+
diff = Differ.new(
|
|
44
|
+
before_nodes.fetch(key, Nokogiri::HTML.fragment("")),
|
|
45
|
+
after_nodes.fetch(key, Nokogiri::HTML.fragment("")),
|
|
46
|
+
).to_html
|
|
47
|
+
after.at("[data-diff-key='#{key}']")&.inner_html = diff
|
|
51
48
|
end
|
|
49
|
+
after.to_html
|
|
52
50
|
end
|
|
53
51
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nokodiff
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- GOV.UK Dev
|
|
@@ -71,14 +71,14 @@ dependencies:
|
|
|
71
71
|
requirements:
|
|
72
72
|
- - '='
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: 5.
|
|
74
|
+
version: 5.2.0
|
|
75
75
|
type: :development
|
|
76
76
|
prerelease: false
|
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - '='
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: 5.
|
|
81
|
+
version: 5.2.0
|
|
82
82
|
- !ruby/object:Gem::Dependency
|
|
83
83
|
name: simplecov
|
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -209,6 +209,7 @@ files:
|
|
|
209
209
|
- lib/nokodiff/differ.rb
|
|
210
210
|
- lib/nokodiff/engine.rb
|
|
211
211
|
- lib/nokodiff/formatting_helpers.rb
|
|
212
|
+
- lib/nokodiff/html_fragment.rb
|
|
212
213
|
- lib/nokodiff/text_node_diffs.rb
|
|
213
214
|
- lib/nokodiff/version.rb
|
|
214
215
|
homepage: https://github.com/alphagov/nokodiff
|
|
@@ -229,7 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
229
230
|
- !ruby/object:Gem::Version
|
|
230
231
|
version: '0'
|
|
231
232
|
requirements: []
|
|
232
|
-
rubygems_version: 4.0.
|
|
233
|
+
rubygems_version: 4.0.7
|
|
233
234
|
specification_version: 4
|
|
234
235
|
summary: A Ruby Gem to highlight additions, deletions and character level changes
|
|
235
236
|
while preserving original HTML
|