@matrixreq/htmldiff 1.3.0 → 1.4.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.
- package/js/htmldiff.js +42 -18
- package/package.json +1 -1
- package/test/html_to_tokens.spec.js +37 -0
package/js/htmldiff.js
CHANGED
|
@@ -89,17 +89,30 @@
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* @param {string} tag The ending tag to look for.
|
|
97
|
-
*
|
|
98
|
-
* @return {boolean} True if the word is now a complete token (including the end tag),
|
|
99
|
-
* false otherwise.
|
|
92
|
+
* Inspects the last tag in the given string its slice from the final '<'. A '>' before the slice's end
|
|
93
|
+
* means text follows e.g. "a > b" in a <script>, not a clean tag, so returns false.
|
|
94
|
+
* @return {boolean} True if word ends with an opening (non-self-closing) tag for the given
|
|
95
|
+
* tag name.
|
|
100
96
|
*/
|
|
101
|
-
function
|
|
102
|
-
|
|
97
|
+
function isOpeningTagOf(word, tag){
|
|
98
|
+
var tagText = word.substring(word.lastIndexOf('<'));
|
|
99
|
+
if (tagText.indexOf('>') !== tagText.length - 1) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return new RegExp('^<' + tag + '(\\s|>)').test(tagText) && !/\/>$/.test(tagText);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Inspects the last tag in the given string its slice from the final '<'. A '>' before the slice's end
|
|
107
|
+
* means text follows e.g. "a > b" in a <script>, not a clean tag, so returns false.
|
|
108
|
+
* @return {boolean} True if word ends with a closing tag for the given tag name.
|
|
109
|
+
*/
|
|
110
|
+
function isClosingTagOf(word, tag){
|
|
111
|
+
var tagText = word.substring(word.lastIndexOf('<'));
|
|
112
|
+
if (tagText.indexOf('>') !== tagText.length - 1) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return new RegExp('^</' + tag + '(\\s|>)').test(tagText);
|
|
103
116
|
}
|
|
104
117
|
|
|
105
118
|
/**
|
|
@@ -175,6 +188,7 @@
|
|
|
175
188
|
var mode = 'char';
|
|
176
189
|
var currentWord = '';
|
|
177
190
|
var currentAtomicTag = '';
|
|
191
|
+
var currentAtomicTagDepth = 0;
|
|
178
192
|
var words = [];
|
|
179
193
|
for (var i = 0; i < html.length; i++){
|
|
180
194
|
var char = html[i];
|
|
@@ -184,6 +198,8 @@
|
|
|
184
198
|
if (atomicTag){
|
|
185
199
|
mode = 'atomic_tag';
|
|
186
200
|
currentAtomicTag = atomicTag;
|
|
201
|
+
// skip standalone tags like <script> and <style>
|
|
202
|
+
currentAtomicTagDepth = isEndOfTag(char) ? 1 : 0;
|
|
187
203
|
currentWord += char;
|
|
188
204
|
} else if (isStartofHTMLComment(currentWord)){
|
|
189
205
|
mode = 'html_comment';
|
|
@@ -202,14 +218,22 @@
|
|
|
202
218
|
}
|
|
203
219
|
break;
|
|
204
220
|
case 'atomic_tag':
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
221
|
+
currentWord += char;
|
|
222
|
+
// track the same name nested tags depth;
|
|
223
|
+
// end the atomic token only when it returns to 0.
|
|
224
|
+
if (isEndOfTag(char)){
|
|
225
|
+
if (isClosingTagOf(currentWord, currentAtomicTag)){
|
|
226
|
+
currentAtomicTagDepth--;
|
|
227
|
+
if (currentAtomicTagDepth <= 0){
|
|
228
|
+
words.push(createToken(currentWord));
|
|
229
|
+
currentWord = '';
|
|
230
|
+
currentAtomicTag = '';
|
|
231
|
+
currentAtomicTagDepth = 0;
|
|
232
|
+
mode = 'char';
|
|
233
|
+
}
|
|
234
|
+
} else if (isOpeningTagOf(currentWord, currentAtomicTag)){
|
|
235
|
+
currentAtomicTagDepth++;
|
|
236
|
+
}
|
|
213
237
|
}
|
|
214
238
|
break;
|
|
215
239
|
case 'html_comment':
|
package/package.json
CHANGED
|
@@ -124,5 +124,42 @@ describe('htmlToTokens', function(){
|
|
|
124
124
|
]
|
|
125
125
|
));
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
describe('nested atomic tags wrapping', function(){
|
|
129
|
+
it('should keep a data-htmldiff-id wrapper with nested same-tag children as one token', function(){
|
|
130
|
+
var atomic = '<span data-htmldiff-id="u1">' +
|
|
131
|
+
'<span class="tooltip"><span class="avatar">AB</span></span>' +
|
|
132
|
+
'Name</span>';
|
|
133
|
+
expect(cut('<div>' + atomic + '</div>')).eql(
|
|
134
|
+
tokenize(['<div>', atomic, '</div>']));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should not close early on the first inner closing tag', function(){
|
|
138
|
+
var atomic = '<span data-htmldiff-id="1"><span>a</span><span>b</span></span>';
|
|
139
|
+
expect(cut(atomic)).eql(tokenize([atomic]));
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should not treat a stray ">" in script content as a tag boundary', function(){
|
|
143
|
+
var atomic = '<script>if (a > b) { return a > 0; }</script>';
|
|
144
|
+
expect(cut('<p>' + atomic + '</p>')).eql(
|
|
145
|
+
tokenize(['<p>', atomic, '</p>']));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should ignore self-closing same-named children when counting depth', function(){
|
|
149
|
+
var atomic = '<span data-htmldiff-id="1">x<span/>y</span>';
|
|
150
|
+
expect(cut(atomic)).eql(tokenize([atomic]));
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should ignore self-closing same-named children written with a space (<span />)', function(){
|
|
154
|
+
var atomic = '<span data-htmldiff-id="1">x<span />y</span>';
|
|
155
|
+
expect(cut(atomic)).eql(tokenize([atomic]));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should not bump depth on differently-named tags that share a prefix', function(){
|
|
159
|
+
// a tag must not be matched by an atomic tag named "a" appearing as <article>.
|
|
160
|
+
var atomic = '<a href="x"><article>hi</article></a>';
|
|
161
|
+
expect(cut(atomic)).eql(tokenize([atomic]));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
127
164
|
});
|
|
128
165
|
});
|