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