html-minifier-next 8.1.0 → 8.1.2
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/dist/types/htmlminifier.d.ts.map +1 -1
- package/dist/types/htmlparser.d.ts.map +1 -1
- package/dist/types/lib/constants.d.ts +8 -1
- package/dist/types/lib/constants.d.ts.map +1 -1
- package/dist/types/lib/utils.d.ts +2 -1
- package/dist/types/lib/utils.d.ts.map +1 -1
- package/dist/types/lib/whitespace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/htmlminifier.js +102 -21
- package/src/htmlparser.js +175 -73
- package/src/lib/constants.js +38 -1
- package/src/lib/utils.js +4 -3
- package/src/lib/whitespace.js +4 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AAeG,YAWG,iBAAiB,GAXV;IACR,KAAK,CAAC,WAAW;IACjB,GAAG,CAAC,WAAW;IACf,KAAK,CAAC,WAAW;IACjB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW;IACnB,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;CACxC,CAAmB;AA0CvB,eAAO,MAAM,MAAM,QAA8C,CAAC;AAoJlE,qBAAa,UAAU;IAMd,IAAI;IACJ,OAAO;IANd;;;OAGG;IACH,YAAY,IAAI,EAHL,MAGK,EAAE,OAAO,EAFd,iBAEc,EAGxB;IAEK,KAAK,kBAouBV;CACF"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
declare const RE_WS_START: RegExp;
|
|
2
2
|
declare const RE_WS_END: RegExp;
|
|
3
|
+
declare const RE_WS_ONLY: RegExp;
|
|
4
|
+
declare const RE_WS_NBSP_END: RegExp;
|
|
3
5
|
declare const RE_ALL_WS_NBSP: RegExp;
|
|
4
6
|
declare const RE_NBSP_LEADING_GROUP: RegExp;
|
|
5
7
|
declare const RE_NBSP_LEAD_GROUP: RegExp;
|
|
@@ -11,6 +13,8 @@ declare const RE_TRAILING_SEMICOLON: RegExp;
|
|
|
11
13
|
declare const RE_AMP_ENTITY: RegExp;
|
|
12
14
|
declare const RE_LEGACY_ENTITIES: RegExp;
|
|
13
15
|
declare const RE_ESCAPE_LT: RegExp;
|
|
16
|
+
declare const RE_ESCAPE_LT_RAW_TEXT: Record<string, RegExp>;
|
|
17
|
+
declare const RE_HTML_ENCODING: RegExp;
|
|
14
18
|
declare const RE_ATTR_WS_CHECK: RegExp;
|
|
15
19
|
declare const RE_ATTR_WS_COLLAPSE: RegExp;
|
|
16
20
|
declare const RE_ATTR_WS_TRIM: RegExp;
|
|
@@ -95,5 +99,8 @@ declare const looseElements: Set<string>;
|
|
|
95
99
|
declare const trailingElements: Set<string>;
|
|
96
100
|
declare const htmlElements: Set<string>;
|
|
97
101
|
declare const specialContentElements: Set<string>;
|
|
98
|
-
|
|
102
|
+
declare const genericRawTextElements: Set<string>;
|
|
103
|
+
declare const escapableRawTextElements: Set<string>;
|
|
104
|
+
declare const rawTextElements: Set<string>;
|
|
105
|
+
export { RE_WS_START, RE_WS_END, RE_WS_ONLY, RE_WS_NBSP_END, RE_ALL_WS_NBSP, RE_NBSP_LEADING_GROUP, RE_NBSP_LEAD_GROUP, RE_NBSP_TRAILING_GROUP, RE_NBSP_TRAILING_STRIP, RE_EVENT_ATTR_DEFAULT, RE_CAN_REMOVE_ATTR_QUOTES, RE_TRAILING_SEMICOLON, RE_AMP_ENTITY, RE_LEGACY_ENTITIES, RE_ESCAPE_LT, RE_ESCAPE_LT_RAW_TEXT, RE_HTML_ENCODING, RE_ATTR_WS_CHECK, RE_ATTR_WS_COLLAPSE, RE_ATTR_WS_TRIM, RE_STYLE_ELEMENT, RE_EMPTY_ATTRIBUTE, inlineElementsToKeepWhitespaceAround, inlineElementsToKeepWhitespaceWithin, inlineElementsToKeepWhitespace, formControlElements, generalDefaults, tagDefaults, executableScriptsMimetypes, keepScriptsMimetypes, jsonScriptTypes, isSimpleBoolean, isBooleanValue, collapsibleValues, srcsetElements, optionalStartTags, optionalEndTags, headerElements, descriptionElements, pBlockElements, pInlineElements, rubyEndTagOmission, rubyRtcEndTagOmission, optionElements, tableContentElements, tableSectionElements, cellElements, topLevelElements, compactElements, looseElements, trailingElements, htmlElements, specialContentElements, genericRawTextElements, escapableRawTextElements, rawTextElements };
|
|
99
106
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.js"],"names":[],"mappings":"AAEA,QAAA,MAAM,WAAW,QAAkB,CAAC;AACpC,QAAA,MAAM,SAAS,QAAkB,CAAC;AAClC,QAAA,MAAM,cAAc,QAAsB,CAAC;AAC3C,QAAA,MAAM,qBAAqB,QAAuB,CAAC;AACnD,QAAA,MAAM,kBAAkB,QAAqB,CAAC;AAC9C,QAAA,MAAM,sBAAsB,QAAqB,CAAC;AAClD,QAAA,MAAM,sBAAsB,QAAc,CAAC;AAC3C,QAAA,MAAM,qBAAqB,QAAkB,CAAC;AAC9C,QAAA,MAAM,yBAAyB,QAA0B,CAAC;AAC1D,QAAA,MAAM,qBAAqB,QAAO,CAAC;AACnC,QAAA,MAAM,aAAa,QAAwB,CAAC;AAC5C,QAAA,MAAM,kBAAkB,QAA+oB,CAAC;AACxqB,QAAA,MAAM,YAAY,QAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.js"],"names":[],"mappings":"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"}
|
|
@@ -89,8 +89,9 @@ declare function describeQuantifierRisk(pattern: RegExp | string): string | null
|
|
|
89
89
|
* over quoted attribute values (which may contain `>`).
|
|
90
90
|
* @param {string} html
|
|
91
91
|
* @param {number} pos - Start position (just after the tag name)
|
|
92
|
+
* @param {number} [limit] - Position to stop the search at (exclusive)
|
|
92
93
|
* @returns {number} Index of the closing `>`, or -1 if not found
|
|
93
94
|
*/
|
|
94
|
-
declare function findTagEnd(html: string, pos: number): number;
|
|
95
|
+
declare function findTagEnd(html: string, pos: number, limit?: number): number;
|
|
95
96
|
export { stableStringify, findTagEnd, LRU, MAX_CACHE_ENTRY_SIZE, hashContent, uniqueId, identity, isThenable, lowercase, replaceAsync, parseRegExp, embedSource, lostFlag, describeQuantifierRisk };
|
|
96
97
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/lib/utils.js"],"names":[],"mappings":"AAEA;;;GAGG;AACH,iBAAS,eAAe,CAAC,GAAG,EAHjB,OAGiB,GAFf,MAAM,CAYlB;AAID,cAAM,GAAG;IAEA,KAAK;IACL,IAAI;IACJ,IAAI;IACT,mCAAmC;IAC9B,GAAG,EADG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAJjC,YAAY,KAAK,SAAM,EAMtB;IACD,0BAA0B;IAC1B,GAAG,CAAC,GAAG,EADK,MACL,WAUN;IACD;;;OAGG;IACH,GAAG,CAAC,GAAG,EAHI,MAGJ,EAAE,KAAK,EAFH,OAEG,QAOb;IACD,0BAA0B;IAC1B,MAAM,CAAC,GAAG,EADE,MACF,QAA2B;IACrC,6EAA6E;IAC7E,KAAK,IADS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAGxE;CACF;AAKD,QAAA,MAAM,oBAAoB,QAAc,CAAC;AAIzC,0BAA0B;AAC1B,iBAAS,WAAW,CAAC,GAAG,EADZ,MACY,UAOvB;AAID,4BAA4B;AAC5B,iBAAS,QAAQ,CAAC,KAAK,EADX,MACW,UAMtB;AAID,4BAA4B;AAC5B,iBAAS,QAAQ,CAAC,KAAK,EADX,MACW,UAEtB;AAED;;;GAGG;AACH,iBAAS,UAAU,CAAC,KAAK,EAHd,OAGc,GAFZ,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,CAIrC;AAED,4BAA4B;AAC5B,iBAAS,SAAS,CAAC,KAAK,EADZ,MACY,UAEvB;AAID;;;;;;GAMG;AACH,iBAAe,YAAY,CAAC,GAAG,EALpB,MAKoB,EAAE,KAAK,EAJ3B,MAI2B,EAAE,OAAO,UAAA,GAFlC,OAAO,CAAC,MAAM,CAAC,CAe3B;AAID,qCAAqC;AACrC,iBAAS,WAAW,CAAC,KAAK,EADd,MAAM,GAAG,MACK,sBAUzB;AAyYD;;;;;;;;;GASG;AACH,iBAAS,QAAQ,CAAC,OAAO,EAJd,MAIc,GAHZ,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,CAqDlC;AAED;;;;;;;;;GASG;AACH,iBAAS,WAAW,CAAC,OAAO,EAHjB,MAGiB,GAFf,MAAM,CAOlB;AAGE,YAA6D,MAAM,GAAzD;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI,CAAA;CAAC,CAE7D;AAqKD;;;;;GAKG;AACH,iBAAS,sBAAsB,CAAC,OAAO,EAL5B,MAAM,GAAG,MAKmB,GAH1B,MAAM,GAAG,IAAI,CAqBzB;AAED
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/lib/utils.js"],"names":[],"mappings":"AAEA;;;GAGG;AACH,iBAAS,eAAe,CAAC,GAAG,EAHjB,OAGiB,GAFf,MAAM,CAYlB;AAID,cAAM,GAAG;IAEA,KAAK;IACL,IAAI;IACJ,IAAI;IACT,mCAAmC;IAC9B,GAAG,EADG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAJjC,YAAY,KAAK,SAAM,EAMtB;IACD,0BAA0B;IAC1B,GAAG,CAAC,GAAG,EADK,MACL,WAUN;IACD;;;OAGG;IACH,GAAG,CAAC,GAAG,EAHI,MAGJ,EAAE,KAAK,EAFH,OAEG,QAOb;IACD,0BAA0B;IAC1B,MAAM,CAAC,GAAG,EADE,MACF,QAA2B;IACrC,6EAA6E;IAC7E,KAAK,IADS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAGxE;CACF;AAKD,QAAA,MAAM,oBAAoB,QAAc,CAAC;AAIzC,0BAA0B;AAC1B,iBAAS,WAAW,CAAC,GAAG,EADZ,MACY,UAOvB;AAID,4BAA4B;AAC5B,iBAAS,QAAQ,CAAC,KAAK,EADX,MACW,UAMtB;AAID,4BAA4B;AAC5B,iBAAS,QAAQ,CAAC,KAAK,EADX,MACW,UAEtB;AAED;;;GAGG;AACH,iBAAS,UAAU,CAAC,KAAK,EAHd,OAGc,GAFZ,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,CAIrC;AAED,4BAA4B;AAC5B,iBAAS,SAAS,CAAC,KAAK,EADZ,MACY,UAEvB;AAID;;;;;;GAMG;AACH,iBAAe,YAAY,CAAC,GAAG,EALpB,MAKoB,EAAE,KAAK,EAJ3B,MAI2B,EAAE,OAAO,UAAA,GAFlC,OAAO,CAAC,MAAM,CAAC,CAe3B;AAID,qCAAqC;AACrC,iBAAS,WAAW,CAAC,KAAK,EADd,MAAM,GAAG,MACK,sBAUzB;AAyYD;;;;;;;;;GASG;AACH,iBAAS,QAAQ,CAAC,OAAO,EAJd,MAIc,GAHZ,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,CAqDlC;AAED;;;;;;;;;GASG;AACH,iBAAS,WAAW,CAAC,OAAO,EAHjB,MAGiB,GAFf,MAAM,CAOlB;AAGE,YAA6D,MAAM,GAAzD;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI,CAAA;CAAC,CAE7D;AAqKD;;;;;GAKG;AACH,iBAAS,sBAAsB,CAAC,OAAO,EAL5B,MAAM,GAAG,MAKmB,GAH1B,MAAM,GAAG,IAAI,CAqBzB;AAED;;;;;;;GAOG;AACH,iBAAS,UAAU,CAAC,IAAI,EALb,MAKa,EAAE,GAAG,EAJlB,MAIkB,EAAE,KAAK,AAHjC,CACA,EADQ,MAGuC,GAFrC,MAAM,CAelB;AAID,OAAO,EACL,eAAe,EACf,UAAU,EACV,GAAG,EACH,oBAAoB,EACpB,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,WAAW,EACX,QAAQ,EACR,sBAAsB,EACvB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whitespace.d.ts","sourceRoot":"","sources":["../../../src/lib/whitespace.js"],"names":[],"mappings":"AAeA,0BAA0B;AAC1B,QAAA,MAAM,cAAc,QADR,MAAM,WAQjB,CAAC;AAIF,0BAA0B;AAC1B,iBAAS,qBAAqB,CAAC,GAAG,EADtB,MACsB,UAgBjC;AAID;;;;;;GAMG;AACH,iBAAS,kBAAkB,CAAC,GAAG,EANpB,MAMoB,EAAE,OAAO,EAL7B;IAAC,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAKxD,EAAE,QAAQ,EAJvC,OAIuC,EAAE,SAAS,EAHlD,OAGkD,EAAE,WAAW,AAFvE,CAA8B,EAAtB,OAEuE,UAqFjF;AAoBD;;;;;;;;;GASG;AACH,iBAAS,uBAAuB,CAAC,GAAG,EATzB,MASyB,EAAE,OAAO,EARlC,MAQkC,EAAE,OAAO,EAP3C,MAO2C,EAAE,SAAS,EANtD,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,CAMkB,EAAE,SAAS,EALjE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,CAK6B,EAAE,OAAO,EAJ1E;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAAC,2BAA2B,CAAC,EAAE,OAAO,CAAA;CAI1B,EAAE,cAAc,EAH1F,GAAG,CAAC,MAAM,CAGgF,EAAE,aAAa,EAFzG,GAAG,CAAC,MAAM,CAE+F,UAiEnH;
|
|
1
|
+
{"version":3,"file":"whitespace.d.ts","sourceRoot":"","sources":["../../../src/lib/whitespace.js"],"names":[],"mappings":"AAeA,0BAA0B;AAC1B,QAAA,MAAM,cAAc,QADR,MAAM,WAQjB,CAAC;AAIF,0BAA0B;AAC1B,iBAAS,qBAAqB,CAAC,GAAG,EADtB,MACsB,UAgBjC;AAID;;;;;;GAMG;AACH,iBAAS,kBAAkB,CAAC,GAAG,EANpB,MAMoB,EAAE,OAAO,EAL7B;IAAC,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAKxD,EAAE,QAAQ,EAJvC,OAIuC,EAAE,SAAS,EAHlD,OAGkD,EAAE,WAAW,AAFvE,CAA8B,EAAtB,OAEuE,UAqFjF;AAoBD;;;;;;;;;GASG;AACH,iBAAS,uBAAuB,CAAC,GAAG,EATzB,MASyB,EAAE,OAAO,EARlC,MAQkC,EAAE,OAAO,EAP3C,MAO2C,EAAE,SAAS,EANtD,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,CAMkB,EAAE,SAAS,EALjE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAC,CAK6B,EAAE,OAAO,EAJ1E;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAAC,2BAA2B,CAAC,EAAE,OAAO,CAAA;CAI1B,EAAE,cAAc,EAH1F,GAAG,CAAC,MAAM,CAGgF,EAAE,aAAa,EAFzG,GAAG,CAAC,MAAM,CAE+F,UAiEnH;AASD,0BAA0B;AAC1B,iBAAS,qBAAqB,CAAC,GAAG,EADtB,MACsB,WAEjC;AAED,0BAA0B;AAC1B,iBAAS,iBAAiB,CAAC,GAAG,EADlB,MACkB,WAE7B;AAID,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,iBAAiB,EAClB,CAAC"}
|
package/package.json
CHANGED
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@commitlint/cli": "^21.2.2",
|
|
18
18
|
"@eslint/js": "^10.0.1",
|
|
19
|
-
"@swc/core": "^1.16.
|
|
19
|
+
"@swc/core": "^1.16.1",
|
|
20
20
|
"@types/node": "^26.2.0",
|
|
21
|
-
"eslint": "^10.
|
|
21
|
+
"eslint": "^10.9.1",
|
|
22
22
|
"globals": "^17.11.0",
|
|
23
23
|
"typescript": "^7.0.2",
|
|
24
24
|
"vite": "^8.2.1"
|
|
@@ -95,5 +95,5 @@
|
|
|
95
95
|
},
|
|
96
96
|
"type": "module",
|
|
97
97
|
"types": "dist/types/htmlminifier.d.ts",
|
|
98
|
-
"version": "8.1.
|
|
98
|
+
"version": "8.1.2"
|
|
99
99
|
}
|
package/src/htmlminifier.js
CHANGED
|
@@ -8,10 +8,17 @@ import { collectUsedSymbols } from './lib/unused-css.js';
|
|
|
8
8
|
import {
|
|
9
9
|
RE_LEGACY_ENTITIES,
|
|
10
10
|
RE_ESCAPE_LT,
|
|
11
|
+
RE_ESCAPE_LT_RAW_TEXT,
|
|
12
|
+
RE_HTML_ENCODING,
|
|
13
|
+
RE_WS_START,
|
|
14
|
+
RE_WS_END,
|
|
15
|
+
RE_WS_ONLY,
|
|
16
|
+
RE_WS_NBSP_END,
|
|
11
17
|
RE_STYLE_ELEMENT,
|
|
12
18
|
inlineElementsToKeepWhitespaceAround,
|
|
13
19
|
inlineElementsToKeepWhitespaceWithin,
|
|
14
20
|
specialContentElements,
|
|
21
|
+
rawTextElements,
|
|
15
22
|
htmlElements,
|
|
16
23
|
optionalStartTags,
|
|
17
24
|
optionalEndTags,
|
|
@@ -574,14 +581,14 @@ const DEFAULT_JS_TYPES = new Set(['', 'text/javascript', 'application/javascript
|
|
|
574
581
|
// Pre-compiled patterns for buffer scanning
|
|
575
582
|
const RE_START_TAG = /^<[^/!]/;
|
|
576
583
|
const RE_END_TAG = /^<\//;
|
|
584
|
+
const RE_WHITESPACE_ONLY = /^\s+$/;
|
|
585
|
+
const RE_TRAILING_WHITESPACE = /\s$/;
|
|
577
586
|
|
|
578
587
|
// Pre-compiled patterns for `htmlmin:ignore` block content analysis
|
|
579
588
|
const RE_HTML_COMMENT_START = /^\s*<!--/;
|
|
580
589
|
const RE_CLOSING_TAG_START = /^\s*<\/([a-zA-Z][\w:-]*)/;
|
|
581
590
|
const RE_LAST_HTML_TAG = /[\s\S]*<(\/?[a-zA-Z][\w:-]*)/;
|
|
582
591
|
|
|
583
|
-
// HTML encoding types for annotation-xml (MathML)
|
|
584
|
-
const RE_HTML_ENCODING = /^(text\/html|application\/xhtml\+xml)$/i;
|
|
585
592
|
|
|
586
593
|
// Probe for SVG/MathML elements—when the input has none (the common case),
|
|
587
594
|
// the per-tag foreign-content machinery can be skipped entirely
|
|
@@ -1117,11 +1124,65 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1117
1124
|
buffer.length = Math.max(0, index);
|
|
1118
1125
|
}
|
|
1119
1126
|
|
|
1127
|
+
// Whether the element being parsed holds text rather than markup. The namespace decides
|
|
1128
|
+
// this, not the name alone: `script` and `style` hold text wherever they stand, the rest
|
|
1129
|
+
// only as HTML elements—an SVG or MathML `title` holds markup like any foreign element
|
|
1130
|
+
function holdsRawText() {
|
|
1131
|
+
return specialContentElements.has(currentTag) ||
|
|
1132
|
+
(rawTextElements.has(currentTag) && !options.insideForeignContent);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Whether whitespace-only text between tags survives minification
|
|
1136
|
+
function keepsWhitespace() {
|
|
1137
|
+
return !options.collapseWhitespace || Boolean(options.conservativeCollapse) || Boolean(options.preserveLineBreaks);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// The two whitespace runs an omitted tag separated become one run, which collapses to the
|
|
1141
|
+
// single character it is due: a line break under `preserveLineBreaks`, a no-break space
|
|
1142
|
+
// where the run holds one, a space under `conservativeCollapse`
|
|
1143
|
+
function mergeWhitespaceRuns(/** @type {number} */ index) {
|
|
1144
|
+
let before = index - 1;
|
|
1145
|
+
while (before >= 0 && buffer[before] === '') {
|
|
1146
|
+
before--;
|
|
1147
|
+
}
|
|
1148
|
+
let after = index;
|
|
1149
|
+
while (after < buffer.length && buffer[after] === '') {
|
|
1150
|
+
after++;
|
|
1151
|
+
}
|
|
1152
|
+
if (before < 0 || after >= buffer.length) {
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const prev = buffer[before] ?? '';
|
|
1156
|
+
const next = buffer[after] ?? '';
|
|
1157
|
+
if (!RE_WS_NBSP_END.test(prev) || !RE_WS_START.test(next)) {
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
// A line break the run already carries outranks the whitespace ahead of it; trimming
|
|
1161
|
+
// the other side takes ASCII whitespace only, so a no-break space stays as is
|
|
1162
|
+
if (options.preserveLineBreaks && /[\n\r]/.test((next.match(RE_WS_START) ?? [''])[0])) {
|
|
1163
|
+
buffer[before] = prev.replace(RE_WS_END, '');
|
|
1164
|
+
} else {
|
|
1165
|
+
buffer[after] = next.replace(RE_WS_START, '');
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1120
1169
|
function removeEndTag() {
|
|
1121
1170
|
let index = buffer.length - 1;
|
|
1122
1171
|
while (index > 0 && !RE_END_TAG.test(buffer[index] ?? '')) {
|
|
1123
1172
|
index--;
|
|
1124
1173
|
}
|
|
1174
|
+
// Text that follows the end tag is kept as is, so drop the tag alone. Whitespace kept
|
|
1175
|
+
// verbatim—in `pre`, in `textarea`, or wherever `canTrimWhitespace` says so—counts as
|
|
1176
|
+
// kept text too, no matter how whitespace is collapsed elsewhere.
|
|
1177
|
+
const noTrim = stackNoTrimWhitespace.length > 0;
|
|
1178
|
+
if (index < buffer.length - 1 && (keepsWhitespace() || noTrim) && RE_END_TAG.test(buffer[index] ?? '')) {
|
|
1179
|
+
buffer.splice(index, 1);
|
|
1180
|
+
// Only collapsed whitespace can merge
|
|
1181
|
+
if (options.collapseWhitespace && !noTrim) {
|
|
1182
|
+
mergeWhitespaceRuns(index);
|
|
1183
|
+
}
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1125
1186
|
buffer.length = Math.max(0, index);
|
|
1126
1187
|
}
|
|
1127
1188
|
|
|
@@ -1143,11 +1204,15 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1143
1204
|
// element which has now been removed
|
|
1144
1205
|
function squashTrailingWhitespace(/** @type {string} */ nextTag) {
|
|
1145
1206
|
let charsIndex = buffer.length - 1;
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1207
|
+
// Step back over every trailing comment and empty entry, not just one—a single step
|
|
1208
|
+
// stops the search at the comment itself, leaving whitespace behind that a second
|
|
1209
|
+
// minification run then removes
|
|
1210
|
+
while (charsIndex > 0) {
|
|
1211
|
+
const item = buffer[charsIndex] ?? '';
|
|
1212
|
+
if (!/^(?:<!|$)/.test(item) || (uidIgnore && item.indexOf(uidIgnore) !== -1)) {
|
|
1213
|
+
break;
|
|
1150
1214
|
}
|
|
1215
|
+
charsIndex--;
|
|
1151
1216
|
}
|
|
1152
1217
|
trimTrailingWhitespace(charsIndex, nextTag);
|
|
1153
1218
|
}
|
|
@@ -1240,7 +1305,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1240
1305
|
} else {
|
|
1241
1306
|
text = collapseWhitespace(text, options, true, true);
|
|
1242
1307
|
}
|
|
1243
|
-
if (!text &&
|
|
1308
|
+
if (!text && RE_TRAILING_WHITESPACE.test(currentChars) && textPrevTag && textPrevTag.charAt(0) === '/') {
|
|
1244
1309
|
trimTrailingWhitespace(buffer.length - 1, textNextTag);
|
|
1245
1310
|
}
|
|
1246
1311
|
}
|
|
@@ -1277,15 +1342,22 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1277
1342
|
// `</head>`, `</colgroup>`, or `</caption>` may be omitted if not followed by space or comment
|
|
1278
1343
|
if (optionalEndTagEmitted && (compactElements.has(optionalEndTag) || (looseElements.has(optionalEndTag) && !/^\s/.test(effectiveText)))) {
|
|
1279
1344
|
removeEndTag();
|
|
1345
|
+
// The tag is gone from the buffer, so nothing is left to omit later
|
|
1346
|
+
optionalEndTag = '';
|
|
1347
|
+
optionalEndTagEmitted = false;
|
|
1280
1348
|
}
|
|
1281
|
-
//
|
|
1282
|
-
|
|
1349
|
+
// Whitespace-only text doesn’t prevent omission of the pending end tag: It’s either
|
|
1350
|
+
// collapsed away, or kept in place while the tag alone is dropped—except for
|
|
1351
|
+
// `</head>`, `</colgroup>`, and `</caption>`, which may not be followed by space.
|
|
1352
|
+
// Only ASCII whitespace counts, as no-break space and friends are content
|
|
1353
|
+
// https://html.spec.whatwg.org/multipage/syntax.html#optional-tags
|
|
1354
|
+
if (!RE_WS_ONLY.test(text) || (keepsWhitespace() && looseElements.has(optionalEndTag))) {
|
|
1283
1355
|
optionalEndTag = '';
|
|
1284
1356
|
optionalEndTagEmitted = false;
|
|
1285
1357
|
}
|
|
1286
1358
|
}
|
|
1287
1359
|
charsPrevTag = /^\s*$/.test(text) ? textPrevTag : 'comment';
|
|
1288
|
-
if (options.decodeEntities && text && !
|
|
1360
|
+
if (options.decodeEntities && text && !holdsRawText()) {
|
|
1289
1361
|
// Escape any `&` symbols that start either:
|
|
1290
1362
|
// 1. a legacy-named character reference (i.e., one that doesn’t end with `;`)
|
|
1291
1363
|
// 2. or any other character reference (i.e., one that does end with `;`)
|
|
@@ -1295,7 +1367,10 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1295
1367
|
text = text.replace(RE_LEGACY_ENTITIES, '&$1');
|
|
1296
1368
|
}
|
|
1297
1369
|
if (text.indexOf('<') !== -1) {
|
|
1298
|
-
text
|
|
1370
|
+
// Escapable raw text ends at its own end tag alone, so only that one needs escaping;
|
|
1371
|
+
// in foreign content the same element holds markup, where every `<` does
|
|
1372
|
+
const escapeLt = (options.insideForeignContent ? undefined : RE_ESCAPE_LT_RAW_TEXT[currentTag]) ?? RE_ESCAPE_LT;
|
|
1373
|
+
text = text.replace(escapeLt, '<');
|
|
1299
1374
|
}
|
|
1300
1375
|
}
|
|
1301
1376
|
if (uidPattern && options.collapseWhitespace && stackNoTrimWhitespace.length) {
|
|
@@ -1347,7 +1422,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1347
1422
|
const prevComment = buffer[buffer.length - 2];
|
|
1348
1423
|
|
|
1349
1424
|
// Check if previous item is whitespace-only and item before that is ignore-placeholder
|
|
1350
|
-
if (prevText &&
|
|
1425
|
+
if (prevText && RE_WHITESPACE_ONLY.test(prevText) && prevComment && uidIgnorePlaceholderPattern.test(prevComment)) {
|
|
1351
1426
|
// Extract the index from both placeholders to check their content
|
|
1352
1427
|
const currentMatch = comment.match(uidIgnorePlaceholderPattern);
|
|
1353
1428
|
const prevMatch = prevComment.match(uidIgnorePlaceholderPattern);
|
|
@@ -1390,7 +1465,11 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1390
1465
|
// Apply `collapseWhitespace` with appropriate context
|
|
1391
1466
|
if (!stackNoTrimWhitespace.length && !stackNoCollapseWhitespace.length) {
|
|
1392
1467
|
// Not in pre or other no-collapse context
|
|
1393
|
-
if (
|
|
1468
|
+
if (prevText.includes('\xA0')) {
|
|
1469
|
+
// No-break spaces are content, so every one of them survives in place;
|
|
1470
|
+
// the ASCII whitespace around them collapses as it would anywhere else
|
|
1471
|
+
collapsedText = collapseWhitespace(prevText, options, true, true, true);
|
|
1472
|
+
} else if (options.preserveLineBreaks && /[\n\r]/.test(prevText)) {
|
|
1394
1473
|
// Preserve line break as single newline
|
|
1395
1474
|
collapsedText = '\n';
|
|
1396
1475
|
} else if (options.conservativeCollapse) {
|
|
@@ -1464,10 +1543,11 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1464
1543
|
options.removeTagWhitespace = false;
|
|
1465
1544
|
}
|
|
1466
1545
|
// `foreignObject` in SVG and `annotation-xml` in MathML contain HTML content
|
|
1467
|
-
//
|
|
1546
|
+
// (the element itself is in SVG/MathML namespace, only its children are HTML);
|
|
1547
|
+
// a repeated attribute is dropped after the first, so the first `encoding` decides
|
|
1468
1548
|
if (options.insideForeignContent && (lowerTag === 'foreignobject' ||
|
|
1469
|
-
(lowerTag === 'annotation-xml' &&
|
|
1470
|
-
|
|
1549
|
+
(lowerTag === 'annotation-xml' && RE_HTML_ENCODING.test(
|
|
1550
|
+
attrs.find((/** @type {HTMLAttribute} */ a) => a.name.toLowerCase() === 'encoding')?.value ?? '')))) {
|
|
1471
1551
|
const nameParent = options.name;
|
|
1472
1552
|
options = Object.create(options);
|
|
1473
1553
|
options.caseSensitive = false;
|
|
@@ -1639,10 +1719,11 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1639
1719
|
removeStartTag();
|
|
1640
1720
|
}
|
|
1641
1721
|
optionalStartTag = '';
|
|
1642
|
-
// `</html>` or `</body>` may be omitted if not followed by comment
|
|
1643
|
-
// `</head>` may be omitted if not followed by space or comment
|
|
1644
|
-
// `</p>` may be omitted if no more content in parent,
|
|
1645
|
-
//
|
|
1722
|
+
// `</html>` or `</body>` may be omitted if not followed by comment,
|
|
1723
|
+
// `</head>` may be omitted if not followed by space or comment,
|
|
1724
|
+
// `</p>` may be omitted if no more content in parent,
|
|
1725
|
+
// unless parent is in `pInlineElements` or is a custom element
|
|
1726
|
+
// (https://html.spec.whatwg.org/multipage/syntax.html#optional-tags);
|
|
1646
1727
|
// except for `</dt>` or `</thead>`, end tags may be omitted if no more content in parent element
|
|
1647
1728
|
if (tag && optionalEndTag && optionalEndTagEmitted && !trailingElements.has(optionalEndTag) && (optionalEndTag !== 'p' || (!pInlineElements.has(tag) && !tag.includes('-')))) {
|
|
1648
1729
|
removeEndTag();
|
|
@@ -1710,7 +1791,7 @@ async function minifyHTML(value, options, partialMarkup) {
|
|
|
1710
1791
|
textNextAttrs = nextAttrs || [];
|
|
1711
1792
|
|
|
1712
1793
|
// Detect whether any async work is actually needed for this text node
|
|
1713
|
-
const needsDecode = options.decodeEntities && text && !
|
|
1794
|
+
const needsDecode = options.decodeEntities && text && !holdsRawText() && text.indexOf('&') !== -1;
|
|
1714
1795
|
const needsProcessScript = specialContentElements.has(currentTag) && (options.processScripts || hasJsonScriptType(currentAttrs));
|
|
1715
1796
|
const needsMinifyJS = options.minifyJS !== identity && isExecutableScript(currentTag, currentAttrs);
|
|
1716
1797
|
const isModuleScript = needsMinifyJS && currentAttrs.some(
|
package/src/htmlparser.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { isThenable, embedSource } from './lib/utils.js';
|
|
8
|
+
import { isThenable, embedSource, findTagEnd } from './lib/utils.js';
|
|
9
|
+
import { escapableRawTextElements, genericRawTextElements, RE_HTML_ENCODING } from './lib/constants.js';
|
|
9
10
|
|
|
10
11
|
/** @import { HTMLAttribute } from './lib/attributes.js' */
|
|
11
12
|
|
|
@@ -64,6 +65,7 @@ const qnameCapture = (function () {
|
|
|
64
65
|
return '((?:' + ncname + '\\:)?' + ncname + ')';
|
|
65
66
|
})();
|
|
66
67
|
const startTagOpen = new RegExp('^<' + qnameCapture);
|
|
68
|
+
const endTagOpen = new RegExp('^</' + qnameCapture);
|
|
67
69
|
export const endTag = new RegExp('^</' + qnameCapture + '[^>]*>');
|
|
68
70
|
|
|
69
71
|
let IS_REGEX_CAPTURING_BROKEN = false;
|
|
@@ -84,45 +86,70 @@ const fillAttrs = new Set(['checked', 'compact', 'declare', 'defer', 'disabled',
|
|
|
84
86
|
// Special elements (can contain anything)
|
|
85
87
|
const special = new Set(['script', 'style']);
|
|
86
88
|
|
|
89
|
+
// Elements whose children are HTML rather than foreign content, each only in
|
|
90
|
+
// the namespace it belongs to (`annotation-xml` is one, too, but only for some
|
|
91
|
+
// `encoding` values, so it is checked separately)
|
|
92
|
+
// https://html.spec.whatwg.org/multipage/parsing.html#html-integration-point
|
|
93
|
+
const svgIntegrationPoints = new Set(['foreignobject', 'desc', 'title']);
|
|
94
|
+
const mathIntegrationPoints = new Set(['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
95
|
+
|
|
87
96
|
// HTML elements, https://html.spec.whatwg.org/multipage/indices.html#elements-3
|
|
88
97
|
// Phrasing content, https://html.spec.whatwg.org/multipage/dom.html#phrasing-content
|
|
89
|
-
const nonPhrasing = new Set(['address', 'article', 'aside', 'base', 'blockquote', 'body', 'caption', 'col', 'colgroup', 'dd', 'details', 'dialog', 'div', 'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'legend', 'li', 'menuitem', 'meta', 'ol', 'optgroup', 'option', 'param', 'rp', 'rt', 'source', 'style', 'summary', 'tbody', 'td', 'tfoot', 'th', 'thead', 'title', 'tr', 'track', 'ul']);
|
|
90
|
-
|
|
91
|
-
const reCache = {};
|
|
98
|
+
const nonPhrasing = new Set(['address', 'article', 'aside', 'base', 'blockquote', 'body', 'caption', 'center', 'col', 'colgroup', 'dd', 'details', 'dialog', 'dir', 'div', 'dl', 'dt', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'legend', 'li', 'listing', 'main', 'menu', 'menuitem', 'meta', 'nav', 'ol', 'optgroup', 'option', 'param', 'plaintext', 'pre', 'rp', 'rt', 'search', 'section', 'source', 'style', 'summary', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'title', 'tr', 'track', 'ul', 'xmp']);
|
|
92
99
|
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
const
|
|
96
|
-
'
|
|
97
|
-
'style': /([\s\S]*?)<\/style[^>]*>/i,
|
|
98
|
-
'noscript': /([\s\S]*?)<\/noscript[^>]*>/i
|
|
99
|
-
};
|
|
100
|
+
// A tag name ends at whitespace, a slash, or the closing bracket, so `</scriptx>` names a
|
|
101
|
+
// different element and does not end this one
|
|
102
|
+
const endsTagName = (/** @type {string} */ character) =>
|
|
103
|
+
character === '' || character === '/' || character === '>' || /\s/.test(character);
|
|
100
104
|
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
/**
|
|
106
|
+
* @param {string} text - The input the tag stands in
|
|
107
|
+
* @param {number} nameEnd - Position right after the tag name
|
|
108
|
+
* @returns {number} Position of the `>` that ends the tag, or -1 where none follows
|
|
109
|
+
*/
|
|
110
|
+
function findEndTagClose(text, nameEnd) {
|
|
111
|
+
const close = text.indexOf('>', nameEnd);
|
|
112
|
+
if (close === -1) {
|
|
113
|
+
return -1;
|
|
114
|
+
}
|
|
115
|
+
// A quoted attribute value can hold a `>` that does not end the tag it stands in, so a
|
|
116
|
+
// quote ahead of that bracket calls for a scan that knows about quoting. It stops at the
|
|
117
|
+
// next `<`, which no tag reaches past: The scans of consecutive tags then never overlap,
|
|
118
|
+
// and an unquoted value that spans one leaves the first bracket as the end, as before.
|
|
119
|
+
for (let index = nameEnd; index < close; index++) {
|
|
120
|
+
const character = text[index];
|
|
121
|
+
if (character === '"' || character === "'") {
|
|
122
|
+
const nextOpen = text.indexOf('<', nameEnd);
|
|
123
|
+
const quoted = findTagEnd(text, nameEnd, nextOpen === -1 ? text.length : nextOpen);
|
|
124
|
+
return quoted === -1 ? close : quoted;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return close;
|
|
128
|
+
}
|
|
103
129
|
|
|
104
|
-
// O(n) helper: Strip all occurrences of `open…close` delimiters, keeping inner content
|
|
105
|
-
// Used instead of a regex replace to avoid O(n²) behavior on adversarial inputs
|
|
106
130
|
/**
|
|
107
|
-
* @param {string}
|
|
108
|
-
* @param {string}
|
|
109
|
-
* @
|
|
131
|
+
* @param {string} text - The input from the start of the content on
|
|
132
|
+
* @param {string} tag - Lowercase element name
|
|
133
|
+
* @returns {{content: string, length: number} | null} What the element holds and how far it
|
|
134
|
+
* reaches, or `null` where no end tag of its own follows
|
|
110
135
|
*/
|
|
111
|
-
function
|
|
112
|
-
|
|
113
|
-
let
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
i = end + close.length;
|
|
136
|
+
function findRawTextEnd(text, tag) {
|
|
137
|
+
const nameEnd = tag.length + 2;
|
|
138
|
+
for (let candidate = text.indexOf('</'); candidate !== -1; candidate = text.indexOf('</', candidate + 2)) {
|
|
139
|
+
if (text.slice(candidate + 2, candidate + nameEnd).toLowerCase() !== tag || !endsTagName(text.charAt(candidate + nameEnd))) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// What follows the name is not part of it, and the tag ends at the next bracket outside
|
|
143
|
+
// its attribute values—where none follows, no later end tag can be complete, either
|
|
144
|
+
const end = findEndTagClose(text, candidate + nameEnd);
|
|
145
|
+
return end === -1 ? null : { content: text.slice(0, candidate), length: end + 1 };
|
|
122
146
|
}
|
|
123
|
-
return
|
|
147
|
+
return null;
|
|
124
148
|
}
|
|
125
149
|
|
|
150
|
+
// Cache for compiled attribute regexes per handler configuration
|
|
151
|
+
const attrRegexCache = new WeakMap();
|
|
152
|
+
|
|
126
153
|
/** @param {HTMLParserHandler} handler */
|
|
127
154
|
function buildAttrRegex(handler) {
|
|
128
155
|
const unquotedValue = handler.continueOnParseError ? singleAttrValueLenientUnquoted : singleAttrValues[2];
|
|
@@ -202,7 +229,7 @@ export class HTMLParser {
|
|
|
202
229
|
const fullHtml = this.html;
|
|
203
230
|
const fullLength = fullHtml.length;
|
|
204
231
|
|
|
205
|
-
/** @type {Array<{tag: string, lowerTag: string, attrs: HTMLAttribute[]}>} */
|
|
232
|
+
/** @type {Array<{tag: string, lowerTag: string, attrs: HTMLAttribute[], namespace: string}>} */
|
|
206
233
|
const stack = [];
|
|
207
234
|
/** @type {string} */
|
|
208
235
|
let lastTag = '';
|
|
@@ -225,7 +252,7 @@ export class HTMLParser {
|
|
|
225
252
|
const startTagOpenY = new RegExp(startTagOpen.source.slice(1), 'y');
|
|
226
253
|
// `\s*` with sticky flag is O(n) at worst—no retry from different positions possible
|
|
227
254
|
const startTagCloseY = /\s*(\/?)>/y;
|
|
228
|
-
const
|
|
255
|
+
const endTagOpenY = new RegExp(endTagOpen.source.slice(1), 'y');
|
|
229
256
|
const doctypeY = /<!DOCTYPE[^<>]+>/iy;
|
|
230
257
|
const commentTestY = /<!--/y;
|
|
231
258
|
const conditionalTestY = /<!\[/y;
|
|
@@ -238,11 +265,11 @@ export class HTMLParser {
|
|
|
238
265
|
let pos = 0;
|
|
239
266
|
let lastPos;
|
|
240
267
|
|
|
241
|
-
// An end tag always ends at a `>`; without one, the
|
|
242
|
-
//
|
|
243
|
-
// (e.g., `</aaaa…` with no `>`). Track the next `>` monotonically
|
|
244
|
-
// advances, so the aggregate scan work stays O(n)—and skip the
|
|
245
|
-
// lies ahead, where
|
|
268
|
+
// An end tag always ends at a `>`; without one ahead, matching the name only to find
|
|
269
|
+
// no bracket behind it repeats the name run at every position, which costs O(n²) on a
|
|
270
|
+
// long unclosed name (e.g., `</aaaa…` with no `>`). Track the next `>` monotonically—
|
|
271
|
+
// `pos` only advances, so the aggregate scan work stays O(n)—and skip the match when
|
|
272
|
+
// none lies ahead, where the tag could not be complete anyway.
|
|
246
273
|
let nextGtPos = fullHtml.indexOf('>');
|
|
247
274
|
const hasCloseAtOrAfter = (/** @type {number} */ p) => {
|
|
248
275
|
if (nextGtPos !== -1 && nextGtPos < p) {
|
|
@@ -251,6 +278,13 @@ export class HTMLParser {
|
|
|
251
278
|
return nextGtPos !== -1;
|
|
252
279
|
};
|
|
253
280
|
|
|
281
|
+
// A `-->` that the opener at `pos` does not find is not there for any later one, either,
|
|
282
|
+
// and the same holds for the `]>` of a downlevel-revealed conditional comment. `pos`
|
|
283
|
+
// only advances, so remembering the search that came up empty keeps the aggregate work
|
|
284
|
+
// O(n), where searching again at every opener rescans the rest of the input each time.
|
|
285
|
+
let commentEndAhead = true;
|
|
286
|
+
let conditionalEndAhead = true;
|
|
287
|
+
|
|
254
288
|
// Helper to advance position
|
|
255
289
|
const advance = (/** @type {number} */ n) => { pos += n; };
|
|
256
290
|
|
|
@@ -274,11 +308,50 @@ export class HTMLParser {
|
|
|
274
308
|
return fullHtml.slice(startPos);
|
|
275
309
|
};
|
|
276
310
|
|
|
311
|
+
// `annotation-xml` holds HTML only where its `encoding` says so; with any other value,
|
|
312
|
+
// and with none, its content stays MathML
|
|
313
|
+
const annotationHoldsHTML = (/** @type {{attrs?: HTMLAttribute[]}} */ entry) => {
|
|
314
|
+
for (const attr of entry.attrs ?? []) {
|
|
315
|
+
if (attr.name.toLowerCase() === 'encoding') {
|
|
316
|
+
return RE_HTML_ENCODING.test(attr.value ?? '');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Escapable raw text is an HTML rule, so what decides it is the namespace the element sits
|
|
323
|
+
// in. Walking down from the root resolves that: `<svg>`/`<math>` lead out of HTML, an
|
|
324
|
+
// integration point leads back in, and a name counts only in the namespace it belongs to
|
|
325
|
+
// (`title` is one in SVG but not in MathML, `annotation-xml` one in MathML but not in SVG)
|
|
326
|
+
/** @param {{lowerTag: string, namespace: string, attrs?: HTMLAttribute[]} | undefined} entry */
|
|
327
|
+
const namespaceInside = (entry) => {
|
|
328
|
+
if (!entry) {
|
|
329
|
+
return '';
|
|
330
|
+
}
|
|
331
|
+
const { namespace, lowerTag } = entry;
|
|
332
|
+
if (!namespace) {
|
|
333
|
+
return lowerTag === 'svg' || lowerTag === 'math' ? lowerTag : '';
|
|
334
|
+
}
|
|
335
|
+
if (namespace === 'svg') {
|
|
336
|
+
return svgIntegrationPoints.has(lowerTag) ? '' : namespace;
|
|
337
|
+
}
|
|
338
|
+
return mathIntegrationPoints.has(lowerTag) || (lowerTag === 'annotation-xml' && annotationHoldsHTML(entry)) ? '' : namespace;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// The element itself never decides this: `<svg><title>` is SVG, what it holds is HTML
|
|
342
|
+
const inForeignContent = () => Boolean(stack[stack.length - 1]?.namespace);
|
|
343
|
+
|
|
344
|
+
// Whether the content of the element being parsed is read as text rather than markup
|
|
345
|
+
// (`script` and `style` hold text in every namespace; the rest do so as HTML elements only)
|
|
346
|
+
const holdsRawText = (/** @type {string} */ tag) =>
|
|
347
|
+
special.has(tag) ||
|
|
348
|
+
((genericRawTextElements.has(tag) || escapableRawTextElements.has(tag)) && !inForeignContent());
|
|
349
|
+
|
|
277
350
|
while (pos < fullLength) {
|
|
278
351
|
lastPos = pos;
|
|
279
352
|
|
|
280
|
-
// Make sure not to be in
|
|
281
|
-
if (!lastTagLower || !
|
|
353
|
+
// Make sure not to be in an element whose content is text, not markup
|
|
354
|
+
if (!lastTagLower || !holdsRawText(lastTagLower)) {
|
|
282
355
|
const textEnd = fullHtml.indexOf('<', pos);
|
|
283
356
|
|
|
284
357
|
if (textEnd === pos) {
|
|
@@ -298,9 +371,9 @@ export class HTMLParser {
|
|
|
298
371
|
const endTagMatch = cachedNextEndTag.match;
|
|
299
372
|
cachedNextStartTag = null;
|
|
300
373
|
cachedNextEndTag = null;
|
|
301
|
-
advance(
|
|
302
|
-
await parseEndTag(endTagMatch
|
|
303
|
-
prevTag = '/' +
|
|
374
|
+
advance(endTagMatch.text.length);
|
|
375
|
+
await parseEndTag(endTagMatch.text, endTagMatch.name);
|
|
376
|
+
prevTag = '/' + endTagMatch.name.toLowerCase();
|
|
304
377
|
prevAttrs = [];
|
|
305
378
|
continue;
|
|
306
379
|
}
|
|
@@ -310,7 +383,8 @@ export class HTMLParser {
|
|
|
310
383
|
// Comment
|
|
311
384
|
commentTestY.lastIndex = pos;
|
|
312
385
|
if (commentTestY.test(fullHtml)) {
|
|
313
|
-
const commentEnd = fullHtml.indexOf('-->', pos + 4);
|
|
386
|
+
const commentEnd = commentEndAhead ? fullHtml.indexOf('-->', pos + 4) : -1;
|
|
387
|
+
if (commentEnd === -1) commentEndAhead = false;
|
|
314
388
|
|
|
315
389
|
if (commentEnd >= 0) {
|
|
316
390
|
if (handler.comment) {
|
|
@@ -327,7 +401,8 @@ export class HTMLParser {
|
|
|
327
401
|
// https://web.archive.org/web/20241201212701/https://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
|
|
328
402
|
conditionalTestY.lastIndex = pos;
|
|
329
403
|
if (conditionalTestY.test(fullHtml)) {
|
|
330
|
-
const conditionalEnd = fullHtml.indexOf(']>', pos + 3);
|
|
404
|
+
const conditionalEnd = conditionalEndAhead ? fullHtml.indexOf(']>', pos + 3) : -1;
|
|
405
|
+
if (conditionalEnd === -1) conditionalEndAhead = false;
|
|
331
406
|
|
|
332
407
|
if (conditionalEnd >= 0) {
|
|
333
408
|
if (handler.comment) {
|
|
@@ -354,15 +429,14 @@ export class HTMLParser {
|
|
|
354
429
|
continue;
|
|
355
430
|
}
|
|
356
431
|
|
|
357
|
-
// End tag—skip when no `>` lies ahead: The
|
|
358
|
-
//
|
|
432
|
+
// End tag—skip when no `>` lies ahead: The tag cannot be complete, and matching
|
|
433
|
+
// its name at every position would cost O(n²) on a long unclosed name
|
|
359
434
|
if (hasCloseAtOrAfter(pos)) {
|
|
360
|
-
|
|
361
|
-
const endTagMatch = endTagY.exec(fullHtml);
|
|
435
|
+
const endTagMatch = matchEndTag(pos);
|
|
362
436
|
if (endTagMatch) {
|
|
363
|
-
advance(
|
|
364
|
-
await parseEndTag(endTagMatch
|
|
365
|
-
prevTag = '/' +
|
|
437
|
+
advance(endTagMatch.text.length);
|
|
438
|
+
await parseEndTag(endTagMatch.text, endTagMatch.name);
|
|
439
|
+
prevTag = '/' + endTagMatch.name.toLowerCase();
|
|
366
440
|
prevAttrs = [];
|
|
367
441
|
continue;
|
|
368
442
|
}
|
|
@@ -401,10 +475,9 @@ export class HTMLParser {
|
|
|
401
475
|
nextAttrs = extractAttrInfo(nextStartTagMatch.attrs);
|
|
402
476
|
cachedNextStartTag = { match: nextStartTagMatch, pos };
|
|
403
477
|
} else if (hasCloseAtOrAfter(pos)) {
|
|
404
|
-
|
|
405
|
-
const nextEndTagMatch = endTagY.exec(fullHtml);
|
|
478
|
+
const nextEndTagMatch = matchEndTag(pos);
|
|
406
479
|
if (nextEndTagMatch) {
|
|
407
|
-
nextTag = '/' + nextEndTagMatch
|
|
480
|
+
nextTag = '/' + nextEndTagMatch.name;
|
|
408
481
|
nextAttrs = [];
|
|
409
482
|
cachedNextEndTag = { match: nextEndTagMatch, pos };
|
|
410
483
|
} else {
|
|
@@ -425,32 +498,36 @@ export class HTMLParser {
|
|
|
425
498
|
prevAttrs = [];
|
|
426
499
|
} else {
|
|
427
500
|
const stackedTag = lastTagLower;
|
|
428
|
-
|
|
429
|
-
const reStackedTag = /** @type {Record<string, RegExp>} */ (preCompiledStackedTags)[stackedTag] || /** @type {Record<string, RegExp>} */ (reCache)[stackedTag] || (/** @type {Record<string, RegExp>} */ (reCache)[stackedTag] = new RegExp('([\\s\\S]*?)\\x3c/' + stackedTag + '[^>]*>', 'i'));
|
|
501
|
+
const isEscapableRawText = escapableRawTextElements.has(stackedTag);
|
|
430
502
|
|
|
431
503
|
const remaining = sliceFromPos(pos);
|
|
432
|
-
const
|
|
433
|
-
if (
|
|
434
|
-
|
|
435
|
-
if (stackedTag !== 'script' && stackedTag !== 'style' && stackedTag !== 'noscript') {
|
|
436
|
-
text = stripDelimited(stripDelimited(text, '<!--', '-->'), '<![CDATA[', ']]>');
|
|
437
|
-
}
|
|
504
|
+
const rawText = findRawTextEnd(remaining, stackedTag);
|
|
505
|
+
if (rawText) {
|
|
506
|
+
const text = rawText.content;
|
|
438
507
|
if (handler.chars) {
|
|
439
|
-
|
|
508
|
+
// Escapable raw text is a text node like any other, so the handler gets the tags
|
|
509
|
+
// around it—it collapses and trims whitespace by what stands next to the text
|
|
510
|
+
const result = isEscapableRawText
|
|
511
|
+
? handler.chars(text, stackedTag, '/' + stackedTag, prevAttrs, [])
|
|
512
|
+
: handler.chars(text);
|
|
440
513
|
if (isThenable(result)) await result;
|
|
441
514
|
}
|
|
442
515
|
// Advance HTML past the matched special tag content and its closing tag
|
|
443
|
-
advance(
|
|
516
|
+
advance(rawText.length);
|
|
444
517
|
await parseEndTag('</' + stackedTag + '>', stackedTag);
|
|
518
|
+
// What follows stands after that end tag, not after the start tag still named here—
|
|
519
|
+
// whitespace next to `textarea` stays verbatim only while the element is open
|
|
520
|
+
prevTag = '/' + stackedTag;
|
|
521
|
+
prevAttrs = [];
|
|
445
522
|
} else {
|
|
446
|
-
//
|
|
447
|
-
if (handler.
|
|
448
|
-
const result =
|
|
523
|
+
// Without an end tag of its own, the element holds the rest of the input as text
|
|
524
|
+
if (handler.chars && remaining) {
|
|
525
|
+
const result = isEscapableRawText
|
|
526
|
+
? handler.chars(remaining, stackedTag, '', prevAttrs, [])
|
|
527
|
+
: handler.chars(remaining);
|
|
449
528
|
if (isThenable(result)) await result;
|
|
450
|
-
advance(1);
|
|
451
|
-
} else {
|
|
452
|
-
break;
|
|
453
529
|
}
|
|
530
|
+
advance(remaining.length);
|
|
454
531
|
}
|
|
455
532
|
}
|
|
456
533
|
|
|
@@ -497,6 +574,21 @@ export class HTMLParser {
|
|
|
497
574
|
}).filter(attr => attr.name)); // Filter out invalid entries
|
|
498
575
|
}
|
|
499
576
|
|
|
577
|
+
/**
|
|
578
|
+
* @param {number} startPos
|
|
579
|
+
* @returns {{text: string, name: string} | null} The end tag standing there, or `null`
|
|
580
|
+
* where none is complete
|
|
581
|
+
*/
|
|
582
|
+
function matchEndTag(startPos) {
|
|
583
|
+
endTagOpenY.lastIndex = startPos;
|
|
584
|
+
const open = endTagOpenY.exec(fullHtml);
|
|
585
|
+
if (!open) {
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
const close = findEndTagClose(fullHtml, startPos + open[0].length);
|
|
589
|
+
return close === -1 ? null : { text: fullHtml.slice(startPos, close + 1), name: open[1] ?? '' };
|
|
590
|
+
}
|
|
591
|
+
|
|
500
592
|
function parseStartTag(/** @type {number} */ startPos) {
|
|
501
593
|
startTagOpenY.lastIndex = startPos;
|
|
502
594
|
const start = startTagOpenY.exec(fullHtml);
|
|
@@ -703,7 +795,17 @@ export class HTMLParser {
|
|
|
703
795
|
|
|
704
796
|
if (lastTagLower === 'p' && nonPhrasing.has(lowerTagName)) {
|
|
705
797
|
await parseEndTag('', lastTag);
|
|
706
|
-
}
|
|
798
|
+
}
|
|
799
|
+
// A row or cell start tag closes the row or cell still open in the same table,
|
|
800
|
+
// which may sit deeper down the stack than the element that was opened last
|
|
801
|
+
if (lowerTagName === 'tr') {
|
|
802
|
+
closeIfFoundInCurrentTable('tr');
|
|
803
|
+
} else if (lowerTagName === 'td' || lowerTagName === 'th') {
|
|
804
|
+
if (!closeIfFoundInCurrentTable('td')) {
|
|
805
|
+
closeIfFoundInCurrentTable('th');
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (lowerTagName === 'tbody') {
|
|
707
809
|
if (!closeIfFoundInCurrentTable('tfoot')) {
|
|
708
810
|
closeIfFoundInCurrentTable('thead');
|
|
709
811
|
}
|
|
@@ -720,7 +822,7 @@ export class HTMLParser {
|
|
|
720
822
|
if (lowerTagName === 'col' && findTagInCurrentTable('colgroup') < 0) {
|
|
721
823
|
lastTag = 'colgroup';
|
|
722
824
|
lastTagLower = 'colgroup';
|
|
723
|
-
stack.push({ tag: lastTag, lowerTag: 'colgroup', attrs: [] });
|
|
825
|
+
stack.push({ tag: lastTag, lowerTag: 'colgroup', attrs: [], namespace: namespaceInside(stack[stack.length - 1]) });
|
|
724
826
|
if (handler.start) {
|
|
725
827
|
await handler.start(lastTag, [], false, '', true);
|
|
726
828
|
}
|
|
@@ -796,7 +898,7 @@ export class HTMLParser {
|
|
|
796
898
|
}));
|
|
797
899
|
|
|
798
900
|
if (!unary) {
|
|
799
|
-
stack.push({ tag: tagName, lowerTag: lowerTagName, attrs });
|
|
901
|
+
stack.push({ tag: tagName, lowerTag: lowerTagName, attrs, namespace: namespaceInside(stack[stack.length - 1]) });
|
|
800
902
|
lastTag = tagName;
|
|
801
903
|
lastTagLower = lowerTagName;
|
|
802
904
|
unarySlash = '';
|
package/src/lib/constants.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const RE_WS_START = /^[ \n\r\t\f]+/;
|
|
4
4
|
const RE_WS_END = /[ \n\r\t\f]+$/;
|
|
5
|
+
const RE_WS_ONLY = /^[ \n\r\t\f]+$/;
|
|
6
|
+
const RE_WS_NBSP_END = /[ \n\r\t\f\xA0]$/;
|
|
5
7
|
const RE_ALL_WS_NBSP = /[ \n\r\t\f\xA0]+/g;
|
|
6
8
|
const RE_NBSP_LEADING_GROUP = /(^|\xA0+)[^\xA0]+/g;
|
|
7
9
|
const RE_NBSP_LEAD_GROUP = /(\xA0+)[^\xA0]+/g;
|
|
@@ -13,6 +15,17 @@ const RE_TRAILING_SEMICOLON = /;$/;
|
|
|
13
15
|
const RE_AMP_ENTITY = /&(#?[0-9a-zA-Z]+;)/g;
|
|
14
16
|
const RE_LEGACY_ENTITIES = /&((?:Iacute|aacute|uacute|plusmn|Otilde|otilde|agrave|Agrave|Yacute|yacute|Oslash|oslash|atilde|Atilde|brvbar|ccedil|Ccedil|Ograve|curren|divide|eacute|Eacute|ograve|Oacute|egrave|Egrave|Ugrave|frac12|frac14|frac34|ugrave|oacute|iacute|Ntilde|ntilde|Uacute|middot|igrave|Igrave|iquest|Aacute|cedil|laquo|micro|iexcl|Icirc|icirc|acirc|Ucirc|Ecirc|ocirc|Ocirc|ecirc|ucirc|Aring|aring|AElig|aelig|acute|pound|raquo|Acirc|times|THORN|szlig|thorn|COPY|auml|ordf|ordm|Uuml|macr|uuml|Auml|ouml|Ouml|para|nbsp|euml|quot|QUOT|Euml|yuml|cent|sect|copy|sup1|sup2|sup3|iuml|Iuml|ETH|shy|reg|not|yen|amp|AMP|REG|uml|eth|deg|gt|GT|LT|lt)(?!;)|(?:#?[0-9a-zA-Z]+;))/g;
|
|
15
17
|
const RE_ESCAPE_LT = /</g;
|
|
18
|
+
// In escapable raw text only the element’s own end tag ends the element, so nothing else
|
|
19
|
+
// that `decodeEntities` decoded to a `<` needs escaping there
|
|
20
|
+
// (the record has no prototype, so a tag named after an inherited property—`constructor`,
|
|
21
|
+
// `__proto__`—finds nothing here and falls back to escaping every `<`)
|
|
22
|
+
const RE_ESCAPE_LT_RAW_TEXT = /** @type {Record<string, RegExp>} */ (Object.assign(Object.create(null), {
|
|
23
|
+
textarea: /<(?=\/textarea[\s/>])/gi,
|
|
24
|
+
title: /<(?=\/title[\s/>])/gi
|
|
25
|
+
}));
|
|
26
|
+
// Encodings that make an `annotation-xml` element hold HTML rather than MathML
|
|
27
|
+
// https://html.spec.whatwg.org/multipage/parsing.html#html-integration-point
|
|
28
|
+
const RE_HTML_ENCODING = /^(text\/html|application\/xhtml\+xml)$/i;
|
|
16
29
|
const RE_ATTR_WS_CHECK = /[ \n\r\t\f]/;
|
|
17
30
|
const RE_ATTR_WS_COLLAPSE = /[ \n\r\t\f]+/g;
|
|
18
31
|
const RE_ATTR_WS_TRIM = /^[ \n\r\t\f]+|[ \n\r\t\f]+$/g;
|
|
@@ -170,12 +183,31 @@ const htmlElements = new Set(['a', 'abbr', 'acronym', 'address', 'applet', 'area
|
|
|
170
183
|
|
|
171
184
|
const specialContentElements = new Set(['script', 'style']);
|
|
172
185
|
|
|
186
|
+
// Elements the parser reads as raw text as well, though only `script` and `style` are raw text
|
|
187
|
+
// elements by name—these are HTML’s alone, where `script` and `style` hold text in SVG, too
|
|
188
|
+
// https://html.spec.whatwg.org/multipage/parsing.html#generic-raw-text-element-parsing-algorithm
|
|
189
|
+
//
|
|
190
|
+
// (`noscript`, `noframes`, and `noembed` are raw text, too, but stay markup here: What they
|
|
191
|
+
// hold is markup to the UA that displays it—one without scripting or frames—so minifying it
|
|
192
|
+
// as markup keeps what that UA sees)
|
|
193
|
+
const genericRawTextElements = new Set(['iframe', 'xmp']);
|
|
194
|
+
|
|
195
|
+
// Escapable raw text elements, whose content is text rather than markup
|
|
196
|
+
// https://html.spec.whatwg.org/multipage/syntax.html#elements-2
|
|
197
|
+
const escapableRawTextElements = new Set(['textarea', 'title']);
|
|
198
|
+
|
|
199
|
+
// Raw text is read without resolving character references, so its text keeps them—unlike that
|
|
200
|
+
// of `textarea` and `title`, where they are resolved as they are anywhere else
|
|
201
|
+
const rawTextElements = new Set([...specialContentElements, ...genericRawTextElements]);
|
|
202
|
+
|
|
173
203
|
// Exports
|
|
174
204
|
|
|
175
205
|
export {
|
|
176
206
|
// Regex patterns
|
|
177
207
|
RE_WS_START,
|
|
178
208
|
RE_WS_END,
|
|
209
|
+
RE_WS_ONLY,
|
|
210
|
+
RE_WS_NBSP_END,
|
|
179
211
|
RE_ALL_WS_NBSP,
|
|
180
212
|
RE_NBSP_LEADING_GROUP,
|
|
181
213
|
RE_NBSP_LEAD_GROUP,
|
|
@@ -187,6 +219,8 @@ export {
|
|
|
187
219
|
RE_AMP_ENTITY,
|
|
188
220
|
RE_LEGACY_ENTITIES,
|
|
189
221
|
RE_ESCAPE_LT,
|
|
222
|
+
RE_ESCAPE_LT_RAW_TEXT,
|
|
223
|
+
RE_HTML_ENCODING,
|
|
190
224
|
RE_ATTR_WS_CHECK,
|
|
191
225
|
RE_ATTR_WS_COLLAPSE,
|
|
192
226
|
RE_ATTR_WS_TRIM,
|
|
@@ -236,5 +270,8 @@ export {
|
|
|
236
270
|
htmlElements,
|
|
237
271
|
|
|
238
272
|
// Special content elements
|
|
239
|
-
specialContentElements
|
|
273
|
+
specialContentElements,
|
|
274
|
+
genericRawTextElements,
|
|
275
|
+
escapableRawTextElements,
|
|
276
|
+
rawTextElements
|
|
240
277
|
};
|
package/src/lib/utils.js
CHANGED
|
@@ -814,17 +814,18 @@ function describeQuantifierRisk(pattern) {
|
|
|
814
814
|
* over quoted attribute values (which may contain `>`).
|
|
815
815
|
* @param {string} html
|
|
816
816
|
* @param {number} pos - Start position (just after the tag name)
|
|
817
|
+
* @param {number} [limit] - Position to stop the search at (exclusive)
|
|
817
818
|
* @returns {number} Index of the closing `>`, or -1 if not found
|
|
818
819
|
*/
|
|
819
|
-
function findTagEnd(html, pos) {
|
|
820
|
+
function findTagEnd(html, pos, limit = html.length) {
|
|
820
821
|
let i = pos;
|
|
821
|
-
while (i <
|
|
822
|
+
while (i < limit) {
|
|
822
823
|
const ch = html[i];
|
|
823
824
|
if (ch === '>') return i;
|
|
824
825
|
if (ch === '"' || ch === "'") {
|
|
825
826
|
const q = ch;
|
|
826
827
|
i++;
|
|
827
|
-
while (i <
|
|
828
|
+
while (i < limit && html[i] !== q) i++;
|
|
828
829
|
}
|
|
829
830
|
i++;
|
|
830
831
|
}
|
package/src/lib/whitespace.js
CHANGED
|
@@ -237,8 +237,10 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, prevAttrs, nextAttrs, op
|
|
|
237
237
|
|
|
238
238
|
// Collapse/trim whitespace for given tag
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
// `xmp`, `listing`, and `plaintext` render preformatted, as `pre` does
|
|
241
|
+
// https://html.spec.whatwg.org/multipage/rendering.html#the-page
|
|
242
|
+
const noCollapseWhitespaceTags = new Set(['script', 'style', 'pre', 'textarea', 'xmp', 'listing', 'plaintext']);
|
|
243
|
+
const noTrimWhitespaceTags = new Set(['pre', 'textarea', 'xmp', 'listing', 'plaintext']);
|
|
242
244
|
|
|
243
245
|
/** @param {string} tag */
|
|
244
246
|
function canCollapseWhitespace(tag) {
|