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
46namespace WebCore {
47
48WTF_MAKE_ISO_ALLOCATED_IMPL(MathMLPresentationElement);
49
50using namespace MathMLNames;
51
52MathMLPresentationElement::MathMLPresentationElement(const QualifiedName& tagName, Document& document)
53 : MathMLElement(tagName, document)
54{
55}
56
57Ref<MathMLPresentationElement> MathMLPresentationElement::create(const QualifiedName& tagName, Document& document)
58{
59 return adoptRef(*new MathMLPresentationElement(tagName, document));
60}
61
62RenderPtr<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
70bool 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
145bool 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
188const 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
205MathMLElement::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
247MathMLElement::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
288MathMLElement::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
313const 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
321bool MathMLPresentationElement::acceptsDisplayStyleAttribute()
322{
323 return hasTagName(mtableTag);
324}
325
326Optional<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
334MathMLElement::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
376Optional<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
385void 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