Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 105
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
HtmlPageBundle
0.00% covered (danger)
0.00%
0 / 104
0.00% covered (danger)
0.00%
0 / 8
240
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 newEmpty
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
2
 responseData
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
30
 fromDomPageBundle
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
12
 toSingleDocumentHtml
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 toInlineAttributeHtml
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 toJsonArray
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 newFromJsonArray
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Core;
5
6use Composer\Semver\Semver;
7use Wikimedia\Parsoid\Config\SiteConfig;
8use Wikimedia\Parsoid\Wt2Html\XHtmlSerializer;
9
10/**
11 * An "HTML page bundle" stores an HTML string with separated data-parsoid and
12 * (optionally) data-mw content.  The data-parsoid and data-mw content
13 * is indexed by the id attributes on individual nodes.  This content
14 * needs to be loaded before the data-parsoid and/or data-mw
15 * information can be used.
16 *
17 * Note that the parsoid/mw properties of the page bundle are in "serialized
18 * array" form; that is, they are flat arrays appropriate for json-encoding
19 * and do not contain DataParsoid or DataMw objects.
20 *
21 * See DomPageBundle for a similar structure used where the HTML string
22 * has been parsed into a DOM.
23 */
24class HtmlPageBundle extends BasePageBundle {
25
26    public function __construct(
27        /** The document, as an HTML string. */
28        public string $html,
29        ?array $parsoid = null, ?array $mw = null,
30        ?array $counters = null,
31        ?string $version = null, ?array $headers = null,
32        ?string $contentmodel = null,
33        /** @var array<string,string> Additional named HTML fragments. */
34        public array $fragments = [],
35    ) {
36        parent::__construct(
37            parsoid: $parsoid,
38            mw: $mw,
39            counters: $counters,
40            version: $version,
41            headers: $headers,
42            contentmodel: $contentmodel,
43        );
44    }
45
46    public static function newEmpty(
47        string $html,
48        ?string $version = null,
49        ?array $headers = null,
50        ?string $contentmodel = null,
51    ): self {
52        return new self(
53            $html,
54            [
55                'ids' => [],
56            ],
57            [
58                'ids' => [],
59            ],
60            [
61                'nodedata' => -1,
62                'annotation' => -1,
63                'transclusion' => -1,
64            ],
65            $version,
66            $headers,
67            $contentmodel
68        );
69    }
70
71    /**
72     * @phpcs:ignore Generic.Files.LineLength.TooLong
73     * @return array{contentmodel: string, html: array{headers: array, body: string}, data-parsoid: array{headers: array{content-type: string}, body: ?array{counter?: int, offsetType?: 'byte'|'char'|'ucs2', ids: array<string, array>}}, data-mw?: array{headers: array{content-type: string}, body: ?array{ids: array<string, array>}}}
74     */
75    public function responseData(): array {
76        $version = $this->version ?? '0.0.0';
77        $responseData = [
78            'contentmodel' => $this->contentmodel ?? '',
79            'html' => [
80                'headers' => array_merge( [
81                    'content-type' => 'text/html; charset=utf-8; '
82                        . 'profile="https://www.mediawiki.org/wiki/Specs/HTML/'
83                        . $version . '"',
84                ], $this->headers ?? [] ),
85                'body' => $this->html,
86            ],
87            'data-parsoid' => [
88                'headers' => [
89                    'content-type' => 'application/json; charset=utf-8; '
90                        . 'profile="https://www.mediawiki.org/wiki/Specs/data-parsoid/'
91                        . $version . '"',
92                ],
93                'body' => $this->parsoid,
94            ],
95        ];
96        if ( $this->counters !== null ) {
97            $responseData['counters'] = [
98                'headers' => [
99                    'content-type' => 'application/json; charset=utf-8; '
100                        . 'profile="https://www.mediawiki.org/wiki/Specs/counters/'
101                        . $version . '"',
102                ],
103                'body' => $this->counters,
104            ];
105        }
106        if ( isset( $this->counters['nodedata'] ) && $this->parsoid !== null ) {
107            // Backward compatibility with Parsoid < 0.23
108            $responseData['data-parsoid']['body'] += [
109                'counter' => $this->counters['nodedata'],
110            ];
111        }
112        if ( Semver::satisfies( $version, '^999.0.0' ) ) {
113            $responseData['data-mw'] = [
114                'headers' => [
115                    'content-type' => 'application/json; charset=utf-8; ' .
116                        'profile="https://www.mediawiki.org/wiki/Specs/data-mw/' .
117                        $version . '"',
118                ],
119                'body' => $this->mw,
120            ];
121        }
122        return $responseData;
123    }
124
125    /**
126     * Convert a DomPageBundle to an HtmlPageBundle.
127     *
128     * This serializes the DOM from the DomPageBundle, with the given $options.
129     * The options can also provide defaults for content version, headers,
130     * content model, and offsetType if they weren't already set in the
131     * DomPageBundle.
132     *
133     * @param DomPageBundle $dpb
134     * @param array $options XHtmlSerializer options
135     * @return self
136     */
137    public static function fromDomPageBundle( DomPageBundle $dpb, array $options = [] ): self {
138        $fragments = array_map(
139            static fn ( $f ) => XHtmlSerializer::serialize( $f, $options )['html'],
140            $dpb->fragments
141        );
142        $node = $dpb->doc;
143        if ( $options['body_only'] ?? false ) {
144            $node = DOMCompat::getBody( $dpb->doc );
145            $options += [ 'innerXML' => true ];
146        }
147        $out = XHtmlSerializer::serialize( $node, $options )['html'];
148        $pb = new self(
149            $out,
150            $dpb->parsoid,
151            $dpb->mw,
152            $dpb->counters,
153            $dpb->version ?? $options['contentversion'] ?? null,
154            $dpb->headers ?? $options['headers'] ?? null,
155            $dpb->contentmodel ?? $options['contentmodel'] ?? null,
156            $fragments,
157        );
158        if ( isset( $options['offsetType'] ) ) {
159            $pb->parsoid['offsetType'] ??= $options['offsetType'];
160        }
161        return $pb;
162    }
163
164    /**
165     * Convert this HtmlPageBundle to "single document" form, where page bundle
166     * information is embedded in the <head> of the document.
167     * @param array $options XHtmlSerializer options
168     * @return string an HTML string
169     */
170    public function toSingleDocumentHtml( array $options = [] ): string {
171        return DomPageBundle::fromHtmlPageBundle( $this )
172            ->toSingleDocumentHtml( $options );
173    }
174
175    /**
176     * Convert this HtmlPageBundle to "inline attribute" form, where page bundle
177     * information is represented as inline JSON-valued attributes.
178     * @param SiteConfig $siteConfig
179     * @param array $options XHtmlSerializer options
180     * @param array<string,string>|null &$fragments Additional fragments from the
181     *  page bundle which will also be serialized to HTML strings.
182     *  This is an output parameter.
183     * @return string an HTML string
184     */
185    public function toInlineAttributeHtml(
186        SiteConfig $siteConfig,
187        array $options = [],
188        ?array &$fragments = null,
189    ): string {
190        return DomPageBundle::fromHtmlPageBundle( $this )
191            ->toInlineAttributeHtml( siteConfig: $siteConfig, options: $options, fragments: $fragments );
192    }
193
194    // JsonCodecable -------------
195
196    /** @inheritDoc */
197    public function toJsonArray(): array {
198        $result = [
199            'html' => $this->html,
200        ] + parent::toJsonArray();
201        if ( $this->fragments ) {
202            $result['fragments'] = $this->fragments;
203        }
204        return $result;
205    }
206
207    /** @inheritDoc */
208    public static function newFromJsonArray( array $json ): self {
209        return parent::newFromJsonArray( $json )->withHtml(
210            html: $json['html'] ?? '',
211            fragments: $json['fragments'] ?? [],
212        );
213    }
214}
215class_alias( HtmlPageBundle::class, PageBundle::class );