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