Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
64 / 64 |
|
100.00% |
3 / 3 |
CRAP | |
100.00% |
1 / 1 |
| OfficeMathML | |
100.00% |
64 / 64 |
|
100.00% |
3 / 3 |
20 | |
100.00% |
1 / 1 |
| read | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
1 | |||
| parseNode | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
| getElement | |
100.00% |
46 / 46 |
|
100.00% |
1 / 1 |
15 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace PhpOffice\Math\Reader; |
| 4 | |
| 5 | use DOMDocument; |
| 6 | use DOMNode; |
| 7 | use DOMXPath; |
| 8 | use PhpOffice\Math\Element; |
| 9 | use PhpOffice\Math\Exception\InvalidInputException; |
| 10 | use PhpOffice\Math\Exception\NotImplementedException; |
| 11 | use PhpOffice\Math\Math; |
| 12 | |
| 13 | class OfficeMathML implements ReaderInterface |
| 14 | { |
| 15 | /** @var DOMDocument */ |
| 16 | protected $dom; |
| 17 | |
| 18 | /** @var Math */ |
| 19 | protected $math; |
| 20 | |
| 21 | /** @var DOMXPath */ |
| 22 | protected $xpath; |
| 23 | |
| 24 | /** @var string[] */ |
| 25 | protected $operators = ['+', '-', '/', '∗']; |
| 26 | |
| 27 | public function read(string $content): ?Math |
| 28 | { |
| 29 | $nsMath = 'xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"'; |
| 30 | $nsWord = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"'; |
| 31 | |
| 32 | $content = str_replace( |
| 33 | $nsMath, |
| 34 | $nsMath . ' ' . $nsWord, |
| 35 | $content |
| 36 | ); |
| 37 | |
| 38 | $this->dom = new DOMDocument(); |
| 39 | $this->dom->loadXML($content); |
| 40 | |
| 41 | $this->math = new Math(); |
| 42 | $this->parseNode(null, $this->math); |
| 43 | |
| 44 | return $this->math; |
| 45 | } |
| 46 | |
| 47 | /** |
| 48 | * @see https://devblogs.microsoft.com/math-in-office/officemath/ |
| 49 | * @see https://learn.microsoft.com/fr-fr/archive/blogs/murrays/mathml-and-ecma-math-omml |
| 50 | * |
| 51 | * @param Math|Element\AbstractGroupElement $parent |
| 52 | */ |
| 53 | protected function parseNode(?DOMNode $nodeRowElement, $parent): void |
| 54 | { |
| 55 | $this->xpath = new DOMXPath($this->dom); |
| 56 | foreach ($this->xpath->query('*', $nodeRowElement) ?: [] as $nodeElement) { |
| 57 | $element = $this->getElement($nodeElement); |
| 58 | $parent->add($element); |
| 59 | |
| 60 | if ($element instanceof Element\AbstractGroupElement) { |
| 61 | $this->parseNode($nodeElement, $element); |
| 62 | } |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | protected function getElement(DOMNode $nodeElement): Element\AbstractElement |
| 67 | { |
| 68 | switch ($nodeElement->nodeName) { |
| 69 | case 'm:f': |
| 70 | // Numerator |
| 71 | $nodeNumerator = $this->xpath->query('m:num/m:r/m:t', $nodeElement); |
| 72 | if ($nodeNumerator && $nodeNumerator->length == 1) { |
| 73 | $value = $nodeNumerator->item(0)->nodeValue; |
| 74 | if (is_numeric($value)) { |
| 75 | $numerator = new Element\Numeric(floatval($value)); |
| 76 | } else { |
| 77 | $numerator = new Element\Identifier($value); |
| 78 | } |
| 79 | } else { |
| 80 | throw new InvalidInputException(sprintf( |
| 81 | '%s : The tag `%s` has no numerator defined', |
| 82 | __METHOD__, |
| 83 | $nodeElement->nodeName |
| 84 | )); |
| 85 | } |
| 86 | // Denominator |
| 87 | $nodeDenominator = $this->xpath->query('m:den/m:r/m:t', $nodeElement); |
| 88 | if ($nodeDenominator && $nodeDenominator->length == 1) { |
| 89 | $value = $nodeDenominator->item(0)->nodeValue; |
| 90 | if (is_numeric($value)) { |
| 91 | $denominator = new Element\Numeric(floatval($value)); |
| 92 | } else { |
| 93 | $denominator = new Element\Identifier($value); |
| 94 | } |
| 95 | } else { |
| 96 | throw new InvalidInputException(sprintf( |
| 97 | '%s : The tag `%s` has no denominator defined', |
| 98 | __METHOD__, |
| 99 | $nodeElement->nodeName |
| 100 | )); |
| 101 | } |
| 102 | |
| 103 | return new Element\Fraction($numerator, $denominator); |
| 104 | case 'm:r': |
| 105 | $nodeText = $this->xpath->query('m:t', $nodeElement); |
| 106 | if ($nodeText && $nodeText->length == 1) { |
| 107 | $value = trim($nodeText->item(0)->nodeValue); |
| 108 | if (in_array($value, $this->operators)) { |
| 109 | return new Element\Operator($value); |
| 110 | } |
| 111 | if (is_numeric($value)) { |
| 112 | return new Element\Numeric(floatval($value)); |
| 113 | } |
| 114 | |
| 115 | return new Element\Identifier($value); |
| 116 | } |
| 117 | |
| 118 | throw new InvalidInputException(sprintf( |
| 119 | '%s : The tag `%s` has no tag `m:t` defined', |
| 120 | __METHOD__, |
| 121 | $nodeElement->nodeName |
| 122 | )); |
| 123 | case 'm:oMath': |
| 124 | return new Element\Row(); |
| 125 | default: |
| 126 | throw new NotImplementedException(sprintf( |
| 127 | '%s : The tag `%s` is not implemented', |
| 128 | __METHOD__, |
| 129 | $nodeElement->nodeName |
| 130 | )); |
| 131 | } |
| 132 | } |
| 133 | } |