Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.38% covered (warning)
84.38%
27 / 32
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
XmpReader
84.38% covered (warning)
84.38%
27 / 32
0.00% covered (danger)
0.00%
0 / 1
13.64
0.00% covered (danger)
0.00%
0 / 1
 parse
84.38% covered (warning)
84.38%
27 / 32
0.00% covered (danger)
0.00%
0 / 1
13.64
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Xmp;
6
7/**
8 * Parse XMP metadata packets from XML into {@see XmpPacket}.
9 *
10 * Strips `<?xpacket?>` processing instructions and extracts
11 * namespaced properties from the RDF structure. Used by the reader
12 * and conformance checker to inspect PDF/A identification metadata.
13 */
14final class XmpReader
15{
16    public function parse(string $xml): XmpPacket
17    {
18        // Strip xpacket processing instructions if present
19        $xmlContent = preg_replace('/<\?xpacket[^?]*\?>/s', '', $xml);
20        $xmlContent = trim($xmlContent);
21
22        if (empty($xmlContent)) {
23            return XmpPacket::create();
24        }
25
26        libxml_use_internal_errors(true);
27        $sxe = simplexml_load_string($xmlContent);
28        libxml_use_internal_errors(false);
29
30        if ($sxe === false) {
31            return XmpPacket::create();
32        }
33
34        $packet = XmpPacket::create();
35
36        $rdfNs = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
37
38        // Navigate: x:xmpmeta → rdf:RDF → rdf:Description
39        // The root is x:xmpmeta, its child (in rdf ns) is RDF
40        $xRdfChildren = $sxe->children($rdfNs);
41        if (!isset($xRdfChildren->RDF)) {
42            return $packet;
43        }
44
45        $rdfRDF = $xRdfChildren->RDF;
46        $descriptions = $rdfRDF->children($rdfNs);
47
48        foreach ($descriptions as $descName => $desc) {
49            if ($descName !== 'Description') {
50                continue;
51            }
52
53            // Get all namespaces defined at this level and below
54            $namespaces = $desc->getNamespaces(true);
55
56            foreach ($namespaces as $prefix => $uri) {
57                if ($prefix === '' || $prefix === 'rdf' || $prefix === 'x') {
58                    continue;
59                }
60
61                // Check child elements in this namespace
62                foreach ($desc->children($uri) as $localName => $child) {
63                    $key = $prefix . ':' . $localName;
64                    $packet = $packet->set($key, (string) $child);
65                }
66
67                // Check attributes in this namespace
68                $attrs = $desc->attributes($uri);
69                if ($attrs !== null) {
70                    foreach ($attrs as $attrName => $attrValue) {
71                        $key = $prefix . ':' . $attrName;
72                        $packet = $packet->set($key, (string) $attrValue);
73                    }
74                }
75            }
76        }
77
78        return $packet;
79    }
80}