1<?php
2
3namespace Sabre\Xml;
4
5/**
6 * XML parsing and writing service.
7 *
8 * You are encouraged to make a instance of this for your application and
9 * potentially extend it, as a central API point for dealing with xml and
10 * configuring the reader and writer.
11 *
12 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
13 * @author Evert Pot (http://evertpot.com/)
14 * @license http://sabre.io/license/ Modified BSD License
15 */
16class Service {
17
18    /**
19     * This is the element map. It contains a list of XML elements (in clark
20     * notation) as keys and PHP class names as values.
21     *
22     * The PHP class names must implement Sabre\Xml\Element.
23     *
24     * Values may also be a callable. In that case the function will be called
25     * directly.
26     *
27     * @var array
28     */
29    public $elementMap = [];
30
31    /**
32     * This is a list of namespaces that you want to give default prefixes.
33     *
34     * You must make sure you create this entire list before starting to write.
35     * They should be registered on the root element.
36     *
37     * @var array
38     */
39    public $namespaceMap = [];
40
41    /**
42     * Returns a fresh XML Reader
43     *
44     * @return Reader
45     */
46    function getReader() {
47
48        $r = new Reader();
49        $r->elementMap = $this->elementMap;
50        return $r;
51
52    }
53
54    /**
55     * Returns a fresh xml writer
56     *
57     * @return Writer
58     */
59    function getWriter() {
60
61        $w = new Writer();
62        $w->namespaceMap = $this->namespaceMap;
63        return $w;
64
65    }
66
67    /**
68     * Parses a document in full.
69     *
70     * Input may be specified as a string or readable stream resource.
71     * The returned value is the value of the root document.
72     *
73     * Specifying the $contextUri allows the parser to figure out what the URI
74     * of the document was. This allows relative URIs within the document to be
75     * expanded easily.
76     *
77     * The $rootElementName is specified by reference and will be populated
78     * with the root element name of the document.
79     *
80     * @param string|resource $input
81     * @param string|null $contextUri
82     * @param string|null $rootElementName
83     * @throws ParseException
84     * @return array|object|string
85     */
86    function parse($input, $contextUri = null, &$rootElementName = null) {
87
88        if (is_resource($input)) {
89            // Unfortunately the XMLReader doesn't support streams. When it
90            // does, we can optimize this.
91            $input = stream_get_contents($input);
92        }
93        $r = $this->getReader();
94        $r->contextUri = $contextUri;
95        $r->xml($input);
96
97        $result = $r->parse();
98        $rootElementName = $result['name'];
99        return $result['value'];
100
101    }
102
103    /**
104     * Parses a document in full, and specify what the expected root element
105     * name is.
106     *
107     * This function works similar to parse, but the difference is that the
108     * user can specify what the expected name of the root element should be,
109     * in clark notation.
110     *
111     * This is useful in cases where you expected a specific document to be
112     * passed, and reduces the amount of if statements.
113     *
114     * @param string $rootElementName
115     * @param string|resource $input
116     * @param string|null $contextUri
117     * @return void
118     */
119    function expect($rootElementName, $input, $contextUri = null) {
120
121        if (is_resource($input)) {
122            // Unfortunately the XMLReader doesn't support streams. When it
123            // does, we can optimize this.
124            $input = stream_get_contents($input);
125        }
126        $r = $this->getReader();
127        $r->contextUri = $contextUri;
128        $r->xml($input);
129
130        $result = $r->parse();
131        if ($rootElementName !== $result['name']) {
132            throw new ParseException('Expected ' . $rootElementName . ' but received ' . $result['name'] . ' as the root element');
133        }
134        return $result['value'];
135
136    }
137
138    /**
139     * Generates an XML document in one go.
140     *
141     * The $rootElement must be specified in clark notation.
142     * The value must be a string, an array or an object implementing
143     * XmlSerializable. Basically, anything that's supported by the Writer
144     * object.
145     *
146     * $contextUri can be used to specify a sort of 'root' of the PHP application,
147     * in case the xml document is used as a http response.
148     *
149     * This allows an implementor to easily create URI's relative to the root
150     * of the domain.
151     *
152     * @param string $rootElementName
153     * @param string|array|XmlSerializable $value
154     * @param string|null $contextUri
155     */
156    function write($rootElementName, $value, $contextUri = null) {
157
158        $w = $this->getWriter();
159        $w->openMemory();
160        $w->contextUri = $contextUri;
161        $w->setIndent(true);
162        $w->startDocument();
163        $w->writeElement($rootElementName, $value);
164        return $w->outputMemory();
165
166    }
167
168
169    /**
170     * Parses a clark-notation string, and returns the namespace and element
171     * name components.
172     *
173     * If the string was invalid, it will throw an InvalidArgumentException.
174     *
175     * @param string $str
176     * @throws InvalidArgumentException
177     * @return array
178     */
179    static function parseClarkNotation($str) {
180
181        if (!preg_match('/^{([^}]*)}(.*)$/', $str, $matches)) {
182            throw new \InvalidArgumentException('\'' . $str . '\' is not a valid clark-notation formatted string');
183        }
184
185        return [
186            $matches[1],
187            $matches[2]
188        ];
189
190    }
191
192
193}
194