Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 37 additions & 18 deletions js/htmldiff.js
Comment thread
sirenevenkii marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,26 @@
return result && result[1];
}

// inspect the last tag in word (from its opening '<'); to an earlier '>' in that
// slice means loose text e.g. "a > b" in a <script>, not a tag, so they bail out.
Comment on lines +91 to +92

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why those comment are inline?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

paraphrase it, I'm struggling to understand what you mean here


/**
* Checks if the current word is the end of an atomic tag (i.e. it has all the characters,
* except for the end bracket of the closing tag, such as '<iframe></iframe').
*
* @param {string} word The characters of the current token read so far.
* @param {string} tag The ending tag to look for.
*
* @return {boolean} True if the word is now a complete token (including the end tag),
* false otherwise.
* @return {boolean} True if word ends with an opening (non-self-closing) tag for the given
* atomic tag name.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not about specifically atomic tags, isn't? same for isClosingTagOf comment

*/
function isOpeningTagOf(word, tag){
var tagText = word.substring(word.lastIndexOf('<'));
if (tagText.indexOf('>') !== tagText.length - 1) return false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not use inline conditions. same for closing tag

return new RegExp('^<' + tag + '(\\s|>)').test(tagText) && !/\/>$/.test(tagText);
}

/**
* @return {boolean} True if word ends with a closing tag for the given atomic tag name.
*/
function isEndOfAtomicTag(word, tag){
return word.substring(word.length - tag.length - 2) === ('</' + tag);
function isClosingTagOf(word, tag){
var tagText = word.substring(word.lastIndexOf('<'));
if (tagText.indexOf('>') !== tagText.length - 1) return false;
return new RegExp('^</' + tag + '(\\s|>)').test(tagText);
}

/**
Expand Down Expand Up @@ -175,6 +183,7 @@
var mode = 'char';
var currentWord = '';
var currentAtomicTag = '';
var currentAtomicTagDepth = 0;
var words = [];
for (var i = 0; i < html.length; i++){
var char = html[i];
Expand All @@ -184,6 +193,8 @@
if (atomicTag){
mode = 'atomic_tag';
currentAtomicTag = atomicTag;
// skip standalone tags like <script> and <style>
currentAtomicTagDepth = isEndOfTag(char) ? 1 : 0;
currentWord += char;
} else if (isStartofHTMLComment(currentWord)){
mode = 'html_comment';
Expand All @@ -202,14 +213,22 @@
}
break;
case 'atomic_tag':
if (isEndOfTag(char) && isEndOfAtomicTag(currentWord, currentAtomicTag)){
currentWord += '>';
words.push(createToken(currentWord));
currentWord = '';
currentAtomicTag = '';
mode = 'char';
} else {
currentWord += char;
currentWord += char;
// track the same name nested tags depth;
// end the atomic token only when it returns to 0.
if (isEndOfTag(char)){
if (isClosingTagOf(currentWord, currentAtomicTag)){
currentAtomicTagDepth--;
if (currentAtomicTagDepth <= 0){
words.push(createToken(currentWord));
currentWord = '';
currentAtomicTag = '';
currentAtomicTagDepth = 0;
mode = 'char';
Comment thread
sirenevenkii marked this conversation as resolved.
}
} else if (isOpeningTagOf(currentWord, currentAtomicTag)){
currentAtomicTagDepth++;
}
}
break;
case 'html_comment':
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@matrixreq/htmldiff",
"description": "Diff and markup HTML with <ins> and <del> tags",
"companyname": "Matrix Requirements",
"version": "1.3.0",
"version": "1.4.0",
"keywords": [
"diff",
"html",
Expand Down
37 changes: 37 additions & 0 deletions test/html_to_tokens.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,42 @@ describe('htmlToTokens', function(){
]
));
});

describe('nested atomic tags wrapping', function(){
it('should keep a data-htmldiff-id wrapper with nested same-tag children as one token', function(){
var atomic = '<span data-htmldiff-id="u1">' +
'<span class="tooltip"><span class="avatar">AB</span></span>' +
'Name</span>';
expect(cut('<div>' + atomic + '</div>')).eql(
tokenize(['<div>', atomic, '</div>']));
});

it('should not close early on the first inner closing tag', function(){
var atomic = '<span data-htmldiff-id="1"><span>a</span><span>b</span></span>';
expect(cut(atomic)).eql(tokenize([atomic]));
});

it('should not treat a stray ">" in script content as a tag boundary', function(){
var atomic = '<script>if (a > b) { return a > 0; }</script>';
expect(cut('<p>' + atomic + '</p>')).eql(
tokenize(['<p>', atomic, '</p>']));
});

it('should ignore self-closing same-named children when counting depth', function(){
var atomic = '<span data-htmldiff-id="1">x<span/>y</span>';
expect(cut(atomic)).eql(tokenize([atomic]));
});

it('should ignore self-closing same-named children written with a space (<span />)', function(){
var atomic = '<span data-htmldiff-id="1">x<span />y</span>';
expect(cut(atomic)).eql(tokenize([atomic]));
});

it('should not bump depth on differently-named tags that share a prefix', function(){
// a tag must not be matched by an atomic tag named "a" appearing as <article>.
var atomic = '<a href="x"><article>hi</article></a>';
expect(cut(atomic)).eql(tokenize([atomic]));
});
});
});
});
Loading