Completed
Push — master ( 78dfd0...3d9a51 )
by Carlos C
01:25
created

SchemaValidator::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 11
cts 11
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 10
nc 3
nop 1
crap 3
1
<?php
2
namespace XmlSchemaValidator;
3
4
use DOMDocument;
5
use DOMXPath;
6
7
/**
8
 * This class is an XML schema validator
9
 * It is needed because some XML can contain more than one external schema
10
 * and DOM library fails to load it.
11
 */
12
class SchemaValidator
13
{
14
    /** @var DOMDocument */
15
    private $document;
16
17
    /** @var string */
18
    private $error = '';
19
20
    /**
21
     * SchemaValidator constructor.
22
     *
23
     * @param string $content
24
     * @throws \InvalidArgumentException if content is empty
25
     * @throws SchemaValidatorException if malformed xml content
26
     */
27 10
    public function __construct(string $content)
28
    {
29 10
        if ('' === $content) {
30 1
            throw new \InvalidArgumentException('The content to validate must be a non-empty string');
31
        }
32 9
        $document = new DOMDocument();
33
        try {
34 9
            LibXmlException::useInternalErrors(function () use ($content, $document) {
35 9
                $document->loadXML($content, LIBXML_NOWARNING);
36 9
            });
37 1
        } catch (LibXmlException $ex) {
38 1
            throw new SchemaValidatorException('Malformed XML Document: ' . $ex->getMessage());
39
        }
40 8
        $this->document = $document;
41 8
    }
42
43
    /**
44
     * Validate the content by:
45
     * - Create the Schemas collection from the document
46
     * - Validate using validateWithSchemas
47
     *
48
     * @see validateWithSchemas
49
     * @return bool
50
     */
51 8
    public function validate(): bool
52
    {
53 8
        $this->error = '';
54
        try {
55
            // create the schemas collection
56 8
            $schemas = $this->buildSchemas();
57
            // validate the document against the schema collection
58 7
            $this->validateWithSchemas($schemas);
59 4
        } catch (LibXmlException $ex) {
60 3
            $this->error = $ex->getMessage();
61 3
            return false;
62
        }
63 4
        return true;
64
    }
65
66 4
    public function getLastError(): string
67
    {
68 4
        return $this->error;
69
    }
70
71
    /**
72
     * Validate against a list of schemas (if any)
73
     *
74
     * @param Schemas $schemas
75
     * @return void
76
     * @throws LibXmlException if schema validation fails
77
     */
78 7
    public function validateWithSchemas(Schemas $schemas)
79
    {
80
        // create the schemas collection, then validate the document against the schemas
81 7
        if (! $schemas->count()) {
82 2
            return;
83
        }
84
        // build the unique importing schema
85 5
        $xsd = $schemas->getImporterXsd();
86 5
        $document = $this->document;
87 5
        LibXmlException::useInternalErrors(function () use ($document, $xsd) {
88 5
            $document->schemaValidateSource($xsd);
89 5
        });
90 2
    }
91
92
    /**
93
     * Retrieve a list of namespaces based on the schemaLocation attributes
94
     *
95
     * @throws SchemaValidatorException if the content of schemaLocation is not an even number of uris
96
     * @return Schemas
97
     */
98 8
    public function buildSchemas(): Schemas
99
    {
100 8
        $schemas = new Schemas();
101 8
        $xpath = new DOMXPath($this->document);
102
103
        // get the http://www.w3.org/2001/XMLSchema-instance namespace (it could not be 'xsi')
104 8
        $xsi = $this->document->lookupPrefix('http://www.w3.org/2001/XMLSchema-instance');
105
106
        // the namespace is not registered, no need to continue
107 8
        if (! $xsi) {
108 1
            return $schemas;
109
        }
110
111
        // get all the xsi:schemaLocation attributes in the document
112 7
        $schemasList = $xpath->query("//@$xsi:schemaLocation");
113
114
        // schemaLocation attribute not found, no need to continue
115 7
        if (false === $schemasList || 0 === $schemasList->length) {
116 1
            return $schemas;
117
        }
118
119
        // process every schemaLocation for even parts
120 6
        for ($s = 0; $s < $schemasList->length; $s++) {
121
            // get the node content
122 6
            $content = $schemasList->item($s)->nodeValue;
123
            // get parts without inner spaces
124 6
            $parts = array_values(array_filter(explode(' ', $content)));
125 6
            $partsCount = count($parts);
126
            // check that the list count is an even number
127 6
            if (0 !== $partsCount % 2) {
128 1
                throw new SchemaValidatorException(
129 1
                    "The schemaLocation value '" . $content . "' must have even number of URIs"
130
                );
131
            }
132
            // insert the uris pairs into the schemas
133 5
            for ($k = 0; $k < $partsCount; $k = $k + 2) {
134 5
                $schemas->create($parts[$k], $parts[$k + 1]);
135
            }
136
        }
137
138 5
        return $schemas;
139
    }
140
}
141