Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
InlineMathmlAdapter
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
2 / 2
5
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
 adapt
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\HtmlToPdf\Mathml;
6
7use Phpdftk\Html\Dom\Element as HtmlElement;
8use Phpdftk\HtmlToPdf\ForeignContent\DomXmlSerializer;
9use Phpdftk\Mathml\MathmlDocument;
10use Phpdftk\Mathml\Parser as MathmlParser;
11
12/**
13 * Convert an inline-MathML subtree from the HTML DOM into a typed
14 * {@see MathmlDocument} so {@see \Phpdftk\MathmlToPdf\MathmlRenderer}
15 * can paint it.
16 *
17 * Sibling to {@see \Phpdftk\HtmlToPdf\Svg\InlineSvgAdapter} and
18 * structurally identical: routes through {@see DomXmlSerializer}
19 * for the HTML-DOM-to-XML walk + namespace plumbing, then hands the
20 * XML to the MathML parser. The adapter's own responsibility is:
21 *
22 *   - Reject elements that aren't a `<math>` in `MATHML_NS`.
23 *   - Cache parsed documents by element identity so the same
24 *     `<math>` painted on N pages only pays the parse cost once.
25 *
26 * The cache uses `===` identity, not equality — two distinct `<math>`
27 * elements with byte-identical markup get parsed once each.
28 */
29final class InlineMathmlAdapter
30{
31    /** @var \SplObjectStorage<HtmlElement, MathmlDocument> */
32    private \SplObjectStorage $cache;
33
34    public function __construct(
35        private readonly MathmlParser $parser = new MathmlParser(),
36        private readonly DomXmlSerializer $serializer = new DomXmlSerializer(),
37    ) {
38        $this->cache = new \SplObjectStorage();
39    }
40
41    /**
42     * Adapt an HTML DOM `<math>` element into a typed MathmlDocument.
43     *
44     * @throws \InvalidArgumentException When the element isn't a
45     *   `<math>` in the MathML namespace.
46     */
47    public function adapt(HtmlElement $element): MathmlDocument
48    {
49        if (strtolower($element->localName) !== 'math') {
50            throw new \InvalidArgumentException(sprintf(
51                'InlineMathmlAdapter expects a <math> element, got <%s>.',
52                $element->localName,
53            ));
54        }
55        if ($element->namespaceUri() !== MathmlParser::MATHML_NS) {
56            throw new \InvalidArgumentException(sprintf(
57                'InlineMathmlAdapter expects namespace %s, got %s.',
58                MathmlParser::MATHML_NS,
59                $element->namespaceUri() ?? '(null)',
60            ));
61        }
62        if ($this->cache->contains($element)) {
63            return $this->cache[$element];
64        }
65        $xml = $this->serializer->serialize($element, MathmlParser::MATHML_NS);
66        $math = $this->parser->parse($xml);
67        $this->cache[$element] = $math;
68        return $math;
69    }
70}