@x-oasis/html-fragment-diff 0.1.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/.turbo/turbo-build.log +15 -0
- package/CHANGELOG.md +13 -0
- package/README.md +193 -0
- package/dist/html-fragment-diff.cjs.development.js +159 -0
- package/dist/html-fragment-diff.cjs.development.js.map +1 -0
- package/dist/html-fragment-diff.cjs.production.min.js +2 -0
- package/dist/html-fragment-diff.cjs.production.min.js.map +1 -0
- package/dist/html-fragment-diff.esm.js +153 -0
- package/dist/html-fragment-diff.esm.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +8 -0
- package/examples/.turbo/turbo-build.log +12 -0
- package/examples/README.md +99 -0
- package/examples/index.html +12 -0
- package/examples/package.json +26 -0
- package/examples/src/App.tsx +252 -0
- package/examples/src/index.css +193 -0
- package/examples/src/main.tsx +10 -0
- package/examples/tsconfig.json +25 -0
- package/examples/tsconfig.node.json +10 -0
- package/examples/vite.config.ts +30 -0
- package/package.json +26 -0
- package/src/index.ts +186 -0
- package/test/index.test.ts +178 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.json +7 -0
- package/vitest.config.ts +21 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
> @x-oasis/html-fragment-diff@0.1.0 build /home/runner/work/x-oasis/x-oasis/packages/diff/html-fragment-diff
|
|
3
|
+
> tsdx build --tsconfig tsconfig.build.json
|
|
4
|
+
|
|
5
|
+
@rollup/plugin-replace: 'preventAssignment' currently defaults to false. It is recommended to set this option to `true`, as the next major version will default this option to `true`.
|
|
6
|
+
@rollup/plugin-replace: 'preventAssignment' currently defaults to false. It is recommended to set this option to `true`, as the next major version will default this option to `true`.
|
|
7
|
+
⠙ Creating entry file
|
|
8
|
+
[2K[1A[2K[G✓ Creating entry file 2.2 secs
|
|
9
|
+
⠙ Building modules
|
|
10
|
+
[2K[1A[2K[G⠹ Building modules
|
|
11
|
+
[2K[1A[2K[G⠸ Building modules
|
|
12
|
+
[tsdx]: Your rootDir is currently set to "./". Please change your rootDir to "./src".
|
|
13
|
+
TSDX has deprecated setting tsconfig.compilerOptions.rootDir to "./" as it caused buggy output for declarationMaps and more.
|
|
14
|
+
You may also need to change your include to remove "test", which also caused declarations to be unnecessarily created for test files.
|
|
15
|
+
[2K[1A[2K[G✓ Building modules 4.2 secs
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @x-oasis/html-fragment-diff
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Initial Release
|
|
6
|
+
|
|
7
|
+
- 实现基于 parse5 的 HTML 片段解析功能
|
|
8
|
+
- 提供 `parseFragmentToElement` 函数用于解析 HTML 片段
|
|
9
|
+
- 提供 `compareHtmlFragments` 函数用于对比两个 HTML 片段
|
|
10
|
+
- 提供 `consumeGroupChangeResult` 函数用于消费 `resolveGroupChangeFragments` 的结果
|
|
11
|
+
- 支持检测 class 增删和文本变更
|
|
12
|
+
- 提供完整的交互式示例(React + Vite)
|
|
13
|
+
- 支持 GitHub Pages 部署
|
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# @x-oasis/html-fragment-diff
|
|
2
|
+
|
|
3
|
+
解析和对比 HTML 片段,检测 class 变更和文本变更。用于消费 `resolveGroupChangeFragments` 得到的 `originalFragment` / `finalFragment`。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- **HTML 解析**:使用 parse5 解析 HTML 片段,提取标签名、class 列表、文本内容等
|
|
8
|
+
- **Class 对比**:检测 class 的新增和删除
|
|
9
|
+
- **文本对比**:检测文本内容的变更
|
|
10
|
+
- **结构化输出**:提供易于展示的结构化对比结果
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @x-oasis/html-fragment-diff
|
|
16
|
+
# 或
|
|
17
|
+
pnpm add @x-oasis/html-fragment-diff
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 在线示例
|
|
21
|
+
|
|
22
|
+
查看 [交互式示例](./examples/index.html) 来可视化地了解如何使用这个库。
|
|
23
|
+
|
|
24
|
+
## 使用方法
|
|
25
|
+
|
|
26
|
+
### 基本用法:解析 HTML 片段
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { parseFragmentToElement } from '@x-oasis/html-fragment-diff';
|
|
30
|
+
|
|
31
|
+
const fragment = '<h1 class="title primary">Hello World</h1>';
|
|
32
|
+
const parsed = parseFragmentToElement(fragment);
|
|
33
|
+
|
|
34
|
+
console.log(parsed?.tagName); // "h1"
|
|
35
|
+
console.log(parsed?.classList); // ["title", "primary"]
|
|
36
|
+
console.log(parsed?.textContent); // "Hello World"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 对比两个 HTML 片段
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { compareHtmlFragments } from '@x-oasis/html-fragment-diff';
|
|
43
|
+
|
|
44
|
+
const original = '<h1 class="title">Hello</h1>';
|
|
45
|
+
const final = '<h1 class="title active">World</h1>';
|
|
46
|
+
|
|
47
|
+
const diff = compareHtmlFragments(original, final);
|
|
48
|
+
|
|
49
|
+
console.log(diff.classAdded); // ["active"]
|
|
50
|
+
console.log(diff.classRemoved); // []
|
|
51
|
+
console.log(diff.textChanged); // true
|
|
52
|
+
console.log(diff.textSummary); // "「Hello」 → 「World」"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 消费 resolveGroupChangeFragments 的结果
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { resolveGroupChangeFragments } from '@x-oasis/map-diff-range';
|
|
59
|
+
import { consumeGroupChangeResult } from '@x-oasis/html-fragment-diff';
|
|
60
|
+
|
|
61
|
+
const result = resolveGroupChangeFragments({
|
|
62
|
+
originalContent: '<h1 class="title">Name</h1>',
|
|
63
|
+
currentContent: '<h1 class="title text-xl">Name</h1>',
|
|
64
|
+
finalContent: '<h1 class="text-xl font-bold">Name</h1>',
|
|
65
|
+
currentTagOffset: { startOffset: 0, endOffset: 30 },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const withHtmlDiff = consumeGroupChangeResult(result);
|
|
69
|
+
|
|
70
|
+
if (withHtmlDiff) {
|
|
71
|
+
console.log(withHtmlDiff.htmlDiff.classAdded); // ["font-bold"]
|
|
72
|
+
console.log(withHtmlDiff.htmlDiff.classRemoved); // ["title"]
|
|
73
|
+
console.log(withHtmlDiff.htmlDiff.textChanged); // false
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## API
|
|
78
|
+
|
|
79
|
+
### ParsedFragmentElement
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
interface ParsedFragmentElement {
|
|
83
|
+
tagName: string; // 标签名(小写)
|
|
84
|
+
classList: string[]; // class 属性按空白切分后的列表
|
|
85
|
+
textContent: string; // 元素内直接+间接文本拼接
|
|
86
|
+
otherAttrs: Record<string, string>; // 除 class 外的其他属性
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### HtmlFragmentDiff
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
interface HtmlFragmentDiff {
|
|
94
|
+
original: ParsedFragmentElement | null; // 原始片段解析结果
|
|
95
|
+
final: ParsedFragmentElement | null; // 最终片段解析结果
|
|
96
|
+
classAdded: string[]; // 新增的 class 列表
|
|
97
|
+
classRemoved: string[]; // 删除的 class 列表
|
|
98
|
+
textOriginal: string; // 原始片段根元素文本
|
|
99
|
+
textFinal: string; // 最终片段根元素文本
|
|
100
|
+
textChanged: boolean; // 文本是否发生变更
|
|
101
|
+
textSummary: string; // 文本变更的简短描述
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### parseFragmentToElement
|
|
106
|
+
|
|
107
|
+
从 HTML 片段中解析出第一个根元素的信息。
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
function parseFragmentToElement(
|
|
111
|
+
fragment: string
|
|
112
|
+
): ParsedFragmentElement | null
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**参数:**
|
|
116
|
+
- `fragment`: 单段 HTML,如 `<h1 class="...">姓名</h1>`
|
|
117
|
+
|
|
118
|
+
**返回:** 第一个根元素的信息;若无元素或解析失败则返回 `null`
|
|
119
|
+
|
|
120
|
+
### compareHtmlFragments
|
|
121
|
+
|
|
122
|
+
对比两个 HTML 片段:解析后比较 class 增删与根元素文本变更。
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
function compareHtmlFragments(
|
|
126
|
+
originalFragment: string,
|
|
127
|
+
finalFragment: string
|
|
128
|
+
): HtmlFragmentDiff
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**参数:**
|
|
132
|
+
- `originalFragment`: 原始片段(如 `resolveGroupChangeFragments` 的 `originalFragment`)
|
|
133
|
+
- `finalFragment`: 最终片段(如 `resolveGroupChangeFragments` 的 `finalFragment`)
|
|
134
|
+
|
|
135
|
+
**返回:** 结构化对比结果
|
|
136
|
+
|
|
137
|
+
### consumeGroupChangeResult
|
|
138
|
+
|
|
139
|
+
消费 `resolveGroupChangeFragments` 的返回值:在原有片段级 diff 基础上,再解析两个 HTML 片段,得到 class 增删与文本变更的结构化结果。
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
function consumeGroupChangeResult<T extends { originalFragment: string; finalFragment: string }>(
|
|
143
|
+
result: T | undefined
|
|
144
|
+
): (T & { htmlDiff: HtmlFragmentDiff }) | undefined
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**参数:**
|
|
148
|
+
- `result`: `resolveGroupChangeFragments` 的返回值
|
|
149
|
+
|
|
150
|
+
**返回:** 原 result 与 HTML 解析对比结果;若 result 为 `undefined` 则返回 `undefined`
|
|
151
|
+
|
|
152
|
+
## 示例场景
|
|
153
|
+
|
|
154
|
+
### 场景 1: Class 变更
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const original = '<h1 class="title">Name</h1>';
|
|
158
|
+
const final = '<h1 class="title active">Name</h1>';
|
|
159
|
+
|
|
160
|
+
const diff = compareHtmlFragments(original, final);
|
|
161
|
+
// diff.classAdded === ["active"]
|
|
162
|
+
// diff.classRemoved === []
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 场景 2: 文本变更
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const original = '<h1>Hello</h1>';
|
|
169
|
+
const final = '<h1>World</h1>';
|
|
170
|
+
|
|
171
|
+
const diff = compareHtmlFragments(original, final);
|
|
172
|
+
// diff.textChanged === true
|
|
173
|
+
// diff.textSummary === "「Hello」 → 「World」"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 场景 3: Class 和文本同时变更
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const original = '<h1 class="title">Hello</h1>';
|
|
180
|
+
const final = '<h1 class="title active">World</h1>';
|
|
181
|
+
|
|
182
|
+
const diff = compareHtmlFragments(original, final);
|
|
183
|
+
// diff.classAdded === ["active"]
|
|
184
|
+
// diff.textChanged === true
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## 依赖
|
|
188
|
+
|
|
189
|
+
- [parse5](https://www.npmjs.com/package/parse5): HTML 解析库
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
ISC
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var parse5 = require('parse5');
|
|
6
|
+
|
|
7
|
+
function _extends() {
|
|
8
|
+
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
9
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
10
|
+
var source = arguments[i];
|
|
11
|
+
for (var key in source) {
|
|
12
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
13
|
+
target[key] = source[key];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return target;
|
|
18
|
+
};
|
|
19
|
+
return _extends.apply(this, arguments);
|
|
20
|
+
}
|
|
21
|
+
function _unsupportedIterableToArray(o, minLen) {
|
|
22
|
+
if (!o) return;
|
|
23
|
+
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
|
|
24
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
25
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
26
|
+
if (n === "Map" || n === "Set") return Array.from(o);
|
|
27
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
|
|
28
|
+
}
|
|
29
|
+
function _arrayLikeToArray(arr, len) {
|
|
30
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
31
|
+
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
|
32
|
+
return arr2;
|
|
33
|
+
}
|
|
34
|
+
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
|
|
35
|
+
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
|
|
36
|
+
if (it) return (it = it.call(o)).next.bind(it);
|
|
37
|
+
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
|
|
38
|
+
if (it) o = it;
|
|
39
|
+
var i = 0;
|
|
40
|
+
return function () {
|
|
41
|
+
if (i >= o.length) return {
|
|
42
|
+
done: true
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
done: false,
|
|
46
|
+
value: o[i++]
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getAttr(node, name) {
|
|
54
|
+
var _node$attrs;
|
|
55
|
+
var attrs = (_node$attrs = node.attrs) != null ? _node$attrs : [];
|
|
56
|
+
var lower = name.toLowerCase();
|
|
57
|
+
var a = attrs.find(function (x) {
|
|
58
|
+
var _x$name;
|
|
59
|
+
return ((_x$name = x.name) == null ? void 0 : _x$name.toLowerCase()) === lower;
|
|
60
|
+
});
|
|
61
|
+
return a == null ? void 0 : a.value;
|
|
62
|
+
}
|
|
63
|
+
function getTextContent(node) {
|
|
64
|
+
var _node$childNodes;
|
|
65
|
+
if (!node) return '';
|
|
66
|
+
if (node.nodeName === '#text') {
|
|
67
|
+
var _node$value;
|
|
68
|
+
return (_node$value = node.value) != null ? _node$value : '';
|
|
69
|
+
}
|
|
70
|
+
var childNodes = (_node$childNodes = node.childNodes) != null ? _node$childNodes : [];
|
|
71
|
+
return childNodes.map(function (child) {
|
|
72
|
+
return getTextContent(child);
|
|
73
|
+
}).join('');
|
|
74
|
+
}
|
|
75
|
+
function isElementNode(node) {
|
|
76
|
+
return node && typeof node.tagName === 'string';
|
|
77
|
+
}
|
|
78
|
+
function splitClassList(classAttr) {
|
|
79
|
+
if (classAttr == null || classAttr === '') return [];
|
|
80
|
+
var list = classAttr.trim().split(/\s+/).filter(Boolean);
|
|
81
|
+
return [].concat(new Set(list));
|
|
82
|
+
}
|
|
83
|
+
function parseFragmentToElement(fragment) {
|
|
84
|
+
var _fragmentNode$childNo, _fragmentNode;
|
|
85
|
+
if (!fragment || typeof fragment !== 'string') return null;
|
|
86
|
+
var wrapped = fragment.trim();
|
|
87
|
+
if (!wrapped) return null;
|
|
88
|
+
var fragmentNode;
|
|
89
|
+
try {
|
|
90
|
+
fragmentNode = parse5.parseFragment(wrapped);
|
|
91
|
+
} catch (_unused) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
var childNodes = (_fragmentNode$childNo = (_fragmentNode = fragmentNode) == null ? void 0 : _fragmentNode.childNodes) != null ? _fragmentNode$childNo : [];
|
|
95
|
+
for (var _iterator = _createForOfIteratorHelperLoose(childNodes), _step; !(_step = _iterator()).done;) {
|
|
96
|
+
var child = _step.value;
|
|
97
|
+
if (isElementNode(child)) {
|
|
98
|
+
var _child$tagName;
|
|
99
|
+
var classAttr = getAttr(child, 'class');
|
|
100
|
+
var classList = splitClassList(classAttr);
|
|
101
|
+
var textContent = getTextContent(child).trim();
|
|
102
|
+
var otherAttrs = {};
|
|
103
|
+
for (var _iterator2 = _createForOfIteratorHelperLoose((_child$attrs = child.attrs) != null ? _child$attrs : []), _step2; !(_step2 = _iterator2()).done;) {
|
|
104
|
+
var _child$attrs, _a$name;
|
|
105
|
+
var a = _step2.value;
|
|
106
|
+
if (((_a$name = a.name) == null ? void 0 : _a$name.toLowerCase()) !== 'class') {
|
|
107
|
+
var _a$value;
|
|
108
|
+
otherAttrs[a.name] = (_a$value = a.value) != null ? _a$value : '';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
tagName: ((_child$tagName = child.tagName) != null ? _child$tagName : '').toLowerCase(),
|
|
113
|
+
classList: classList,
|
|
114
|
+
textContent: textContent,
|
|
115
|
+
otherAttrs: otherAttrs
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
function compareHtmlFragments(originalFragment, finalFragment) {
|
|
122
|
+
var _original$classList, _final$classList, _final$classList2, _original$classList2, _original$textContent, _final$textContent;
|
|
123
|
+
var original = parseFragmentToElement(originalFragment);
|
|
124
|
+
var _final = parseFragmentToElement(finalFragment);
|
|
125
|
+
var originalClasses = new Set((_original$classList = original == null ? void 0 : original.classList) != null ? _original$classList : []);
|
|
126
|
+
var finalClasses = new Set((_final$classList = _final == null ? void 0 : _final.classList) != null ? _final$classList : []);
|
|
127
|
+
var classAdded = ((_final$classList2 = _final == null ? void 0 : _final.classList) != null ? _final$classList2 : []).filter(function (c) {
|
|
128
|
+
return !originalClasses.has(c);
|
|
129
|
+
});
|
|
130
|
+
var classRemoved = ((_original$classList2 = original == null ? void 0 : original.classList) != null ? _original$classList2 : []).filter(function (c) {
|
|
131
|
+
return !finalClasses.has(c);
|
|
132
|
+
});
|
|
133
|
+
var textOriginal = (_original$textContent = original == null ? void 0 : original.textContent) != null ? _original$textContent : '';
|
|
134
|
+
var textFinal = (_final$textContent = _final == null ? void 0 : _final.textContent) != null ? _final$textContent : '';
|
|
135
|
+
var textChanged = textOriginal !== textFinal;
|
|
136
|
+
var textSummary = textChanged ? "\u300C" + (textOriginal || '(空)') + "\u300D \u2192 \u300C" + (textFinal || '(空)') + "\u300D" : '无变更';
|
|
137
|
+
return {
|
|
138
|
+
original: original,
|
|
139
|
+
"final": _final,
|
|
140
|
+
classAdded: classAdded,
|
|
141
|
+
classRemoved: classRemoved,
|
|
142
|
+
textOriginal: textOriginal,
|
|
143
|
+
textFinal: textFinal,
|
|
144
|
+
textChanged: textChanged,
|
|
145
|
+
textSummary: textSummary
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function consumeGroupChangeResult(result) {
|
|
149
|
+
if (result == null) return undefined;
|
|
150
|
+
var htmlDiff = compareHtmlFragments(result.originalFragment, result.finalFragment);
|
|
151
|
+
return _extends({}, result, {
|
|
152
|
+
htmlDiff: htmlDiff
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
exports.compareHtmlFragments = compareHtmlFragments;
|
|
157
|
+
exports.consumeGroupChangeResult = consumeGroupChangeResult;
|
|
158
|
+
exports.parseFragmentToElement = parseFragmentToElement;
|
|
159
|
+
//# sourceMappingURL=html-fragment-diff.cjs.development.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-fragment-diff.cjs.development.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * 对两个 HTML 片段做 parse 级别的对比:解析出标签、class、文本等,并输出 class 增删与文本变更。\n * 用于消费 resolveGroupChangeFragments 得到的 originalFragment / finalFragment。\n */\n\nimport { parseFragment } from 'parse5';\n\n/** 解析出的单个根元素信息(只关心第一个根元素) */\nexport interface ParsedFragmentElement {\n tagName: string;\n /** class 属性按空白切分后的列表 */\n classList: string[];\n /** 元素内直接+间接文本拼接(不含子标签的 tag,只取文本) */\n textContent: string;\n /** 除 class 外的其他属性(name -> value) */\n otherAttrs: Record<string, string>;\n}\n\n/** 两个 HTML 片段的对比结果 */\nexport interface HtmlFragmentDiff {\n /** 原始片段解析结果(若解析失败为 null) */\n original: ParsedFragmentElement | null;\n /** 最终片段解析结果(若解析失败为 null) */\n final: ParsedFragmentElement | null;\n /** class:最终相对原始新增的 class 列表 */\n classAdded: string[];\n /** class:最终相对原始删除的 class 列表 */\n classRemoved: string[];\n /** 文本:原始片段根元素文本 */\n textOriginal: string;\n /** 文本:最终片段根元素文本 */\n textFinal: string;\n /** 文本是否发生变更 */\n textChanged: boolean;\n /** 文本变更的简短描述(便于展示) */\n textSummary: string;\n}\n\n/**\n * 从 parse5 的节点中取属性值\n */\nfunction getAttr(\n node: { attrs?: Array<{ name: string; value: string }> },\n name: string\n): string | undefined {\n const attrs = node.attrs ?? [];\n const lower = name.toLowerCase();\n const a = attrs.find((x) => x.name?.toLowerCase() === lower);\n return a?.value;\n}\n\n/**\n * 递归收集元素的文本内容(不含标签名,只取文本节点)\n */\nfunction getTextContent(node: any): string {\n if (!node) return '';\n if (node.nodeName === '#text') {\n return node.value ?? '';\n }\n const childNodes = node.childNodes ?? [];\n return childNodes.map((child: any) => getTextContent(child)).join('');\n}\n\n/**\n * 判断是否为元素节点(有 tagName)\n */\nfunction isElementNode(node: any): node is {\n tagName: string;\n attrs: Array<{ name: string; value: string }>;\n childNodes?: any[];\n} {\n return node && typeof (node as any).tagName === 'string';\n}\n\n/**\n * 将 class 属性字符串按空白切分为有序列表,去重保留顺序\n */\nfunction splitClassList(classAttr: string | undefined): string[] {\n if (classAttr == null || classAttr === '') return [];\n const list = classAttr.trim().split(/\\s+/).filter(Boolean);\n return [...new Set(list)];\n}\n\n/**\n * 从 HTML 片段中解析出第一个根元素的信息\n *\n * @param fragment 单段 HTML,如 `<h1 class=\"...\">姓名</h1>`\n * @returns 第一个根元素的信息;若无元素或解析失败则返回 null\n */\nexport function parseFragmentToElement(\n fragment: string\n): ParsedFragmentElement | null {\n if (!fragment || typeof fragment !== 'string') return null;\n\n const wrapped = fragment.trim();\n if (!wrapped) return null;\n\n let fragmentNode: any;\n try {\n fragmentNode = parseFragment(wrapped);\n } catch {\n return null;\n }\n\n const childNodes = fragmentNode?.childNodes ?? [];\n for (const child of childNodes) {\n if (isElementNode(child)) {\n const classAttr = getAttr(child, 'class');\n const classList = splitClassList(classAttr);\n const textContent = getTextContent(child).trim();\n const otherAttrs: Record<string, string> = {};\n for (const a of child.attrs ?? []) {\n if (a.name?.toLowerCase() !== 'class') {\n otherAttrs[a.name] = a.value ?? '';\n }\n }\n return {\n tagName: (child.tagName ?? '').toLowerCase(),\n classList,\n textContent,\n otherAttrs,\n };\n }\n }\n return null;\n}\n\n/**\n * 对比两个 HTML 片段:解析后比较 class 增删与根元素文本变更\n *\n * @param originalFragment 原始片段(如 resolveGroupChangeFragments 的 originalFragment)\n * @param finalFragment 最终片段(如 resolveGroupChangeFragments 的 finalFragment)\n * @returns 结构化对比结果,便于展示「class 多了啥、少了啥」和「text 变更了啥」\n */\nexport function compareHtmlFragments(\n originalFragment: string,\n finalFragment: string\n): HtmlFragmentDiff {\n const original = parseFragmentToElement(originalFragment);\n const final = parseFragmentToElement(finalFragment);\n\n const originalClasses = new Set(original?.classList ?? []);\n const finalClasses = new Set(final?.classList ?? []);\n const classAdded = (final?.classList ?? []).filter(\n (c) => !originalClasses.has(c)\n );\n const classRemoved = (original?.classList ?? []).filter(\n (c) => !finalClasses.has(c)\n );\n\n const textOriginal = original?.textContent ?? '';\n const textFinal = final?.textContent ?? '';\n const textChanged = textOriginal !== textFinal;\n const textSummary = textChanged\n ? `「${textOriginal || '(空)'}」 → 「${textFinal || '(空)'}」`\n : '无变更';\n\n return {\n original,\n final,\n classAdded,\n classRemoved,\n textOriginal,\n textFinal,\n textChanged,\n textSummary,\n };\n}\n\n/**\n * 消费 resolveGroupChangeFragments 的返回值:在原有片段级 diff 基础上,再解析两个 HTML 片段,\n * 得到 class 增删与文本变更的结构化结果。\n *\n * @param result resolveGroupChangeMessage / resolveGroupChangeFragments 的返回值\n * @returns 原 result 与 HTML 解析对比结果;若 result 为 undefined 则返回 undefined\n */\nexport function consumeGroupChangeResult<\n T extends { originalFragment: string; finalFragment: string }\n>(result: T | undefined): (T & { htmlDiff: HtmlFragmentDiff }) | undefined {\n if (result == null) return undefined;\n const htmlDiff = compareHtmlFragments(\n result.originalFragment,\n result.finalFragment\n );\n return { ...result, htmlDiff };\n}\n"],"names":["getAttr","node","name","attrs","_node$attrs","lower","toLowerCase","a","find","x","_x$name","value","getTextContent","nodeName","_node$value","childNodes","_node$childNodes","map","child","join","isElementNode","tagName","splitClassList","classAttr","list","trim","split","filter","Boolean","concat","Set","parseFragmentToElement","fragment","wrapped","fragmentNode","parseFragment","_unused","_fragmentNode$childNo","_fragmentNode","_iterator","_createForOfIteratorHelperLoose","_step","done","_child$tagName","classList","textContent","otherAttrs","_iterator2","_child$attrs","_step2","_a$name","_a$value","compareHtmlFragments","originalFragment","finalFragment","original","final","originalClasses","_original$classList","finalClasses","_final$classList","classAdded","_final$classList2","c","has","classRemoved","_original$classList2","textOriginal","_original$textContent","textFinal","_final$textContent","textChanged","textSummary","consumeGroupChangeResult","result","undefined","htmlDiff","_extends"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t=require("parse5");function n(){return(n=Object.assign?Object.assign.bind():function(t){for(var n=1;n<arguments.length;n++){var e=arguments[n];for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r])}return t}).apply(this,arguments)}function e(t,n){(null==n||n>t.length)&&(n=t.length);for(var e=0,r=new Array(n);e<n;e++)r[e]=t[e];return r}function r(t,n){var r="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(r)return(r=r.call(t)).next.bind(r);if(Array.isArray(t)||(r=function(t,n){if(t){if("string"==typeof t)return e(t,void 0);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?e(t,void 0):void 0}}(t))||n&&t&&"number"==typeof t.length){r&&(t=r);var l=0;return function(){return l>=t.length?{done:!0}:{done:!1,value:t[l++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function l(t,n){var e,r=null!=(e=t.attrs)?e:[],l=n.toLowerCase(),a=r.find((function(t){var n;return(null==(n=t.name)?void 0:n.toLowerCase())===l}));return null==a?void 0:a.value}function a(t){var n,e;return t?"#text"===t.nodeName?null!=(e=t.value)?e:"":(null!=(n=t.childNodes)?n:[]).map((function(t){return a(t)})).join(""):""}function o(t){if(null==t||""===t)return[];var n=t.trim().split(/\s+/).filter(Boolean);return[].concat(new Set(n))}function i(n){var e,i;if(!n||"string"!=typeof n)return null;var u,s=n.trim();if(!s)return null;try{u=t.parseFragment(s)}catch(t){return null}for(var c,f,v=r(null!=(e=null==(i=u)?void 0:i.childNodes)?e:[]);!(c=v()).done;){var d=c.value;if((f=d)&&"string"==typeof f.tagName){for(var m,p,g=o(l(d,"class")),y=a(d).trim(),h={},b=r(null!=(x=d.attrs)?x:[]);!(p=b()).done;){var x,w,C,L=p.value;"class"!==(null==(w=L.name)?void 0:w.toLowerCase())&&(h[L.name]=null!=(C=L.value)?C:"")}return{tagName:(null!=(m=d.tagName)?m:"").toLowerCase(),classList:g,textContent:y,otherAttrs:h}}}return null}function u(t,n){var e,r,l,a,o,u,s=i(t),c=i(n),f=new Set(null!=(e=null==s?void 0:s.classList)?e:[]),v=new Set(null!=(r=null==c?void 0:c.classList)?r:[]),d=(null!=(l=null==c?void 0:c.classList)?l:[]).filter((function(t){return!f.has(t)})),m=(null!=(a=null==s?void 0:s.classList)?a:[]).filter((function(t){return!v.has(t)})),p=null!=(o=null==s?void 0:s.textContent)?o:"",g=null!=(u=null==c?void 0:c.textContent)?u:"",y=p!==g;return{original:s,final:c,classAdded:d,classRemoved:m,textOriginal:p,textFinal:g,textChanged:y,textSummary:y?"「"+(p||"(空)")+"」 → 「"+(g||"(空)")+"」":"无变更"}}exports.compareHtmlFragments=u,exports.consumeGroupChangeResult=function(t){if(null!=t)return n({},t,{htmlDiff:u(t.originalFragment,t.finalFragment)})},exports.parseFragmentToElement=i;
|
|
2
|
+
//# sourceMappingURL=html-fragment-diff.cjs.production.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-fragment-diff.cjs.production.min.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * 对两个 HTML 片段做 parse 级别的对比:解析出标签、class、文本等,并输出 class 增删与文本变更。\n * 用于消费 resolveGroupChangeFragments 得到的 originalFragment / finalFragment。\n */\n\nimport { parseFragment } from 'parse5';\n\n/** 解析出的单个根元素信息(只关心第一个根元素) */\nexport interface ParsedFragmentElement {\n tagName: string;\n /** class 属性按空白切分后的列表 */\n classList: string[];\n /** 元素内直接+间接文本拼接(不含子标签的 tag,只取文本) */\n textContent: string;\n /** 除 class 外的其他属性(name -> value) */\n otherAttrs: Record<string, string>;\n}\n\n/** 两个 HTML 片段的对比结果 */\nexport interface HtmlFragmentDiff {\n /** 原始片段解析结果(若解析失败为 null) */\n original: ParsedFragmentElement | null;\n /** 最终片段解析结果(若解析失败为 null) */\n final: ParsedFragmentElement | null;\n /** class:最终相对原始新增的 class 列表 */\n classAdded: string[];\n /** class:最终相对原始删除的 class 列表 */\n classRemoved: string[];\n /** 文本:原始片段根元素文本 */\n textOriginal: string;\n /** 文本:最终片段根元素文本 */\n textFinal: string;\n /** 文本是否发生变更 */\n textChanged: boolean;\n /** 文本变更的简短描述(便于展示) */\n textSummary: string;\n}\n\n/**\n * 从 parse5 的节点中取属性值\n */\nfunction getAttr(\n node: { attrs?: Array<{ name: string; value: string }> },\n name: string\n): string | undefined {\n const attrs = node.attrs ?? [];\n const lower = name.toLowerCase();\n const a = attrs.find((x) => x.name?.toLowerCase() === lower);\n return a?.value;\n}\n\n/**\n * 递归收集元素的文本内容(不含标签名,只取文本节点)\n */\nfunction getTextContent(node: any): string {\n if (!node) return '';\n if (node.nodeName === '#text') {\n return node.value ?? '';\n }\n const childNodes = node.childNodes ?? [];\n return childNodes.map((child: any) => getTextContent(child)).join('');\n}\n\n/**\n * 判断是否为元素节点(有 tagName)\n */\nfunction isElementNode(node: any): node is {\n tagName: string;\n attrs: Array<{ name: string; value: string }>;\n childNodes?: any[];\n} {\n return node && typeof (node as any).tagName === 'string';\n}\n\n/**\n * 将 class 属性字符串按空白切分为有序列表,去重保留顺序\n */\nfunction splitClassList(classAttr: string | undefined): string[] {\n if (classAttr == null || classAttr === '') return [];\n const list = classAttr.trim().split(/\\s+/).filter(Boolean);\n return [...new Set(list)];\n}\n\n/**\n * 从 HTML 片段中解析出第一个根元素的信息\n *\n * @param fragment 单段 HTML,如 `<h1 class=\"...\">姓名</h1>`\n * @returns 第一个根元素的信息;若无元素或解析失败则返回 null\n */\nexport function parseFragmentToElement(\n fragment: string\n): ParsedFragmentElement | null {\n if (!fragment || typeof fragment !== 'string') return null;\n\n const wrapped = fragment.trim();\n if (!wrapped) return null;\n\n let fragmentNode: any;\n try {\n fragmentNode = parseFragment(wrapped);\n } catch {\n return null;\n }\n\n const childNodes = fragmentNode?.childNodes ?? [];\n for (const child of childNodes) {\n if (isElementNode(child)) {\n const classAttr = getAttr(child, 'class');\n const classList = splitClassList(classAttr);\n const textContent = getTextContent(child).trim();\n const otherAttrs: Record<string, string> = {};\n for (const a of child.attrs ?? []) {\n if (a.name?.toLowerCase() !== 'class') {\n otherAttrs[a.name] = a.value ?? '';\n }\n }\n return {\n tagName: (child.tagName ?? '').toLowerCase(),\n classList,\n textContent,\n otherAttrs,\n };\n }\n }\n return null;\n}\n\n/**\n * 对比两个 HTML 片段:解析后比较 class 增删与根元素文本变更\n *\n * @param originalFragment 原始片段(如 resolveGroupChangeFragments 的 originalFragment)\n * @param finalFragment 最终片段(如 resolveGroupChangeFragments 的 finalFragment)\n * @returns 结构化对比结果,便于展示「class 多了啥、少了啥」和「text 变更了啥」\n */\nexport function compareHtmlFragments(\n originalFragment: string,\n finalFragment: string\n): HtmlFragmentDiff {\n const original = parseFragmentToElement(originalFragment);\n const final = parseFragmentToElement(finalFragment);\n\n const originalClasses = new Set(original?.classList ?? []);\n const finalClasses = new Set(final?.classList ?? []);\n const classAdded = (final?.classList ?? []).filter(\n (c) => !originalClasses.has(c)\n );\n const classRemoved = (original?.classList ?? []).filter(\n (c) => !finalClasses.has(c)\n );\n\n const textOriginal = original?.textContent ?? '';\n const textFinal = final?.textContent ?? '';\n const textChanged = textOriginal !== textFinal;\n const textSummary = textChanged\n ? `「${textOriginal || '(空)'}」 → 「${textFinal || '(空)'}」`\n : '无变更';\n\n return {\n original,\n final,\n classAdded,\n classRemoved,\n textOriginal,\n textFinal,\n textChanged,\n textSummary,\n };\n}\n\n/**\n * 消费 resolveGroupChangeFragments 的返回值:在原有片段级 diff 基础上,再解析两个 HTML 片段,\n * 得到 class 增删与文本变更的结构化结果。\n *\n * @param result resolveGroupChangeMessage / resolveGroupChangeFragments 的返回值\n * @returns 原 result 与 HTML 解析对比结果;若 result 为 undefined 则返回 undefined\n */\nexport function consumeGroupChangeResult<\n T extends { originalFragment: string; finalFragment: string }\n>(result: T | undefined): (T & { htmlDiff: HtmlFragmentDiff }) | undefined {\n if (result == null) return undefined;\n const htmlDiff = compareHtmlFragments(\n result.originalFragment,\n result.finalFragment\n );\n return { ...result, htmlDiff };\n}\n"],"names":["getAttr","node","name","attrs","_node$attrs","lower","toLowerCase","a","find","x","_x$name","value","getTextContent","_node$value","nodeName","_node$childNodes","childNodes","map","child","join","splitClassList","classAttr","list","trim","split","filter","Boolean","concat","Set","parseFragmentToElement","fragment","fragmentNode","wrapped","parseFragment","_unused","_step","_iterator","_createForOfIteratorHelperLoose","_fragmentNode$childNo","_fragmentNode","done","tagName","_child$tagName","_step2","classList","textContent","otherAttrs","_iterator2","_child$attrs","_a$name","_a$value","compareHtmlFragments","originalFragment","finalFragment","original","final","originalClasses","_original$classList","finalClasses","_final$classList","classAdded","_final$classList2","c","has","classRemoved","_original$classList2","textOriginal","_original$textContent","textFinal","_final$textContent","textChanged","textSummary","result","_extends","htmlDiff"],"mappings":"+nCAyCA,SAASA,EACPC,EACAC,SAEMC,SAAKC,EAAGH,EAAKE,OAAKC,EAAI,GACtBC,EAAQH,EAAKI,cACbC,EAAIJ,EAAMK,MAAK,SAACC,GAAC,IAAAC,EAAA,cAAKA,EAAAD,EAAEP,aAAFQ,EAAQJ,iBAAkBD,KACtD,aAAOE,SAAAA,EAAGI,MAMZ,SAASC,EAAeX,SAESY,EAD/B,OAAKZ,EACiB,UAAlBA,EAAKa,gBACPD,EAAOZ,EAAKU,OAAKE,EAAI,WAEPE,EAAGd,EAAKe,YAAUD,EAAI,IACpBE,KAAI,SAACC,GAAU,OAAKN,EAAeM,MAAQC,KAAK,IALhD,GAsBpB,SAASC,EAAeC,GACtB,GAAiB,MAAbA,GAAmC,KAAdA,EAAkB,MAAO,GAClD,IAAMC,EAAOD,EAAUE,OAAOC,MAAM,OAAOC,OAAOC,SAClD,SAAAC,OAAW,IAAIC,IAAIN,aASLO,EACdC,WAEA,IAAKA,GAAgC,iBAAbA,EAAuB,OAAO,KAEtD,IAGIC,EAHEC,EAAUF,EAASP,OACzB,IAAKS,EAAS,OAAO,KAGrB,IACED,EAAeE,gBAAcD,GAC7B,MAAAE,GACA,OAAO,KAIT,IADA,IAC8BC,EAvCTlC,EAuCrBmC,EAAAC,SADgBC,SAAAC,EAAGR,UAAAQ,EAAcvB,YAAUsB,EAAI,MACjBH,EAAAC,KAAAI,MAAE,CAAA,IAArBtB,EAAKiB,EAAAxB,MACd,IAxCmBV,EAwCDiB,IAnC4B,iBAAzBjB,EAAawC,QAmCR,CAKxB,IALwB,IAAAC,EAKSC,EAH3BC,EAAYxB,EADApB,EAAQkB,EAAO,UAE3B2B,EAAcjC,EAAeM,GAAOK,OACpCuB,EAAqC,GAC3CC,EAAAV,SAAAW,EAAgB9B,EAAMf,OAAK6C,EAAI,MAAEL,EAAAI,KAAAP,MAAE,CAAA,IAAAQ,EAAAC,EACMC,EAD9B3C,EAACoC,EAAAhC,MACoB,kBAA1BsC,EAAA1C,EAAEL,aAAF+C,EAAQ3C,iBACVwC,EAAWvC,EAAEL,aAAKgD,EAAG3C,EAAEI,OAAKuC,EAAI,IAGpC,MAAO,CACLT,gBAASC,EAACxB,EAAMuB,SAAOC,EAAI,IAAIpC,cAC/BsC,UAAAA,EACAC,YAAAA,EACAC,WAAAA,IAIN,OAAO,cAUOK,EACdC,EACAC,mBAEMC,EAAWzB,EAAuBuB,GAClCG,EAAQ1B,EAAuBwB,GAE/BG,EAAkB,IAAI5B,WAAG6B,QAACH,SAAAA,EAAUV,WAASa,EAAI,IACjDC,EAAe,IAAI9B,WAAG+B,QAACJ,SAAAA,EAAOX,WAASe,EAAI,IAC3CC,UAAaC,QAACN,SAAAA,EAAOX,WAASiB,EAAI,IAAIpC,QAC1C,SAACqC,GAAC,OAAMN,EAAgBO,IAAID,MAExBE,UAAeC,QAACX,SAAAA,EAAUV,WAASqB,EAAI,IAAIxC,QAC/C,SAACqC,GAAC,OAAMJ,EAAaK,IAAID,MAGrBI,SAAYC,QAAGb,SAAAA,EAAUT,aAAWsB,EAAI,GACxCC,SAASC,QAAGd,SAAAA,EAAOV,aAAWwB,EAAI,GAClCC,EAAcJ,IAAiBE,EAKrC,MAAO,CACLd,SAAAA,EACAC,MAAAA,EACAK,WAAAA,EACAI,aAAAA,EACAE,aAAAA,EACAE,UAAAA,EACAE,YAAAA,EACAC,YAZkBD,OACZJ,GAAgB,gBAAaE,GAAa,WAC9C,gFAuBJI,GACA,GAAc,MAAVA,EAKJ,OAAAC,KAAYD,GAAQE,SAJHvB,EACfqB,EAAOpB,iBACPoB,EAAOnB"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { parseFragment } from 'parse5';
|
|
2
|
+
|
|
3
|
+
function _extends() {
|
|
4
|
+
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
5
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
6
|
+
var source = arguments[i];
|
|
7
|
+
for (var key in source) {
|
|
8
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
9
|
+
target[key] = source[key];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return target;
|
|
14
|
+
};
|
|
15
|
+
return _extends.apply(this, arguments);
|
|
16
|
+
}
|
|
17
|
+
function _unsupportedIterableToArray(o, minLen) {
|
|
18
|
+
if (!o) return;
|
|
19
|
+
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
|
|
20
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
21
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
22
|
+
if (n === "Map" || n === "Set") return Array.from(o);
|
|
23
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
|
|
24
|
+
}
|
|
25
|
+
function _arrayLikeToArray(arr, len) {
|
|
26
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
27
|
+
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
|
28
|
+
return arr2;
|
|
29
|
+
}
|
|
30
|
+
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
|
|
31
|
+
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
|
|
32
|
+
if (it) return (it = it.call(o)).next.bind(it);
|
|
33
|
+
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
|
|
34
|
+
if (it) o = it;
|
|
35
|
+
var i = 0;
|
|
36
|
+
return function () {
|
|
37
|
+
if (i >= o.length) return {
|
|
38
|
+
done: true
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
done: false,
|
|
42
|
+
value: o[i++]
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getAttr(node, name) {
|
|
50
|
+
var _node$attrs;
|
|
51
|
+
var attrs = (_node$attrs = node.attrs) != null ? _node$attrs : [];
|
|
52
|
+
var lower = name.toLowerCase();
|
|
53
|
+
var a = attrs.find(function (x) {
|
|
54
|
+
var _x$name;
|
|
55
|
+
return ((_x$name = x.name) == null ? void 0 : _x$name.toLowerCase()) === lower;
|
|
56
|
+
});
|
|
57
|
+
return a == null ? void 0 : a.value;
|
|
58
|
+
}
|
|
59
|
+
function getTextContent(node) {
|
|
60
|
+
var _node$childNodes;
|
|
61
|
+
if (!node) return '';
|
|
62
|
+
if (node.nodeName === '#text') {
|
|
63
|
+
var _node$value;
|
|
64
|
+
return (_node$value = node.value) != null ? _node$value : '';
|
|
65
|
+
}
|
|
66
|
+
var childNodes = (_node$childNodes = node.childNodes) != null ? _node$childNodes : [];
|
|
67
|
+
return childNodes.map(function (child) {
|
|
68
|
+
return getTextContent(child);
|
|
69
|
+
}).join('');
|
|
70
|
+
}
|
|
71
|
+
function isElementNode(node) {
|
|
72
|
+
return node && typeof node.tagName === 'string';
|
|
73
|
+
}
|
|
74
|
+
function splitClassList(classAttr) {
|
|
75
|
+
if (classAttr == null || classAttr === '') return [];
|
|
76
|
+
var list = classAttr.trim().split(/\s+/).filter(Boolean);
|
|
77
|
+
return [].concat(new Set(list));
|
|
78
|
+
}
|
|
79
|
+
function parseFragmentToElement(fragment) {
|
|
80
|
+
var _fragmentNode$childNo, _fragmentNode;
|
|
81
|
+
if (!fragment || typeof fragment !== 'string') return null;
|
|
82
|
+
var wrapped = fragment.trim();
|
|
83
|
+
if (!wrapped) return null;
|
|
84
|
+
var fragmentNode;
|
|
85
|
+
try {
|
|
86
|
+
fragmentNode = parseFragment(wrapped);
|
|
87
|
+
} catch (_unused) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
var childNodes = (_fragmentNode$childNo = (_fragmentNode = fragmentNode) == null ? void 0 : _fragmentNode.childNodes) != null ? _fragmentNode$childNo : [];
|
|
91
|
+
for (var _iterator = _createForOfIteratorHelperLoose(childNodes), _step; !(_step = _iterator()).done;) {
|
|
92
|
+
var child = _step.value;
|
|
93
|
+
if (isElementNode(child)) {
|
|
94
|
+
var _child$tagName;
|
|
95
|
+
var classAttr = getAttr(child, 'class');
|
|
96
|
+
var classList = splitClassList(classAttr);
|
|
97
|
+
var textContent = getTextContent(child).trim();
|
|
98
|
+
var otherAttrs = {};
|
|
99
|
+
for (var _iterator2 = _createForOfIteratorHelperLoose((_child$attrs = child.attrs) != null ? _child$attrs : []), _step2; !(_step2 = _iterator2()).done;) {
|
|
100
|
+
var _child$attrs, _a$name;
|
|
101
|
+
var a = _step2.value;
|
|
102
|
+
if (((_a$name = a.name) == null ? void 0 : _a$name.toLowerCase()) !== 'class') {
|
|
103
|
+
var _a$value;
|
|
104
|
+
otherAttrs[a.name] = (_a$value = a.value) != null ? _a$value : '';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
tagName: ((_child$tagName = child.tagName) != null ? _child$tagName : '').toLowerCase(),
|
|
109
|
+
classList: classList,
|
|
110
|
+
textContent: textContent,
|
|
111
|
+
otherAttrs: otherAttrs
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function compareHtmlFragments(originalFragment, finalFragment) {
|
|
118
|
+
var _original$classList, _final$classList, _final$classList2, _original$classList2, _original$textContent, _final$textContent;
|
|
119
|
+
var original = parseFragmentToElement(originalFragment);
|
|
120
|
+
var _final = parseFragmentToElement(finalFragment);
|
|
121
|
+
var originalClasses = new Set((_original$classList = original == null ? void 0 : original.classList) != null ? _original$classList : []);
|
|
122
|
+
var finalClasses = new Set((_final$classList = _final == null ? void 0 : _final.classList) != null ? _final$classList : []);
|
|
123
|
+
var classAdded = ((_final$classList2 = _final == null ? void 0 : _final.classList) != null ? _final$classList2 : []).filter(function (c) {
|
|
124
|
+
return !originalClasses.has(c);
|
|
125
|
+
});
|
|
126
|
+
var classRemoved = ((_original$classList2 = original == null ? void 0 : original.classList) != null ? _original$classList2 : []).filter(function (c) {
|
|
127
|
+
return !finalClasses.has(c);
|
|
128
|
+
});
|
|
129
|
+
var textOriginal = (_original$textContent = original == null ? void 0 : original.textContent) != null ? _original$textContent : '';
|
|
130
|
+
var textFinal = (_final$textContent = _final == null ? void 0 : _final.textContent) != null ? _final$textContent : '';
|
|
131
|
+
var textChanged = textOriginal !== textFinal;
|
|
132
|
+
var textSummary = textChanged ? "\u300C" + (textOriginal || '(空)') + "\u300D \u2192 \u300C" + (textFinal || '(空)') + "\u300D" : '无变更';
|
|
133
|
+
return {
|
|
134
|
+
original: original,
|
|
135
|
+
"final": _final,
|
|
136
|
+
classAdded: classAdded,
|
|
137
|
+
classRemoved: classRemoved,
|
|
138
|
+
textOriginal: textOriginal,
|
|
139
|
+
textFinal: textFinal,
|
|
140
|
+
textChanged: textChanged,
|
|
141
|
+
textSummary: textSummary
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function consumeGroupChangeResult(result) {
|
|
145
|
+
if (result == null) return undefined;
|
|
146
|
+
var htmlDiff = compareHtmlFragments(result.originalFragment, result.finalFragment);
|
|
147
|
+
return _extends({}, result, {
|
|
148
|
+
htmlDiff: htmlDiff
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export { compareHtmlFragments, consumeGroupChangeResult, parseFragmentToElement };
|
|
153
|
+
//# sourceMappingURL=html-fragment-diff.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-fragment-diff.esm.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * 对两个 HTML 片段做 parse 级别的对比:解析出标签、class、文本等,并输出 class 增删与文本变更。\n * 用于消费 resolveGroupChangeFragments 得到的 originalFragment / finalFragment。\n */\n\nimport { parseFragment } from 'parse5';\n\n/** 解析出的单个根元素信息(只关心第一个根元素) */\nexport interface ParsedFragmentElement {\n tagName: string;\n /** class 属性按空白切分后的列表 */\n classList: string[];\n /** 元素内直接+间接文本拼接(不含子标签的 tag,只取文本) */\n textContent: string;\n /** 除 class 外的其他属性(name -> value) */\n otherAttrs: Record<string, string>;\n}\n\n/** 两个 HTML 片段的对比结果 */\nexport interface HtmlFragmentDiff {\n /** 原始片段解析结果(若解析失败为 null) */\n original: ParsedFragmentElement | null;\n /** 最终片段解析结果(若解析失败为 null) */\n final: ParsedFragmentElement | null;\n /** class:最终相对原始新增的 class 列表 */\n classAdded: string[];\n /** class:最终相对原始删除的 class 列表 */\n classRemoved: string[];\n /** 文本:原始片段根元素文本 */\n textOriginal: string;\n /** 文本:最终片段根元素文本 */\n textFinal: string;\n /** 文本是否发生变更 */\n textChanged: boolean;\n /** 文本变更的简短描述(便于展示) */\n textSummary: string;\n}\n\n/**\n * 从 parse5 的节点中取属性值\n */\nfunction getAttr(\n node: { attrs?: Array<{ name: string; value: string }> },\n name: string\n): string | undefined {\n const attrs = node.attrs ?? [];\n const lower = name.toLowerCase();\n const a = attrs.find((x) => x.name?.toLowerCase() === lower);\n return a?.value;\n}\n\n/**\n * 递归收集元素的文本内容(不含标签名,只取文本节点)\n */\nfunction getTextContent(node: any): string {\n if (!node) return '';\n if (node.nodeName === '#text') {\n return node.value ?? '';\n }\n const childNodes = node.childNodes ?? [];\n return childNodes.map((child: any) => getTextContent(child)).join('');\n}\n\n/**\n * 判断是否为元素节点(有 tagName)\n */\nfunction isElementNode(node: any): node is {\n tagName: string;\n attrs: Array<{ name: string; value: string }>;\n childNodes?: any[];\n} {\n return node && typeof (node as any).tagName === 'string';\n}\n\n/**\n * 将 class 属性字符串按空白切分为有序列表,去重保留顺序\n */\nfunction splitClassList(classAttr: string | undefined): string[] {\n if (classAttr == null || classAttr === '') return [];\n const list = classAttr.trim().split(/\\s+/).filter(Boolean);\n return [...new Set(list)];\n}\n\n/**\n * 从 HTML 片段中解析出第一个根元素的信息\n *\n * @param fragment 单段 HTML,如 `<h1 class=\"...\">姓名</h1>`\n * @returns 第一个根元素的信息;若无元素或解析失败则返回 null\n */\nexport function parseFragmentToElement(\n fragment: string\n): ParsedFragmentElement | null {\n if (!fragment || typeof fragment !== 'string') return null;\n\n const wrapped = fragment.trim();\n if (!wrapped) return null;\n\n let fragmentNode: any;\n try {\n fragmentNode = parseFragment(wrapped);\n } catch {\n return null;\n }\n\n const childNodes = fragmentNode?.childNodes ?? [];\n for (const child of childNodes) {\n if (isElementNode(child)) {\n const classAttr = getAttr(child, 'class');\n const classList = splitClassList(classAttr);\n const textContent = getTextContent(child).trim();\n const otherAttrs: Record<string, string> = {};\n for (const a of child.attrs ?? []) {\n if (a.name?.toLowerCase() !== 'class') {\n otherAttrs[a.name] = a.value ?? '';\n }\n }\n return {\n tagName: (child.tagName ?? '').toLowerCase(),\n classList,\n textContent,\n otherAttrs,\n };\n }\n }\n return null;\n}\n\n/**\n * 对比两个 HTML 片段:解析后比较 class 增删与根元素文本变更\n *\n * @param originalFragment 原始片段(如 resolveGroupChangeFragments 的 originalFragment)\n * @param finalFragment 最终片段(如 resolveGroupChangeFragments 的 finalFragment)\n * @returns 结构化对比结果,便于展示「class 多了啥、少了啥」和「text 变更了啥」\n */\nexport function compareHtmlFragments(\n originalFragment: string,\n finalFragment: string\n): HtmlFragmentDiff {\n const original = parseFragmentToElement(originalFragment);\n const final = parseFragmentToElement(finalFragment);\n\n const originalClasses = new Set(original?.classList ?? []);\n const finalClasses = new Set(final?.classList ?? []);\n const classAdded = (final?.classList ?? []).filter(\n (c) => !originalClasses.has(c)\n );\n const classRemoved = (original?.classList ?? []).filter(\n (c) => !finalClasses.has(c)\n );\n\n const textOriginal = original?.textContent ?? '';\n const textFinal = final?.textContent ?? '';\n const textChanged = textOriginal !== textFinal;\n const textSummary = textChanged\n ? `「${textOriginal || '(空)'}」 → 「${textFinal || '(空)'}」`\n : '无变更';\n\n return {\n original,\n final,\n classAdded,\n classRemoved,\n textOriginal,\n textFinal,\n textChanged,\n textSummary,\n };\n}\n\n/**\n * 消费 resolveGroupChangeFragments 的返回值:在原有片段级 diff 基础上,再解析两个 HTML 片段,\n * 得到 class 增删与文本变更的结构化结果。\n *\n * @param result resolveGroupChangeMessage / resolveGroupChangeFragments 的返回值\n * @returns 原 result 与 HTML 解析对比结果;若 result 为 undefined 则返回 undefined\n */\nexport function consumeGroupChangeResult<\n T extends { originalFragment: string; finalFragment: string }\n>(result: T | undefined): (T & { htmlDiff: HtmlFragmentDiff }) | undefined {\n if (result == null) return undefined;\n const htmlDiff = compareHtmlFragments(\n result.originalFragment,\n result.finalFragment\n );\n return { ...result, htmlDiff };\n}\n"],"names":["getAttr","node","name","attrs","_node$attrs","lower","toLowerCase","a","find","x","_x$name","value","getTextContent","nodeName","_node$value","childNodes","_node$childNodes","map","child","join","isElementNode","tagName","splitClassList","classAttr","list","trim","split","filter","Boolean","concat","Set","parseFragmentToElement","fragment","wrapped","fragmentNode","parseFragment","_unused","_fragmentNode$childNo","_fragmentNode","_iterator","_createForOfIteratorHelperLoose","_step","done","_child$tagName","classList","textContent","otherAttrs","_iterator2","_child$attrs","_step2","_a$name","_a$value","compareHtmlFragments","originalFragment","finalFragment","original","final","originalClasses","_original$classList","finalClasses","_final$classList","classAdded","_final$classList2","c","has","classRemoved","_original$classList2","textOriginal","_original$textContent","textFinal","_final$textContent","textChanged","textSummary","consumeGroupChangeResult","result","undefined","htmlDiff","_extends"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ParsedFragmentElement {
|
|
2
|
+
tagName: string;
|
|
3
|
+
classList: string[];
|
|
4
|
+
textContent: string;
|
|
5
|
+
otherAttrs: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export interface HtmlFragmentDiff {
|
|
8
|
+
original: ParsedFragmentElement | null;
|
|
9
|
+
final: ParsedFragmentElement | null;
|
|
10
|
+
classAdded: string[];
|
|
11
|
+
classRemoved: string[];
|
|
12
|
+
textOriginal: string;
|
|
13
|
+
textFinal: string;
|
|
14
|
+
textChanged: boolean;
|
|
15
|
+
textSummary: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function parseFragmentToElement(fragment: string): ParsedFragmentElement | null;
|
|
18
|
+
export declare function compareHtmlFragments(originalFragment: string, finalFragment: string): HtmlFragmentDiff;
|
|
19
|
+
export declare function consumeGroupChangeResult<T extends {
|
|
20
|
+
originalFragment: string;
|
|
21
|
+
finalFragment: string;
|
|
22
|
+
}>(result: T | undefined): (T & {
|
|
23
|
+
htmlDiff: HtmlFragmentDiff;
|
|
24
|
+
}) | undefined;
|