Commit | Line | Data |
---|---|---|
6f5d6b49 | 1 | <?php |
a9229942 | 2 | |
6f5d6b49 | 3 | namespace wcf\util; |
a9229942 | 4 | |
6f5d6b49 AE |
5 | use wcf\system\exception\SystemException; |
6 | ||
7 | /** | |
8 | * Writes XML documents. | |
a9229942 TD |
9 | * |
10 | * @author Alexander Ebert | |
11 | * @copyright 2001-2019 WoltLab GmbH | |
12 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
6f5d6b49 | 13 | */ |
a9229942 TD |
14 | class XMLWriter |
15 | { | |
16 | /** | |
17 | * true, if a document is still processed | |
18 | * @var bool | |
19 | */ | |
20 | protected $activeDocument = false; | |
21 | ||
22 | /** | |
23 | * number of open elements | |
24 | * @var int | |
25 | */ | |
26 | protected $openElements = 0; | |
27 | ||
28 | /** | |
29 | * XMLWriter object | |
30 | * @var \XMLWriter | |
31 | */ | |
32 | protected $xml; | |
33 | ||
34 | /** | |
35 | * Creates a new XML document. | |
36 | * | |
37 | * @param string $rootElement | |
38 | * @param string $namespace | |
39 | * @param string $schemaLocation | |
40 | * @param string[] $attributes | |
41 | * @throws SystemException | |
42 | */ | |
43 | public function beginDocument($rootElement, $namespace, $schemaLocation, array $attributes = []) | |
44 | { | |
45 | if ($this->activeDocument) { | |
46 | throw new SystemException("Could not begin a new document unless the previous is finished"); | |
47 | } | |
48 | ||
49 | if ($this->xml === null) { | |
50 | $this->xml = new \XMLWriter(); | |
51 | $this->xml->openMemory(); | |
52 | $this->xml->setIndent(true); | |
53 | $this->xml->setIndentString("\t"); | |
54 | } | |
55 | ||
56 | $this->xml->startDocument('1.0', 'UTF-8'); | |
57 | $this->startElement($rootElement); | |
58 | $attributes = \array_merge( | |
59 | [ | |
60 | 'xmlns' => $namespace, | |
61 | 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', | |
62 | 'xsi:schemaLocation' => $namespace . ' ' . $schemaLocation, | |
63 | ], | |
64 | // `xmlns`, `xmlns:xsi`, and `xsi:schemaLocation` are explicitly set | |
65 | // as first attributes in that order | |
66 | \array_filter($attributes, static function ($attributeName) { | |
67 | return !\in_array($attributeName, ['xmlns', 'xmlns:xsi', 'xsi:schemaLocation']); | |
68 | }, \ARRAY_FILTER_USE_KEY) | |
69 | ); | |
70 | $this->writeAttributes($attributes); | |
71 | ||
72 | $this->activeDocument = true; | |
73 | } | |
74 | ||
75 | /** | |
76 | * Returns the generated XML document or writes it to given filename. All open | |
77 | * elements will be automatically closed before flushing. | |
78 | * | |
79 | * @param string $filename | |
80 | * @return mixed | |
81 | */ | |
82 | public function endDocument($filename = '') | |
83 | { | |
84 | // mark document as done | |
85 | $this->activeDocument = false; | |
86 | ||
87 | // close all open tags | |
88 | while ($this->openElements) { | |
89 | $this->endElement(); | |
90 | } | |
91 | ||
92 | if (empty($filename)) { | |
93 | // return XML as string | |
94 | return $this->xml->flush(true); | |
95 | } else { | |
96 | // write to file | |
97 | \file_put_contents($filename, $this->xml->flush(true)); | |
98 | } | |
99 | } | |
100 | ||
101 | /** | |
102 | * Begins a new element. | |
103 | * | |
104 | * @param string $element | |
105 | * @param string[] $attributes | |
106 | */ | |
107 | public function startElement($element, array $attributes = []) | |
108 | { | |
109 | $this->xml->startElement($element); | |
110 | $this->openElements++; | |
111 | ||
112 | if (!empty($attributes)) { | |
113 | $this->writeAttributes($attributes); | |
114 | } | |
115 | } | |
116 | ||
117 | /** | |
118 | * Ends the last opened element. | |
119 | */ | |
120 | public function endElement() | |
121 | { | |
122 | if ($this->openElements) { | |
123 | $this->xml->endElement(); | |
124 | $this->openElements--; | |
125 | } | |
126 | } | |
127 | ||
128 | /** | |
129 | * Writes an element directly. | |
130 | * | |
131 | * @param string $element | |
132 | * @param string $cdata | |
133 | * @param string[] $attributes | |
134 | * @param bool $writeAsCdata | |
135 | */ | |
136 | public function writeElement($element, $cdata, array $attributes = [], $writeAsCdata = true) | |
137 | { | |
138 | $this->startElement($element); | |
139 | ||
140 | // write attributes | |
141 | if (!empty($attributes)) { | |
142 | $this->writeAttributes($attributes); | |
143 | } | |
144 | ||
145 | // content | |
146 | if ($cdata !== '') { | |
147 | if ($writeAsCdata) { | |
148 | $this->xml->writeCdata(StringUtil::escapeCDATA($cdata)); | |
149 | } else { | |
150 | $this->xml->text($cdata); | |
151 | } | |
152 | } | |
153 | ||
154 | $this->endElement(); | |
155 | } | |
156 | ||
157 | /** | |
158 | * Writes a comment. | |
159 | * | |
160 | * @param string $comment | |
161 | * @since 5.2 | |
162 | */ | |
163 | public function writeComment($comment) | |
164 | { | |
165 | $this->xml->writeComment($comment); | |
166 | } | |
167 | ||
168 | /** | |
169 | * Writes an attribute to last opened element. | |
170 | * | |
171 | * @param string $attribute | |
172 | * @param string $value | |
173 | */ | |
174 | public function writeAttribute($attribute, $value) | |
175 | { | |
176 | $this->writeAttributes([$attribute => $value]); | |
177 | } | |
178 | ||
179 | /** | |
180 | * Writes a list of attributes to last opened element. | |
181 | * | |
182 | * @param string[] $attributes | |
183 | */ | |
184 | public function writeAttributes(array $attributes) | |
185 | { | |
186 | foreach ($attributes as $attribute => $value) { | |
187 | $this->xml->writeAttribute($attribute, $value); | |
188 | } | |
189 | } | |
6f5d6b49 | 190 | } |