xref: /plugin/davcal/vendor/sabre/xml/lib/Writer.php (revision a1a3b6794e0e143a4a8b51d3185ce2d339be61ab)
1*a1a3b679SAndreas Boehler<?php
2*a1a3b679SAndreas Boehler
3*a1a3b679SAndreas Boehlernamespace Sabre\Xml;
4*a1a3b679SAndreas Boehler
5*a1a3b679SAndreas Boehleruse XMLWriter;
6*a1a3b679SAndreas Boehleruse InvalidArgumentException;
7*a1a3b679SAndreas Boehler
8*a1a3b679SAndreas Boehler/**
9*a1a3b679SAndreas Boehler * The XML Writer class.
10*a1a3b679SAndreas Boehler *
11*a1a3b679SAndreas Boehler * This class works exactly as PHP's built-in XMLWriter, with a few additions.
12*a1a3b679SAndreas Boehler *
13*a1a3b679SAndreas Boehler * Namespaces can be registered beforehand, globally. When the first element is
14*a1a3b679SAndreas Boehler * written, namespaces will automatically be declared.
15*a1a3b679SAndreas Boehler *
16*a1a3b679SAndreas Boehler * The writeAttribute, startElement and writeElement can now take a
17*a1a3b679SAndreas Boehler * clark-notation element name (example: {http://www.w3.org/2005/Atom}link).
18*a1a3b679SAndreas Boehler *
19*a1a3b679SAndreas Boehler * If, when writing the namespace is a known one a prefix will automatically be
20*a1a3b679SAndreas Boehler * selected, otherwise a random prefix will be generated.
21*a1a3b679SAndreas Boehler *
22*a1a3b679SAndreas Boehler * Instead of standard string values, the writer can take Element classes (as
23*a1a3b679SAndreas Boehler * defined by this library) to delegate the serialization.
24*a1a3b679SAndreas Boehler *
25*a1a3b679SAndreas Boehler * The write() method can take array structures to quickly write out simple xml
26*a1a3b679SAndreas Boehler * trees.
27*a1a3b679SAndreas Boehler *
28*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
29*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/)
30*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License
31*a1a3b679SAndreas Boehler */
32*a1a3b679SAndreas Boehlerclass Writer extends XMLWriter {
33*a1a3b679SAndreas Boehler
34*a1a3b679SAndreas Boehler    use ContextStackTrait;
35*a1a3b679SAndreas Boehler
36*a1a3b679SAndreas Boehler    /**
37*a1a3b679SAndreas Boehler     * Any namespace that the writer is asked to write, will be added here.
38*a1a3b679SAndreas Boehler     *
39*a1a3b679SAndreas Boehler     * Any of these elements will get a new namespace definition *every single
40*a1a3b679SAndreas Boehler     * time* they are used, but this array allows the writer to make sure that
41*a1a3b679SAndreas Boehler     * the prefixes are consistent anyway.
42*a1a3b679SAndreas Boehler     *
43*a1a3b679SAndreas Boehler     * @var array
44*a1a3b679SAndreas Boehler     */
45*a1a3b679SAndreas Boehler    protected $adhocNamespaces = [];
46*a1a3b679SAndreas Boehler
47*a1a3b679SAndreas Boehler    /**
48*a1a3b679SAndreas Boehler     * When the first element is written, this flag is set to true.
49*a1a3b679SAndreas Boehler     *
50*a1a3b679SAndreas Boehler     * This ensures that the namespaces in the namespaces map are only written
51*a1a3b679SAndreas Boehler     * once.
52*a1a3b679SAndreas Boehler     *
53*a1a3b679SAndreas Boehler     * @var bool
54*a1a3b679SAndreas Boehler     */
55*a1a3b679SAndreas Boehler    protected $namespacesWritten = false;
56*a1a3b679SAndreas Boehler
57*a1a3b679SAndreas Boehler    /**
58*a1a3b679SAndreas Boehler     * Writes a value to the output stream.
59*a1a3b679SAndreas Boehler     *
60*a1a3b679SAndreas Boehler     * The following values are supported:
61*a1a3b679SAndreas Boehler     *   1. Scalar values will be written as-is, as text.
62*a1a3b679SAndreas Boehler     *   2. Null values will be skipped (resulting in a short xml tag).
63*a1a3b679SAndreas Boehler     *   3. If a value is an instance of an Element class, writing will be
64*a1a3b679SAndreas Boehler     *      delegated to the object.
65*a1a3b679SAndreas Boehler     *   4. If a value is an array, two formats are supported.
66*a1a3b679SAndreas Boehler     *
67*a1a3b679SAndreas Boehler     *  Array format 1:
68*a1a3b679SAndreas Boehler     *  [
69*a1a3b679SAndreas Boehler     *    "{namespace}name1" => "..",
70*a1a3b679SAndreas Boehler     *    "{namespace}name2" => "..",
71*a1a3b679SAndreas Boehler     *  ]
72*a1a3b679SAndreas Boehler     *
73*a1a3b679SAndreas Boehler     *  One element will be created for each key in this array. The values of
74*a1a3b679SAndreas Boehler     *  this array support any format this method supports (this method is
75*a1a3b679SAndreas Boehler     *  called recursively).
76*a1a3b679SAndreas Boehler     *
77*a1a3b679SAndreas Boehler     *  Array format 2:
78*a1a3b679SAndreas Boehler     *
79*a1a3b679SAndreas Boehler     *  [
80*a1a3b679SAndreas Boehler     *    [
81*a1a3b679SAndreas Boehler     *      "name" => "{namespace}name1"
82*a1a3b679SAndreas Boehler     *      "value" => "..",
83*a1a3b679SAndreas Boehler     *      "attributes" => [
84*a1a3b679SAndreas Boehler     *          "attr" => "attribute value",
85*a1a3b679SAndreas Boehler     *      ]
86*a1a3b679SAndreas Boehler     *    ],
87*a1a3b679SAndreas Boehler     *    [
88*a1a3b679SAndreas Boehler     *      "name" => "{namespace}name1"
89*a1a3b679SAndreas Boehler     *      "value" => "..",
90*a1a3b679SAndreas Boehler     *      "attributes" => [
91*a1a3b679SAndreas Boehler     *          "attr" => "attribute value",
92*a1a3b679SAndreas Boehler     *      ]
93*a1a3b679SAndreas Boehler     *    ]
94*a1a3b679SAndreas Boehler     * ]
95*a1a3b679SAndreas Boehler     *
96*a1a3b679SAndreas Boehler     * @param mixed $value
97*a1a3b679SAndreas Boehler     * @return void
98*a1a3b679SAndreas Boehler     */
99*a1a3b679SAndreas Boehler    function write($value) {
100*a1a3b679SAndreas Boehler
101*a1a3b679SAndreas Boehler        if (is_scalar($value)) {
102*a1a3b679SAndreas Boehler            $this->text($value);
103*a1a3b679SAndreas Boehler        } elseif ($value instanceof XmlSerializable) {
104*a1a3b679SAndreas Boehler            $value->xmlSerialize($this);
105*a1a3b679SAndreas Boehler        } elseif (is_null($value)) {
106*a1a3b679SAndreas Boehler            // noop
107*a1a3b679SAndreas Boehler        } elseif (is_array($value)) {
108*a1a3b679SAndreas Boehler
109*a1a3b679SAndreas Boehler            reset($value);
110*a1a3b679SAndreas Boehler            foreach ($value as $name => $item) {
111*a1a3b679SAndreas Boehler
112*a1a3b679SAndreas Boehler                if (is_int($name)) {
113*a1a3b679SAndreas Boehler
114*a1a3b679SAndreas Boehler                    // This item has a numeric index. We expect to be an array with a name and a value.
115*a1a3b679SAndreas Boehler                    if (!is_array($item) || !array_key_exists('name', $item) || !array_key_exists('value', $item)) {
116*a1a3b679SAndreas Boehler                        throw new InvalidArgumentException('When passing an array to ->write with numeric indices, every item must be an array containing the "name" and "value" key');
117*a1a3b679SAndreas Boehler                    }
118*a1a3b679SAndreas Boehler
119*a1a3b679SAndreas Boehler                    $attributes = isset($item['attributes']) ? $item['attributes'] : [];
120*a1a3b679SAndreas Boehler                    $name = $item['name'];
121*a1a3b679SAndreas Boehler                    $item = $item['value'];
122*a1a3b679SAndreas Boehler
123*a1a3b679SAndreas Boehler                } elseif (is_array($item) && array_key_exists('value', $item)) {
124*a1a3b679SAndreas Boehler
125*a1a3b679SAndreas Boehler                    // This item has a text index. We expect to be an array with a value and optional attributes.
126*a1a3b679SAndreas Boehler                    $attributes = isset($item['attributes']) ? $item['attributes'] : [];
127*a1a3b679SAndreas Boehler                    $item = $item['value'];
128*a1a3b679SAndreas Boehler
129*a1a3b679SAndreas Boehler                } else {
130*a1a3b679SAndreas Boehler                    // If it's an array with text-indices, we expect every item's
131*a1a3b679SAndreas Boehler                    // key to be an xml element name in clark notation.
132*a1a3b679SAndreas Boehler                    // No attributes can be passed.
133*a1a3b679SAndreas Boehler                    $attributes = [];
134*a1a3b679SAndreas Boehler                }
135*a1a3b679SAndreas Boehler
136*a1a3b679SAndreas Boehler                $this->startElement($name);
137*a1a3b679SAndreas Boehler                $this->writeAttributes($attributes);
138*a1a3b679SAndreas Boehler                $this->write($item);
139*a1a3b679SAndreas Boehler                $this->endElement();
140*a1a3b679SAndreas Boehler
141*a1a3b679SAndreas Boehler            }
142*a1a3b679SAndreas Boehler
143*a1a3b679SAndreas Boehler        } elseif (is_object($value)) {
144*a1a3b679SAndreas Boehler
145*a1a3b679SAndreas Boehler            throw new InvalidArgumentException('The writer cannot serialize objects of type: ' . get_class($value));
146*a1a3b679SAndreas Boehler
147*a1a3b679SAndreas Boehler        }
148*a1a3b679SAndreas Boehler
149*a1a3b679SAndreas Boehler    }
150*a1a3b679SAndreas Boehler
151*a1a3b679SAndreas Boehler    /**
152*a1a3b679SAndreas Boehler     * Starts an element.
153*a1a3b679SAndreas Boehler     *
154*a1a3b679SAndreas Boehler     * @param string $name
155*a1a3b679SAndreas Boehler     * @return bool
156*a1a3b679SAndreas Boehler     */
157*a1a3b679SAndreas Boehler    function startElement($name) {
158*a1a3b679SAndreas Boehler
159*a1a3b679SAndreas Boehler        if ($name[0] === '{') {
160*a1a3b679SAndreas Boehler
161*a1a3b679SAndreas Boehler            list($namespace, $localName) =
162*a1a3b679SAndreas Boehler                Service::parseClarkNotation($name);
163*a1a3b679SAndreas Boehler
164*a1a3b679SAndreas Boehler            if (array_key_exists($namespace, $this->namespaceMap)) {
165*a1a3b679SAndreas Boehler                $result = $this->startElementNS($this->namespaceMap[$namespace], $localName, null);
166*a1a3b679SAndreas Boehler            } else {
167*a1a3b679SAndreas Boehler
168*a1a3b679SAndreas Boehler                // An empty namespace means it's the global namespace. This is
169*a1a3b679SAndreas Boehler                // allowed, but it mustn't get a prefix.
170*a1a3b679SAndreas Boehler                if ($namespace === "") {
171*a1a3b679SAndreas Boehler                    $result = $this->startElement($localName);
172*a1a3b679SAndreas Boehler                    $this->writeAttribute('xmlns', '');
173*a1a3b679SAndreas Boehler                } else {
174*a1a3b679SAndreas Boehler                    if (!isset($this->adhocNamespaces[$namespace])) {
175*a1a3b679SAndreas Boehler                        $this->adhocNamespaces[$namespace] = 'x' . (count($this->adhocNamespaces) + 1);
176*a1a3b679SAndreas Boehler                    }
177*a1a3b679SAndreas Boehler                    $result = $this->startElementNS($this->adhocNamespaces[$namespace], $localName, $namespace);
178*a1a3b679SAndreas Boehler                }
179*a1a3b679SAndreas Boehler            }
180*a1a3b679SAndreas Boehler
181*a1a3b679SAndreas Boehler        } else {
182*a1a3b679SAndreas Boehler            $result = parent::startElement($name);
183*a1a3b679SAndreas Boehler        }
184*a1a3b679SAndreas Boehler
185*a1a3b679SAndreas Boehler        if (!$this->namespacesWritten) {
186*a1a3b679SAndreas Boehler
187*a1a3b679SAndreas Boehler            foreach ($this->namespaceMap as $namespace => $prefix) {
188*a1a3b679SAndreas Boehler                $this->writeAttribute(($prefix ? 'xmlns:' . $prefix : 'xmlns'), $namespace);
189*a1a3b679SAndreas Boehler            }
190*a1a3b679SAndreas Boehler            $this->namespacesWritten = true;
191*a1a3b679SAndreas Boehler
192*a1a3b679SAndreas Boehler        }
193*a1a3b679SAndreas Boehler
194*a1a3b679SAndreas Boehler        return $result;
195*a1a3b679SAndreas Boehler
196*a1a3b679SAndreas Boehler    }
197*a1a3b679SAndreas Boehler
198*a1a3b679SAndreas Boehler    /**
199*a1a3b679SAndreas Boehler     * Write a full element tag.
200*a1a3b679SAndreas Boehler     *
201*a1a3b679SAndreas Boehler     * This method automatically closes the element as well.
202*a1a3b679SAndreas Boehler     *
203*a1a3b679SAndreas Boehler     * @param string $name
204*a1a3b679SAndreas Boehler     * @param string $content
205*a1a3b679SAndreas Boehler     * @return bool
206*a1a3b679SAndreas Boehler     */
207*a1a3b679SAndreas Boehler    function writeElement($name, $content = null) {
208*a1a3b679SAndreas Boehler
209*a1a3b679SAndreas Boehler        $this->startElement($name);
210*a1a3b679SAndreas Boehler        if (!is_null($content)) {
211*a1a3b679SAndreas Boehler            $this->write($content);
212*a1a3b679SAndreas Boehler        }
213*a1a3b679SAndreas Boehler        $this->endElement();
214*a1a3b679SAndreas Boehler
215*a1a3b679SAndreas Boehler    }
216*a1a3b679SAndreas Boehler
217*a1a3b679SAndreas Boehler    /**
218*a1a3b679SAndreas Boehler     * Writes a list of attributes.
219*a1a3b679SAndreas Boehler     *
220*a1a3b679SAndreas Boehler     * Attributes are specified as a key->value array.
221*a1a3b679SAndreas Boehler     *
222*a1a3b679SAndreas Boehler     * The key is an attribute name. If the key is a 'localName', the current
223*a1a3b679SAndreas Boehler     * xml namespace is assumed. If it's a 'clark notation key', this namespace
224*a1a3b679SAndreas Boehler     * will be used instead.
225*a1a3b679SAndreas Boehler     *
226*a1a3b679SAndreas Boehler     * @param array $attributes
227*a1a3b679SAndreas Boehler     * @return void
228*a1a3b679SAndreas Boehler     */
229*a1a3b679SAndreas Boehler    function writeAttributes(array $attributes) {
230*a1a3b679SAndreas Boehler
231*a1a3b679SAndreas Boehler        foreach ($attributes as $name => $value) {
232*a1a3b679SAndreas Boehler            $this->writeAttribute($name, $value);
233*a1a3b679SAndreas Boehler        }
234*a1a3b679SAndreas Boehler
235*a1a3b679SAndreas Boehler    }
236*a1a3b679SAndreas Boehler
237*a1a3b679SAndreas Boehler    /**
238*a1a3b679SAndreas Boehler     * Writes a new attribute.
239*a1a3b679SAndreas Boehler     *
240*a1a3b679SAndreas Boehler     * The name may be specified in clark-notation.
241*a1a3b679SAndreas Boehler     *
242*a1a3b679SAndreas Boehler     * Returns true when successful.
243*a1a3b679SAndreas Boehler     *
244*a1a3b679SAndreas Boehler     * @param string $name
245*a1a3b679SAndreas Boehler     * @param string $value
246*a1a3b679SAndreas Boehler     * @return bool
247*a1a3b679SAndreas Boehler     */
248*a1a3b679SAndreas Boehler    function writeAttribute($name, $value) {
249*a1a3b679SAndreas Boehler
250*a1a3b679SAndreas Boehler        if ($name[0] === '{') {
251*a1a3b679SAndreas Boehler
252*a1a3b679SAndreas Boehler            list(
253*a1a3b679SAndreas Boehler                $namespace,
254*a1a3b679SAndreas Boehler                $localName
255*a1a3b679SAndreas Boehler            ) = Service::parseClarkNotation($name);
256*a1a3b679SAndreas Boehler
257*a1a3b679SAndreas Boehler            if (array_key_exists($namespace, $this->namespaceMap)) {
258*a1a3b679SAndreas Boehler                // It's an attribute with a namespace we know
259*a1a3b679SAndreas Boehler                $this->writeAttribute(
260*a1a3b679SAndreas Boehler                    $this->namespaceMap[$namespace] . ':' . $localName,
261*a1a3b679SAndreas Boehler                    $value
262*a1a3b679SAndreas Boehler                );
263*a1a3b679SAndreas Boehler            } else {
264*a1a3b679SAndreas Boehler
265*a1a3b679SAndreas Boehler                // We don't know the namespace, we must add it in-line
266*a1a3b679SAndreas Boehler                if (!isset($this->adhocNamespaces[$namespace])) {
267*a1a3b679SAndreas Boehler                    $this->adhocNamespaces[$namespace] = 'x' . (count($this->adhocNamespaces) + 1);
268*a1a3b679SAndreas Boehler                }
269*a1a3b679SAndreas Boehler                $this->writeAttributeNS(
270*a1a3b679SAndreas Boehler                    $this->adhocNamespaces[$namespace],
271*a1a3b679SAndreas Boehler                    $localName,
272*a1a3b679SAndreas Boehler                    $namespace,
273*a1a3b679SAndreas Boehler                    $value
274*a1a3b679SAndreas Boehler                );
275*a1a3b679SAndreas Boehler
276*a1a3b679SAndreas Boehler            }
277*a1a3b679SAndreas Boehler
278*a1a3b679SAndreas Boehler        } else {
279*a1a3b679SAndreas Boehler            return parent::writeAttribute($name, $value);
280*a1a3b679SAndreas Boehler        }
281*a1a3b679SAndreas Boehler
282*a1a3b679SAndreas Boehler    }
283*a1a3b679SAndreas Boehler
284*a1a3b679SAndreas Boehler}
285