Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
78 / 78
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
MathML
100.00% covered (success)
100.00%
78 / 78
100.00% covered (success)
100.00%
3 / 3
26
100.00% covered (success)
100.00%
1 / 1
 read
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
 parseNode
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 getElement
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
1 / 1
19
1<?php
2
3namespace PhpOffice\Math\Reader;
4
5use DOMDocument;
6use DOMElement;
7use DOMNode;
8use DOMXPath;
9use PhpOffice\Math\Element;
10use PhpOffice\Math\Exception\InvalidInputException;
11use PhpOffice\Math\Exception\NotImplementedException;
12use PhpOffice\Math\Math;
13
14class MathML implements ReaderInterface
15{
16    /** @var Math */
17    private $math;
18
19    /** @var DOMDocument */
20    private $dom;
21
22    /** @var DOMXPath */
23    private $xpath;
24
25    public function read(string $content): ?Math
26    {
27        $content = str_replace(
28            [
29                '&InvisibleTimes;',
30            ],
31            [
32                '<mchar name="InvisibleTimes"/>',
33            ],
34            $content
35        );
36
37        $this->dom = new DOMDocument();
38        $this->dom->loadXML($content, LIBXML_DTDLOAD);
39
40        $this->math = new Math();
41        $this->parseNode(null, $this->math);
42
43        return $this->math;
44    }
45
46    /**
47     * @param Math|Element\AbstractGroupElement $parent
48     */
49    protected function parseNode(?DOMNode $nodeRowElement, $parent): void
50    {
51        $this->xpath = new DOMXPath($this->dom);
52        foreach ($this->xpath->query('*', $nodeRowElement) ?: [] as $nodeElement) {
53            if ($parent instanceof Element\Semantics
54                && $nodeElement instanceof DOMElement
55                && $nodeElement->nodeName == 'annotation') {
56                $parent->addAnnotation(
57                    $nodeElement->getAttribute('encoding'),
58                    trim($nodeElement->nodeValue)
59                );
60
61                continue;
62            }
63
64            $parent->add($this->getElement($nodeElement));
65        }
66    }
67
68    protected function getElement(DOMNode $nodeElement): Element\AbstractElement
69    {
70        $nodeValue = trim($nodeElement->nodeValue);
71        switch ($nodeElement->nodeName) {
72            case 'mfrac':
73                $nodeList = $this->xpath->query('*', $nodeElement);
74                if ($nodeList && $nodeList->length == 2) {
75                    return new Element\Fraction(
76                        $this->getElement($nodeList->item(0)),
77                        $this->getElement($nodeList->item(1))
78                    );
79                }
80
81                throw new InvalidInputException(sprintf(
82                    '%s : The tag `%s` has not two subelements',
83                    __METHOD__,
84                    $nodeElement->nodeName
85                ));
86            case 'mi':
87                return new Element\Identifier($nodeValue);
88            case 'mn':
89                return new Element\Numeric(floatval($nodeValue));
90            case 'mo':
91                if (empty($nodeValue)) {
92                    $nodeList = $this->xpath->query('*', $nodeElement);
93                    if (
94                        $nodeList
95                        && $nodeList->length == 1
96                        && $nodeList->item(0)->nodeName == 'mchar'
97                        && $nodeList->item(0) instanceof DOMElement
98                        && $nodeList->item(0)->hasAttribute('name')
99                    ) {
100                        $nodeValue = $nodeList->item(0)->getAttribute('name');
101                    }
102                }
103
104                return new Element\Operator($nodeValue);
105            case 'mrow':
106                $mrow = new Element\Row();
107
108                $this->parseNode($nodeElement, $mrow);
109
110                return $mrow;
111            case 'msup':
112                $nodeList = $this->xpath->query('*', $nodeElement);
113                if ($nodeList && $nodeList->length == 2) {
114                    return new Element\Superscript(
115                        $this->getElement($nodeList->item(0)),
116                        $this->getElement($nodeList->item(1))
117                    );
118                }
119
120                throw new InvalidInputException(sprintf(
121                    '%s : The tag `%s` has not two subelements',
122                    __METHOD__,
123                    $nodeElement->nodeName
124                ));
125            case 'semantics':
126                $semantics = new Element\Semantics();
127
128                $this->parseNode($nodeElement, $semantics);
129
130                return $semantics;
131            default:
132                throw new NotImplementedException(sprintf(
133                    '%s : The tag `%s` is not implemented',
134                    __METHOD__,
135                    $nodeElement->nodeName
136                ));
137        }
138    }
139}