Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "xmldom.h"
23 :
24 : #include <algorithm>
25 : #include <cstring>
26 : #include <type_traits>
27 :
28 : #include "pugixml.hpp"
29 :
30 : #include "log.h"
31 :
32 : using namespace muse;
33 :
34 : struct muse::XmlDomImplData
35 : {
36 : pugi::xml_document doc;
37 : pugi::xml_parse_result result{};
38 : bool triedload = false;
39 : };
40 :
41 : using xml_node_impl = pugi::xml_node;
42 : using xml_attr_impl = pugi::xml_attribute;
43 :
44 : // ================================================
45 : // generic pack/unpack
46 : // ================================================
47 :
48 : namespace {
49 : template<class T>
50 136239 : inline xml_handle pack_handle(const T& t) noexcept
51 : {
52 : static_assert(std::is_trivially_copyable_v<T>,
53 : "Backend handle must be trivially copyable");
54 : static_assert(sizeof(T) <= sizeof(xml_handle),
55 : "Increase xml_handle slots/size");
56 136239 : xml_handle h{}; // zero both slots
57 136239 : const auto* src = reinterpret_cast<const unsigned char*>(&t);
58 136239 : auto* dst = reinterpret_cast<unsigned char*>(&h);
59 136239 : std::copy_n(src, sizeof(T), dst);
60 136239 : return h;
61 : }
62 :
63 : template<class T>
64 216296 : inline T unpack_handle(xml_handle h) noexcept
65 : {
66 : static_assert(std::is_trivially_copyable_v<T>,
67 : "Backend handle must be trivially copyable");
68 : static_assert(sizeof(T) <= sizeof(xml_handle),
69 : "Increase xml_handle slots/size");
70 216296 : T t{}; // zero-init destination
71 216296 : const auto* src = reinterpret_cast<const unsigned char*>(&h);
72 216296 : auto* dst = reinterpret_cast<unsigned char*>(&t);
73 216296 : std::copy_n(src, sizeof(T), dst);
74 216296 : return t;
75 : }
76 : } // anonymous namespace
77 :
78 : // ================================================
79 : // XmlDomNode
80 : // ================================================
81 :
82 117165 : XmlDomNode::XmlDomNode(const std::shared_ptr<XmlDomImplData>& xml, xml_node_handle node)
83 117165 : : m_xml(xml), m_node(node)
84 : {
85 117165 : }
86 :
87 64902 : bool XmlDomNode::isNull() const
88 : {
89 64902 : return !static_cast<bool>(m_node);
90 : }
91 :
92 37058 : String XmlDomNode::nodeName() const
93 : {
94 37058 : if (!m_node) {
95 0 : return String();
96 : }
97 :
98 37058 : xml_node_impl n = unpack_handle<xml_node_impl>(m_node);
99 37058 : switch (n.type()) {
100 37058 : case pugi::node_element:
101 : case pugi::node_pi:
102 : case pugi::node_declaration:
103 : case pugi::node_doctype:
104 : case pugi::node_document: // usually empty
105 37058 : return String::fromUtf8(n.name());
106 0 : case pugi::node_pcdata:
107 : case pugi::node_cdata:
108 0 : return String::fromUtf8(n.value());
109 0 : case pugi::node_comment:
110 0 : return String::fromUtf8(n.value());
111 0 : case pugi::node_null:
112 : default:
113 0 : return String();
114 : }
115 : }
116 :
117 305 : bool XmlDomNode::hasChildNodes() const
118 : {
119 305 : return m_node ? static_cast<bool>(unpack_handle<xml_node_impl>(m_node).first_child()) : false;
120 : }
121 :
122 21026 : XmlDomNode XmlDomNode::firstChild() const
123 : {
124 21026 : if (!m_node) {
125 76 : return XmlDomNode(m_xml, xml_node_handle());
126 : }
127 20950 : xml_node_impl n = unpack_handle<xml_node_impl>(m_node).first_child();
128 20950 : return XmlDomNode(m_xml, pack_handle(n));
129 : }
130 :
131 7300 : XmlDomElement XmlDomNode::firstChildElement(const char* name) const
132 : {
133 7300 : if (!m_node) {
134 0 : return XmlDomElement(m_xml, xml_node_handle());
135 : }
136 7300 : xml_node_impl n = unpack_handle<xml_node_impl>(m_node);
137 7300 : xml_node_impl c = name ? n.child(name)
138 5 : : n.find_child([](xml_node_impl x){ return x.type() == pugi::node_element; });
139 7300 : if (c && c.type() == pugi::node_element) {
140 6425 : return XmlDomElement(m_xml, pack_handle(c));
141 : }
142 875 : return XmlDomElement(m_xml, xml_node_handle());
143 : }
144 :
145 49224 : XmlDomNode XmlDomNode::nextSibling() const
146 : {
147 49224 : if (!m_node) {
148 0 : return XmlDomNode(m_xml, xml_node_handle());
149 : }
150 49224 : xml_node_impl n = unpack_handle<xml_node_impl>(m_node).next_sibling();
151 49224 : return XmlDomNode(m_xml, pack_handle(n));
152 : }
153 :
154 0 : XmlDomNode XmlDomNode::previousSibling() const
155 : {
156 0 : if (!m_node) {
157 0 : return XmlDomNode(m_xml, xml_node_handle());
158 : }
159 0 : xml_node_impl n = unpack_handle<xml_node_impl>(m_node).previous_sibling();
160 0 : return XmlDomNode(m_xml, pack_handle(n));
161 : }
162 :
163 1 : XmlDomNode XmlDomNode::parent() const
164 : {
165 1 : if (!m_node) {
166 0 : return XmlDomNode(m_xml, xml_node_handle());
167 : }
168 1 : xml_node_impl n = unpack_handle<xml_node_impl>(m_node).parent();
169 1 : return XmlDomNode(m_xml, pack_handle(n));
170 : }
171 :
172 1 : XmlDomElement XmlDomNode::nextSiblingElement(const char* name) const
173 : {
174 1 : if (!m_node) {
175 0 : return XmlDomElement(m_xml, xml_node_handle());
176 : }
177 1 : xml_node_impl n = unpack_handle<xml_node_impl>(m_node);
178 1 : xml_node_impl s = name ? n.next_sibling(name) : n.next_sibling();
179 1 : if (!name) {
180 2 : while (s && s.type() != pugi::node_element) {
181 1 : s = s.next_sibling();
182 : }
183 : }
184 1 : if (s && s.type() == pugi::node_element) {
185 1 : return XmlDomElement(m_xml, pack_handle(s));
186 : }
187 0 : return XmlDomElement(m_xml, xml_node_handle());
188 : }
189 :
190 1 : XmlDomElement XmlDomNode::previousSiblingElement(const char* name) const
191 : {
192 1 : if (!m_node) {
193 0 : return XmlDomElement(m_xml, xml_node_handle());
194 : }
195 1 : xml_node_impl n = unpack_handle<xml_node_impl>(m_node);
196 1 : xml_node_impl s = name ? n.previous_sibling(name) : n.previous_sibling();
197 1 : if (!name) {
198 2 : while (s && s.type() != pugi::node_element) {
199 1 : s = s.previous_sibling();
200 : }
201 : }
202 1 : if (s && s.type() == pugi::node_element) {
203 1 : return XmlDomElement(m_xml, pack_handle(s));
204 : }
205 0 : return XmlDomElement(m_xml, xml_node_handle());
206 : }
207 :
208 39386 : XmlDomElement XmlDomNode::toElement() const
209 : {
210 39386 : if (!m_node) {
211 0 : return XmlDomElement(m_xml, xml_node_handle());
212 : }
213 39386 : xml_node_impl n = unpack_handle<xml_node_impl>(m_node);
214 39386 : if (n.type() == pugi::node_element) {
215 39386 : return XmlDomElement(m_xml, pack_handle(n));
216 : }
217 0 : return XmlDomElement(m_xml, xml_node_handle());
218 : }
219 :
220 : // ================================================
221 : // XmlDomAttribute
222 : // ================================================
223 :
224 20025 : XmlDomAttribute::XmlDomAttribute(const std::shared_ptr<XmlDomImplData>& data, xml_attr_handle attribute)
225 20025 : : m_xml(data), m_attribute(attribute)
226 : {
227 20025 : }
228 :
229 7 : bool XmlDomAttribute::isNull() const
230 : {
231 7 : return !static_cast<bool>(m_attribute);
232 : }
233 :
234 1 : String XmlDomAttribute::attributeName() const
235 : {
236 1 : if (!m_attribute) {
237 0 : return String();
238 : }
239 1 : xml_attr_impl a = unpack_handle<xml_attr_impl>(m_attribute);
240 1 : return String::fromUtf8(a.name());
241 : }
242 :
243 20021 : String XmlDomAttribute::value() const
244 : {
245 20021 : if (!m_attribute) {
246 172 : return String();
247 : }
248 19849 : xml_attr_impl a = unpack_handle<xml_attr_impl>(m_attribute);
249 19849 : return String::fromUtf8(a.value());
250 : }
251 :
252 3 : XmlDomAttribute XmlDomAttribute::nextAttribute() const
253 : {
254 3 : if (!m_attribute) {
255 0 : return XmlDomAttribute(m_xml, xml_attr_handle());
256 : }
257 3 : xml_attr_impl a = unpack_handle<xml_attr_impl>(m_attribute).next_attribute();
258 3 : return XmlDomAttribute(m_xml, pack_handle(a));
259 : }
260 :
261 : // ================================================
262 : // XmlDomElement
263 : // ================================================
264 :
265 46914 : XmlDomElement::XmlDomElement(const std::shared_ptr<XmlDomImplData>& data, xml_node_handle node)
266 46914 : : XmlDomNode(data, node)
267 : {
268 46914 : }
269 :
270 22390 : String XmlDomElement::text() const
271 : {
272 22390 : xml_node_impl e = unpack_handle<xml_node_impl>(m_node);
273 22390 : if (!e || e.type() != pugi::node_element) {
274 0 : return String();
275 : }
276 22390 : String result;
277 65296 : for (xml_node_impl c = e.first_child(); c; c = c.next_sibling()) {
278 21453 : const pugi::xml_node_type t = c.type();
279 21453 : if (t == pugi::node_pcdata || t == pugi::node_cdata) {
280 21452 : result += String::fromUtf8(c.value());
281 : }
282 : }
283 22390 : return result;
284 22390 : }
285 :
286 1 : XmlDomAttribute XmlDomElement::firstAttribute() const
287 : {
288 1 : if (!m_node) {
289 0 : return XmlDomAttribute(m_xml, xml_attr_handle());
290 : }
291 1 : xml_node_impl e = unpack_handle<xml_node_impl>(m_node);
292 1 : if (e && e.type() == pugi::node_element) {
293 1 : return XmlDomAttribute(m_xml, pack_handle(e.first_attribute()));
294 : }
295 0 : return XmlDomAttribute(m_xml, xml_attr_handle());
296 : }
297 :
298 20021 : XmlDomAttribute XmlDomElement::attribute(const char* name) const
299 : {
300 20021 : if (!m_node) {
301 0 : return XmlDomAttribute(m_xml, xml_attr_handle());
302 : }
303 20021 : xml_node_impl e = unpack_handle<pugi::xml_node>(m_node);
304 20021 : if (e && e.type() == pugi::node_element) {
305 20021 : return XmlDomAttribute(m_xml, pack_handle(e.attribute(name)));
306 : }
307 0 : return XmlDomAttribute(m_xml, xml_attr_handle());
308 : }
309 :
310 : // ================================================
311 : // XmlDomDocument
312 : // ================================================
313 :
314 228 : XmlDomDocument::XmlDomDocument()
315 : {
316 228 : m_xml = std::make_shared<XmlDomImplData>();
317 228 : }
318 :
319 228 : void XmlDomDocument::setContent(const ByteArray& data)
320 : {
321 230 : TRACEFUNC;
322 :
323 228 : m_xml->doc.reset();
324 228 : m_xml->result = m_xml->doc.load_buffer(data.constData(), data.size());
325 228 : m_xml->triedload = true;
326 :
327 228 : if (m_xml->result.status != pugi::status_ok) {
328 2 : LOGE() << errorString();
329 : }
330 228 : }
331 :
332 226 : XmlDomElement XmlDomDocument::rootElement() const
333 : {
334 226 : xml_node_impl e = m_xml->doc.document_element();
335 452 : return XmlDomElement(m_xml, pack_handle(e));
336 : }
337 :
338 3 : bool XmlDomDocument::hasError() const
339 : {
340 3 : return m_xml->triedload && m_xml->result.status != pugi::status_ok;
341 : }
342 :
343 3 : String XmlDomDocument::errorString() const
344 : {
345 3 : if (m_xml->triedload) {
346 3 : return String::fromUtf8(m_xml->result.description());
347 : }
348 0 : return String();
349 : }
|