XmlDomLoader::getXmlErrors()   A
last analyzed

Complexity

Conditions 4
Paths 2

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 4
eloc 13
c 1
b 1
f 0
nc 2
nop 1
dl 0
loc 18
rs 9.8333
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * @author hashashiyyin [email protected] / [email protected]
5
 * Date: 22/04/24
6
 * Time: 18:48
7
 *
8
 */
9
10
namespace Matecat\XmlParser;
11
12
use DOMDocument;
13
use Exception;
14
use Matecat\XmlParser\Exception\InvalidXmlException;
15
use Matecat\XmlParser\Exception\XmlParsingException;
16
use RuntimeException;
17
18
/**
19
 * This class is copied from Symfony\Component\Config\Util\XmlUtils:
20
 *
21
 * Please see:
22
 * https://github.com/symfony/config/blob/v4.0.0/Util/XmlUtils.php
23
 */
24
class XmlDomLoader {
25
    /**
26
     * Parses an XML string.
27
     *
28
     * @param string      $content An XML string
29
     * @param Config|null $config
30
     *
31
     * @return DOMDocument
32
     *
33
     * @throws InvalidXmlException When parsing of XML with schema or callable produces any errors unrelated to the XML parsing itself
34
     * @throws XmlParsingException When parsing of XML file returns error
35
     */
36
    public static function load( $content, Config $config = null ) {
37
        if ( !extension_loaded( 'dom' ) ) {
38
            throw new RuntimeException( 'Extension DOM is required.' );
39
        }
40
41
        if ( is_null( $config ) ) {
42
            $config = new Config();
43
        }
44
45
        $internalErrors  = libxml_use_internal_errors( true );
46
        $disableEntities = libxml_disable_entity_loader();
47
        libxml_clear_errors();
48
49
        $dom                  = new DOMDocument( '1.0', 'UTF-8' );
50
        $dom->validateOnParse = true;
51
52
        if ( is_string( $config->getSetRootElement() ) && !empty( $config->getSetRootElement() ) ) {
53
            $content = "<{$config->getSetRootElement()}>$content</{$config->getSetRootElement()}>";
54
        }
55
56
        $res = $dom->loadXML( $content, $config->getXML_OPTIONS() );
57
58
        if ( !$res ) {
59
            libxml_disable_entity_loader( $disableEntities );
60
61
            throw new XmlParsingException( implode( "\n", static::getXmlErrors( $internalErrors ) ) );
62
        }
63
64
        $dom->normalizeDocument();
65
66
        libxml_use_internal_errors( $internalErrors );
67
        libxml_disable_entity_loader( $disableEntities );
68
69
        foreach ( $dom->childNodes as $child ) {
70
            if ( XML_DOCUMENT_TYPE_NODE === $child->nodeType && !$config->getAllowDocumentType() ) {
71
                throw new XmlParsingException( 'Document types are not allowed.' );
72
            }
73
        }
74
75
        if ( null !== $config->getSchemaOrCallable() ) {
76
            $internalErrors = libxml_use_internal_errors( true );
77
            libxml_clear_errors();
78
79
            $e = null;
80
            if ( is_callable( $config->getSchemaOrCallable() ) ) {
81
                try {
82
                    $valid = call_user_func( $config->getSchemaOrCallable(), $dom, $internalErrors );
83
                } catch ( Exception $e ) {
84
                    $valid = false;
85
                }
86
            } elseif ( !is_array( $config->getSchemaOrCallable() ) && is_file( (string)$config->getSchemaOrCallable() ) ) {
87
                $schemaSource = file_get_contents( (string)$config->getSchemaOrCallable() );
88
                $valid        = @$dom->schemaValidateSource( $schemaSource );
89
            } else {
90
                libxml_use_internal_errors( $internalErrors );
91
92
                throw new XmlParsingException( 'The schemaOrCallable argument has to be a valid path to XSD file or callable.' );
93
            }
94
95
            if ( !$valid ) {
96
                $messages = static::getXmlErrors( $internalErrors );
97
                if ( empty( $messages ) ) {
98
                    throw new InvalidXmlException( 'The XML is not valid.', 0, $e );
99
                }
100
                throw new XmlParsingException( implode( "\n", $messages ), 0, $e );
101
            }
102
        }
103
104
        libxml_clear_errors();
105
        libxml_use_internal_errors( $internalErrors );
106
107
        return $dom;
108
    }
109
110
    /**
111
     * @param $internalErrors
112
     *
113
     * @return array
114
     */
115
    private static function getXmlErrors( $internalErrors ) {
116
        $errors = [];
117
        foreach ( libxml_get_errors() as $error ) {
118
            $errors[] = sprintf(
119
                    '[%s %s] %s (in %s - line %d, column %d)',
120
                    LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
121
                    $error->code,
122
                    trim( $error->message ),
123
                    $error->file ?: 'n/a',
124
                    $error->line,
125
                    $error->column
126
            );
127
        }
128
129
        libxml_clear_errors();
130
        libxml_use_internal_errors( $internalErrors );
131
132
        return $errors;
133
    }
134
}