@matrixreq/htmldiff 1.5.0 → 2.0.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/README.md +76 -8
- package/js/constants.d.ts +3 -0
- package/js/constants.js +6 -0
- package/js/core/atomicTags.d.ts +10 -0
- package/js/core/atomicTags.js +28 -0
- package/js/core/diff.d.ts +2 -0
- package/js/core/diff.js +15 -0
- package/js/core/matching.d.ts +27 -0
- package/js/core/matching.js +207 -0
- package/js/core/operations.d.ts +10 -0
- package/js/core/operations.js +76 -0
- package/js/core/rendering.d.ts +25 -0
- package/js/core/rendering.js +212 -0
- package/js/core/tokens.d.ts +11 -0
- package/js/core/tokens.js +256 -0
- package/js/htmldiff.d.ts +0 -15
- package/js/htmldiff.js +14 -1343
- package/js/tables/Cell.d.ts +24 -0
- package/js/tables/Cell.js +115 -0
- package/js/tables/ColumnAligner.d.ts +11 -0
- package/js/tables/ColumnAligner.js +83 -0
- package/js/tables/MergedCells.d.ts +11 -0
- package/js/tables/MergedCells.js +171 -0
- package/js/tables/Row.d.ts +22 -0
- package/js/tables/Row.js +49 -0
- package/js/tables/RowAligner.d.ts +12 -0
- package/js/tables/RowAligner.js +107 -0
- package/js/tables/SequenceAligner.d.ts +47 -0
- package/js/tables/SequenceAligner.js +125 -0
- package/js/tables/Table.d.ts +27 -0
- package/js/tables/Table.js +166 -0
- package/js/tables/TableAligner.d.ts +9 -0
- package/js/tables/TableAligner.js +69 -0
- package/js/tables/TableMerger.d.ts +16 -0
- package/js/tables/TableMerger.js +216 -0
- package/js/tables/TableRedlining.d.ts +10 -0
- package/js/tables/TableRedlining.js +65 -0
- package/js/tables/TableVersion.d.ts +32 -0
- package/js/tables/TableVersion.js +128 -0
- package/js/tables/constants.d.ts +6 -0
- package/js/tables/constants.js +8 -0
- package/js/tables/helpers.d.ts +4 -0
- package/js/tables/helpers.js +23 -0
- package/js/tables/html.d.ts +32 -0
- package/js/tables/html.js +107 -0
- package/js/tables/index.d.ts +2 -0
- package/js/tables/index.js +5 -0
- package/js/tables/similarity.d.ts +10 -0
- package/js/tables/similarity.js +107 -0
- package/package.json +21 -14
- package/test/calculate_operations.spec.js +0 -278
- package/test/config.js +0 -2
- package/test/diff.spec.js +0 -318
- package/test/edge_cases.spec.js +0 -41
- package/test/find_matching_blocks.spec.js +0 -168
- package/test/from_port_source.spec.js +0 -30
- package/test/html_to_tokens.spec.js +0 -238
- package/test/inner_diff.spec.js +0 -333
- package/test/mocha.opts +0 -3
- package/test/module.spec.js +0 -11
- package/test/pain_games.spec.js +0 -18
- package/test/render_operations.spec.js +0 -178
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderOperations = exports.renderInnerDiff = exports.splitAtomicTokenString = exports.findOpeningTagEnd = exports.isInnerDiffToken = exports.wrap = exports.TokenWrapper = void 0;
|
|
4
|
+
const atomicTags_1 = require("./atomicTags");
|
|
5
|
+
const tokens_1 = require("./tokens");
|
|
6
|
+
let innerDiffDepth = 0;
|
|
7
|
+
const maxInnerDiffDepth = 10;
|
|
8
|
+
class TokenWrapper {
|
|
9
|
+
tokens;
|
|
10
|
+
notes;
|
|
11
|
+
constructor(tokens) {
|
|
12
|
+
this.tokens = tokens;
|
|
13
|
+
this.notes = tokens.reduce((data, token, index) => {
|
|
14
|
+
data.notes.push({
|
|
15
|
+
isWrappable: (0, tokens_1.isWrappable)(token),
|
|
16
|
+
insertedTag: false,
|
|
17
|
+
});
|
|
18
|
+
const tag = !(0, tokens_1.isVoidTag)(token) && (0, tokens_1.isTag)(token);
|
|
19
|
+
const lastEntry = data.tagStack[data.tagStack.length - 1];
|
|
20
|
+
if (tag) {
|
|
21
|
+
if (lastEntry && "/" + lastEntry.tag === tag) {
|
|
22
|
+
data.notes[lastEntry.position].insertedTag = true;
|
|
23
|
+
data.tagStack.pop();
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
data.tagStack.push({ tag, position: index });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return data;
|
|
30
|
+
}, { notes: [], tagStack: [] }).notes;
|
|
31
|
+
}
|
|
32
|
+
combine(mapFn, tagFn) {
|
|
33
|
+
const notes = this.notes;
|
|
34
|
+
const tokens = this.tokens.slice();
|
|
35
|
+
const segments = tokens.reduce((data, token, index) => {
|
|
36
|
+
if (notes[index].insertedTag) {
|
|
37
|
+
tokens[index] = tagFn(tokens[index]);
|
|
38
|
+
}
|
|
39
|
+
if (data.status === null) {
|
|
40
|
+
data.status = notes[index].isWrappable;
|
|
41
|
+
}
|
|
42
|
+
const status = notes[index].isWrappable;
|
|
43
|
+
const isAtomic = !!(0, atomicTags_1.isStartOfAtomicTag)(token);
|
|
44
|
+
if (status !== data.status || (isAtomic && index > data.lastIndex) || data.lastWasAtomic) {
|
|
45
|
+
data.list.push({
|
|
46
|
+
isWrappable: data.status,
|
|
47
|
+
tokens: tokens.slice(data.lastIndex, index),
|
|
48
|
+
});
|
|
49
|
+
data.lastIndex = index;
|
|
50
|
+
data.status = status;
|
|
51
|
+
}
|
|
52
|
+
data.lastWasAtomic = isAtomic;
|
|
53
|
+
if (index === tokens.length - 1) {
|
|
54
|
+
data.list.push({
|
|
55
|
+
isWrappable: data.status,
|
|
56
|
+
tokens: tokens.slice(data.lastIndex, index + 1),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return data;
|
|
60
|
+
}, { list: [], status: null, lastIndex: 0, lastWasAtomic: false }).list;
|
|
61
|
+
return segments.map(mapFn).join("");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.TokenWrapper = TokenWrapper;
|
|
65
|
+
function wrap(tag, content, opIndex, dataPrefix, className) {
|
|
66
|
+
const wrapper = new TokenWrapper(content);
|
|
67
|
+
const prefix = dataPrefix ? dataPrefix + "-" : "";
|
|
68
|
+
let attrs = ` data-${prefix}operation-index="${opIndex}"`;
|
|
69
|
+
if (className) {
|
|
70
|
+
attrs += ' class="' + className + '"';
|
|
71
|
+
}
|
|
72
|
+
return wrapper.combine((segment) => {
|
|
73
|
+
if (segment.isWrappable) {
|
|
74
|
+
const val = segment.tokens.join("");
|
|
75
|
+
if (val.trim()) {
|
|
76
|
+
return "<" + tag + attrs + ">" + val + "</" + tag + ">";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
return segment.tokens.join("");
|
|
81
|
+
}
|
|
82
|
+
return "";
|
|
83
|
+
}, (openingTag) => {
|
|
84
|
+
let dataAttrs = ' data-diff-node="' + tag + '"';
|
|
85
|
+
dataAttrs += ` data-${prefix}operation-index="${opIndex}"`;
|
|
86
|
+
return openingTag.replace(/>\s*$/, dataAttrs + "$&");
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
exports.wrap = wrap;
|
|
90
|
+
function isInnerDiffToken(tokenString) {
|
|
91
|
+
if (innerDiffDepth >= maxInnerDiffDepth || !(0, atomicTags_1.isStartOfAtomicTag)(tokenString)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const attr = atomicTags_1.dataHtmlDiffInnerDiffRegExp.exec(tokenString);
|
|
95
|
+
return !!attr && attr[1] !== "false";
|
|
96
|
+
}
|
|
97
|
+
exports.isInnerDiffToken = isInnerDiffToken;
|
|
98
|
+
function findOpeningTagEnd(tokenString) {
|
|
99
|
+
let quote = null;
|
|
100
|
+
for (let i = 0; i < tokenString.length; i++) {
|
|
101
|
+
const char = tokenString[i];
|
|
102
|
+
if (char === quote) {
|
|
103
|
+
quote = null;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (quote) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (char === '"' || char === "'") {
|
|
110
|
+
quote = char;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (char === ">") {
|
|
114
|
+
return i;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return -1;
|
|
118
|
+
}
|
|
119
|
+
exports.findOpeningTagEnd = findOpeningTagEnd;
|
|
120
|
+
function splitAtomicTokenString(tokenString) {
|
|
121
|
+
const openingTagEnd = findOpeningTagEnd(tokenString);
|
|
122
|
+
if (openingTagEnd === -1) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
if (openingTagEnd === tokenString.length - 1) {
|
|
126
|
+
return {
|
|
127
|
+
openingTag: tokenString,
|
|
128
|
+
innerHtml: "",
|
|
129
|
+
closingTag: "",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const closingTagStart = tokenString.lastIndexOf("<");
|
|
133
|
+
if (closingTagStart <= openingTagEnd || tokenString[closingTagStart + 1] !== "/") {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
openingTag: tokenString.slice(0, openingTagEnd + 1),
|
|
138
|
+
innerHtml: tokenString.slice(openingTagEnd + 1, closingTagStart),
|
|
139
|
+
closingTag: tokenString.slice(closingTagStart),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
exports.splitAtomicTokenString = splitAtomicTokenString;
|
|
143
|
+
function renderInnerDiff(beforeString, afterString, diffContent, dataPrefix, className) {
|
|
144
|
+
const before = splitAtomicTokenString(beforeString);
|
|
145
|
+
const after = splitAtomicTokenString(afterString);
|
|
146
|
+
if (!before || !after) {
|
|
147
|
+
return afterString;
|
|
148
|
+
}
|
|
149
|
+
const atomicTagsOverride = atomicTags_1.dataHtmlDiffInnerDiffAtomicTagsRegExp.exec(afterString);
|
|
150
|
+
const outerAtomicTagsRegExp = (0, atomicTags_1.getAtomicTagsRegExp)();
|
|
151
|
+
innerDiffDepth++;
|
|
152
|
+
let innerDiff;
|
|
153
|
+
try {
|
|
154
|
+
(0, atomicTags_1.setAtomicTagsRegExp)(atomicTags_1.defaultInnerDiffAtomicTagsRegExp);
|
|
155
|
+
if (atomicTagsOverride) {
|
|
156
|
+
(0, atomicTags_1.setAtomicTagsRegExp)(atomicTagsOverride[1] ? (0, atomicTags_1.buildAtomicTagsRegExp)(atomicTagsOverride[1]) : atomicTags_1.noAtomicTagsRegExp);
|
|
157
|
+
}
|
|
158
|
+
innerDiff = diffContent(before.innerHtml, after.innerHtml, className, dataPrefix);
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
innerDiffDepth--;
|
|
162
|
+
(0, atomicTags_1.setAtomicTagsRegExp)(outerAtomicTagsRegExp);
|
|
163
|
+
}
|
|
164
|
+
return after.openingTag + innerDiff + after.closingTag;
|
|
165
|
+
}
|
|
166
|
+
exports.renderInnerDiff = renderInnerDiff;
|
|
167
|
+
function renderEqual(op, beforeTokens, afterTokens, _opIndex, diffContent, dataPrefix, className) {
|
|
168
|
+
let result = "";
|
|
169
|
+
for (let i = 0; op.startInAfter + i <= op.endInAfter; i++) {
|
|
170
|
+
const afterToken = afterTokens[op.startInAfter + i];
|
|
171
|
+
const beforeToken = beforeTokens[op.startInBefore + i];
|
|
172
|
+
if (beforeToken && beforeToken.string !== afterToken.string && isInnerDiffToken(afterToken.string)) {
|
|
173
|
+
result += renderInnerDiff(beforeToken.string, afterToken.string, diffContent, dataPrefix, className);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
result += afterToken.string;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
function renderInsert(op, _beforeTokens, afterTokens, opIndex, _diffContent, dataPrefix, className) {
|
|
182
|
+
const tokens = afterTokens.slice(op.startInAfter, op.endInAfter + 1);
|
|
183
|
+
const val = tokens.map((token) => token.string);
|
|
184
|
+
const res = wrap("ins", val, opIndex, dataPrefix, className);
|
|
185
|
+
if (/^<[^./]+?>$/.exec(res)) {
|
|
186
|
+
return `${res.slice(0, res.length - 1)} data-inserted="true">`;
|
|
187
|
+
}
|
|
188
|
+
return res;
|
|
189
|
+
}
|
|
190
|
+
function renderDelete(op, beforeTokens, _afterTokens, opIndex, _diffContent, dataPrefix, className) {
|
|
191
|
+
const tokens = beforeTokens.slice(op.startInBefore, op.endInBefore + 1);
|
|
192
|
+
const val = tokens.map((token) => token.string);
|
|
193
|
+
const res = wrap("del", val, opIndex, dataPrefix, className);
|
|
194
|
+
if (/^<\/.+?><.+?>$/.exec(res) && !res.includes("del")) {
|
|
195
|
+
return `<del>${val.slice(1, val.length - 1).join("")}</del>`;
|
|
196
|
+
}
|
|
197
|
+
return res;
|
|
198
|
+
}
|
|
199
|
+
function renderReplace(op, beforeTokens, afterTokens, opIndex, diffContent, dataPrefix, className) {
|
|
200
|
+
return (renderDelete(op, beforeTokens, afterTokens, opIndex, diffContent, dataPrefix, className) +
|
|
201
|
+
renderInsert(op, beforeTokens, afterTokens, opIndex, diffContent, dataPrefix, className));
|
|
202
|
+
}
|
|
203
|
+
const OPS = {
|
|
204
|
+
equal: renderEqual,
|
|
205
|
+
insert: renderInsert,
|
|
206
|
+
delete: renderDelete,
|
|
207
|
+
replace: renderReplace,
|
|
208
|
+
};
|
|
209
|
+
function renderOperations(beforeTokens, afterTokens, operations, diffContent, dataPrefix, className) {
|
|
210
|
+
return operations.reduce((rendering, op, index) => rendering + OPS[op.action](op, beforeTokens, afterTokens, index, diffContent, dataPrefix, className), "");
|
|
211
|
+
}
|
|
212
|
+
exports.renderOperations = renderOperations;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface Token {
|
|
2
|
+
string: string;
|
|
3
|
+
key: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function isTag(token: string): string | false;
|
|
6
|
+
export declare function isVoidTag(token: string): boolean;
|
|
7
|
+
export declare function isVoidTagName(tag: string): boolean;
|
|
8
|
+
export declare function isWrappable(token: string): boolean;
|
|
9
|
+
export declare function createToken(currentWord: string): Token;
|
|
10
|
+
export declare function getKeyForToken(token: string): string;
|
|
11
|
+
export declare function htmlToTokens(html: string): Token[];
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.htmlToTokens = exports.getKeyForToken = exports.createToken = exports.isWrappable = exports.isVoidTagName = exports.isVoidTag = exports.isTag = void 0;
|
|
4
|
+
const atomicTags_1 = require("./atomicTags");
|
|
5
|
+
function isTag(token) {
|
|
6
|
+
const match = token.match(/^\s*<([^!>][^>]*)>\s*$/);
|
|
7
|
+
return !!match && match[1].trim().split(" ")[0];
|
|
8
|
+
}
|
|
9
|
+
exports.isTag = isTag;
|
|
10
|
+
function isVoidTag(token) {
|
|
11
|
+
return /^\s*<[^>]+\/>\s*$/.test(token);
|
|
12
|
+
}
|
|
13
|
+
exports.isVoidTag = isVoidTag;
|
|
14
|
+
function isVoidTagName(tag) {
|
|
15
|
+
return /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/.test(tag);
|
|
16
|
+
}
|
|
17
|
+
exports.isVoidTagName = isVoidTagName;
|
|
18
|
+
function isWrappable(token) {
|
|
19
|
+
const isImage = /^<img[\s>]/.test(token);
|
|
20
|
+
return isImage || !isTag(token) || !!(0, atomicTags_1.isStartOfAtomicTag)(token) || isVoidTag(token);
|
|
21
|
+
}
|
|
22
|
+
exports.isWrappable = isWrappable;
|
|
23
|
+
function createToken(currentWord) {
|
|
24
|
+
return {
|
|
25
|
+
string: currentWord,
|
|
26
|
+
key: getKeyForToken(currentWord),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
exports.createToken = createToken;
|
|
30
|
+
function getKeyForToken(token) {
|
|
31
|
+
const img = /^<img.*src=['"]([^"']*)['"].*>$/.exec(token);
|
|
32
|
+
if (img) {
|
|
33
|
+
return '<img src="' + img[1] + '">';
|
|
34
|
+
}
|
|
35
|
+
const a = /^<a.*href=['"]([^"']*)['"]/.exec(token);
|
|
36
|
+
if (a && (0, atomicTags_1.getAtomicTagsRegExp)().test("<a ")) {
|
|
37
|
+
return '<a href="' + a[1] + '"></a>';
|
|
38
|
+
}
|
|
39
|
+
const object = /^<object.*data=['"]([^"']*)['"]/.exec(token);
|
|
40
|
+
if (object) {
|
|
41
|
+
return '<object src="' + object[1] + '"></object>';
|
|
42
|
+
}
|
|
43
|
+
if (/^<(svg|math|video)[\s>]/.test(token)) {
|
|
44
|
+
const uuid = token.indexOf('data-uuid="');
|
|
45
|
+
if (uuid !== -1) {
|
|
46
|
+
const start = token.slice(0, uuid);
|
|
47
|
+
const end = token.slice(uuid + 44);
|
|
48
|
+
return start + end;
|
|
49
|
+
}
|
|
50
|
+
return token;
|
|
51
|
+
}
|
|
52
|
+
const iframe = /^<iframe.*src=['"]([^"']*)['"].*>/.exec(token);
|
|
53
|
+
if (iframe) {
|
|
54
|
+
return '<iframe src="' + iframe[1] + '"></iframe>';
|
|
55
|
+
}
|
|
56
|
+
const uuidTag = atomicTags_1.dataHtmlDiffIdRegExp.exec(token);
|
|
57
|
+
if (uuidTag) {
|
|
58
|
+
return uuidTag[2];
|
|
59
|
+
}
|
|
60
|
+
const tagName = /<([^\s>]+)[\s>]/.exec(token);
|
|
61
|
+
if (tagName) {
|
|
62
|
+
return "<" + tagName[1].toLowerCase() + ">";
|
|
63
|
+
}
|
|
64
|
+
if (token) {
|
|
65
|
+
return token.replace(/([^\S\r\n]+| | )/g, " ");
|
|
66
|
+
}
|
|
67
|
+
return token;
|
|
68
|
+
}
|
|
69
|
+
exports.getKeyForToken = getKeyForToken;
|
|
70
|
+
function isEndOfTag(char) {
|
|
71
|
+
return char === ">";
|
|
72
|
+
}
|
|
73
|
+
function isStartOfTag(char) {
|
|
74
|
+
return char === "<";
|
|
75
|
+
}
|
|
76
|
+
function isWhitespace(char) {
|
|
77
|
+
return /^\s+$/.test(char);
|
|
78
|
+
}
|
|
79
|
+
function isStartOfHtmlComment(word) {
|
|
80
|
+
return /^<!--/.test(word);
|
|
81
|
+
}
|
|
82
|
+
function isEndOfHtmlComment(word) {
|
|
83
|
+
return /-->$/.test(word);
|
|
84
|
+
}
|
|
85
|
+
function isOpeningTagOf(tagText, tag) {
|
|
86
|
+
if (tagText.indexOf(">") !== tagText.length - 1) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return new RegExp("^<" + tag + "(\\s|>)").test(tagText) && !/\/>$/.test(tagText);
|
|
90
|
+
}
|
|
91
|
+
function isClosingTagOf(tagText, tag) {
|
|
92
|
+
if (tagText.indexOf(">") !== tagText.length - 1) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return new RegExp("^</" + tag + "(\\s|>)").test(tagText);
|
|
96
|
+
}
|
|
97
|
+
function htmlToTokens(html) {
|
|
98
|
+
let mode = "char";
|
|
99
|
+
let currentWord = "";
|
|
100
|
+
let currentAtomicTag = "";
|
|
101
|
+
let currentAtomicTagDepth = 0;
|
|
102
|
+
let currentQuote = null;
|
|
103
|
+
let inAtomicOpeningTag = false;
|
|
104
|
+
let lastTagStart = -1;
|
|
105
|
+
const words = [];
|
|
106
|
+
function consumeAttributeQuote(char, canOpenQuote) {
|
|
107
|
+
if (currentQuote) {
|
|
108
|
+
if (char === currentQuote) {
|
|
109
|
+
currentQuote = null;
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (canOpenQuote && (char === '"' || char === "'")) {
|
|
114
|
+
currentQuote = char;
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
for (let i = 0; i < html.length; i++) {
|
|
120
|
+
const char = html[i];
|
|
121
|
+
if (isStartOfTag(char)) {
|
|
122
|
+
lastTagStart = i;
|
|
123
|
+
}
|
|
124
|
+
switch (mode) {
|
|
125
|
+
case "tag": {
|
|
126
|
+
if (consumeAttributeQuote(char, true)) {
|
|
127
|
+
currentWord += char;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
const atomicTag = (0, atomicTags_1.isStartOfAtomicTag)(currentWord + char);
|
|
131
|
+
if (atomicTag && isEndOfTag(char) && (isVoidTagName(atomicTag) || /\/$/.test(currentWord))) {
|
|
132
|
+
currentWord += ">";
|
|
133
|
+
words.push(createToken(currentWord));
|
|
134
|
+
currentWord = "";
|
|
135
|
+
mode = "char";
|
|
136
|
+
}
|
|
137
|
+
else if (atomicTag) {
|
|
138
|
+
mode = "atomic_tag";
|
|
139
|
+
currentAtomicTag = atomicTag;
|
|
140
|
+
inAtomicOpeningTag = !isEndOfTag(char);
|
|
141
|
+
currentAtomicTagDepth = isEndOfTag(char) ? 1 : 0;
|
|
142
|
+
currentWord += char;
|
|
143
|
+
}
|
|
144
|
+
else if (isStartOfHtmlComment(currentWord)) {
|
|
145
|
+
mode = "html_comment";
|
|
146
|
+
currentWord += char;
|
|
147
|
+
}
|
|
148
|
+
else if (isEndOfTag(char)) {
|
|
149
|
+
currentWord += ">";
|
|
150
|
+
words.push(createToken(currentWord));
|
|
151
|
+
currentWord = "";
|
|
152
|
+
if (isWhitespace(char)) {
|
|
153
|
+
mode = "whitespace";
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
mode = "char";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
currentWord += char;
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case "atomic_tag":
|
|
165
|
+
currentWord += char;
|
|
166
|
+
if (consumeAttributeQuote(char, inAtomicOpeningTag)) {
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
if (isEndOfTag(char)) {
|
|
170
|
+
inAtomicOpeningTag = false;
|
|
171
|
+
const tagText = html.slice(lastTagStart, i + 1);
|
|
172
|
+
if (isClosingTagOf(tagText, currentAtomicTag)) {
|
|
173
|
+
currentAtomicTagDepth--;
|
|
174
|
+
if (currentAtomicTagDepth <= 0) {
|
|
175
|
+
words.push(createToken(currentWord));
|
|
176
|
+
currentWord = "";
|
|
177
|
+
currentAtomicTag = "";
|
|
178
|
+
currentAtomicTagDepth = 0;
|
|
179
|
+
mode = "char";
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if (currentAtomicTagDepth === 0 && (isVoidTagName(currentAtomicTag) || html[i - 1] === "/")) {
|
|
183
|
+
words.push(createToken(currentWord));
|
|
184
|
+
currentWord = "";
|
|
185
|
+
currentAtomicTag = "";
|
|
186
|
+
mode = "char";
|
|
187
|
+
}
|
|
188
|
+
else if (isOpeningTagOf(tagText, currentAtomicTag)) {
|
|
189
|
+
currentAtomicTagDepth++;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
case "html_comment":
|
|
194
|
+
currentWord += char;
|
|
195
|
+
if (isEndOfHtmlComment(currentWord)) {
|
|
196
|
+
currentWord = "";
|
|
197
|
+
mode = "char";
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
case "char":
|
|
201
|
+
if (isStartOfTag(char)) {
|
|
202
|
+
if (currentWord) {
|
|
203
|
+
words.push(createToken(currentWord));
|
|
204
|
+
}
|
|
205
|
+
currentWord = "<";
|
|
206
|
+
mode = "tag";
|
|
207
|
+
}
|
|
208
|
+
else if (/\s/.test(char)) {
|
|
209
|
+
if (currentWord) {
|
|
210
|
+
words.push(createToken(currentWord));
|
|
211
|
+
}
|
|
212
|
+
currentWord = char;
|
|
213
|
+
mode = "whitespace";
|
|
214
|
+
}
|
|
215
|
+
else if (/[\w\d#@]/.test(char)) {
|
|
216
|
+
currentWord += char;
|
|
217
|
+
}
|
|
218
|
+
else if (/&/.test(char)) {
|
|
219
|
+
if (currentWord) {
|
|
220
|
+
words.push(createToken(currentWord));
|
|
221
|
+
}
|
|
222
|
+
currentWord = char;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
currentWord += char;
|
|
226
|
+
words.push(createToken(currentWord));
|
|
227
|
+
currentWord = "";
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
case "whitespace":
|
|
231
|
+
if (isStartOfTag(char)) {
|
|
232
|
+
if (currentWord) {
|
|
233
|
+
words.push(createToken(currentWord));
|
|
234
|
+
}
|
|
235
|
+
currentWord = "<";
|
|
236
|
+
mode = "tag";
|
|
237
|
+
}
|
|
238
|
+
else if (isWhitespace(char)) {
|
|
239
|
+
currentWord += char;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
if (currentWord) {
|
|
243
|
+
words.push(createToken(currentWord));
|
|
244
|
+
}
|
|
245
|
+
currentWord = char;
|
|
246
|
+
mode = "char";
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (currentWord) {
|
|
252
|
+
words.push(createToken(currentWord));
|
|
253
|
+
}
|
|
254
|
+
return words;
|
|
255
|
+
}
|
|
256
|
+
exports.htmlToTokens = htmlToTokens;
|
package/js/htmldiff.d.ts
CHANGED
|
@@ -1,17 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compares two pieces of HTML content and returns the combined content with differences
|
|
3
|
-
* wrapped in <ins> and <del> tags.
|
|
4
|
-
* @param before The HTML content before the changes.
|
|
5
|
-
* @param after The HTML content after the changes.
|
|
6
|
-
* @param className (Optional) The class attribute to include in `<ins>` and `<del>` tags.
|
|
7
|
-
* @param dataPrefix (Optional) The data prefix to use for data attributes. The operation index data
|
|
8
|
-
* attribute will be named `data-${dataPrefix-}operation-index`.
|
|
9
|
-
* @param atomicTags (Optional) Comma separated list of tag names. The list has to be in the form
|
|
10
|
-
* `tag1,tag2,...` e. g. `head,script,style`. An atomic tag is one whose child nodes should not be
|
|
11
|
-
* compared - the entire tag should be treated as one token. This is useful for tags where it does
|
|
12
|
-
* not make sense to insert `<ins>` and `<del>` tags. If not used, the default list
|
|
13
|
-
* `iframe,object,math,svg,script,video,head,style` will be used.
|
|
14
|
-
* @return The combined HTML content with differences wrapped in `<ins>` and `<del>` tags.
|
|
15
|
-
*/
|
|
16
1
|
declare function diff(before: string, after: string, className?: string | null, dataPrefix?: string | null, atomicTags?: string | null): string;
|
|
17
2
|
export = diff;
|