Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
80 / 80 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
1 / 1 |
| MathML | |
100.00% |
80 / 80 |
|
100.00% |
4 / 4 |
27 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| read | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
1 | |||
| parseNode | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
| getElement | |
100.00% |
53 / 53 |
|
100.00% |
1 / 1 |
19 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace PhpOffice\Math\Reader; |
| 4 | |
| 5 | use DOMDocument; |
| 6 | use DOMElement; |
| 7 | use DOMNode; |
| 8 | use DOMXPath; |
| 9 | use PhpOffice\Math\Element; |
| 10 | use PhpOffice\Math\Exception\InvalidInputException; |
| 11 | use PhpOffice\Math\Exception\NotImplementedException; |
| 12 | use PhpOffice\Math\Math; |
| 13 | use PhpOffice\Math\Reader\Security\XmlScanner; |
| 14 | |
| 15 | class 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 | '⁢', |
| 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 | } |