Passed
Pull Request — master (#17)
by Chad
02:13
created

XmlFilter::validate()   A

Complexity

Conditions 2
Paths 7

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 11
c 1
b 0
f 0
nc 7
nop 2
dl 0
loc 16
rs 9.9
1
<?php
2
3
namespace TraderInteractive\Filter;
4
5
use DOMDocument;
6
use LibXMLError;
7
use SimpleXMLElement;
8
use Throwable;
9
use TraderInteractive\Exceptions\FilterException;
10
11
final class XmlFilter
12
{
13
    /**
14
     * @var string
15
     */
16
    const LIBXML_ERROR_FORMAT = '%s on line %d at column %d';
17
18
    /**
19
     * @var string
20
     */
21
    const EXTRACT_NO_ELEMENT_FOUND_ERROR_FORMAT = "No element found at xpath '%s'";
22
23
    /**
24
     * @var string
25
     */
26
    const EXTRACT_MULTIPLE_ELEMENTS_FOUND_ERROR_FORMAT = "Multiple elements found at xpath '%s'";
27
28
    /**
29
     * @var string
30
     */
31
    const EXTRACT_XML_ERROR_FORMAT = "Could not filter '%s' as XML with xpath '%s'. Reason: %s";
32
33
    /**
34
     * @param string $xml            The value to be filtered.
35
     * @param string $schemaFilePath The full path to the XSD file used for validation.
36
     *
37
     * @return string
38
     *
39
     * @throws FilterException Thrown if the given value cannot be filtered.
40
     */
41
    public static function validate(string $xml, string $schemaFilePath) : string
42
    {
43
        $previousLibxmlUserInternalErrors = libxml_use_internal_errors(true);
44
        try {
45
            libxml_clear_errors();
46
47
            $document = new DOMDocument();
48
            $document->loadXml($xml);
49
            if ($document->schemaValidate($schemaFilePath)) {
50
                return $xml;
51
            }
52
53
            $formattedXmlError = self::formatXmlError(libxml_get_last_error());
54
            throw new FilterException($formattedXmlError);
55
        } finally {
56
            libxml_use_internal_errors($previousLibxmlUserInternalErrors);
57
        }
58
    } //@codeCoverageIgnore
59
60
    /**
61
     * @param string $xml   The value to be filtered.
62
     * @param string $xpath The xpath to the element to be extracted.
63
     *
64
     * @return string
65
     *
66
     * @throws FilterException Thrown if the value cannot be filtered.
67
     */
68
    public static function extract(string $xml, string $xpath) : string
69
    {
70
        try {
71
            $simpleXmlElement = new SimpleXMLElement($xml);
72
            $elements = $simpleXmlElement->xpath($xpath);
73
        } catch (Throwable $throwable) {
74
            throw new FilterException(sprintf(self::EXTRACT_XML_ERROR_FORMAT, $xml, $xpath, $throwable->getMessage()));
75
        }
76
77
        $elementCount = count($elements);
78
79
        if ($elementCount === 0) {
80
            throw new FilterException(sprintf(self::EXTRACT_NO_ELEMENT_FOUND_ERROR_FORMAT, $xpath));
81
        }
82
83
        if ($elementCount > 1) {
84
            throw new FilterException(sprintf(self::EXTRACT_MULTIPLE_ELEMENTS_FOUND_ERROR_FORMAT, $xpath));
85
        }
86
87
        return $elements[0]->asXML();
88
    }
89
90
    private static function formatXmlError(LibXMLError $error) : string
91
    {
92
        $message = trim($error->message);
93
        return sprintf(self::LIBXML_ERROR_FORMAT, $message, $error->line, $error->column);
94
    }
95
}
96