11f6e49586498ea5d6baf231305378df202f6eea
[WebKit-https.git] / Source / WebCore / mathml / MathMLElement.cpp
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) 2010 Fran├žois Sausset (sausset@gmail.com). All rights reserved.
5  * Copyright (C) 2016 Igalia S.L.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30
31 #if ENABLE(MATHML)
32
33 #include "MathMLElement.h"
34
35 #include "ElementIterator.h"
36 #include "Event.h"
37 #include "EventHandler.h"
38 #include "HTMLAnchorElement.h"
39 #include "HTMLElement.h"
40 #include "HTMLHtmlElement.h"
41 #include "HTMLMapElement.h"
42 #include "HTMLNames.h"
43 #include "HTMLParserIdioms.h"
44 #include "MathMLMathElement.h"
45 #include "MathMLNames.h"
46 #include "MathMLSelectElement.h"
47 #include "MouseEvent.h"
48 #include "RenderTableCell.h"
49 #include "SVGElement.h"
50 #include "SVGNames.h"
51 #include "SVGSVGElement.h"
52 #include "XLinkNames.h"
53
54 namespace WebCore {
55
56 using namespace MathMLNames;
57
58 MathMLElement::MathMLElement(const QualifiedName& tagName, Document& document)
59     : StyledElement(tagName, document, CreateMathMLElement)
60 {
61 }
62
63 Ref<MathMLElement> MathMLElement::create(const QualifiedName& tagName, Document& document)
64 {
65     return adoptRef(*new MathMLElement(tagName, document));
66 }
67
68 bool MathMLElement::isPresentationMathML() const
69 {
70     return hasTagName(MathMLNames::mtrTag)
71         || hasTagName(MathMLNames::mtdTag)
72         || hasTagName(MathMLNames::maligngroupTag)
73         || hasTagName(MathMLNames::malignmarkTag)
74         || hasTagName(MathMLNames::mencloseTag)
75         || hasTagName(MathMLNames::mglyphTag)
76         || hasTagName(MathMLNames::mlabeledtrTag)
77         || hasTagName(MathMLNames::mlongdivTag)
78         || hasTagName(MathMLNames::mpaddedTag)
79         || hasTagName(MathMLNames::msTag)
80         || hasTagName(MathMLNames::mscarriesTag)
81         || hasTagName(MathMLNames::mscarryTag)
82         || hasTagName(MathMLNames::msgroupTag)
83         || hasTagName(MathMLNames::mslineTag)
84         || hasTagName(MathMLNames::msrowTag)
85         || hasTagName(MathMLNames::mstackTag);
86 }
87
88 bool MathMLElement::isPhrasingContent(const Node& node) const
89 {
90     // Phrasing content is described in the HTML 5 specification:
91     // http://www.w3.org/TR/html5/dom.html#phrasing-content.
92
93     if (!node.isElementNode())
94         return node.isTextNode();
95
96     if (is<MathMLElement>(node)) {
97         auto& mathmlElement = downcast<MathMLElement>(node);
98         return is<MathMLMathElement>(mathmlElement);
99     }
100
101     if (is<SVGElement>(node)) {
102         auto& svgElement = downcast<SVGElement>(node);
103         return is<SVGSVGElement>(svgElement);
104     }
105
106     if (is<HTMLElement>(node)) {
107         // FIXME: add the <data> and <time> tags when they are implemented.
108         auto& htmlElement = downcast<HTMLElement>(node);
109         return htmlElement.hasTagName(HTMLNames::aTag)
110             || htmlElement.hasTagName(HTMLNames::abbrTag)
111             || (htmlElement.hasTagName(HTMLNames::areaTag) && ancestorsOfType<HTMLMapElement>(htmlElement).first())
112             || htmlElement.hasTagName(HTMLNames::audioTag)
113             || htmlElement.hasTagName(HTMLNames::bTag)
114             || htmlElement.hasTagName(HTMLNames::bdiTag)
115             || htmlElement.hasTagName(HTMLNames::bdoTag)
116             || htmlElement.hasTagName(HTMLNames::brTag)
117             || htmlElement.hasTagName(HTMLNames::buttonTag)
118             || htmlElement.hasTagName(HTMLNames::canvasTag)
119             || htmlElement.hasTagName(HTMLNames::citeTag)
120             || htmlElement.hasTagName(HTMLNames::codeTag)
121             || htmlElement.hasTagName(HTMLNames::datalistTag)
122             || htmlElement.hasTagName(HTMLNames::delTag)
123             || htmlElement.hasTagName(HTMLNames::dfnTag)
124             || htmlElement.hasTagName(HTMLNames::emTag)
125             || htmlElement.hasTagName(HTMLNames::embedTag)
126             || htmlElement.hasTagName(HTMLNames::iTag)
127             || htmlElement.hasTagName(HTMLNames::iframeTag)
128             || htmlElement.hasTagName(HTMLNames::imgTag)
129             || htmlElement.hasTagName(HTMLNames::inputTag)
130             || htmlElement.hasTagName(HTMLNames::insTag)
131             || htmlElement.hasTagName(HTMLNames::kbdTag)
132             || htmlElement.hasTagName(HTMLNames::keygenTag)
133             || htmlElement.hasTagName(HTMLNames::labelTag)
134             || htmlElement.hasTagName(HTMLNames::mapTag)
135             || htmlElement.hasTagName(HTMLNames::markTag)
136             || htmlElement.hasTagName(HTMLNames::meterTag)
137             || htmlElement.hasTagName(HTMLNames::noscriptTag)
138             || htmlElement.hasTagName(HTMLNames::objectTag)
139             || htmlElement.hasTagName(HTMLNames::outputTag)
140             || htmlElement.hasTagName(HTMLNames::progressTag)
141             || htmlElement.hasTagName(HTMLNames::qTag)
142             || htmlElement.hasTagName(HTMLNames::rubyTag)
143             || htmlElement.hasTagName(HTMLNames::sTag)
144             || htmlElement.hasTagName(HTMLNames::sampTag)
145             || htmlElement.hasTagName(HTMLNames::scriptTag)
146             || htmlElement.hasTagName(HTMLNames::selectTag)
147             || htmlElement.hasTagName(HTMLNames::smallTag)
148             || htmlElement.hasTagName(HTMLNames::spanTag)
149             || htmlElement.hasTagName(HTMLNames::strongTag)
150             || htmlElement.hasTagName(HTMLNames::subTag)
151             || htmlElement.hasTagName(HTMLNames::supTag)
152             || htmlElement.hasTagName(HTMLNames::templateTag)
153             || htmlElement.hasTagName(HTMLNames::textareaTag)
154             || htmlElement.hasTagName(HTMLNames::uTag)
155             || htmlElement.hasTagName(HTMLNames::varTag)
156             || htmlElement.hasTagName(HTMLNames::videoTag)
157             || htmlElement.hasTagName(HTMLNames::wbrTag);
158     }
159
160     return false;
161 }
162
163 bool MathMLElement::isFlowContent(const Node& node) const
164 {
165     // Flow content is described in the HTML 5 specification:
166     // http://www.w3.org/TR/html5/dom.html#flow-content
167
168     if (isPhrasingContent(node))
169         return true;
170
171     if (!is<HTMLElement>(node))
172         return false;
173
174     auto& htmlElement = downcast<HTMLElement>(node);
175     // FIXME add the <dialog> tag when it is implemented.
176     return htmlElement.hasTagName(HTMLNames::addressTag)
177         || htmlElement.hasTagName(HTMLNames::articleTag)
178         || htmlElement.hasTagName(HTMLNames::asideTag)
179         || htmlElement.hasTagName(HTMLNames::blockquoteTag)
180         || htmlElement.hasTagName(HTMLNames::detailsTag)
181         || htmlElement.hasTagName(HTMLNames::divTag)
182         || htmlElement.hasTagName(HTMLNames::dlTag)
183         || htmlElement.hasTagName(HTMLNames::fieldsetTag)
184         || htmlElement.hasTagName(HTMLNames::figureTag)
185         || htmlElement.hasTagName(HTMLNames::footerTag)
186         || htmlElement.hasTagName(HTMLNames::formTag)
187         || htmlElement.hasTagName(HTMLNames::h1Tag)
188         || htmlElement.hasTagName(HTMLNames::h2Tag)
189         || htmlElement.hasTagName(HTMLNames::h3Tag)
190         || htmlElement.hasTagName(HTMLNames::h4Tag)
191         || htmlElement.hasTagName(HTMLNames::h5Tag)
192         || htmlElement.hasTagName(HTMLNames::h6Tag)
193         || htmlElement.hasTagName(HTMLNames::headerTag)
194         || htmlElement.hasTagName(HTMLNames::hrTag)
195         || htmlElement.hasTagName(HTMLNames::mainTag)
196         || htmlElement.hasTagName(HTMLNames::navTag)
197         || htmlElement.hasTagName(HTMLNames::olTag)
198         || htmlElement.hasTagName(HTMLNames::pTag)
199         || htmlElement.hasTagName(HTMLNames::preTag)
200         || htmlElement.hasTagName(HTMLNames::sectionTag)
201         || (htmlElement.hasTagName(HTMLNames::styleTag) && htmlElement.hasAttribute("scoped"))
202         || htmlElement.hasTagName(HTMLNames::tableTag)
203         || htmlElement.hasTagName(HTMLNames::ulTag);
204 }
205
206 unsigned MathMLElement::colSpan() const
207 {
208     if (!hasTagName(mtdTag))
209         return 1u;
210     const AtomicString& colSpanValue = attributeWithoutSynchronization(columnspanAttr);
211     return std::max(1u, limitToOnlyHTMLNonNegative(colSpanValue, 1u));
212 }
213
214 unsigned MathMLElement::rowSpan() const
215 {
216     if (!hasTagName(mtdTag))
217         return 1u;
218     const AtomicString& rowSpanValue = attributeWithoutSynchronization(rowspanAttr);
219     static const unsigned maxRowspan = 8190; // This constant comes from HTMLTableCellElement.
220     return std::max(1u, std::min(limitToOnlyHTMLNonNegative(rowSpanValue, 1u), maxRowspan));
221 }
222
223 void MathMLElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
224 {
225     if (name == hrefAttr) {
226         bool wasLink = isLink();
227         setIsLink(!value.isNull() && !shouldProhibitLinks(this));
228         if (wasLink != isLink())
229             setNeedsStyleRecalc();
230     } else if (name == rowspanAttr) {
231         if (is<RenderTableCell>(renderer()) && hasTagName(mtdTag))
232             downcast<RenderTableCell>(*renderer()).colSpanOrRowSpanChanged();
233     } else if (name == columnspanAttr) {
234         if (is<RenderTableCell>(renderer()) && hasTagName(mtdTag))
235             downcast<RenderTableCell>(renderer())->colSpanOrRowSpanChanged();
236     } else
237         StyledElement::parseAttribute(name, value);
238 }
239
240 bool MathMLElement::isPresentationAttribute(const QualifiedName& name) const
241 {
242     if (name == backgroundAttr || name == colorAttr || name == dirAttr || name == fontfamilyAttr || name == fontsizeAttr || name == fontstyleAttr || name == fontweightAttr || name == mathbackgroundAttr || name == mathcolorAttr || name == mathsizeAttr)
243         return true;
244     return StyledElement::isPresentationAttribute(name);
245 }
246
247 void MathMLElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
248 {
249     if (name == mathbackgroundAttr)
250         addPropertyToPresentationAttributeStyle(style, CSSPropertyBackgroundColor, value);
251     else if (name == mathsizeAttr) {
252         // The following three values of mathsize are handled in WebCore/css/mathml.css
253         if (value != "normal" && value != "small" && value != "big")
254             addPropertyToPresentationAttributeStyle(style, CSSPropertyFontSize, value);
255     } else if (name == mathcolorAttr)
256         addPropertyToPresentationAttributeStyle(style, CSSPropertyColor, value);
257     // FIXME: deprecated attributes that should loose in a conflict with a non deprecated attribute
258     else if (name == fontsizeAttr)
259         addPropertyToPresentationAttributeStyle(style, CSSPropertyFontSize, value);
260     else if (name == backgroundAttr)
261         addPropertyToPresentationAttributeStyle(style, CSSPropertyBackgroundColor, value);
262     else if (name == colorAttr)
263         addPropertyToPresentationAttributeStyle(style, CSSPropertyColor, value);
264     else if (name == fontstyleAttr)
265         addPropertyToPresentationAttributeStyle(style, CSSPropertyFontStyle, value);
266     else if (name == fontweightAttr)
267         addPropertyToPresentationAttributeStyle(style, CSSPropertyFontWeight, value);
268     else if (name == fontfamilyAttr)
269         addPropertyToPresentationAttributeStyle(style, CSSPropertyFontFamily, value);
270     else if (name == dirAttr) {
271         if (hasTagName(mathTag) || hasTagName(mrowTag) || hasTagName(mstyleTag) || isMathMLToken())
272             addPropertyToPresentationAttributeStyle(style, CSSPropertyDirection, value);
273     }  else {
274         ASSERT(!isPresentationAttribute(name));
275         StyledElement::collectStyleForPresentationAttribute(name, value
276         , style);
277     }
278 }
279
280 bool MathMLElement::childShouldCreateRenderer(const Node& child) const
281 {
282     if (hasTagName(annotation_xmlTag)) {
283         const AtomicString& value = attributeWithoutSynchronization(MathMLNames::encodingAttr);
284
285         // See annotation-xml.model.mathml, annotation-xml.model.svg and annotation-xml.model.xhtml in the HTML5 RelaxNG schema.
286
287         if (is<MathMLElement>(child) && (MathMLSelectElement::isMathMLEncoding(value) || MathMLSelectElement::isHTMLEncoding(value))) {
288             auto& mathmlElement = downcast<MathMLElement>(child);
289             return is<MathMLMathElement>(mathmlElement);
290         }
291
292         if (is<SVGElement>(child) && (MathMLSelectElement::isSVGEncoding(value) || MathMLSelectElement::isHTMLEncoding(value))) {
293             auto& svgElement = downcast<SVGElement>(child);
294             return is<SVGSVGElement>(svgElement);
295         }
296
297         if (is<HTMLElement>(child) && MathMLSelectElement::isHTMLEncoding(value)) {
298             auto& htmlElement = downcast<HTMLElement>(child);
299             return is<HTMLHtmlElement>(htmlElement) || (isFlowContent(htmlElement) && StyledElement::childShouldCreateRenderer(child));
300         }
301
302         return false;
303     }
304
305     // In general, only MathML children are allowed. Text nodes are only visible in token MathML elements.
306     return is<MathMLElement>(child);
307 }
308
309 void MathMLElement::attributeChanged(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue, AttributeModificationReason reason)
310 {
311     if (isSemanticAnnotation() && (name == MathMLNames::srcAttr || name == MathMLNames::encodingAttr)) {
312         auto* parent = parentElement();
313         if (is<MathMLElement>(parent) && parent->hasTagName(semanticsTag))
314             downcast<MathMLElement>(*parent).updateSelectedChild();
315     }
316     StyledElement::attributeChanged(name, oldValue, newValue, reason);
317 }
318
319 bool MathMLElement::willRespondToMouseClickEvents()
320 {
321     return isLink() || StyledElement::willRespondToMouseClickEvents();
322 }
323
324 void MathMLElement::defaultEventHandler(Event* event)
325 {
326     if (isLink()) {
327         if (focused() && isEnterKeyKeydownEvent(event)) {
328             event->setDefaultHandled();
329             dispatchSimulatedClick(event);
330             return;
331         }
332         if (MouseEvent::canTriggerActivationBehavior(*event)) {
333             const AtomicString& href = attributeWithoutSynchronization(hrefAttr);
334             String url = stripLeadingAndTrailingHTMLSpaces(href);
335             event->setDefaultHandled();
336             if (Frame* frame = document().frame())
337                 frame->loader().urlSelected(document().completeURL(url), "_self", event, LockHistory::No, LockBackForwardList::No, MaybeSendReferrer, document().shouldOpenExternalURLsPolicyToPropagate());
338             return;
339         }
340     }
341
342     StyledElement::defaultEventHandler(event);
343 }
344
345 bool MathMLElement::canStartSelection() const
346 {
347     if (!isLink())
348         return StyledElement::canStartSelection();
349
350     return hasEditableStyle();
351 }
352
353 bool MathMLElement::isFocusable() const
354 {
355     if (renderer() && renderer()->absoluteClippedOverflowRect().isEmpty())
356         return false;
357
358     return StyledElement::isFocusable();
359 }
360
361 bool MathMLElement::isKeyboardFocusable(KeyboardEvent* event) const
362 {
363     if (isFocusable() && StyledElement::supportsFocus())
364         return StyledElement::isKeyboardFocusable(event);
365
366     if (isLink())
367         return document().frame()->eventHandler().tabsToLinks(event);
368
369     return StyledElement::isKeyboardFocusable(event);
370 }
371
372 bool MathMLElement::isMouseFocusable() const
373 {
374     // Links are focusable by default, but only allow links with tabindex or contenteditable to be mouse focusable.
375     // https://bugs.webkit.org/show_bug.cgi?id=26856
376     if (isLink())
377         return StyledElement::supportsFocus();
378
379     return StyledElement::isMouseFocusable();
380 }
381
382 bool MathMLElement::isURLAttribute(const Attribute& attribute) const
383 {
384     return attribute.name().localName() == hrefAttr || StyledElement::isURLAttribute(attribute);
385 }
386
387 bool MathMLElement::supportsFocus() const
388 {
389     if (hasEditableStyle())
390         return StyledElement::supportsFocus();
391     // If not a link we should still be able to focus the element if it has tabIndex.
392     return isLink() || StyledElement::supportsFocus();
393 }
394
395 int MathMLElement::tabIndex() const
396 {
397     // Skip the supportsFocus check in StyledElement.
398     return Element::tabIndex();
399 }
400
401 static inline StringView skipLeadingAndTrailingWhitespace(const StringView& stringView)
402 {
403     unsigned start = 0, stringLength = stringView.length();
404     while (stringLength > 0 && isHTMLSpace(stringView[start])) {
405         start++;
406         stringLength--;
407     }
408     while (stringLength > 0 && isHTMLSpace(stringView[start + stringLength - 1]))
409         stringLength--;
410     return stringView.substring(start, stringLength);
411 }
412
413 MathMLElement::Length MathMLElement::parseNumberAndUnit(const StringView& string)
414 {
415     LengthType lengthType = LengthType::UnitLess;
416     unsigned stringLength = string.length();
417     UChar lastChar = string[stringLength - 1];
418     if (lastChar == '%') {
419         lengthType = LengthType::Percentage;
420         stringLength--;
421     } else if (stringLength >= 2) {
422         UChar penultimateChar = string[stringLength - 2];
423         if (penultimateChar == 'c' && lastChar == 'm')
424             lengthType = LengthType::Cm;
425         if (penultimateChar == 'e' && lastChar == 'm')
426             lengthType = LengthType::Em;
427         else if (penultimateChar == 'e' && lastChar == 'x')
428             lengthType = LengthType::Ex;
429         else if (penultimateChar == 'i' && lastChar == 'n')
430             lengthType = LengthType::In;
431         else if (penultimateChar == 'm' && lastChar == 'm')
432             lengthType = LengthType::Mm;
433         else if (penultimateChar == 'p' && lastChar == 'c')
434             lengthType = LengthType::Pc;
435         else if (penultimateChar == 'p' && lastChar == 't')
436             lengthType = LengthType::Pt;
437         else if (penultimateChar == 'p' && lastChar == 'x')
438             lengthType = LengthType::Px;
439
440         if (lengthType != LengthType::UnitLess)
441             stringLength -= 2;
442     }
443
444     bool ok;
445     float lengthValue = string.substring(0, stringLength).toFloat(ok);
446     if (!ok)
447         return Length();
448
449     Length length;
450     length.type = lengthType;
451     length.value = lengthValue;
452     return length;
453 }
454
455 MathMLElement::Length MathMLElement::parseNamedSpace(const StringView& string)
456 {
457     // Named space values are case-sensitive.
458     int namedSpaceValue;
459     if (string == "veryverythinmathspace")
460         namedSpaceValue = 1;
461     else if (string == "verythinmathspace")
462         namedSpaceValue = 2;
463     else if (string == "thinmathspace")
464         namedSpaceValue = 3;
465     else if (string == "mediummathspace")
466         namedSpaceValue = 4;
467     else if (string == "thickmathspace")
468         namedSpaceValue = 5;
469     else if (string == "verythickmathspace")
470         namedSpaceValue = 6;
471     else if (string == "veryverythickmathspace")
472         namedSpaceValue = 7;
473     else if (string == "negativeveryverythinmathspace")
474         namedSpaceValue = -1;
475     else if (string == "negativeverythinmathspace")
476         namedSpaceValue = -2;
477     else if (string == "negativethinmathspace")
478         namedSpaceValue = -3;
479     else if (string == "negativemediummathspace")
480         namedSpaceValue = -4;
481     else if (string == "negativethickmathspace")
482         namedSpaceValue = -5;
483     else if (string == "negativeverythickmathspace")
484         namedSpaceValue = -6;
485     else if (string == "negativeveryverythickmathspace")
486         namedSpaceValue = -7;
487     else
488         return Length();
489
490     Length length;
491     length.type = LengthType::MathUnit;
492     length.value = namedSpaceValue;
493     return length;
494 }
495
496 MathMLElement::Length MathMLElement::parseMathMLLength(const String& string)
497 {
498     // The regular expression from the MathML Relax NG schema is as follows:
499     //
500     //   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*'
501     //
502     // We do not perform a strict verification of the syntax of whitespaces and number.
503     // Instead, we just use isHTMLSpace and toFloat to parse these parts.
504
505     // We first skip whitespace from both ends of the string.
506     StringView stringView = skipLeadingAndTrailingWhitespace(string);
507
508     if (stringView.isEmpty())
509         return Length();
510
511     // We consider the most typical case: a number followed by an optional unit.
512     UChar firstChar = stringView[0];
513     if (isASCIIDigit(firstChar) || firstChar == '-' || firstChar == '.')
514         return parseNumberAndUnit(stringView);
515
516     // Otherwise, we try and parse a named space.
517     return parseNamedSpace(stringView);
518 }
519
520 const MathMLElement::Length& MathMLElement::cachedMathMLLength(const QualifiedName& name, Optional<Length>& length)
521 {
522     if (length)
523         return length.value();
524     length = parseMathMLLength(attributeWithoutSynchronization(name));
525     return length.value();
526 }
527
528 const MathMLElement::BooleanValue& MathMLElement::cachedBooleanAttribute(const QualifiedName& name, Optional<BooleanValue>& attribute)
529 {
530     if (attribute)
531         return attribute.value();
532
533     // In MathML, attribute values are case-sensitive.
534     const AtomicString& value = attributeWithoutSynchronization(name);
535     if (value == "true")
536         attribute = BooleanValue::True;
537     else if (value == "false")
538         attribute = BooleanValue::False;
539     else
540         attribute = BooleanValue::Default;
541
542     return attribute.value();
543 }
544
545 MathMLElement::MathVariant MathMLElement::parseMathVariantAttribute(const AtomicString& attributeValue)
546 {
547     // The mathvariant attribute values is case-sensitive.
548     if (attributeValue == "normal")
549         return MathVariant::Normal;
550     if (attributeValue == "bold")
551         return MathVariant::Bold;
552     if (attributeValue == "italic")
553         return MathVariant::Italic;
554     if (attributeValue == "bold-italic")
555         return MathVariant::BoldItalic;
556     if (attributeValue == "double-struck")
557         return MathVariant::DoubleStruck;
558     if (attributeValue == "bold-fraktur")
559         return MathVariant::BoldFraktur;
560     if (attributeValue == "script")
561         return MathVariant::Script;
562     if (attributeValue == "bold-script")
563         return MathVariant::BoldScript;
564     if (attributeValue == "fraktur")
565         return MathVariant::Fraktur;
566     if (attributeValue == "sans-serif")
567         return MathVariant::SansSerif;
568     if (attributeValue == "bold-sans-serif")
569         return MathVariant::BoldSansSerif;
570     if (attributeValue == "sans-serif-italic")
571         return MathVariant::SansSerifItalic;
572     if (attributeValue == "sans-serif-bold-italic")
573         return MathVariant::SansSerifBoldItalic;
574     if (attributeValue == "monospace")
575         return MathVariant::Monospace;
576     if (attributeValue == "initial")
577         return MathVariant::Initial;
578     if (attributeValue == "tailed")
579         return MathVariant::Tailed;
580     if (attributeValue == "looped")
581         return MathVariant::Looped;
582     if (attributeValue == "stretched")
583         return MathVariant::Stretched;
584     return MathVariant::None;
585 }
586
587 Optional<bool> MathMLElement::specifiedDisplayStyle()
588 {
589     if (!acceptsDisplayStyleAttribute())
590         return Nullopt;
591     const MathMLElement::BooleanValue& specifiedDisplayStyle = cachedBooleanAttribute(displaystyleAttr, m_displayStyle);
592     return toOptionalBool(specifiedDisplayStyle);
593 }
594
595 Optional<MathMLElement::MathVariant> MathMLElement::specifiedMathVariant()
596 {
597     if (!acceptsMathVariantAttribute())
598         return Nullopt;
599     if (!m_mathVariant)
600         m_mathVariant = parseMathVariantAttribute(attributeWithoutSynchronization(mathvariantAttr));
601     return m_mathVariant.value() == MathVariant::None ? Nullopt : m_mathVariant;
602 }
603
604 }
605
606 #endif // ENABLE(MATHML)