1'use strict'; 2 3const Tokenizer = require('../tokenizer'); 4const HTML = require('./html'); 5 6//Aliases 7const $ = HTML.TAG_NAMES; 8const NS = HTML.NAMESPACES; 9const ATTRS = HTML.ATTRS; 10 11//MIME types 12const MIME_TYPES = { 13 TEXT_HTML: 'text/html', 14 APPLICATION_XML: 'application/xhtml+xml' 15}; 16 17//Attributes 18const DEFINITION_URL_ATTR = 'definitionurl'; 19const ADJUSTED_DEFINITION_URL_ATTR = 'definitionURL'; 20const SVG_ATTRS_ADJUSTMENT_MAP = { 21 attributename: 'attributeName', 22 attributetype: 'attributeType', 23 basefrequency: 'baseFrequency', 24 baseprofile: 'baseProfile', 25 calcmode: 'calcMode', 26 clippathunits: 'clipPathUnits', 27 diffuseconstant: 'diffuseConstant', 28 edgemode: 'edgeMode', 29 filterunits: 'filterUnits', 30 glyphref: 'glyphRef', 31 gradienttransform: 'gradientTransform', 32 gradientunits: 'gradientUnits', 33 kernelmatrix: 'kernelMatrix', 34 kernelunitlength: 'kernelUnitLength', 35 keypoints: 'keyPoints', 36 keysplines: 'keySplines', 37 keytimes: 'keyTimes', 38 lengthadjust: 'lengthAdjust', 39 limitingconeangle: 'limitingConeAngle', 40 markerheight: 'markerHeight', 41 markerunits: 'markerUnits', 42 markerwidth: 'markerWidth', 43 maskcontentunits: 'maskContentUnits', 44 maskunits: 'maskUnits', 45 numoctaves: 'numOctaves', 46 pathlength: 'pathLength', 47 patterncontentunits: 'patternContentUnits', 48 patterntransform: 'patternTransform', 49 patternunits: 'patternUnits', 50 pointsatx: 'pointsAtX', 51 pointsaty: 'pointsAtY', 52 pointsatz: 'pointsAtZ', 53 preservealpha: 'preserveAlpha', 54 preserveaspectratio: 'preserveAspectRatio', 55 primitiveunits: 'primitiveUnits', 56 refx: 'refX', 57 refy: 'refY', 58 repeatcount: 'repeatCount', 59 repeatdur: 'repeatDur', 60 requiredextensions: 'requiredExtensions', 61 requiredfeatures: 'requiredFeatures', 62 specularconstant: 'specularConstant', 63 specularexponent: 'specularExponent', 64 spreadmethod: 'spreadMethod', 65 startoffset: 'startOffset', 66 stddeviation: 'stdDeviation', 67 stitchtiles: 'stitchTiles', 68 surfacescale: 'surfaceScale', 69 systemlanguage: 'systemLanguage', 70 tablevalues: 'tableValues', 71 targetx: 'targetX', 72 targety: 'targetY', 73 textlength: 'textLength', 74 viewbox: 'viewBox', 75 viewtarget: 'viewTarget', 76 xchannelselector: 'xChannelSelector', 77 ychannelselector: 'yChannelSelector', 78 zoomandpan: 'zoomAndPan' 79}; 80 81const XML_ATTRS_ADJUSTMENT_MAP = { 82 'xlink:actuate': { prefix: 'xlink', name: 'actuate', namespace: NS.XLINK }, 83 'xlink:arcrole': { prefix: 'xlink', name: 'arcrole', namespace: NS.XLINK }, 84 'xlink:href': { prefix: 'xlink', name: 'href', namespace: NS.XLINK }, 85 'xlink:role': { prefix: 'xlink', name: 'role', namespace: NS.XLINK }, 86 'xlink:show': { prefix: 'xlink', name: 'show', namespace: NS.XLINK }, 87 'xlink:title': { prefix: 'xlink', name: 'title', namespace: NS.XLINK }, 88 'xlink:type': { prefix: 'xlink', name: 'type', namespace: NS.XLINK }, 89 'xml:base': { prefix: 'xml', name: 'base', namespace: NS.XML }, 90 'xml:lang': { prefix: 'xml', name: 'lang', namespace: NS.XML }, 91 'xml:space': { prefix: 'xml', name: 'space', namespace: NS.XML }, 92 xmlns: { prefix: '', name: 'xmlns', namespace: NS.XMLNS }, 93 'xmlns:xlink': { prefix: 'xmlns', name: 'xlink', namespace: NS.XMLNS } 94}; 95 96//SVG tag names adjustment map 97const SVG_TAG_NAMES_ADJUSTMENT_MAP = (exports.SVG_TAG_NAMES_ADJUSTMENT_MAP = { 98 altglyph: 'altGlyph', 99 altglyphdef: 'altGlyphDef', 100 altglyphitem: 'altGlyphItem', 101 animatecolor: 'animateColor', 102 animatemotion: 'animateMotion', 103 animatetransform: 'animateTransform', 104 clippath: 'clipPath', 105 feblend: 'feBlend', 106 fecolormatrix: 'feColorMatrix', 107 fecomponenttransfer: 'feComponentTransfer', 108 fecomposite: 'feComposite', 109 feconvolvematrix: 'feConvolveMatrix', 110 fediffuselighting: 'feDiffuseLighting', 111 fedisplacementmap: 'feDisplacementMap', 112 fedistantlight: 'feDistantLight', 113 feflood: 'feFlood', 114 fefunca: 'feFuncA', 115 fefuncb: 'feFuncB', 116 fefuncg: 'feFuncG', 117 fefuncr: 'feFuncR', 118 fegaussianblur: 'feGaussianBlur', 119 feimage: 'feImage', 120 femerge: 'feMerge', 121 femergenode: 'feMergeNode', 122 femorphology: 'feMorphology', 123 feoffset: 'feOffset', 124 fepointlight: 'fePointLight', 125 fespecularlighting: 'feSpecularLighting', 126 fespotlight: 'feSpotLight', 127 fetile: 'feTile', 128 feturbulence: 'feTurbulence', 129 foreignobject: 'foreignObject', 130 glyphref: 'glyphRef', 131 lineargradient: 'linearGradient', 132 radialgradient: 'radialGradient', 133 textpath: 'textPath' 134}); 135 136//Tags that causes exit from foreign content 137const EXITS_FOREIGN_CONTENT = { 138 [$.B]: true, 139 [$.BIG]: true, 140 [$.BLOCKQUOTE]: true, 141 [$.BODY]: true, 142 [$.BR]: true, 143 [$.CENTER]: true, 144 [$.CODE]: true, 145 [$.DD]: true, 146 [$.DIV]: true, 147 [$.DL]: true, 148 [$.DT]: true, 149 [$.EM]: true, 150 [$.EMBED]: true, 151 [$.H1]: true, 152 [$.H2]: true, 153 [$.H3]: true, 154 [$.H4]: true, 155 [$.H5]: true, 156 [$.H6]: true, 157 [$.HEAD]: true, 158 [$.HR]: true, 159 [$.I]: true, 160 [$.IMG]: true, 161 [$.LI]: true, 162 [$.LISTING]: true, 163 [$.MENU]: true, 164 [$.META]: true, 165 [$.NOBR]: true, 166 [$.OL]: true, 167 [$.P]: true, 168 [$.PRE]: true, 169 [$.RUBY]: true, 170 [$.S]: true, 171 [$.SMALL]: true, 172 [$.SPAN]: true, 173 [$.STRONG]: true, 174 [$.STRIKE]: true, 175 [$.SUB]: true, 176 [$.SUP]: true, 177 [$.TABLE]: true, 178 [$.TT]: true, 179 [$.U]: true, 180 [$.UL]: true, 181 [$.VAR]: true 182}; 183 184//Check exit from foreign content 185exports.causesExit = function(startTagToken) { 186 const tn = startTagToken.tagName; 187 const isFontWithAttrs = 188 tn === $.FONT && 189 (Tokenizer.getTokenAttr(startTagToken, ATTRS.COLOR) !== null || 190 Tokenizer.getTokenAttr(startTagToken, ATTRS.SIZE) !== null || 191 Tokenizer.getTokenAttr(startTagToken, ATTRS.FACE) !== null); 192 193 return isFontWithAttrs ? true : EXITS_FOREIGN_CONTENT[tn]; 194}; 195 196//Token adjustments 197exports.adjustTokenMathMLAttrs = function(token) { 198 for (let i = 0; i < token.attrs.length; i++) { 199 if (token.attrs[i].name === DEFINITION_URL_ATTR) { 200 token.attrs[i].name = ADJUSTED_DEFINITION_URL_ATTR; 201 break; 202 } 203 } 204}; 205 206exports.adjustTokenSVGAttrs = function(token) { 207 for (let i = 0; i < token.attrs.length; i++) { 208 const adjustedAttrName = SVG_ATTRS_ADJUSTMENT_MAP[token.attrs[i].name]; 209 210 if (adjustedAttrName) { 211 token.attrs[i].name = adjustedAttrName; 212 } 213 } 214}; 215 216exports.adjustTokenXMLAttrs = function(token) { 217 for (let i = 0; i < token.attrs.length; i++) { 218 const adjustedAttrEntry = XML_ATTRS_ADJUSTMENT_MAP[token.attrs[i].name]; 219 220 if (adjustedAttrEntry) { 221 token.attrs[i].prefix = adjustedAttrEntry.prefix; 222 token.attrs[i].name = adjustedAttrEntry.name; 223 token.attrs[i].namespace = adjustedAttrEntry.namespace; 224 } 225 } 226}; 227 228exports.adjustTokenSVGTagName = function(token) { 229 const adjustedTagName = SVG_TAG_NAMES_ADJUSTMENT_MAP[token.tagName]; 230 231 if (adjustedTagName) { 232 token.tagName = adjustedTagName; 233 } 234}; 235 236//Integration points 237function isMathMLTextIntegrationPoint(tn, ns) { 238 return ns === NS.MATHML && (tn === $.MI || tn === $.MO || tn === $.MN || tn === $.MS || tn === $.MTEXT); 239} 240 241function isHtmlIntegrationPoint(tn, ns, attrs) { 242 if (ns === NS.MATHML && tn === $.ANNOTATION_XML) { 243 for (let i = 0; i < attrs.length; i++) { 244 if (attrs[i].name === ATTRS.ENCODING) { 245 const value = attrs[i].value.toLowerCase(); 246 247 return value === MIME_TYPES.TEXT_HTML || value === MIME_TYPES.APPLICATION_XML; 248 } 249 } 250 } 251 252 return ns === NS.SVG && (tn === $.FOREIGN_OBJECT || tn === $.DESC || tn === $.TITLE); 253} 254 255exports.isIntegrationPoint = function(tn, ns, attrs, foreignNS) { 256 if ((!foreignNS || foreignNS === NS.HTML) && isHtmlIntegrationPoint(tn, ns, attrs)) { 257 return true; 258 } 259 260 if ((!foreignNS || foreignNS === NS.MATHML) && isMathMLTextIntegrationPoint(tn, ns)) { 261 return true; 262 } 263 264 return false; 265}; 266