Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
ElementFactory
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
7 / 7
11
100.00% covered (success)
100.00%
1 / 1
 getDom
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 createElement
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 createTextNode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 importNode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 saveHTML
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 dom
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createRawMarker
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Epic64\Elem;
6
7use Dom\Comment;
8use Dom\DocumentFragment;
9use Dom\Element;
10use Dom\HTMLDocument;
11use Dom\Node;
12use Dom\Text;
13
14/**
15 * Factory for creating DOM elements.
16 * Uses a shared HTMLDocument for efficient element creation.
17 */
18class ElementFactory
19{
20    private static ?HTMLDocument $dom = null;
21
22    /** @var array<int|string, string> Raw HTML storage keyed by marker ID */
23    private static array $rawHtmlStore = [];
24
25    /** @var int Counter for unique marker IDs */
26    private static int $rawHtmlCounter = 0;
27
28    private static function getDom(): HTMLDocument
29    {
30        if (self::$dom === null) {
31            self::$dom = HTMLDocument::createEmpty();
32        }
33        return self::$dom;
34    }
35
36    public static function createElement(string $tagName, ?string $text = null): Element
37    {
38        $element = self::getDom()->createElement($tagName);
39        if ($text !== null && $text !== '') {
40            $element->appendChild(self::createTextNode($text));
41        }
42        return $element;
43    }
44
45    public static function createTextNode(string $text): Text
46    {
47        return self::getDom()->createTextNode($text);
48    }
49
50    public static function importNode(Node $node, bool $deep = true): Node
51    {
52        return self::getDom()->importNode($node, $deep);
53    }
54
55    public static function saveHTML(Node $node): string
56    {
57        $html = self::getDom()->saveHtml($node);
58
59        // Replace raw HTML markers with actual content
60        if (self::$rawHtmlStore !== []) {
61            $html = preg_replace_callback(
62                '/<!--RAW:(\d+)-->/',
63                static fn(array $m) => self::$rawHtmlStore[(string) $m[1]] ?? '',
64                $html
65            ) ?? '';
66        }
67
68        return $html;
69    }
70
71    public static function dom(): HTMLDocument
72    {
73        return self::getDom();
74    }
75
76    /**
77     * Create a comment marker for raw HTML that will be replaced during serialization.
78     * This avoids expensive DOM parsing for raw HTML content.
79     */
80    public static function createRawMarker(string $html): Comment
81    {
82        $id = (string) self::$rawHtmlCounter++;
83        self::$rawHtmlStore[$id] = $html;
84        return self::getDom()->createComment("RAW:$id");
85    }
86}