├── src
└── Math
│ ├── Element
│ ├── Row.php
│ ├── AbstractElement.php
│ ├── Numeric.php
│ ├── Identifier.php
│ ├── Operator.php
│ ├── Semantics.php
│ ├── AbstractGroupElement.php
│ ├── Superscript.php
│ └── Fraction.php
│ ├── Exception
│ ├── SecurityException.php
│ ├── InvalidInputException.php
│ ├── NotImplementedException.php
│ └── MathException.php
│ ├── Math.php
│ ├── Reader
│ ├── ReaderInterface.php
│ ├── Security
│ │ └── XmlScanner.php
│ ├── OfficeMathML.php
│ └── MathML.php
│ └── Writer
│ ├── WriterInterface.php
│ ├── OfficeMathML.php
│ └── MathML.php
├── docs
├── credits.md
├── changes
│ ├── 0.4.0.md
│ ├── 0.3.0.md
│ ├── 0.2.0.md
│ └── 0.1.0.md
├── assets
│ └── mathjax.js
├── install.md
├── usage
│ ├── elements
│ │ ├── numeric.md
│ │ ├── operator.md
│ │ ├── identifier.md
│ │ ├── fraction.md
│ │ ├── superscript.md
│ │ ├── row.md
│ │ └── semantics.md
│ ├── writers.md
│ └── readers.md
└── index.md
├── .gitignore
├── roave-bc-check.yaml
├── phpstan.neon.dist
├── .github
├── dependabot.yml
└── workflows
│ ├── deploy.yml
│ └── php.yml
├── tests
├── Math
│ ├── Element
│ │ ├── NumericTest.php
│ │ ├── OperatorTest.php
│ │ ├── IdentifierTest.php
│ │ ├── SemanticsTest.php
│ │ ├── AbstractGroupElementTest.php
│ │ ├── SuperscriptTest.php
│ │ └── FractionTest.php
│ ├── Writer
│ │ ├── WriterTestCase.php
│ │ ├── OfficeMathMLTest.php
│ │ └── MathMLTest.php
│ └── Reader
│ │ ├── OfficeMathMLTest.php
│ │ └── MathMLTest.php
└── resources
│ └── schema
│ └── mathml3
│ ├── mathml3.xsd
│ ├── mathml3-common.xsd
│ ├── mathml3-strict-content.xsd
│ └── mathml3-content.xsd
├── composer.json
├── phpunit.xml.dist
├── LICENSE
├── .php-cs-fixer.dist.php
├── mkdocs.yml
└── README.md
/src/Math/Element/Row.php:
--------------------------------------------------------------------------------
1 | {
15 | MathJax.typesetPromise()
16 | })
17 |
--------------------------------------------------------------------------------
/src/Math/Element/Numeric.php:
--------------------------------------------------------------------------------
1 | value = $value;
15 | }
16 |
17 | public function getValue(): float
18 | {
19 | return $this->value;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Math/Element/Identifier.php:
--------------------------------------------------------------------------------
1 | value = $value;
15 | }
16 |
17 | public function getValue(): string
18 | {
19 | return $this->value;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Math/Element/Operator.php:
--------------------------------------------------------------------------------
1 | value = $value;
15 | }
16 |
17 | public function getValue(): string
18 | {
19 | return $this->value;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Math/Element/NumericTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(2, $numeric->getValue());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Math/Element/OperatorTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('+', $operator->getValue());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/resources/schema/mathml3/mathml3.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/tests/Math/Element/IdentifierTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('x', $operator->getValue());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/docs/install.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## Requirements
4 |
5 | Mandatory:
6 |
7 | - PHP 7.1+
8 | - PHP [DOM extension](http://php.net/manual/en/book.dom.php)
9 | - PHP [XML Parser extension](http://www.php.net/manual/en/xml.installation.php)
10 | - PHP [XMLWriter extension](http://php.net/manual/en/book.xmlwriter.php)
11 |
12 |
13 | ## Installation
14 |
15 | ### Using Composer
16 |
17 | To install via [Composer](http://getcomposer.org), add the following lines to your `composer.json`:
18 |
19 | ``` json
20 | {
21 | "require": {
22 | "phpoffice/math": "dev-master"
23 | }
24 | }
25 | ```
--------------------------------------------------------------------------------
/docs/usage/elements/numeric.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | To create a numeric, use the `PhpOffice\Math\Element\Numeric` class.
4 |
5 | ### Methods
6 | #### getValue
7 |
8 | The method has no parameter.
9 |
10 | #### setValue
11 |
12 | The method has one parameter :
13 |
14 | * `float` **$value**
15 |
16 | ## Example
17 |
18 | ### Math
19 |
22 |
23 | ### XML
24 | ``` xml
25 |
28 | ```
29 |
30 | ### PHP
31 |
32 | ``` php
33 | add($identifier);
43 | ```
--------------------------------------------------------------------------------
/docs/usage/elements/operator.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | To create an operator, use the `PhpOffice\Math\Element\Operator` class.
4 |
5 | ### Methods
6 | #### getValue
7 |
8 | The method has no parameter.
9 |
10 | #### setValue
11 |
12 | The method has one parameter :
13 |
14 | * `string` **$value**
15 |
16 | ## Example
17 |
18 | ### Math
19 |
22 |
23 | ### XML
24 | ``` xml
25 |
28 | ```
29 |
30 | ### PHP
31 |
32 | ``` php
33 | add($identifier);
43 | ```
--------------------------------------------------------------------------------
/docs/usage/elements/identifier.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | To create an identifier, use the `PhpOffice\Math\Element\Identifier` class.
4 |
5 | ### Methods
6 | #### getValue
7 |
8 | The method has no parameter.
9 |
10 | #### setValue
11 |
12 | The method has one parameter :
13 |
14 | * `string` **$value**
15 |
16 | ## Example
17 |
18 | ### Math
19 |
22 |
23 | ### XML
24 | ``` xml
25 |
28 | ```
29 |
30 | ### PHP
31 |
32 | ``` php
33 | add($identifier);
43 | ```
--------------------------------------------------------------------------------
/src/Math/Element/Semantics.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | protected $annotations = [];
13 |
14 | public function addAnnotation(string $encoding, string $annotation): self
15 | {
16 | $this->annotations[$encoding] = $annotation;
17 |
18 | return $this;
19 | }
20 |
21 | public function getAnnotation(string $encoding): ?string
22 | {
23 | return $this->annotations[$encoding] ?? null;
24 | }
25 |
26 | /**
27 | * @return array
28 | */
29 | public function getAnnotations(): array
30 | {
31 | return $this->annotations;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Math/Element/AbstractGroupElement.php:
--------------------------------------------------------------------------------
1 | elements[] = $element;
15 |
16 | return $this;
17 | }
18 |
19 | public function remove(AbstractElement $element): self
20 | {
21 | $this->elements = array_filter($this->elements, function ($child) use ($element) {
22 | return $child != $element;
23 | });
24 |
25 | return $this;
26 | }
27 |
28 | /**
29 | * @return AbstractElement[]
30 | */
31 | public function getElements(): array
32 | {
33 | return $this->elements;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/docs/changes/0.3.0.md:
--------------------------------------------------------------------------------
1 | # 0.3.0
2 |
3 | ## Enhancements
4 |
5 | - Added support for PHP 8.4 by [@Progi1984](https://github/Progi1984) in [#18](https://github.com/PHPOffice/Math/pull/18)
6 | - MathML Writer : Write Semantics by [@codinglist](https://github/codinglist) & [@Progi1984](https://github/Progi1984) in [#19](https://github.com/PHPOffice/Math/pull/19) & [#20](https://github.com/PHPOffice/Math/pull/20)
7 |
8 | ## Bug fixes
9 |
10 | - Documentation : Added Release 0.3.0 by [@Progi1984](https://github/Progi1984) in [#17](https://github.com/PHPOffice/Math/pull/17)
11 |
12 | ## Miscellaneous
13 |
14 | - N/A
15 |
16 | ## Security fixes
17 |
18 | - Fixed XXE when processing an XML file in the MathML format by Aleksandr Zhurnakov (Positive Technologies) & [@Progi1984](https://github/Progi1984) in [GHSA-42hm-pq2f-3r7m](https://github.com/PHPOffice/Math/security/advisories/GHSA-42hm-pq2f-3r7m)
--------------------------------------------------------------------------------
/docs/usage/writers.md:
--------------------------------------------------------------------------------
1 |
2 | ## Writers
3 | ### MathML
4 | The name of the writer is `MathML`.
5 |
6 | ``` php
7 | add(new Element\Operator('+'));
15 |
16 | $writer = new MathML();
17 | $output = $writer->write($math);
18 | ```
19 |
20 | ### OfficeMathML
21 |
22 | The name of the writer is `OfficeMathML`.
23 |
24 | ``` php
25 | add(new Element\Operator('+'));
33 |
34 | $writer = new OfficeMathML();
35 | $output = $writer->write($math);
36 | ```
37 |
38 | ## Methods
39 |
40 | ### writer
41 |
42 | The method has one parameter :
43 |
44 | * `PhpOffice\Math\Math` **$math**
45 |
46 | The method returns a `string`.
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phpoffice/math",
3 | "description": "Math - Manipulate Math Formula",
4 | "keywords": ["PHP","mathml", "officemathml"],
5 | "homepage": "https://phpoffice.github.io/Math/",
6 | "type": "library",
7 | "license": "MIT",
8 | "autoload": {
9 | "psr-4": {
10 | "PhpOffice\\Math\\": "src/Math/"
11 | }
12 | },
13 | "autoload-dev": {
14 | "psr-4": {
15 | "Tests\\PhpOffice\\Math\\": "tests/Math/"
16 | }
17 | },
18 | "authors": [
19 | {
20 | "name": "Progi1984",
21 | "homepage": "https://lefevre.dev"
22 | }
23 | ],
24 | "require": {
25 | "php": "^7.1|^8.0",
26 | "ext-dom": "*",
27 | "ext-xml": "*"
28 | },
29 | "require-dev": {
30 | "phpunit/phpunit": "^7.0 || ^9.0",
31 | "phpstan/phpstan": "^0.12.88 || ^1.0.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/docs/changes/0.2.0.md:
--------------------------------------------------------------------------------
1 | # 0.2.0
2 |
3 | ## Enhancements
4 |
5 | - N/A
6 |
7 | ## Bug fixes
8 |
9 | - MathML Reader : Support for mrow in mfrac by [@Progi1984](https://github/Progi1984) in [#16](https://github.com/PHPOffice/Math/pull/16)
10 |
11 | ## Miscellaneous
12 |
13 | - Github Action : Roave BC Check by [@Progi1984](https://github/Progi1984) in [#9](https://github.com/PHPOffice/Math/pull/9)
14 | - Bump actions/checkout from 2 to 4 by [@dependabot](https://github/dependabot) in [#10](https://github.com/PHPOffice/Math/pull/10)
15 | - Bump actions/setup-python from 2 to 4 by [@dependabot](https://github/dependabot) in [#11](https://github.com/PHPOffice/Math/pull/11)
16 | - Bump actions/setup-python from 4 to 5 by [@dependabot](https://github/dependabot) in [#12](https://github.com/PHPOffice/Math/pull/12)
17 | - Bump peaceiris/actions-gh-pages from 3 to 4 by [@dependabot](https://github/dependabot) in [#13](https://github.com/PHPOffice/Math/pull/13)
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | ./tests/Math
13 |
14 |
15 |
16 |
17 | ./src
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | src
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/Math/Element/Superscript.php:
--------------------------------------------------------------------------------
1 | setBase($base);
20 | $this->setSuperscript($superscript);
21 | }
22 |
23 | public function getBase(): AbstractElement
24 | {
25 | return $this->base;
26 | }
27 |
28 | public function getSuperscript(): AbstractElement
29 | {
30 | return $this->superscript;
31 | }
32 |
33 | public function setBase(AbstractElement $element): self
34 | {
35 | $this->base = $element;
36 |
37 | return $this;
38 | }
39 |
40 | public function setSuperscript(AbstractElement $element): self
41 | {
42 | $this->superscript = $element;
43 |
44 | return $this;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Math/Element/Fraction.php:
--------------------------------------------------------------------------------
1 | setNumerator($numerator);
20 | $this->setDenominator($denominator);
21 | }
22 |
23 | public function getDenominator(): AbstractElement
24 | {
25 | return $this->denominator;
26 | }
27 |
28 | public function getNumerator(): AbstractElement
29 | {
30 | return $this->numerator;
31 | }
32 |
33 | public function setDenominator(AbstractElement $element): self
34 | {
35 | $this->denominator = $element;
36 |
37 | return $this;
38 | }
39 |
40 | public function setNumerator(AbstractElement $element): self
41 | {
42 | $this->numerator = $element;
43 |
44 | return $this;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 PhpSpreadsheet Authors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/tests/Math/Element/SemanticsTest.php:
--------------------------------------------------------------------------------
1 | assertIsArray($semantics->getAnnotations());
17 | $this->assertCount(0, $semantics->getAnnotations());
18 | }
19 |
20 | public function testAnnotation(): void
21 | {
22 | $semantics = new Semantics();
23 |
24 | $this->assertIsArray($semantics->getAnnotations());
25 | $this->assertCount(0, $semantics->getAnnotations());
26 |
27 | $this->assertInstanceOf(Semantics::class, $semantics->addAnnotation('encoding', 'content'));
28 | $this->assertEquals(['encoding' => 'content'], $semantics->getAnnotations());
29 | $this->assertCount(1, $semantics->getAnnotations());
30 |
31 | $this->assertEquals('content', $semantics->getAnnotation('encoding'));
32 | $this->assertNull($semantics->getAnnotation('notexisting'));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/docs/usage/elements/fraction.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | To create a fraction, use the `PhpOffice\Math\Element\Fraction` class.
4 |
5 | ### Methods
6 | #### getDenominator
7 |
8 | The method has no parameter.
9 |
10 | #### getNumerator
11 |
12 | The method has no parameter.
13 |
14 | #### setDenominator
15 |
16 | The method has one parameter :
17 |
18 | * `PhpOffice\Math\Element\AbstractElement` **$element**
19 |
20 | #### setNumerator
21 |
22 | The method has one parameter :
23 |
24 | * `PhpOffice\Math\Element\AbstractElement` **$element**
25 |
26 | ## Example
27 |
28 | ### Math
29 |
35 |
36 | ### XML
37 | ``` xml
38 |
44 | ```
45 |
46 | ### PHP
47 |
48 | ``` php
49 | setDenominator(new Element\Identifier('a'));
58 | $fraction->setNumerator(new Element\Numeric(3));
59 |
60 | $math->add($fraction);
61 | ```
--------------------------------------------------------------------------------
/docs/usage/elements/superscript.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | To attach a superscript to an expression, use the `PhpOffice\Math\Element\Superscript` class.
4 |
5 | ### Methods
6 | #### getBase
7 |
8 | The method has no parameter.
9 |
10 | #### getSuperscript
11 |
12 | The method has no parameter.
13 |
14 | #### setBase
15 |
16 | The method has one parameter :
17 |
18 | * `PhpOffice\Math\Element\AbstractElement` **$element**
19 |
20 | #### setSuperscript
21 |
22 | The method has one parameter :
23 |
24 | * `PhpOffice\Math\Element\AbstractElement` **$element**
25 |
26 | ## Example
27 |
28 | ### Math
29 |
35 |
36 | ### XML
37 | ``` xml
38 |
44 | ```
45 |
46 | ### PHP
47 |
48 | ``` php
49 | setBase(new Element\Identifier('X'));
58 | $superscript->setSuperscript(new Element\Numeric(2));
59 |
60 | $math->add($superscript);
61 | ```
--------------------------------------------------------------------------------
/docs/usage/readers.md:
--------------------------------------------------------------------------------
1 |
2 | ## Readers
3 | ### MathML
4 | The name of the reader is `MathML`.
5 |
6 | ``` php
7 | read(
13 | '
14 |
15 | '
18 | );
19 | ```
20 |
21 | ### OfficeMathML
22 | The name of the reader is `OfficeMathML`.
23 |
24 | ``` php
25 | read(
31 | '
32 |
33 |
34 | π
35 | 2
36 |
37 |
38 | '
39 | );
40 | ```
41 |
42 | ## Methods
43 |
44 | ### read
45 |
46 | The method has one parameter :
47 |
48 | * `string` **$content**
49 |
50 | The method returns a `PhpOffice\Math\Math` object.
--------------------------------------------------------------------------------
/docs/usage/elements/row.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | To create a row, use the `PhpOffice\Math\Element\Row` class.
4 |
5 | ### Methods
6 | #### add
7 |
8 | The method add an element to the row.
9 | The method has one parameter :
10 |
11 | * `PhpOffice\Math\Element\AbstractElement` **$element**
12 |
13 | #### getElements
14 |
15 | The method return all elements of the row.
16 |
17 | #### remove
18 |
19 | The method remove an element to the row.
20 | The method has one parameter :
21 |
22 | * `PhpOffice\Math\Element\AbstractElement` **$element**
23 |
24 | ## Example
25 |
26 | ### Math
27 |
34 |
35 | ### XML
36 | ``` xml
37 |
44 | ```
45 |
46 | ### PHP
47 |
48 | ``` php
49 | add(new Element\Numeric(1));
58 | $row->add(new Element\Operator('+'));
59 | $row->add(new Element\Identifier('K'));
60 |
61 | $math->add($row);
62 | ```
--------------------------------------------------------------------------------
/docs/changes/0.1.0.md:
--------------------------------------------------------------------------------
1 | # 0.1.0
2 |
3 | ## Enhancements
4 |
5 | - Initial version by [@Progi1984](https://github/Progi1984)
6 | - MathML Reader : Support for Semantics by [@Progi1984](https://github/Progi1984) in [#4](https://github.com/PHPOffice/Math/pull/4)
7 | - PHPUnit : Improved Unit Tests by [@Progi1984](https://github/Progi1984) in [#8](https://github.com/PHPOffice/Math/pull/8)
8 |
9 | ## Bug fixes
10 |
11 | - N/A
12 |
13 | ## Miscellaneous
14 | - Github Actions : PHPCSFixer by [@Progi1984](https://github/Progi1984) in [#1](https://github.com/PHPOffice/Math/pull/1)
15 | - Github Actions : PHPStan by [@Progi1984](https://github/Progi1984) in [#2](https://github.com/PHPOffice/Math/pull/2)
16 | - Removed dependency friendsofphp/php-cs-fixer by [@Progi1984](https://github/Progi1984) in [#3](https://github.com/PHPOffice/Math/pull/3)
17 | - Github Actions : Dependabot by [@Progi1984](https://github/Progi1984) in [#5](https://github.com/PHPOffice/Math/pull/5)
18 | - Bump actions/checkout from 2 to 4 by [@dependabot](https://github/dependabot) in [#6](https://github.com/PHPOffice/Math/pull/6)
19 | - Added documentation (MkDocs / Coverage / PHPDoc) by [@Progi1984](https://github/Progi1984) in [#7](https://github.com/PHPOffice/Math/pull/7)
--------------------------------------------------------------------------------
/tests/Math/Element/AbstractGroupElementTest.php:
--------------------------------------------------------------------------------
1 | assertIsArray($row->getElements());
17 | $this->assertCount(0, $row->getElements());
18 | }
19 |
20 | public function testAdd(): void
21 | {
22 | $identifierA = new Element\Identifier('a');
23 | $row = new Element\Row();
24 |
25 | $this->assertCount(0, $row->getElements());
26 |
27 | $this->assertInstanceOf(Element\AbstractGroupElement::class, $row->add($identifierA));
28 |
29 | $this->assertCount(1, $row->getElements());
30 | $this->assertEquals([$identifierA], $row->getElements());
31 | }
32 |
33 | public function testRemove(): void
34 | {
35 | $identifierA = new Element\Identifier('a');
36 |
37 | $row = new Element\Row();
38 | $row->add($identifierA);
39 |
40 | $this->assertCount(1, $row->getElements());
41 |
42 | $this->assertInstanceOf(Element\AbstractGroupElement::class, $row->remove($identifierA));
43 |
44 | $this->assertCount(0, $row->getElements());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | setUsingCache(true)
7 | ->setRiskyAllowed(true)
8 | ->setRules([
9 | '@Symfony' => true,
10 | 'array_indentation' => true,
11 | 'cast_spaces' => [
12 | 'space' => 'single',
13 | ],
14 | 'combine_consecutive_issets' => true,
15 | 'concat_space' => [
16 | 'spacing' => 'one',
17 | ],
18 | 'error_suppression' => [
19 | 'mute_deprecation_error' => false,
20 | 'noise_remaining_usages' => false,
21 | 'noise_remaining_usages_exclude' => [],
22 | ],
23 | 'function_to_constant' => false,
24 | 'global_namespace_import' => true,
25 | 'method_chaining_indentation' => true,
26 | 'no_alias_functions' => false,
27 | 'no_superfluous_phpdoc_tags' => false,
28 | 'non_printable_character' => [
29 | 'use_escape_sequences_in_strings' => true,
30 | ],
31 | 'phpdoc_align' => [
32 | 'align' => 'left',
33 | ],
34 | 'phpdoc_summary' => false,
35 | 'protected_to_private' => false,
36 | 'self_accessor' => false,
37 | 'yoda_style' => false,
38 | 'single_line_throw' => false,
39 | 'no_alias_language_construct_call' => false,
40 | ])
41 | ->getFinder()
42 | ->in(__DIR__)
43 | ->exclude('vendor');
44 |
45 | return $config;
--------------------------------------------------------------------------------
/tests/Math/Element/SuperscriptTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Element\Identifier::class, $superscript->getBase());
18 | $this->assertInstanceOf(Element\Identifier::class, $superscript->getSuperscript());
19 | }
20 |
21 | public function testBase(): void
22 | {
23 | $identifierA = new Element\Identifier('a');
24 | $identifierB = new Element\Identifier('b');
25 | $identifierC = new Element\Identifier('c');
26 |
27 | $superscript = new Superscript($identifierA, $identifierB);
28 |
29 | $this->assertEquals($identifierA, $superscript->getBase());
30 | $this->assertInstanceOf(Superscript::class, $superscript->setBase($identifierC));
31 | $this->assertEquals($identifierC, $superscript->getBase());
32 | }
33 |
34 | public function testSuperscript(): void
35 | {
36 | $identifierA = new Element\Identifier('a');
37 | $identifierB = new Element\Identifier('b');
38 | $identifierC = new Element\Identifier('c');
39 |
40 | $superscript = new Superscript($identifierA, $identifierB);
41 |
42 | $this->assertEquals($identifierB, $superscript->getSuperscript());
43 | $this->assertInstanceOf(Superscript::class, $superscript->setSuperscript($identifierC));
44 | $this->assertEquals($identifierC, $superscript->getSuperscript());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | deploy:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v5
15 | ### MkDocs
16 | - name: Setup Python
17 | uses: actions/setup-python@v6
18 | with:
19 | python-version: 3.x
20 | - name: Install Python Dependencies
21 | run: pip install mkdocs-material autolink-references-mkdocs-plugin
22 | - name: Build documentation
23 | run: mkdocs build --site-dir public
24 | ### PHPUnit
25 | - name: Setup PHP
26 | uses: shivammathur/setup-php@v2
27 | with:
28 | php-version: 8.1
29 | extensions: dom, xml
30 | coverage: xdebug
31 | - name: Create directory public/coverage
32 | run: mkdir ./public/coverage
33 | - name: Install PHP Dependencies
34 | run: composer install --ansi --prefer-dist --no-interaction --no-progress
35 | - name: Build Coverage Report
36 | run: XDEBUG_MODE=coverage ./vendor/bin/phpunit -c ./ --coverage-text --coverage-html ./public/coverage
37 | ### PHPDoc
38 | - name: Create directory public/docs
39 | run: mkdir ./public/docs
40 | - name: Install PhpDocumentor
41 | run: wget https://phpdoc.org/phpDocumentor.phar && chmod +x phpDocumentor.phar
42 | - name: Build Documentation
43 | run: ./phpDocumentor.phar run -d ./src -t ./public/docs
44 |
45 | ### Deploy
46 | - name: Deploy
47 | uses: peaceiris/actions-gh-pages@v4
48 | if: github.ref == 'refs/heads/master'
49 | with:
50 | github_token: ${{ secrets.GITHUB_TOKEN }}
51 | publish_dir: ./public
--------------------------------------------------------------------------------
/tests/Math/Element/FractionTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($identifierA, $fraction->getNumerator());
21 | $this->assertEquals($identifierB, $fraction->getDenominator());
22 | }
23 |
24 | public function testBase(): void
25 | {
26 | $identifierA = new Element\Identifier('a');
27 | $identifierB = new Element\Identifier('b');
28 | $identifierC = new Element\Identifier('c');
29 |
30 | $fraction = new Fraction($identifierA, $identifierB);
31 |
32 | $this->assertEquals($identifierA, $fraction->getNumerator());
33 | $this->assertInstanceOf(Fraction::class, $fraction->setNumerator($identifierC));
34 | $this->assertEquals($identifierC, $fraction->getNumerator());
35 | }
36 |
37 | public function testFraction(): void
38 | {
39 | $identifierA = new Element\Identifier('a');
40 | $identifierB = new Element\Identifier('b');
41 | $identifierC = new Element\Identifier('c');
42 |
43 | $fraction = new Fraction($identifierA, $identifierB);
44 |
45 | $this->assertEquals($identifierB, $fraction->getDenominator());
46 | $this->assertInstanceOf(Fraction::class, $fraction->setDenominator($identifierC));
47 | $this->assertEquals($identifierC, $fraction->getDenominator());
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/docs/usage/elements/semantics.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | To create a semantics, use the `PhpOffice\Math\Element\Semantics` class.
4 |
5 | ### Methods
6 | #### add
7 |
8 | The method add an element to the `semantics` element.
9 | The method has one parameter :
10 |
11 | * `PhpOffice\Math\Element\AbstractElement` **$element**
12 |
13 | #### addAnnotation
14 |
15 | The method add an annotation to the `semantics` element.
16 | The method has two parameters :
17 |
18 | * `string` **$encoding**
19 | * `string` **$annotation**
20 |
21 | #### getAnnotation
22 |
23 | The method return an annotation based on its encoding.
24 | The method has one parameter :
25 |
26 | * `string` **$encoding**
27 |
28 | #### getAnnotations
29 |
30 | The method return alls annotation of the `semantics` element.
31 | The method has no parameter.
32 |
33 | #### getElements
34 |
35 | The method return all elements of the `semantics` element.
36 |
37 | #### remove
38 |
39 | The method remove an element to the `semantics` element.
40 | The method has one parameter :
41 |
42 | * `PhpOffice\Math\Element\AbstractElement` **$element**
43 |
44 | ## Example
45 |
46 | ### Math
47 |
54 |
55 | ### XML
56 | ``` xml
57 |
64 | ```
65 |
66 | ### PHP
67 |
68 | ``` php
69 | add(new Element\Identifier('y'));
78 | $semantics->addAnnotation('application/x-tex', ' y ');
79 |
80 | $math->add($semantics);
81 | ```
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Math
2 | site_url: https://phpoffice.github.io/Math
3 | repo_url: https://github.com/PHPOffice/Math
4 | repo_name: PHPOffice/Math
5 | edit_uri: edit/master/docs/
6 |
7 | ## Theme
8 | theme:
9 | name: material
10 | palette:
11 | primary: grey
12 | features:
13 | - search.highlight
14 | - search.suggest
15 |
16 | ## Plugins
17 | plugins:
18 | - search
19 |
20 | ## Config
21 | extra:
22 | generator: false
23 | extra_javascript:
24 | - assets/mathjax.js
25 | - https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?version=3.111.0&features=es6
26 | - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
27 | markdown_extensions:
28 | ## Syntax highlighting
29 | - pymdownx.highlight
30 | - pymdownx.superfences
31 | ## Support for Math
32 | - pymdownx.arithmatex:
33 | generic: true
34 | ## Support for emojis
35 | - pymdownx.emoji:
36 | emoji_index: !!python/name:materialx.emoji.twemoji
37 | emoji_generator: !!python/name:materialx.emoji.to_svg
38 | ## Support for call-outs
39 | - admonition
40 | - pymdownx.details
41 | use_directory_urls: false
42 |
43 | ## Navigation
44 | nav:
45 | - Introduction: 'index.md'
46 | - Install: 'install.md'
47 | - Usage:
48 | - Elements:
49 | - Fraction: 'usage/elements/fraction.md'
50 | - Identifier: 'usage/elements/identifier.md'
51 | - Numeric: 'usage/elements/numeric.md'
52 | - Operator: 'usage/elements/operator.md'
53 | - Row: 'usage/elements/row.md'
54 | - Semantics: 'usage/elements/semantics.md'
55 | - Superscript: 'usage/elements/superscript.md'
56 | - Readers: 'usage/readers.md'
57 | - Writers: 'usage/writers.md'
58 | - Credits: 'credits.md'
59 | - Releases:
60 | - '0.3.0 (WIP)': 'changes/0.3.0.md'
61 | - '0.2.0': 'changes/0.2.0.md'
62 | - '0.1.0': 'changes/0.1.0.md'
63 | - Developers:
64 | - 'Coveralls': 'https://coveralls.io/github/PHPOffice/Math'
65 | - 'Code Coverage': 'coverage/index.html'
66 | - 'PHPDoc': 'docs/index.html'
67 |
--------------------------------------------------------------------------------
/tests/Math/Writer/WriterTestCase.php:
--------------------------------------------------------------------------------
1 | loadXML($content);
17 | $xmlSource = $dom->saveXML();
18 |
19 | if (is_string($xmlSource)) {
20 | $dom->loadXML($xmlSource);
21 | $dom->schemaValidate(dirname(__DIR__, 2) . '/resources/schema/mathml3/mathml3.xsd');
22 |
23 | $error = libxml_get_last_error();
24 | if ($error instanceof LibXMLError) {
25 | $this->failXmlError($error, $xmlSource);
26 | } else {
27 | $this->assertTrue(true);
28 | }
29 |
30 | return;
31 | }
32 |
33 | $this->fail(sprintf('The XML is not valid : %s', $content));
34 | }
35 |
36 | /**
37 | * @param array $params
38 | */
39 | protected function failXmlError(LibXMLError $error, string $source, array $params = []): void
40 | {
41 | switch ($error->level) {
42 | case LIBXML_ERR_WARNING:
43 | $errorType = 'warning';
44 | break;
45 | case LIBXML_ERR_ERROR:
46 | $errorType = 'error';
47 | break;
48 | case LIBXML_ERR_FATAL:
49 | $errorType = 'fatal';
50 | break;
51 | default:
52 | $errorType = 'Error';
53 | break;
54 | }
55 | $errorLine = (int) $error->line;
56 | $contents = explode("\n", $source);
57 | $lines = [];
58 | if (isset($contents[$errorLine - 2])) {
59 | $lines[] = '>> ' . $contents[$errorLine - 2];
60 | }
61 | if (isset($contents[$errorLine - 1])) {
62 | $lines[] = '>>> ' . $contents[$errorLine - 1];
63 | }
64 | if (isset($contents[$errorLine])) {
65 | $lines[] = '>> ' . $contents[$errorLine];
66 | }
67 | $paramStr = '';
68 | if (!empty($params)) {
69 | $paramStr .= "\n" . ' - Parameters :' . "\n";
70 | foreach ($params as $key => $val) {
71 | $paramStr .= ' - ' . $key . ' : ' . $val . "\n";
72 | }
73 | }
74 | $this->fail(sprintf(
75 | "Validation %s :\n - - Line : %s\n - Message : %s - Lines :\n%s%s",
76 | $errorType,
77 | $error->line,
78 | $error->message,
79 | implode(PHP_EOL, $lines),
80 | $paramStr
81 | ));
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/tests/Math/Writer/OfficeMathMLTest.php:
--------------------------------------------------------------------------------
1 | add($fraction);
23 |
24 | $writer = new OfficeMathML();
25 | $output = $writer->write($math);
26 |
27 | $expected = ''
28 | . ''
29 | . ''
30 | . 'π'
31 | . '2'
32 | . ''
33 | . ''
34 | . '';
35 | $this->assertEquals($expected, $output);
36 | }
37 |
38 | public function testWriteRow(): void
39 | {
40 | $math = new Math();
41 |
42 | $row = new Element\Row();
43 | $math->add($row);
44 |
45 | $row->add(new Element\Identifier('x'));
46 |
47 | $writer = new OfficeMathML();
48 | $output = $writer->write($math);
49 |
50 | $expected = ''
51 | . ''
52 | . 'x'
53 | . ''
54 | . '';
55 | $this->assertEquals($expected, $output);
56 | }
57 |
58 | public function testWriteNotImplemented(): void
59 | {
60 | $this->expectException(NotImplementedException::class);
61 | if (method_exists($this, 'expectExceptionMessageRegExp')) {
62 | $this->expectExceptionMessageRegExp('/PhpOffice\\\Math\\\Writer\\\OfficeMathML::getElementTagName : The element of the class/');
63 | $this->expectExceptionMessageRegExp('/has no tag name/');
64 | } else {
65 | // @phpstan-ignore-next-line
66 | $this->expectExceptionMessageMatches('/PhpOffice\\\Math\\\Writer\\\OfficeMathML::getElementTagName : The element of the class/');
67 | // @phpstan-ignore-next-line
68 | $this->expectExceptionMessageMatches('/has no tag name/');
69 | }
70 |
71 | $math = new Math();
72 |
73 | $object = new class extends Element\AbstractElement {};
74 | $math->add($object);
75 |
76 | $writer = new OfficeMathML();
77 | $output = $writer->write($math);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Math/Reader/Security/XmlScanner.php:
--------------------------------------------------------------------------------
1 | $split_length
35 | * @param string|null $encoding
36 | *
37 | * @return array|bool|null
38 | */
39 | public static function mb_str_split(string $string, int $split_length = 1, ?string $encoding = null)
40 | {
41 | if (extension_loaded('mbstring')) {
42 | if (function_exists('mb_str_split')) {
43 | return mb_str_split($string, $split_length, $encoding);
44 | }
45 | }
46 | // @phpstan-ignore-next-line
47 | if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) {
48 | trigger_error('mb_str_split() expects parameter 1 to be string, ' . \gettype($string) . ' given', \E_USER_WARNING);
49 |
50 | return null;
51 | }
52 |
53 | // @phpstan-ignore-next-line
54 | if (1 > $split_length = (int) $split_length) {
55 | trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING);
56 |
57 | return false;
58 | }
59 |
60 | if (null === $encoding) {
61 | $encoding = mb_internal_encoding();
62 | }
63 |
64 | if ('UTF-8' === $encoding || \in_array(strtoupper($encoding), ['UTF-8', 'UTF8'], true)) {
65 | return preg_split("/(.{{$split_length}})/u", $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
66 | }
67 |
68 | $result = [];
69 | $length = mb_strlen($string, $encoding);
70 |
71 | for ($i = 0; $i < $length; $i += $split_length) {
72 | $result[] = mb_substr($string, $i, $split_length, $encoding);
73 | }
74 |
75 | return $result;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Math/Writer/OfficeMathML.php:
--------------------------------------------------------------------------------
1 | output = new XMLWriter();
23 | $this->output->openMemory();
24 | $this->output->startElement('m:oMathPara');
25 | $this->output->writeAttribute('xmlns:m', 'http://schemas.openxmlformats.org/officeDocument/2006/math');
26 | $this->output->startElement('m:oMath');
27 |
28 | foreach ($math->getElements() as $element) {
29 | $this->writeElementItem($element);
30 | }
31 |
32 | $this->output->endElement();
33 | $this->output->endElement();
34 |
35 | return $this->output->outputMemory();
36 | }
37 |
38 | protected function writeElementItem(Element\AbstractElement $element): void
39 | {
40 | // Element\Row
41 | if ($element instanceof Element\Row) {
42 | foreach ($element->getElements() as $childElement) {
43 | $this->writeElementItem($childElement);
44 | }
45 |
46 | return;
47 | }
48 |
49 | // Element\Fraction
50 | if ($element instanceof Element\Fraction) {
51 | $this->output->startElement($this->getElementTagName($element));
52 | $this->output->startElement('m:num');
53 | $this->writeElementItem($element->getNumerator());
54 | $this->output->endElement();
55 | $this->output->startElement('m:den');
56 | $this->writeElementItem($element->getDenominator());
57 | $this->output->endElement();
58 | $this->output->endElement();
59 |
60 | return;
61 | }
62 |
63 | if ($element instanceof Element\Identifier
64 | || $element instanceof Element\Numeric
65 | || $element instanceof Element\Operator) {
66 | $this->output->startElement('m:r');
67 | $this->output->startElement('m:t');
68 | $this->output->text((string) $element->getValue());
69 | $this->output->endElement();
70 | $this->output->endElement();
71 |
72 | return;
73 | }
74 |
75 | // Check if managed
76 | $this->getElementTagName($element);
77 | }
78 |
79 | protected function getElementTagName(Element\AbstractElement $element): string
80 | {
81 | // Group
82 | if ($element instanceof Element\AbstractGroupElement) {
83 | /*
84 | throw new NotImplementedException(sprintf(
85 | '%s : The element of the class `%s` has no tag name',
86 | __METHOD__,
87 | get_class($element)
88 | ));
89 | */
90 | }
91 |
92 | if ($element instanceof Element\Fraction) {
93 | return 'm:f';
94 | }
95 |
96 | throw new NotImplementedException(sprintf(
97 | '%s : The element of the class `%s` has no tag name',
98 | __METHOD__,
99 | get_class($element)
100 | ));
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Math
2 |
3 | [](https://packagist.org/packages/phpoffice/math)
4 | [](https://coveralls.io/github/PHPOffice/Math?branch=master)
5 | [](https://packagist.org/packages/phpoffice/math)
6 | [](https://packagist.org/packages/phpoffice/math)
7 | [](https://github.com/PHPOffice/Math/actions/workflows/php.yml)
8 |
9 | Math is a library written in pure PHP that provides a set of classes to manipulate different formula file formats, i.e. [MathML](https://en.wikipedia.org/wiki/MathML) and [Office MathML (OOML)](https://en.wikipedia.org/wiki/Office_Open_XML_file_formats#Office_MathML_(OMML)).
10 |
11 | Math is an open source project licensed under the terms of [MIT](https://github.com/PHPOffice/Math/blob/master/LICENCE). Math is aimed to be a high quality software product by incorporating [continuous integration and unit testing](https://github.com/PHPOffice/Math/actions/workflows/php.yml). You can learn more about Math by reading this Developers'Documentation and the [API Documentation](http://phpoffice.github.io/Math/docs/)
12 |
13 | If you have any questions, please ask on [StackOverFlow](https://stackoverflow.com/questions/tagged/phpoffice-math)
14 |
15 | Read more about Math:
16 |
17 | - [Features](#features)
18 | - [Requirements](#requirements)
19 | - [Installation](#installation)
20 | - [Contributing](#contributing)
21 | - [Developers' Documentation](https://phpoffice.github.io/Math/)
22 |
23 | ## Features
24 |
25 | - Insert elements:
26 |
27 | * Basic :
28 |
29 | * Identifier :
30 | * Operator :
31 | * Numeric :
32 |
33 | * Simple :
34 |
35 | * Fraction :
36 | * Superscript :
37 |
38 | * Architectural :
39 |
40 | * Row
41 | * Semantics
42 |
43 | - Support
44 |
45 | * MathML
46 | * OfficeMathML
47 | ## Requirements
48 |
49 | Math requires the following:
50 |
51 | - PHP 7.1+
52 | - [XML Parser extension](http://www.php.net/manual/en/xml.installation.php)
53 | - [XMLWriter extension](http://php.net/manual/en/book.xmlwriter.php)
54 |
55 | ## Installation
56 |
57 | Math is installed via [Composer](https://getcomposer.org/).
58 | To [add a dependency](https://getcomposer.org/doc/04-schema.md#package-links) to Math in your project, either
59 |
60 | Run the following to use the latest stable version
61 | ```sh
62 | composer require phpoffice/math
63 | ```
64 | or if you want the latest unreleased version
65 | ```sh
66 | composer require phpoffice/math:dev-master
67 | ```
68 |
69 | ## Contributing
70 |
71 | We welcome everyone to contribute to PHPWord. Below are some of the things that you can do to contribute.
72 |
73 | - [Fork us](https://github.com/PHPOffice/Math/fork) and [request a pull](https://github.com/PHPOffice/Math/pulls) to the [master](https://github.com/PHPOffice/Math/tree/master) branch.
74 | - Submit [bug reports or feature requests](https://github.com/PHPOffice/Math/issues) to GitHub.
75 | - Follow [@PHPOffice](https://twitter.com/PHPOffice) on Twitter.
76 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | #
2 |
3 | Math is a library written in pure PHP that provides a set of classes to manipulate different formula file formats, i.e. [MathML](https://en.wikipedia.org/wiki/MathML) and [Office MathML (OOML)](https://en.wikipedia.org/wiki/Office_Open_XML_file_formats#Office_MathML_(OMML)).
4 |
5 | Math is an open source project licensed under the terms of [MIT](https://github.com/PHPOffice/Math/blob/master/LICENCE). Math is aimed to be a high quality software product by incorporating [continuous integration and unit testing](https://github.com/PHPOffice/Math/actions/workflows/php.yml). You can learn more about Math by reading this Developers'Documentation and the [API Documentation](http://phpoffice.github.io/Math/docs/develop/)
6 |
7 | ## Features
8 |
9 | - Insert elements:
10 |
11 | * Basic :
12 |
13 | * Identifier :
14 | * Operator :
15 | * Numeric :
16 |
17 | * Simple :
18 |
19 | * Fraction :
20 | * Superscript :
21 |
22 | * Architectural :
23 |
24 | * Row
25 | * Semantics
26 |
27 | ## Support
28 |
29 | ### Readers
30 |
31 | | Features | | MathML | Office MathML |
32 | |---------------------------|----------------------|:----------------:|:----------------:|
33 | | **Basic** | Identifier | :material-check: | :material-check: |
34 | | | Operator | :material-check: | :material-check: |
35 | | | Numeric | :material-check: | :material-check: |
36 | | **Simple** | Fraction | :material-check: | :material-check: |
37 | | | Superscript | :material-check: | |
38 | | **Architectural** | Row | :material-check: | |
39 | | | Semantics | :material-check: | |
40 |
41 | ### Writers
42 |
43 | | Features | | MathML | Office MathML |
44 | |---------------------------|----------------------|:----------------:|:----------------:|
45 | | **Basic** | Identifier | :material-check: | :material-check: |
46 | | | Operator | :material-check: | :material-check: |
47 | | | Numeric | :material-check: | :material-check: |
48 | | **Simple** | Fraction | :material-check: | :material-check: |
49 | | | Superscript | :material-check: | |
50 | | **Architectural** | Row | :material-check: | :material-check: |
51 | | | Semantics | :material-check: | |
52 |
53 | ## Contributing
54 |
55 | We welcome everyone to contribute to Math. Below are some of the things that you can do to contribute:
56 |
57 | - [Fork us](https://github.com/PHPOffice/Math/fork) and [request a pull](https://github.com/PHPOffice/Math/pulls) to the [master](https://github.com/PHPOffice/Math/tree/master) branch
58 | - Submit [bug reports or feature requests](https://github.com/PHPOffice/Math/issues) to GitHub
59 | - Follow [@PHPOffice](https://twitter.com/PHPOffice) on Twitter
60 |
--------------------------------------------------------------------------------
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: PHP
2 | on:
3 | push:
4 | branches:
5 | - master
6 | pull_request:
7 |
8 | jobs:
9 | php-cs-fixer:
10 | name: PHP CS Fixer
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Setup PHP
14 | uses: shivammathur/setup-php@v2
15 | with:
16 | php-version: '7.4'
17 | extensions: xml
18 |
19 | - uses: actions/checkout@v5
20 |
21 | - name: Validate composer config
22 | run: composer validate --strict
23 |
24 | - name: Composer Install
25 | run: composer global require friendsofphp/php-cs-fixer
26 |
27 | - name: Add environment path
28 | run: export PATH="$PATH:$HOME/.composer/vendor/bin"
29 |
30 | - name: Run PHPCSFixer
31 | run: php-cs-fixer fix --dry-run --diff
32 |
33 | phpstan:
34 | name: PHP Static Analysis
35 | runs-on: ubuntu-latest
36 | strategy:
37 | fail-fast: false
38 | matrix:
39 | php:
40 | - '7.1'
41 | - '7.2'
42 | - '7.3'
43 | - '7.4'
44 | - '8.0'
45 | - '8.1'
46 | - '8.2'
47 | - '8.3'
48 | - '8.4'
49 | steps:
50 | - name: Setup PHP
51 | uses: shivammathur/setup-php@v2
52 | with:
53 | php-version: ${{ matrix.php }}
54 | extensions: xml
55 |
56 | - uses: actions/checkout@v5
57 |
58 | - name: Composer Install
59 | run: composer install --ansi --prefer-dist --no-interaction --no-progress
60 |
61 | - name: Run phpstan
62 | run: ./vendor/bin/phpstan analyse -c phpstan.neon.dist
63 |
64 | phpunit:
65 | name: PHPUnit
66 | runs-on: ubuntu-latest
67 | strategy:
68 | fail-fast: false
69 | matrix:
70 | php:
71 | - '7.1'
72 | - '7.2'
73 | - '7.3'
74 | - '7.4'
75 | - '8.0'
76 | - '8.1'
77 | - '8.2'
78 | - '8.3'
79 | - '8.4'
80 | steps:
81 | - name: Setup PHP
82 | uses: shivammathur/setup-php@v2
83 | with:
84 | php-version: ${{ matrix.php }}
85 | extensions: xml
86 | coverage: ${{ (matrix.php == '8.1') && 'xdebug' || 'none' }}
87 |
88 | - uses: actions/checkout@v5
89 |
90 | - name: Install dependencies
91 | run: composer install --ansi --prefer-dist --no-interaction --no-progress
92 |
93 | - name: Run PHPUnit
94 | if: matrix.php != '8.1'
95 | run: ./vendor/bin/phpunit -c phpunit.xml.dist
96 |
97 | - name: Run PHPUnit (w CodeCoverage)
98 | if: matrix.php == '8.1'
99 | run: XDEBUG_MODE=coverage ./vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover build/clover.xml
100 |
101 | - name: Upload coverage results to Coveralls
102 | if: matrix.php == '8.1'
103 | env:
104 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
105 | run: |
106 | wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar
107 | chmod +x php-coveralls.phar
108 | php php-coveralls.phar --coverage_clover=build/clover.xml --json_path=build/coveralls-upload.json -vvv
109 |
110 | roave-backwards-compatibility-check:
111 | name: Roave Backwards Compatibility Check
112 | runs-on: ubuntu-latest
113 | steps:
114 | - uses: actions/checkout@v5
115 | with:
116 | fetch-depth: 0
117 | - name: "Check for BC breaks"
118 | run: docker run -u $(id -u) -v $(pwd):/app nyholm/roave-bc-check-ga
119 |
--------------------------------------------------------------------------------
/tests/resources/schema/mathml3/mathml3-common.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/tests/Math/Writer/MathMLTest.php:
--------------------------------------------------------------------------------
1 | add($row);
22 |
23 | $row->add(new Element\Identifier('a'));
24 | $row->add(clone $opTimes);
25 |
26 | $superscript = new Element\Superscript(
27 | new Element\Identifier('x'),
28 | new Element\Numeric(2)
29 | );
30 | $row->add($superscript);
31 |
32 | $row->add(new Element\Operator('+'));
33 |
34 | $row->add(new Element\Identifier('b'));
35 | $row->add(clone $opTimes);
36 | $row->add(new Element\Identifier('x'));
37 |
38 | $row->add(new Element\Operator('+'));
39 |
40 | $row->add(new Element\Identifier('c'));
41 |
42 | $writer = new MathML();
43 | $output = $writer->write($math);
44 |
45 | $expected = ''
46 | . PHP_EOL
47 | . ''
48 | . ''
52 | . PHP_EOL;
53 | $this->assertEquals($expected, $output);
54 | $this->assertIsSchemaMathMLValid($output);
55 | }
56 |
57 | public function testWriteFraction(): void
58 | {
59 | $math = new Math();
60 |
61 | $fraction = new Element\Fraction(
62 | new Element\Identifier('π'),
63 | new Element\Numeric(2)
64 | );
65 | $math->add($fraction);
66 |
67 | $writer = new MathML();
68 | $output = $writer->write($math);
69 |
70 | $expected = ''
71 | . PHP_EOL
72 | . ''
73 | . ''
78 | . PHP_EOL;
79 | $this->assertEquals($expected, $output);
80 | $this->assertIsSchemaMathMLValid($output);
81 | }
82 |
83 | public function testWriteSemantics(): void
84 | {
85 | $opTimes = new Element\Operator('⁢');
86 |
87 | $math = new Math();
88 |
89 | $semantics = new Element\Semantics();
90 | $semantics->add(new Element\Identifier('y'));
91 | $semantics->addAnnotation('application/x-tex', ' y ');
92 |
93 | $math->add($semantics);
94 |
95 | $writer = new MathML();
96 | $output = $writer->write($math);
97 |
98 | $expected = ''
99 | . PHP_EOL
100 | . ''
101 | . ''
107 | . PHP_EOL;
108 | $this->assertEquals($expected, $output);
109 | $this->assertIsSchemaMathMLValid($output);
110 | }
111 |
112 | public function testWriteNotImplemented(): void
113 | {
114 | $this->expectException(NotImplementedException::class);
115 | if (method_exists($this, 'expectExceptionMessageRegExp')) {
116 | $this->expectExceptionMessageRegExp('/PhpOffice\\\Math\\\Writer\\\MathML::getElementTagName : The element of the class/');
117 | $this->expectExceptionMessageRegExp('/has no tag name/');
118 | } else {
119 | // @phpstan-ignore-next-line
120 | $this->expectExceptionMessageMatches('/PhpOffice\\\Math\\\Writer\\\MathML::getElementTagName : The element of the class/');
121 | // @phpstan-ignore-next-line
122 | $this->expectExceptionMessageMatches('/has no tag name/');
123 | }
124 |
125 | $math = new Math();
126 |
127 | $object = new class extends Element\AbstractElement {};
128 | $math->add($object);
129 |
130 | $writer = new MathML();
131 | $output = $writer->write($math);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/Math/Reader/OfficeMathML.php:
--------------------------------------------------------------------------------
1 | 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 | }
134 |
--------------------------------------------------------------------------------
/src/Math/Reader/MathML.php:
--------------------------------------------------------------------------------
1 | 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 | '',
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 | }
150 |
--------------------------------------------------------------------------------
/src/Math/Writer/MathML.php:
--------------------------------------------------------------------------------
1 | output = new XMLWriter();
23 | $this->output->openMemory();
24 | $this->output->startDocument('1.0', 'UTF-8');
25 | $this->output->writeDtd('math', '-//W3C//DTD MathML 2.0//EN', 'http://www.w3.org/Math/DTD/mathml2/mathml2.dtd');
26 | $this->output->startElement('math');
27 | $this->output->writeAttribute('xmlns', 'http://www.w3.org/1998/Math/MathML');
28 |
29 | foreach ($math->getElements() as $element) {
30 | $this->writeElementItem($element);
31 | }
32 |
33 | $this->output->endElement();
34 | $this->output->endDocument();
35 |
36 | return $this->output->outputMemory();
37 | }
38 |
39 | protected function writeElementItem(Element\AbstractElement $element): void
40 | {
41 | $tagName = $this->getElementTagName($element);
42 |
43 | // Element\AbstractGroupElement
44 | if ($element instanceof Element\Semantics) {
45 | $this->output->startElement($tagName);
46 | // Write elements
47 | foreach ($element->getElements() as $childElement) {
48 | $this->writeElementItem($childElement);
49 | }
50 |
51 | // Write annotations
52 | foreach ($element->getAnnotations() as $encoding => $annotation) {
53 | $this->output->startElement('annotation');
54 | $this->output->writeAttribute('encoding', $encoding);
55 | $this->output->text($annotation);
56 | $this->output->endElement();
57 | }
58 | $this->output->endElement();
59 |
60 | return;
61 | }
62 |
63 | // Element\AbstractGroupElement
64 | if ($element instanceof Element\AbstractGroupElement) {
65 | $this->output->startElement($tagName);
66 | foreach ($element->getElements() as $childElement) {
67 | $this->writeElementItem($childElement);
68 | }
69 | $this->output->endElement();
70 |
71 | return;
72 | }
73 |
74 | // Element\Superscript
75 | if ($element instanceof Element\Superscript) {
76 | $this->output->startElement($tagName);
77 | $this->writeElementItem($element->getBase());
78 | $this->writeElementItem($element->getSuperscript());
79 | $this->output->endElement();
80 |
81 | return;
82 | }
83 |
84 | // Element\Fraction
85 | if ($element instanceof Element\Fraction) {
86 | $this->output->startElement($tagName);
87 | $this->writeElementItem($element->getNumerator());
88 | $this->writeElementItem($element->getDenominator());
89 | $this->output->endElement();
90 |
91 | return;
92 | }
93 |
94 | if ($element instanceof Element\Identifier
95 | || $element instanceof Element\Numeric
96 | || $element instanceof Element\Operator) {
97 | $this->output->startElement($tagName);
98 | $this->output->text((string) $element->getValue());
99 | $this->output->endElement();
100 |
101 | return;
102 | }
103 |
104 | /*
105 | throw new NotImplementedException(sprintf(
106 | '%s : The class `%s` is not implemented',
107 | __METHOD__,
108 | get_class($element)
109 | ));
110 | */
111 | }
112 |
113 | protected function getElementTagName(Element\AbstractElement $element): string
114 | {
115 | // Group
116 | if ($element instanceof Element\Row) {
117 | return 'mrow';
118 | }
119 | if ($element instanceof Element\AbstractGroupElement) {
120 | /*
121 | throw new NotImplementedException(sprintf(
122 | '%s : The element of the class `%s` has no tag name',
123 | __METHOD__,
124 | get_class($element)
125 | ));
126 | */
127 | }
128 |
129 | if ($element instanceof Element\Superscript) {
130 | return 'msup';
131 | }
132 | if ($element instanceof Element\Fraction) {
133 | return 'mfrac';
134 | }
135 | if ($element instanceof Element\Identifier) {
136 | return 'mi';
137 | }
138 | if ($element instanceof Element\Numeric) {
139 | return 'mn';
140 | }
141 | if ($element instanceof Element\Operator) {
142 | return 'mo';
143 | }
144 | if ($element instanceof Element\Semantics) {
145 | return 'semantics';
146 | }
147 |
148 | throw new NotImplementedException(sprintf(
149 | '%s : The element of the class `%s` has no tag name',
150 | __METHOD__,
151 | get_class($element)
152 | ));
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/tests/resources/schema/mathml3/mathml3-strict-content.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
--------------------------------------------------------------------------------
/tests/Math/Reader/OfficeMathMLTest.php:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | 2
22 | π
23 |
24 |
25 | ';
26 |
27 | $reader = new OfficeMathML();
28 | $math = $reader->read($content);
29 | $this->assertInstanceOf(Math::class, $math);
30 |
31 | $elements = $math->getElements();
32 | $this->assertCount(1, $elements);
33 | $this->assertInstanceOf(Element\Row::class, $elements[0]);
34 |
35 | /** @var Element\Row $element */
36 | $element = $elements[0];
37 | $subElements = $element->getElements();
38 | $this->assertCount(1, $subElements);
39 | $this->assertInstanceOf(Element\Fraction::class, $subElements[0]);
40 |
41 | /** @var Element\Fraction $subElement */
42 | $subElement = $subElements[0];
43 |
44 | /** @var Element\Identifier $numerator */
45 | $numerator = $subElement->getNumerator();
46 | $this->assertInstanceOf(Element\Numeric::class, $numerator);
47 | $this->assertEquals(2, $numerator->getValue());
48 |
49 | /** @var Element\Numeric $denominator */
50 | $denominator = $subElement->getDenominator();
51 | $this->assertInstanceOf(Element\Identifier::class, $denominator);
52 | $this->assertEquals('π', $denominator->getValue());
53 | }
54 |
55 | public function testReadWithWTag(): void
56 | {
57 | $content = '
58 |
59 |
60 |
61 |
62 | π
63 |
64 |
65 |
66 |
67 |
68 | 2
69 |
70 |
71 |
72 |
73 |
74 | +
75 |
76 |
77 |
78 | a
79 |
80 |
81 |
82 | ∗
83 |
84 |
85 |
86 | 2
87 |
88 | ';
89 |
90 | $reader = new OfficeMathML();
91 | $math = $reader->read($content);
92 | $this->assertInstanceOf(Math::class, $math);
93 |
94 | $elements = $math->getElements();
95 | $this->assertCount(5, $elements);
96 |
97 | /** @var Element\Fraction $element */
98 | $element = $elements[0];
99 | $this->assertInstanceOf(Element\Fraction::class, $element);
100 | /** @var Element\Identifier $numerator */
101 | $numerator = $element->getNumerator();
102 | $this->assertInstanceOf(Element\Identifier::class, $numerator);
103 | $this->assertEquals('π', $numerator->getValue());
104 | /** @var Element\Numeric $denominator */
105 | $denominator = $element->getDenominator();
106 | $this->assertInstanceOf(Element\Numeric::class, $denominator);
107 | $this->assertEquals(2, $denominator->getValue());
108 |
109 | /** @var Element\Operator $element */
110 | $element = $elements[1];
111 | $this->assertInstanceOf(Element\Operator::class, $element);
112 | $this->assertEquals('+', $element->getValue());
113 |
114 | /** @var Element\Identifier $element */
115 | $element = $elements[2];
116 | $this->assertInstanceOf(Element\Identifier::class, $element);
117 | $this->assertEquals('a', $element->getValue());
118 |
119 | /** @var Element\Operator $element */
120 | $element = $elements[3];
121 | $this->assertInstanceOf(Element\Operator::class, $element);
122 | $this->assertEquals('∗', $element->getValue());
123 |
124 | /** @var Element\Numeric $element */
125 | $element = $elements[4];
126 | $this->assertInstanceOf(Element\Numeric::class, $element);
127 | $this->assertEquals(2, $element->getValue());
128 | }
129 |
130 | public function testReadFractionNoNumerator(): void
131 | {
132 | $this->expectException(InvalidInputException::class);
133 | $this->expectExceptionMessage('PhpOffice\Math\Reader\OfficeMathML::getElement : The tag `m:f` has no numerator defined');
134 |
135 | $content = '
136 |
137 |
138 | 2
139 |
140 |
141 | ';
142 |
143 | $reader = new OfficeMathML();
144 | $math = $reader->read($content);
145 | }
146 |
147 | public function testReadFractionNoDenominator(): void
148 | {
149 | $this->expectException(InvalidInputException::class);
150 | $this->expectExceptionMessage('PhpOffice\Math\Reader\OfficeMathML::getElement : The tag `m:f` has no denominator defined');
151 |
152 | $content = '
153 |
154 |
155 | π
156 |
157 |
158 | ';
159 |
160 | $reader = new OfficeMathML();
161 | $math = $reader->read($content);
162 | }
163 |
164 | public function testReadBasicNoText(): void
165 | {
166 | $this->expectException(InvalidInputException::class);
167 | $this->expectExceptionMessage('PhpOffice\Math\Reader\OfficeMathML::getElement : The tag `m:r` has no tag `m:t` defined');
168 |
169 | $content = '
170 |
171 |
172 | a
173 |
174 |
175 | ';
176 |
177 | $reader = new OfficeMathML();
178 | $math = $reader->read($content);
179 | }
180 |
181 | public function testReadNotImplemented(): void
182 | {
183 | $this->expectException(NotImplementedException::class);
184 | $this->expectExceptionMessage('PhpOffice\Math\Reader\OfficeMathML::getElement : The tag `m:mnotexisting` is not implemented');
185 |
186 | $content = '
187 |
188 |
189 | π
190 |
191 |
192 | ';
193 |
194 | $reader = new OfficeMathML();
195 | $math = $reader->read($content);
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/tests/Math/Reader/MathMLTest.php:
--------------------------------------------------------------------------------
1 |
20 |
21 | ';
28 |
29 | $reader = new MathML();
30 | $math = $reader->read($content);
31 | $this->assertInstanceOf(Math::class, $math);
32 |
33 | $elements = $math->getElements();
34 | $this->assertCount(1, $elements);
35 | $this->assertInstanceOf(Element\Row::class, $elements[0]);
36 |
37 | /** @var Element\Row $element */
38 | $element = $elements[0];
39 | $subElements = $element->getElements();
40 | $this->assertCount(9, $subElements);
41 |
42 | /** @var Element\Identifier $subElement */
43 | $subElement = $subElements[0];
44 | $this->assertInstanceOf(Element\Identifier::class, $subElement);
45 | $this->assertEquals('a', $subElement->getValue());
46 |
47 | /** @var Element\Identifier $subElement */
48 | $subElement = $subElements[1];
49 | $this->assertInstanceOf(Element\Operator::class, $subElement);
50 | $this->assertEquals('InvisibleTimes', $subElement->getValue());
51 |
52 | /** @var Element\Superscript $subElement */
53 | $subElement = $subElements[2];
54 | $this->assertInstanceOf(Element\Superscript::class, $subElements[2]);
55 |
56 | /** @var Element\Identifier $base */
57 | $base = $subElement->getBase();
58 | $this->assertInstanceOf(Element\Identifier::class, $base);
59 | $this->assertEquals('x', $base->getValue());
60 |
61 | /** @var Element\Numeric $superscript */
62 | $superscript = $subElement->getSuperscript();
63 | $this->assertInstanceOf(Element\Numeric::class, $superscript);
64 | $this->assertEquals(2, $superscript->getValue());
65 |
66 | /** @var Element\Operator $subElement */
67 | $subElement = $subElements[3];
68 | $this->assertInstanceOf(Element\Operator::class, $subElement);
69 | $this->assertEquals('+', $subElement->getValue());
70 |
71 | /** @var Element\Identifier $subElement */
72 | $subElement = $subElements[4];
73 | $this->assertInstanceOf(Element\Identifier::class, $subElement);
74 | $this->assertEquals('b', $subElement->getValue());
75 |
76 | /** @var Element\Operator $subElement */
77 | $subElement = $subElements[5];
78 | $this->assertInstanceOf(Element\Operator::class, $subElement);
79 | $this->assertEquals('InvisibleTimes', $subElement->getValue());
80 |
81 | /** @var Element\Identifier $subElement */
82 | $subElement = $subElements[6];
83 | $this->assertInstanceOf(Element\Identifier::class, $subElement);
84 | $this->assertEquals('x', $subElement->getValue());
85 |
86 | /** @var Element\Operator $subElement */
87 | $subElement = $subElements[7];
88 | $this->assertInstanceOf(Element\Operator::class, $subElement);
89 | $this->assertEquals('+', $subElement->getValue());
90 |
91 | /** @var Element\Identifier $subElement */
92 | $subElement = $subElements[8];
93 | $this->assertInstanceOf(Element\Identifier::class, $subElement);
94 | $this->assertEquals('c', $subElement->getValue());
95 | }
96 |
97 | public function testReadFraction(): void
98 | {
99 | $content = '
100 |
101 | ';
113 |
114 | $reader = new MathML();
115 | $math = $reader->read($content);
116 | $this->assertInstanceOf(Math::class, $math);
117 |
118 | $elements = $math->getElements();
119 | $this->assertCount(1, $elements);
120 | $this->assertInstanceOf(Element\Fraction::class, $elements[0]);
121 |
122 | /** @var Element\Fraction $element */
123 | $element = $elements[0];
124 |
125 | $this->assertInstanceOf(Element\Fraction::class, $element->getNumerator());
126 | /** @var Element\Fraction $subElement */
127 | $subElement = $element->getNumerator();
128 |
129 | /** @var Element\Identifier $numerator */
130 | $numerator = $subElement->getNumerator();
131 | $this->assertInstanceOf(Element\Identifier::class, $numerator);
132 | $this->assertEquals('a', $numerator->getValue());
133 | /** @var Element\Identifier $denominator */
134 | $denominator = $subElement->getDenominator();
135 | $this->assertInstanceOf(Element\Identifier::class, $denominator);
136 | $this->assertEquals('b', $denominator->getValue());
137 |
138 | $this->assertInstanceOf(Element\Fraction::class, $element->getDenominator());
139 | /** @var Element\Fraction $subElement */
140 | $subElement = $element->getDenominator();
141 |
142 | /** @var Element\Identifier $numerator */
143 | $numerator = $subElement->getNumerator();
144 | $this->assertInstanceOf(Element\Identifier::class, $numerator);
145 | $this->assertEquals('c', $numerator->getValue());
146 | /** @var Element\Identifier $denominator */
147 | $denominator = $subElement->getDenominator();
148 | $this->assertInstanceOf(Element\Identifier::class, $denominator);
149 | $this->assertEquals('d', $denominator->getValue());
150 | }
151 |
152 | public function testReadFractionInvalid(): void
153 | {
154 | $this->expectException(InvalidInputException::class);
155 | $this->expectExceptionMessage('PhpOffice\Math\Reader\MathML::getElement : The tag `mfrac` has not two subelements');
156 |
157 | $content = '
158 |
159 | ';
164 |
165 | $reader = new MathML();
166 | $math = $reader->read($content);
167 | }
168 |
169 | public function testReadFractionWithRow(): void
170 | {
171 | $content = '
172 |
173 | ';
183 |
184 | $reader = new MathML();
185 | $math = $reader->read($content);
186 | $this->assertInstanceOf(Math::class, $math);
187 |
188 | $elements = $math->getElements();
189 | $this->assertCount(1, $elements);
190 | $this->assertInstanceOf(Element\Fraction::class, $elements[0]);
191 |
192 | /** @var Element\Fraction $element */
193 | $element = $elements[0];
194 |
195 | $this->assertInstanceOf(Element\Row::class, $element->getNumerator());
196 | /** @var Element\Row $subElement */
197 | $subElement = $element->getNumerator();
198 |
199 | $subsubElements = $subElement->getElements();
200 | $this->assertCount(3, $subsubElements);
201 |
202 | /** @var Element\Numeric $subsubElement */
203 | $subsubElement = $subsubElements[0];
204 | $this->assertInstanceOf(Element\Numeric::class, $subsubElement);
205 | $this->assertEquals('3', $subsubElement->getValue());
206 |
207 | /** @var Element\Operator $subsubElement */
208 | $subsubElement = $subsubElements[1];
209 | $this->assertInstanceOf(Element\Operator::class, $subsubElement);
210 | $this->assertEquals('-', $subsubElement->getValue());
211 |
212 | /** @var Element\Identifier $subsubElement */
213 | $subsubElement = $subsubElements[2];
214 | $this->assertInstanceOf(Element\Identifier::class, $subsubElement);
215 | $this->assertEquals('x', $subsubElement->getValue());
216 |
217 | $this->assertInstanceOf(Element\Numeric::class, $element->getDenominator());
218 | /** @var Element\Numeric $subElement */
219 | $subElement = $element->getDenominator();
220 | $this->assertEquals('2', $subElement->getValue());
221 | }
222 |
223 | public function testReadSuperscriptInvalid(): void
224 | {
225 | $this->expectException(InvalidInputException::class);
226 | $this->expectExceptionMessage('PhpOffice\Math\Reader\MathML::getElement : The tag `msup` has not two subelements');
227 |
228 | $content = '
229 |
230 | ';
235 |
236 | $reader = new MathML();
237 | $math = $reader->read($content);
238 | }
239 |
240 | public function testReadSemantics(): void
241 | {
242 | $content = '
243 | ';
260 |
261 | $reader = new MathML();
262 | $math = $reader->read($content);
263 | $this->assertInstanceOf(Math::class, $math);
264 |
265 | $elements = $math->getElements();
266 | $this->assertCount(1, $elements);
267 | $this->assertInstanceOf(Element\Semantics::class, $elements[0]);
268 |
269 | /** @var Element\Semantics $element */
270 | $element = $elements[0];
271 |
272 | // Check MathML
273 | $subElements = $element->getElements();
274 | $this->assertCount(1, $subElements);
275 | $this->assertInstanceOf(Element\Row::class, $subElements[0]);
276 |
277 | // Check Annotation
278 | $this->assertCount(1, $element->getAnnotations());
279 | $this->assertEquals('{π} over {2} + { a } * 2', $element->getAnnotation('StarMath 5.0'));
280 | }
281 |
282 | public function testReadNotImplemented(): void
283 | {
284 | $this->expectException(NotImplementedException::class);
285 | $this->expectExceptionMessage('PhpOffice\Math\Reader\MathML::getElement : The tag `mnotexisting` is not implemented');
286 |
287 | $content = '
288 |
289 | ';
294 |
295 | $reader = new MathML();
296 | $math = $reader->read($content);
297 | }
298 |
299 | public function testReadSecurity(): void
300 | {
301 | $this->expectException(SecurityException::class);
302 | $this->expectExceptionMessage('Detected use of ENTITY in XML, loading aborted to prevent XXE/XEE attacks');
303 |
304 | $content = ' M';
305 |
306 | $reader = new MathML();
307 | $math = $reader->read($content);
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/tests/resources/schema/mathml3/mathml3-content.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
--------------------------------------------------------------------------------