1 | /* |
2 | * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved. |
3 | * Copyright (C) 2010 Apple Inc. All rights reserved. |
4 | * Copyright (C) 2016 Igalia S.L. |
5 | * |
6 | * Redistribution and use in source and binary forms, with or without |
7 | * modification, are permitted provided that the following conditions |
8 | * are met: |
9 | * 1. Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * 2. Redistributions in binary form must reproduce the above copyright |
12 | * notice, this list of conditions and the following disclaimer in the |
13 | * documentation and/or other materials provided with the distribution. |
14 | * |
15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
19 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ |
27 | |
28 | #include "config.h" |
29 | #include "MathMLPresentationElement.h" |
30 | |
31 | #if ENABLE(MATHML) |
32 | |
33 | #include "ElementIterator.h" |
34 | #include "HTMLHtmlElement.h" |
35 | #include "HTMLMapElement.h" |
36 | #include "HTMLNames.h" |
37 | #include "HTMLParserIdioms.h" |
38 | #include "HTTPParsers.h" |
39 | #include "MathMLMathElement.h" |
40 | #include "MathMLNames.h" |
41 | #include "RenderMathMLBlock.h" |
42 | #include "RenderTableCell.h" |
43 | #include "SVGSVGElement.h" |
44 | #include <wtf/IsoMallocInlines.h> |
45 | |
46 | namespace WebCore { |
47 | |
48 | WTF_MAKE_ISO_ALLOCATED_IMPL(MathMLPresentationElement); |
49 | |
50 | using namespace MathMLNames; |
51 | |
52 | MathMLPresentationElement::MathMLPresentationElement(const QualifiedName& tagName, Document& document) |
53 | : MathMLElement(tagName, document) |
54 | { |
55 | } |
56 | |
57 | Ref<MathMLPresentationElement> MathMLPresentationElement::create(const QualifiedName& tagName, Document& document) |
58 | { |
59 | return adoptRef(*new MathMLPresentationElement(tagName, document)); |
60 | } |
61 | |
62 | RenderPtr<RenderElement> MathMLPresentationElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition& insertionPosition) |
63 | { |
64 | if (hasTagName(mtableTag)) |
65 | return createRenderer<RenderMathMLTable>(*this, WTFMove(style)); |
66 | |
67 | return MathMLElement::createElementRenderer(WTFMove(style), insertionPosition); |
68 | } |
69 | |
70 | bool MathMLPresentationElement::isPhrasingContent(const Node& node) |
71 | { |
72 | // Phrasing content is described in the HTML 5 specification: |
73 | // http://www.w3.org/TR/html5/dom.html#phrasing-content. |
74 | |
75 | if (!node.isElementNode()) |
76 | return node.isTextNode(); |
77 | |
78 | if (is<MathMLElement>(node)) { |
79 | auto& mathmlElement = downcast<MathMLElement>(node); |
80 | return is<MathMLMathElement>(mathmlElement); |
81 | } |
82 | |
83 | if (is<SVGElement>(node)) { |
84 | auto& svgElement = downcast<SVGElement>(node); |
85 | return is<SVGSVGElement>(svgElement); |
86 | } |
87 | |
88 | if (is<HTMLElement>(node)) { |
89 | // FIXME: add the <data> and <time> tags when they are implemented. |
90 | auto& htmlElement = downcast<HTMLElement>(node); |
91 | return htmlElement.hasTagName(HTMLNames::aTag) |
92 | || htmlElement.hasTagName(HTMLNames::abbrTag) |
93 | || (htmlElement.hasTagName(HTMLNames::areaTag) && ancestorsOfType<HTMLMapElement>(htmlElement).first()) |
94 | || htmlElement.hasTagName(HTMLNames::audioTag) |
95 | || htmlElement.hasTagName(HTMLNames::bTag) |
96 | || htmlElement.hasTagName(HTMLNames::bdiTag) |
97 | || htmlElement.hasTagName(HTMLNames::bdoTag) |
98 | || htmlElement.hasTagName(HTMLNames::brTag) |
99 | || htmlElement.hasTagName(HTMLNames::buttonTag) |
100 | || htmlElement.hasTagName(HTMLNames::canvasTag) |
101 | || htmlElement.hasTagName(HTMLNames::citeTag) |
102 | || htmlElement.hasTagName(HTMLNames::codeTag) |
103 | || htmlElement.hasTagName(HTMLNames::datalistTag) |
104 | || htmlElement.hasTagName(HTMLNames::delTag) |
105 | || htmlElement.hasTagName(HTMLNames::dfnTag) |
106 | || htmlElement.hasTagName(HTMLNames::emTag) |
107 | || htmlElement.hasTagName(HTMLNames::embedTag) |
108 | || htmlElement.hasTagName(HTMLNames::iTag) |
109 | || htmlElement.hasTagName(HTMLNames::iframeTag) |
110 | || htmlElement.hasTagName(HTMLNames::imgTag) |
111 | || htmlElement.hasTagName(HTMLNames::inputTag) |
112 | || htmlElement.hasTagName(HTMLNames::insTag) |
113 | || htmlElement.hasTagName(HTMLNames::kbdTag) |
114 | || htmlElement.hasTagName(HTMLNames::keygenTag) |
115 | || htmlElement.hasTagName(HTMLNames::labelTag) |
116 | || htmlElement.hasTagName(HTMLNames::mapTag) |
117 | || htmlElement.hasTagName(HTMLNames::markTag) |
118 | || htmlElement.hasTagName(HTMLNames::meterTag) |
119 | || htmlElement.hasTagName(HTMLNames::noscriptTag) |
120 | || htmlElement.hasTagName(HTMLNames::objectTag) |
121 | || htmlElement.hasTagName(HTMLNames::outputTag) |
122 | || htmlElement.hasTagName(HTMLNames::progressTag) |
123 | || htmlElement.hasTagName(HTMLNames::qTag) |
124 | || htmlElement.hasTagName(HTMLNames::rubyTag) |
125 | || htmlElement.hasTagName(HTMLNames::sTag) |
126 | || htmlElement.hasTagName(HTMLNames::sampTag) |
127 | || htmlElement.hasTagName(HTMLNames::scriptTag) |
128 | || htmlElement.hasTagName(HTMLNames::selectTag) |
129 | || htmlElement.hasTagName(HTMLNames::smallTag) |
130 | || htmlElement.hasTagName(HTMLNames::spanTag) |
131 | || htmlElement.hasTagName(HTMLNames::strongTag) |
132 | || htmlElement.hasTagName(HTMLNames::subTag) |
133 | || htmlElement.hasTagName(HTMLNames::supTag) |
134 | || htmlElement.hasTagName(HTMLNames::templateTag) |
135 | || htmlElement.hasTagName(HTMLNames::textareaTag) |
136 | || htmlElement.hasTagName(HTMLNames::uTag) |
137 | || htmlElement.hasTagName(HTMLNames::varTag) |
138 | || htmlElement.hasTagName(HTMLNames::videoTag) |
139 | || htmlElement.hasTagName(HTMLNames::wbrTag); |
140 | } |
141 | |
142 | return false; |
143 | } |
144 | |
145 | bool MathMLPresentationElement::isFlowContent(const Node& node) |
146 | { |
147 | // Flow content is described in the HTML 5 specification: |
148 | // http://www.w3.org/TR/html5/dom.html#flow-content |
149 | |
150 | if (isPhrasingContent(node)) |
151 | return true; |
152 | |
153 | if (!is<HTMLElement>(node)) |
154 | return false; |
155 | |
156 | auto& htmlElement = downcast<HTMLElement>(node); |
157 | // FIXME add the <dialog> tag when it is implemented. |
158 | return htmlElement.hasTagName(HTMLNames::addressTag) |
159 | || htmlElement.hasTagName(HTMLNames::articleTag) |
160 | || htmlElement.hasTagName(HTMLNames::asideTag) |
161 | || htmlElement.hasTagName(HTMLNames::blockquoteTag) |
162 | || htmlElement.hasTagName(HTMLNames::detailsTag) |
163 | || htmlElement.hasTagName(HTMLNames::divTag) |
164 | || htmlElement.hasTagName(HTMLNames::dlTag) |
165 | || htmlElement.hasTagName(HTMLNames::fieldsetTag) |
166 | || htmlElement.hasTagName(HTMLNames::figureTag) |
167 | || htmlElement.hasTagName(HTMLNames::footerTag) |
168 | || htmlElement.hasTagName(HTMLNames::formTag) |
169 | || htmlElement.hasTagName(HTMLNames::h1Tag) |
170 | || htmlElement.hasTagName(HTMLNames::h2Tag) |
171 | || htmlElement.hasTagName(HTMLNames::h3Tag) |
172 | || htmlElement.hasTagName(HTMLNames::h4Tag) |
173 | || htmlElement.hasTagName(HTMLNames::h5Tag) |
174 | || htmlElement.hasTagName(HTMLNames::h6Tag) |
175 | || htmlElement.hasTagName(HTMLNames::headerTag) |
176 | || htmlElement.hasTagName(HTMLNames::hrTag) |
177 | || htmlElement.hasTagName(HTMLNames::mainTag) |
178 | || htmlElement.hasTagName(HTMLNames::navTag) |
179 | || htmlElement.hasTagName(HTMLNames::olTag) |
180 | || htmlElement.hasTagName(HTMLNames::pTag) |
181 | || htmlElement.hasTagName(HTMLNames::preTag) |
182 | || htmlElement.hasTagName(HTMLNames::sectionTag) |
183 | || (htmlElement.hasTagName(HTMLNames::styleTag) && htmlElement.hasAttribute("scoped" )) |
184 | || htmlElement.hasTagName(HTMLNames::tableTag) |
185 | || htmlElement.hasTagName(HTMLNames::ulTag); |
186 | } |
187 | |
188 | const MathMLElement::BooleanValue& MathMLPresentationElement::cachedBooleanAttribute(const QualifiedName& name, Optional<BooleanValue>& attribute) |
189 | { |
190 | if (attribute) |
191 | return attribute.value(); |
192 | |
193 | // In MathML, attribute values are case-sensitive. |
194 | const AtomicString& value = attributeWithoutSynchronization(name); |
195 | if (value == "true" ) |
196 | attribute = BooleanValue::True; |
197 | else if (value == "false" ) |
198 | attribute = BooleanValue::False; |
199 | else |
200 | attribute = BooleanValue::Default; |
201 | |
202 | return attribute.value(); |
203 | } |
204 | |
205 | MathMLElement::Length MathMLPresentationElement::parseNumberAndUnit(const StringView& string) |
206 | { |
207 | LengthType lengthType = LengthType::UnitLess; |
208 | unsigned stringLength = string.length(); |
209 | UChar lastChar = string[stringLength - 1]; |
210 | if (lastChar == '%') { |
211 | lengthType = LengthType::Percentage; |
212 | stringLength--; |
213 | } else if (stringLength >= 2) { |
214 | UChar penultimateChar = string[stringLength - 2]; |
215 | if (penultimateChar == 'c' && lastChar == 'm') |
216 | lengthType = LengthType::Cm; |
217 | if (penultimateChar == 'e' && lastChar == 'm') |
218 | lengthType = LengthType::Em; |
219 | else if (penultimateChar == 'e' && lastChar == 'x') |
220 | lengthType = LengthType::Ex; |
221 | else if (penultimateChar == 'i' && lastChar == 'n') |
222 | lengthType = LengthType::In; |
223 | else if (penultimateChar == 'm' && lastChar == 'm') |
224 | lengthType = LengthType::Mm; |
225 | else if (penultimateChar == 'p' && lastChar == 'c') |
226 | lengthType = LengthType::Pc; |
227 | else if (penultimateChar == 'p' && lastChar == 't') |
228 | lengthType = LengthType::Pt; |
229 | else if (penultimateChar == 'p' && lastChar == 'x') |
230 | lengthType = LengthType::Px; |
231 | |
232 | if (lengthType != LengthType::UnitLess) |
233 | stringLength -= 2; |
234 | } |
235 | |
236 | bool ok; |
237 | float lengthValue = string.substring(0, stringLength).toFloat(ok); |
238 | if (!ok) |
239 | return Length(); |
240 | |
241 | Length length; |
242 | length.type = lengthType; |
243 | length.value = lengthValue; |
244 | return length; |
245 | } |
246 | |
247 | MathMLElement::Length MathMLPresentationElement::parseNamedSpace(const StringView& string) |
248 | { |
249 | // Named space values are case-sensitive. |
250 | int namedSpaceValue; |
251 | if (string == "veryverythinmathspace" ) |
252 | namedSpaceValue = 1; |
253 | else if (string == "verythinmathspace" ) |
254 | namedSpaceValue = 2; |
255 | else if (string == "thinmathspace" ) |
256 | namedSpaceValue = 3; |
257 | else if (string == "mediummathspace" ) |
258 | namedSpaceValue = 4; |
259 | else if (string == "thickmathspace" ) |
260 | namedSpaceValue = 5; |
261 | else if (string == "verythickmathspace" ) |
262 | namedSpaceValue = 6; |
263 | else if (string == "veryverythickmathspace" ) |
264 | namedSpaceValue = 7; |
265 | else if (string == "negativeveryverythinmathspace" ) |
266 | namedSpaceValue = -1; |
267 | else if (string == "negativeverythinmathspace" ) |
268 | namedSpaceValue = -2; |
269 | else if (string == "negativethinmathspace" ) |
270 | namedSpaceValue = -3; |
271 | else if (string == "negativemediummathspace" ) |
272 | namedSpaceValue = -4; |
273 | else if (string == "negativethickmathspace" ) |
274 | namedSpaceValue = -5; |
275 | else if (string == "negativeverythickmathspace" ) |
276 | namedSpaceValue = -6; |
277 | else if (string == "negativeveryverythickmathspace" ) |
278 | namedSpaceValue = -7; |
279 | else |
280 | return Length(); |
281 | |
282 | Length length; |
283 | length.type = LengthType::MathUnit; |
284 | length.value = namedSpaceValue; |
285 | return length; |
286 | } |
287 | |
288 | MathMLElement::Length MathMLPresentationElement::parseMathMLLength(const String& string) |
289 | { |
290 | // The regular expression from the MathML Relax NG schema is as follows: |
291 | // |
292 | // pattern = '\s*((-?[0-9]*([0-9]\.?|\.[0-9])[0-9]*(e[mx]|in|cm|mm|p[xtc]|%)?)|(negative)?((very){0,2}thi(n|ck)|medium)mathspace)\s*' |
293 | // |
294 | // We do not perform a strict verification of the syntax of whitespaces and number. |
295 | // Instead, we just use isHTMLSpace and toFloat to parse these parts. |
296 | |
297 | // We first skip whitespace from both ends of the string. |
298 | StringView stringView = string; |
299 | StringView strippedLength = stripLeadingAndTrailingHTTPSpaces(stringView); |
300 | |
301 | if (strippedLength.isEmpty()) |
302 | return Length(); |
303 | |
304 | // We consider the most typical case: a number followed by an optional unit. |
305 | UChar firstChar = strippedLength[0]; |
306 | if (isASCIIDigit(firstChar) || firstChar == '-' || firstChar == '.') |
307 | return parseNumberAndUnit(strippedLength); |
308 | |
309 | // Otherwise, we try and parse a named space. |
310 | return parseNamedSpace(strippedLength); |
311 | } |
312 | |
313 | const MathMLElement::Length& MathMLPresentationElement::cachedMathMLLength(const QualifiedName& name, Optional<Length>& length) |
314 | { |
315 | if (length) |
316 | return length.value(); |
317 | length = parseMathMLLength(attributeWithoutSynchronization(name)); |
318 | return length.value(); |
319 | } |
320 | |
321 | bool MathMLPresentationElement::acceptsDisplayStyleAttribute() |
322 | { |
323 | return hasTagName(mtableTag); |
324 | } |
325 | |
326 | Optional<bool> MathMLPresentationElement::specifiedDisplayStyle() |
327 | { |
328 | if (!acceptsDisplayStyleAttribute()) |
329 | return WTF::nullopt; |
330 | const MathMLElement::BooleanValue& specifiedDisplayStyle = cachedBooleanAttribute(displaystyleAttr, m_displayStyle); |
331 | return toOptionalBool(specifiedDisplayStyle); |
332 | } |
333 | |
334 | MathMLElement::MathVariant MathMLPresentationElement::parseMathVariantAttribute(const AtomicString& attributeValue) |
335 | { |
336 | // The mathvariant attribute values is case-sensitive. |
337 | if (attributeValue == "normal" ) |
338 | return MathVariant::Normal; |
339 | if (attributeValue == "bold" ) |
340 | return MathVariant::Bold; |
341 | if (attributeValue == "italic" ) |
342 | return MathVariant::Italic; |
343 | if (attributeValue == "bold-italic" ) |
344 | return MathVariant::BoldItalic; |
345 | if (attributeValue == "double-struck" ) |
346 | return MathVariant::DoubleStruck; |
347 | if (attributeValue == "bold-fraktur" ) |
348 | return MathVariant::BoldFraktur; |
349 | if (attributeValue == "script" ) |
350 | return MathVariant::Script; |
351 | if (attributeValue == "bold-script" ) |
352 | return MathVariant::BoldScript; |
353 | if (attributeValue == "fraktur" ) |
354 | return MathVariant::Fraktur; |
355 | if (attributeValue == "sans-serif" ) |
356 | return MathVariant::SansSerif; |
357 | if (attributeValue == "bold-sans-serif" ) |
358 | return MathVariant::BoldSansSerif; |
359 | if (attributeValue == "sans-serif-italic" ) |
360 | return MathVariant::SansSerifItalic; |
361 | if (attributeValue == "sans-serif-bold-italic" ) |
362 | return MathVariant::SansSerifBoldItalic; |
363 | if (attributeValue == "monospace" ) |
364 | return MathVariant::Monospace; |
365 | if (attributeValue == "initial" ) |
366 | return MathVariant::Initial; |
367 | if (attributeValue == "tailed" ) |
368 | return MathVariant::Tailed; |
369 | if (attributeValue == "looped" ) |
370 | return MathVariant::Looped; |
371 | if (attributeValue == "stretched" ) |
372 | return MathVariant::Stretched; |
373 | return MathVariant::None; |
374 | } |
375 | |
376 | Optional<MathMLElement::MathVariant> MathMLPresentationElement::specifiedMathVariant() |
377 | { |
378 | if (!acceptsMathVariantAttribute()) |
379 | return WTF::nullopt; |
380 | if (!m_mathVariant) |
381 | m_mathVariant = parseMathVariantAttribute(attributeWithoutSynchronization(mathvariantAttr)); |
382 | return m_mathVariant.value() == MathVariant::None ? WTF::nullopt : m_mathVariant; |
383 | } |
384 | |
385 | void MathMLPresentationElement::parseAttribute(const QualifiedName& name, const AtomicString& value) |
386 | { |
387 | bool displayStyleAttribute = name == displaystyleAttr && acceptsDisplayStyleAttribute(); |
388 | bool mathVariantAttribute = name == mathvariantAttr && acceptsMathVariantAttribute(); |
389 | if (displayStyleAttribute) |
390 | m_displayStyle = WTF::nullopt; |
391 | if (mathVariantAttribute) |
392 | m_mathVariant = WTF::nullopt; |
393 | if ((displayStyleAttribute || mathVariantAttribute) && renderer()) |
394 | MathMLStyle::resolveMathMLStyleTree(renderer()); |
395 | |
396 | MathMLElement::parseAttribute(name, value); |
397 | } |
398 | |
399 | } |
400 | |
401 | #endif // ENABLE(MATHML) |
402 | |