• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import { TAG_ID as $, NS, ATTRS, getTagID } from './html.js';
2import type { TagToken, Attribute } from './token.js';
3
4//MIME types
5const MIME_TYPES = {
6    TEXT_HTML: 'text/html',
7    APPLICATION_XML: 'application/xhtml+xml',
8};
9
10//Attributes
11const DEFINITION_URL_ATTR = 'definitionurl';
12const ADJUSTED_DEFINITION_URL_ATTR = 'definitionURL';
13const SVG_ATTRS_ADJUSTMENT_MAP = new Map(
14    [
15        'attributeName',
16        'attributeType',
17        'baseFrequency',
18        'baseProfile',
19        'calcMode',
20        'clipPathUnits',
21        'diffuseConstant',
22        'edgeMode',
23        'filterUnits',
24        'glyphRef',
25        'gradientTransform',
26        'gradientUnits',
27        'kernelMatrix',
28        'kernelUnitLength',
29        'keyPoints',
30        'keySplines',
31        'keyTimes',
32        'lengthAdjust',
33        'limitingConeAngle',
34        'markerHeight',
35        'markerUnits',
36        'markerWidth',
37        'maskContentUnits',
38        'maskUnits',
39        'numOctaves',
40        'pathLength',
41        'patternContentUnits',
42        'patternTransform',
43        'patternUnits',
44        'pointsAtX',
45        'pointsAtY',
46        'pointsAtZ',
47        'preserveAlpha',
48        'preserveAspectRatio',
49        'primitiveUnits',
50        'refX',
51        'refY',
52        'repeatCount',
53        'repeatDur',
54        'requiredExtensions',
55        'requiredFeatures',
56        'specularConstant',
57        'specularExponent',
58        'spreadMethod',
59        'startOffset',
60        'stdDeviation',
61        'stitchTiles',
62        'surfaceScale',
63        'systemLanguage',
64        'tableValues',
65        'targetX',
66        'targetY',
67        'textLength',
68        'viewBox',
69        'viewTarget',
70        'xChannelSelector',
71        'yChannelSelector',
72        'zoomAndPan',
73    ].map((attr) => [attr.toLowerCase(), attr]),
74);
75
76const XML_ATTRS_ADJUSTMENT_MAP = new Map([
77    ['xlink:actuate', { prefix: 'xlink', name: 'actuate', namespace: NS.XLINK }],
78    ['xlink:arcrole', { prefix: 'xlink', name: 'arcrole', namespace: NS.XLINK }],
79    ['xlink:href', { prefix: 'xlink', name: 'href', namespace: NS.XLINK }],
80    ['xlink:role', { prefix: 'xlink', name: 'role', namespace: NS.XLINK }],
81    ['xlink:show', { prefix: 'xlink', name: 'show', namespace: NS.XLINK }],
82    ['xlink:title', { prefix: 'xlink', name: 'title', namespace: NS.XLINK }],
83    ['xlink:type', { prefix: 'xlink', name: 'type', namespace: NS.XLINK }],
84    ['xml:lang', { prefix: 'xml', name: 'lang', namespace: NS.XML }],
85    ['xml:space', { prefix: 'xml', name: 'space', namespace: NS.XML }],
86    ['xmlns', { prefix: '', name: 'xmlns', namespace: NS.XMLNS }],
87    ['xmlns:xlink', { prefix: 'xmlns', name: 'xlink', namespace: NS.XMLNS }],
88]);
89
90//SVG tag names adjustment map
91export const SVG_TAG_NAMES_ADJUSTMENT_MAP = new Map(
92    [
93        'altGlyph',
94        'altGlyphDef',
95        'altGlyphItem',
96        'animateColor',
97        'animateMotion',
98        'animateTransform',
99        'clipPath',
100        'feBlend',
101        'feColorMatrix',
102        'feComponentTransfer',
103        'feComposite',
104        'feConvolveMatrix',
105        'feDiffuseLighting',
106        'feDisplacementMap',
107        'feDistantLight',
108        'feFlood',
109        'feFuncA',
110        'feFuncB',
111        'feFuncG',
112        'feFuncR',
113        'feGaussianBlur',
114        'feImage',
115        'feMerge',
116        'feMergeNode',
117        'feMorphology',
118        'feOffset',
119        'fePointLight',
120        'feSpecularLighting',
121        'feSpotLight',
122        'feTile',
123        'feTurbulence',
124        'foreignObject',
125        'glyphRef',
126        'linearGradient',
127        'radialGradient',
128        'textPath',
129    ].map((tn) => [tn.toLowerCase(), tn]),
130);
131
132//Tags that causes exit from foreign content
133const EXITS_FOREIGN_CONTENT = new Set([
134    $.B,
135    $.BIG,
136    $.BLOCKQUOTE,
137    $.BODY,
138    $.BR,
139    $.CENTER,
140    $.CODE,
141    $.DD,
142    $.DIV,
143    $.DL,
144    $.DT,
145    $.EM,
146    $.EMBED,
147    $.H1,
148    $.H2,
149    $.H3,
150    $.H4,
151    $.H5,
152    $.H6,
153    $.HEAD,
154    $.HR,
155    $.I,
156    $.IMG,
157    $.LI,
158    $.LISTING,
159    $.MENU,
160    $.META,
161    $.NOBR,
162    $.OL,
163    $.P,
164    $.PRE,
165    $.RUBY,
166    $.S,
167    $.SMALL,
168    $.SPAN,
169    $.STRONG,
170    $.STRIKE,
171    $.SUB,
172    $.SUP,
173    $.TABLE,
174    $.TT,
175    $.U,
176    $.UL,
177    $.VAR,
178]);
179
180//Check exit from foreign content
181export function causesExit(startTagToken: TagToken): boolean {
182    const tn = startTagToken.tagID;
183    const isFontWithAttrs =
184        tn === $.FONT &&
185        startTagToken.attrs.some(({ name }) => name === ATTRS.COLOR || name === ATTRS.SIZE || name === ATTRS.FACE);
186
187    return isFontWithAttrs || EXITS_FOREIGN_CONTENT.has(tn);
188}
189
190//Token adjustments
191export function adjustTokenMathMLAttrs(token: TagToken): void {
192    for (let i = 0; i < token.attrs.length; i++) {
193        if (token.attrs[i].name === DEFINITION_URL_ATTR) {
194            token.attrs[i].name = ADJUSTED_DEFINITION_URL_ATTR;
195            break;
196        }
197    }
198}
199
200export function adjustTokenSVGAttrs(token: TagToken): void {
201    for (let i = 0; i < token.attrs.length; i++) {
202        const adjustedAttrName = SVG_ATTRS_ADJUSTMENT_MAP.get(token.attrs[i].name);
203
204        if (adjustedAttrName != null) {
205            token.attrs[i].name = adjustedAttrName;
206        }
207    }
208}
209
210export function adjustTokenXMLAttrs(token: TagToken): void {
211    for (let i = 0; i < token.attrs.length; i++) {
212        const adjustedAttrEntry = XML_ATTRS_ADJUSTMENT_MAP.get(token.attrs[i].name);
213
214        if (adjustedAttrEntry) {
215            token.attrs[i].prefix = adjustedAttrEntry.prefix;
216            token.attrs[i].name = adjustedAttrEntry.name;
217            token.attrs[i].namespace = adjustedAttrEntry.namespace;
218        }
219    }
220}
221
222export function adjustTokenSVGTagName(token: TagToken): void {
223    const adjustedTagName = SVG_TAG_NAMES_ADJUSTMENT_MAP.get(token.tagName);
224
225    if (adjustedTagName != null) {
226        token.tagName = adjustedTagName;
227        token.tagID = getTagID(token.tagName);
228    }
229}
230
231//Integration points
232function isMathMLTextIntegrationPoint(tn: $, ns: NS): boolean {
233    return ns === NS.MATHML && (tn === $.MI || tn === $.MO || tn === $.MN || tn === $.MS || tn === $.MTEXT);
234}
235
236function isHtmlIntegrationPoint(tn: $, ns: NS, attrs: Attribute[]): boolean {
237    if (ns === NS.MATHML && tn === $.ANNOTATION_XML) {
238        for (let i = 0; i < attrs.length; i++) {
239            if (attrs[i].name === ATTRS.ENCODING) {
240                const value = attrs[i].value.toLowerCase();
241
242                return value === MIME_TYPES.TEXT_HTML || value === MIME_TYPES.APPLICATION_XML;
243            }
244        }
245    }
246
247    return ns === NS.SVG && (tn === $.FOREIGN_OBJECT || tn === $.DESC || tn === $.TITLE);
248}
249
250export function isIntegrationPoint(tn: $, ns: NS, attrs: Attribute[], foreignNS?: NS): boolean {
251    return (
252        ((!foreignNS || foreignNS === NS.HTML) && isHtmlIntegrationPoint(tn, ns, attrs)) ||
253        ((!foreignNS || foreignNS === NS.MATHML) && isMathMLTextIntegrationPoint(tn, ns))
254    );
255}
256