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 "xmlstreamreader.h"
23 :
24 : #include <cstring>
25 : #include <sstream>
26 :
27 : #include "pugixml.hpp"
28 :
29 : #include "log.h"
30 :
31 : using namespace muse;
32 : using namespace muse::io;
33 :
34 : struct XmlStreamReader::Xml {
35 : pugi::xml_document doc;
36 : pugi::xml_node node{};
37 : pugi::xml_parse_result result{};
38 : String customErr;
39 : };
40 :
41 742 : XmlStreamReader::XmlStreamReader()
42 : {
43 742 : m_xml = new Xml();
44 742 : }
45 :
46 1625 : XmlStreamReader::XmlStreamReader(IODevice* device)
47 : {
48 1625 : m_xml = new Xml();
49 1625 : ByteArray data = device->readAll();
50 1625 : setData(data);
51 1625 : }
52 :
53 2236 : XmlStreamReader::XmlStreamReader(const ByteArray& data)
54 : {
55 2236 : m_xml = new Xml();
56 2236 : setData(data);
57 2236 : }
58 :
59 : #ifndef NO_QT_SUPPORT
60 0 : XmlStreamReader::XmlStreamReader(const QByteArray& data)
61 : {
62 0 : m_xml = new Xml();
63 0 : ByteArray ba = ByteArray::fromQByteArrayNoCopy(data);
64 0 : setData(ba);
65 0 : }
66 :
67 : #endif
68 :
69 4603 : XmlStreamReader::~XmlStreamReader()
70 : {
71 4603 : delete m_xml;
72 4603 : }
73 :
74 4603 : void XmlStreamReader::setData(const ByteArray& data_)
75 : {
76 4622 : TRACEFUNC;
77 :
78 4603 : m_xml->doc.reset();
79 4603 : m_xml->customErr.clear();
80 4603 : m_token = TokenType::Invalid;
81 :
82 4603 : if (data_.size() < 4) {
83 0 : m_xml->result = pugi::xml_parse_result{}; // zero it
84 0 : m_xml->result.status = pugi::status_no_document_element;
85 0 : m_xml->customErr = String(u"empty document");
86 0 : LOGE() << m_xml->customErr;
87 0 : return;
88 : }
89 :
90 4603 : UtfCodec::Encoding enc = UtfCodec::xmlEncoding(data_);
91 4603 : if (enc == UtfCodec::Encoding::Unknown) {
92 0 : m_xml->result = pugi::xml_parse_result{};
93 0 : m_xml->result.status = pugi::status_internal_error;
94 0 : m_xml->customErr = String(u"unknown encoding");
95 0 : LOGE() << m_xml->customErr;
96 0 : return;
97 : }
98 :
99 4603 : ByteArray data = data_; // no copy, implicit sharing
100 4603 : if (enc == UtfCodec::Encoding::UTF_16LE) {
101 28 : String u16 = String::fromUtf16LE(data_);
102 28 : data = u16.toUtf8();
103 4603 : } else if (enc == UtfCodec::Encoding::UTF_16BE) {
104 0 : String u16 = String::fromUtf16BE(data_);
105 0 : data = u16.toUtf8();
106 0 : }
107 :
108 : // pugi needs explicit flags to surface declaration/doctype/comments as nodes
109 4603 : unsigned flags = pugi::parse_default
110 : | pugi::parse_declaration
111 : | pugi::parse_doctype
112 : | pugi::parse_comments;
113 4603 : m_xml->result = m_xml->doc.load_buffer(data.constData(), data.size(), flags);
114 :
115 4603 : if (m_xml->result.status == pugi::status_ok) {
116 4602 : m_token = TokenType::NoToken;
117 : } else {
118 1 : LOGE() << String::fromUtf8(m_xml->result.description());
119 : }
120 4603 : }
121 :
122 1770775 : bool XmlStreamReader::readNextStartElement()
123 : {
124 1830663 : while (readNext() != Invalid) {
125 1829798 : if (isEndElement()) {
126 358428 : return false;
127 1471370 : } else if (isStartElement()) {
128 1411482 : return true;
129 : }
130 : }
131 865 : return false;
132 : }
133 :
134 461739 : bool XmlStreamReader::atEnd() const
135 : {
136 461739 : return m_token == TokenType::EndDocument || m_token == TokenType::Invalid;
137 : }
138 :
139 4258260 : static XmlStreamReader::TokenType resolveToken(pugi::xml_node n, bool isStartElement)
140 : {
141 4258260 : switch (n.type()) {
142 3180370 : case pugi::node_element:
143 : return isStartElement
144 3180370 : ? XmlStreamReader::TokenType::StartElement
145 3180370 : : XmlStreamReader::TokenType::EndElement;
146 :
147 1021481 : case pugi::node_pcdata:
148 : case pugi::node_cdata:
149 1021481 : return XmlStreamReader::TokenType::Characters;
150 :
151 53535 : case pugi::node_comment:
152 53535 : return XmlStreamReader::TokenType::Comment;
153 :
154 0 : case pugi::node_declaration: // <?xml ... ?>
155 0 : return XmlStreamReader::TokenType::StartDocument;
156 :
157 1340 : case pugi::node_document: // the document node
158 1340 : return XmlStreamReader::TokenType::EndDocument;
159 :
160 1534 : case pugi::node_doctype: // <!DOCTYPE ...>
161 1534 : return XmlStreamReader::TokenType::DTD;
162 :
163 0 : default: // includes node_pi, node_null, etc.
164 0 : return XmlStreamReader::TokenType::Unknown;
165 : }
166 : }
167 :
168 : static std::pair<pugi::xml_node, XmlStreamReader::TokenType>
169 4454439 : resolveNode(pugi::xml_node currentNode, XmlStreamReader::TokenType currentToken)
170 : {
171 4454439 : if (currentToken == XmlStreamReader::TokenType::StartElement) {
172 1688331 : pugi::xml_node child = currentNode.first_child();
173 1688331 : if (child) {
174 1492063 : return { child, resolveToken(child, /*isStartElement=*/ true) };
175 : }
176 :
177 196268 : pugi::xml_node sibling = currentNode.next_sibling();
178 196268 : if (!sibling
179 178535 : || sibling.type() == pugi::node_element
180 20148 : || sibling.type() == pugi::node_pcdata
181 19652 : || sibling.type() == pugi::node_cdata
182 374803 : || sibling.type() == pugi::node_comment) {
183 : // Mirror tiny: close the element before stepping to "content" siblings
184 196268 : return { currentNode, XmlStreamReader::TokenType::EndElement };
185 : }
186 : // else: fall through to handle non-content siblings (doctype, declaration, etc.)
187 : }
188 :
189 2766108 : pugi::xml_node sibling = currentNode.next_sibling();
190 2766108 : if (sibling) {
191 1272731 : return { sibling, resolveToken(sibling, /*isStartElement=*/ true) };
192 : }
193 :
194 1493377 : pugi::xml_node parent = currentNode.parent();
195 1493377 : if (parent) {
196 1493377 : return { parent, resolveToken(parent, /*isStartElement=*/ false) };
197 : }
198 :
199 0 : return { pugi::xml_node(), XmlStreamReader::TokenType::EndDocument };
200 : }
201 :
202 4459907 : XmlStreamReader::TokenType XmlStreamReader::readNext()
203 : {
204 4459907 : if (m_token == TokenType::Invalid) {
205 1 : return m_token;
206 : }
207 :
208 4459906 : if (m_xml->result.status != pugi::status_ok || m_token == TokenType::EndDocument) {
209 865 : m_xml->node = pugi::xml_node();
210 865 : m_token = TokenType::Invalid;
211 865 : return m_token;
212 : }
213 :
214 4459041 : if (!m_xml->node) {
215 4602 : m_xml->node = m_xml->doc.first_child();
216 4602 : if (!m_xml->node) {
217 : // Empty doc — treat as end
218 0 : m_token = TokenType::EndDocument;
219 0 : return m_token;
220 : }
221 4602 : m_token = (m_xml->node.type() == pugi::node_declaration)
222 4602 : ? TokenType::StartDocument
223 89 : : resolveToken(m_xml->node, /*isStartElement=*/ true);
224 4602 : return m_token;
225 : }
226 :
227 4454439 : std::pair<pugi::xml_node, XmlStreamReader::TokenType> p = resolveNode(m_xml->node, m_token);
228 :
229 4454439 : m_xml->node = p.first;
230 4454439 : m_token = p.second;
231 :
232 4454439 : if (m_token == TokenType::DTD) {
233 1534 : tryParseEntity(m_xml);
234 : }
235 :
236 4454439 : return m_token;
237 : }
238 :
239 1534 : void XmlStreamReader::tryParseEntity(Xml* xml)
240 : {
241 1534 : const char* nodeValue = xml->node.value();
242 :
243 1534 : if (!nodeValue || *nodeValue == '\0') {
244 0 : return;
245 : }
246 :
247 1534 : const char* scanPos = nodeValue;
248 : while (true) {
249 2525 : const char* entityPos = std::strstr(scanPos, "ENTITY");
250 2525 : if (!entityPos) {
251 1534 : break;
252 : }
253 :
254 991 : const char* cur = entityPos + 6; // after "ENTITY"
255 :
256 : // Skip whitespace
257 1985 : while (*cur == ' ' || *cur == '\t' || *cur == '\r' || *cur == '\n') {
258 994 : ++cur;
259 : }
260 :
261 : // Optional leading '%' (parameter entity)
262 991 : if (*cur == '%') {
263 1 : ++cur;
264 4 : while (*cur == ' ' || *cur == '\t' || *cur == '\r' || *cur == '\n') {
265 3 : ++cur;
266 : }
267 : }
268 :
269 : // Name token: up to whitespace / quote / '>'
270 991 : const char* nameBegin = cur;
271 991 : while (*cur
272 6212 : && *cur != ' ' && *cur != '\t' && *cur != '\r' && *cur != '\n'
273 11433 : && *cur != '"' && *cur != '\'' && *cur != '>') {
274 5221 : ++cur;
275 : }
276 991 : const char* nameEnd = cur;
277 :
278 : // Handle a leading '%' glued to the name (parameter entity without a space)
279 991 : if (nameBegin < nameEnd && *nameBegin == '%') {
280 0 : ++nameBegin;
281 0 : while (nameBegin < nameEnd
282 0 : && (*nameBegin == ' ' || *nameBegin == '\t' || *nameBegin == '\r' || *nameBegin == '\n')) {
283 0 : ++nameBegin;
284 : }
285 : }
286 :
287 : // Find first quote (either ' or ")
288 2336 : while (*cur && *cur != '"' && *cur != '\'') {
289 1345 : ++cur;
290 : }
291 991 : if (*cur != '"' && *cur != '\'') {
292 0 : scanPos = entityPos + 6;
293 0 : continue;
294 : }
295 :
296 : // Capture quoted value
297 991 : const char quoteChar = *cur++;
298 991 : const char* valueBegin = cur;
299 5597 : while (*cur && *cur != quoteChar) {
300 4606 : ++cur;
301 : }
302 991 : if (*cur != quoteChar) {
303 0 : scanPos = entityPos + 6;
304 0 : continue;
305 : }
306 991 : const char* valueEnd = cur;
307 991 : ++cur; // past closing quote
308 :
309 : // Build name/value:
310 : // - DO NOT remove "SYSTEM"/"PUBLIC" from the name
311 : // - Trim whitespace
312 991 : std::string nameToken(nameBegin, static_cast<size_t>(nameEnd - nameBegin));
313 991 : String name = String::fromUtf8(nameToken.c_str()).trimmed();
314 :
315 991 : std::string valueToken(valueBegin, static_cast<size_t>(valueEnd - valueBegin));
316 991 : String value = String::fromUtf8(valueToken.c_str());
317 :
318 991 : if (!name.empty()) {
319 991 : m_entities[u'&' + name + u';'] = value;
320 : } else {
321 0 : LOGW() << "Ignoring malformed ENTITY: " << nodeValue;
322 : }
323 :
324 991 : scanPos = cur; // continue scanning for more ENTITY decls
325 1982 : }
326 : }
327 :
328 : // emulate tinyxml2::XMLNode::Value
329 478853 : String XmlStreamReader::nodeValue(Xml* xml) const
330 : {
331 478853 : const pugi::xml_node n = xml->node;
332 :
333 478853 : const char* raw = "";
334 478853 : switch (n.type()) {
335 0 : case pugi::node_element:
336 : case pugi::node_pi:
337 : case pugi::node_declaration:
338 : case pugi::node_doctype:
339 : case pugi::node_document: // usually empty
340 0 : raw = n.name();
341 0 : break;
342 :
343 478853 : case pugi::node_pcdata:
344 : case pugi::node_cdata:
345 : case pugi::node_comment:
346 478853 : raw = n.value();
347 478853 : break;
348 :
349 0 : default:
350 0 : break;
351 : }
352 :
353 478853 : String str = String::fromUtf8(raw);
354 478853 : if (!m_entities.empty()) {
355 320595 : for (const auto& p : m_entities) {
356 170199 : str.replace(p.first, p.second);
357 : }
358 : }
359 478853 : return str;
360 : }
361 :
362 4687586 : XmlStreamReader::TokenType XmlStreamReader::tokenType() const
363 : {
364 4687586 : return m_token;
365 : }
366 :
367 0 : AsciiStringView XmlStreamReader::tokenString() const
368 : {
369 0 : switch (m_token) {
370 0 : case TokenType::NoToken: return "NoToken";
371 0 : case TokenType::Invalid: return "Invalid";
372 0 : case TokenType::StartDocument: return "StartDocument";
373 0 : case TokenType::EndDocument: return "EndDocument";
374 0 : case TokenType::StartElement: return "StartElement";
375 0 : case TokenType::EndElement: return "EndElement";
376 0 : case TokenType::Characters: return "Characters";
377 0 : case TokenType::Comment: return "Comment";
378 0 : case TokenType::DTD: return "DTD";
379 0 : case TokenType::Unknown: return "Unknown";
380 : }
381 0 : return AsciiStringView();
382 : }
383 :
384 7056 : bool XmlStreamReader::isWhitespace() const
385 : {
386 7056 : return false;
387 : }
388 :
389 40219 : void XmlStreamReader::skipCurrentElement()
390 : {
391 40219 : int depth = 1;
392 345657 : while (depth && readNext() != Invalid) {
393 305438 : if (isEndElement()) {
394 136924 : --depth;
395 168514 : } else if (isStartElement()) {
396 96705 : ++depth;
397 : }
398 : }
399 40219 : }
400 :
401 3130632 : AsciiStringView XmlStreamReader::name() const
402 : {
403 6261264 : return (m_xml->node && m_xml->node.type() == pugi::node_element)
404 3130632 : ? AsciiStringView(m_xml->node.name())
405 3130632 : : AsciiStringView();
406 : }
407 :
408 195107 : bool XmlStreamReader::hasAttribute(const char* name) const
409 : {
410 195107 : if (m_token != TokenType::StartElement) {
411 64 : return false;
412 : }
413 :
414 195043 : if (!m_xml->node || m_xml->node.type() != pugi::node_element) {
415 0 : return false;
416 : }
417 :
418 195043 : return m_xml->node.attribute(name);
419 : }
420 :
421 671890 : String XmlStreamReader::attribute(const char* name) const
422 : {
423 671890 : if (m_token != TokenType::StartElement) {
424 16 : return String();
425 : }
426 :
427 671874 : if (!m_xml->node || m_xml->node.type() != pugi::node_element) {
428 0 : return String();
429 : }
430 :
431 671874 : pugi::xml_attribute attr = m_xml->node.attribute(name);
432 671874 : if (!attr) {
433 258564 : return String();
434 : }
435 :
436 413310 : return String::fromUtf8(attr.value());
437 : }
438 :
439 10612 : String XmlStreamReader::attribute(const char* name, const String& def) const
440 : {
441 10612 : return hasAttribute(name) ? attribute(name) : def;
442 : }
443 :
444 222120 : AsciiStringView XmlStreamReader::asciiAttribute(const char* name) const
445 : {
446 222120 : if (m_token != TokenType::StartElement) {
447 0 : return AsciiStringView();
448 : }
449 :
450 222120 : if (!m_xml->node || m_xml->node.type() != pugi::node_element) {
451 0 : return AsciiStringView();
452 : }
453 :
454 222120 : pugi::xml_attribute attr = m_xml->node.attribute(name);
455 222120 : return attr ? AsciiStringView(attr.value()) : AsciiStringView();
456 : }
457 :
458 765 : AsciiStringView XmlStreamReader::asciiAttribute(const char* name, const AsciiStringView& def) const
459 : {
460 765 : return hasAttribute(name) ? asciiAttribute(name) : def;
461 : }
462 :
463 109801 : int XmlStreamReader::intAttribute(const char* name) const
464 : {
465 109801 : return asciiAttribute(name).toInt();
466 : }
467 :
468 133339 : int XmlStreamReader::intAttribute(const char* name, int def) const
469 : {
470 133339 : return hasAttribute(name) ? intAttribute(name) : def;
471 : }
472 :
473 27097 : double XmlStreamReader::doubleAttribute(const char* name) const
474 : {
475 27097 : return asciiAttribute(name).toDouble();
476 : }
477 :
478 17861 : double XmlStreamReader::doubleAttribute(const char* name, double def) const
479 : {
480 17861 : return hasAttribute(name) ? doubleAttribute(name) : def;
481 : }
482 :
483 2443 : std::vector<XmlStreamReader::Attribute> XmlStreamReader::attributes() const
484 : {
485 2443 : std::vector<Attribute> attrs;
486 2443 : if (m_token != TokenType::StartElement) {
487 5 : return attrs;
488 : }
489 :
490 2438 : if (!m_xml->node || m_xml->node.type() != pugi::node_element) {
491 0 : return attrs;
492 : }
493 :
494 5966 : for (pugi::xml_attribute xa = m_xml->node.first_attribute(); xa; xa = xa.next_attribute()) {
495 1764 : Attribute a;
496 1764 : a.name = xa.name();
497 1764 : a.value = String::fromUtf8(xa.value());
498 1764 : attrs.push_back(std::move(a));
499 1764 : }
500 :
501 2438 : return attrs;
502 0 : }
503 :
504 1572 : bool XmlStreamReader::noChildren() const
505 : {
506 1572 : return m_xml->node ? !m_xml->node.first_child() && !m_xml->node.last_child() : false;
507 : }
508 :
509 6248 : String XmlStreamReader::readBody() const
510 : {
511 6248 : if (!m_xml->node) {
512 0 : return String();
513 : }
514 :
515 6248 : std::ostringstream oss;
516 :
517 6248 : for (pugi::xml_node child = m_xml->node.first_child();
518 14812 : child;
519 8564 : child = child.next_sibling()) {
520 8564 : if (child.type() == pugi::node_element) {
521 : // Match tinyxml2::XMLPrinter default (no indentation/line breaks)
522 8564 : child.print(oss, "", pugi::format_raw);
523 : }
524 : }
525 :
526 6248 : return String::fromStdString(oss.str());
527 6248 : }
528 :
529 7821 : String XmlStreamReader::text() const
530 : {
531 7821 : if (m_xml->node) {
532 7821 : pugi::xml_node_type t = m_xml->node.type();
533 7821 : if (t == pugi::node_pcdata || t == pugi::node_cdata || t == pugi::node_comment) {
534 7821 : return nodeValue(m_xml);
535 : }
536 : }
537 0 : return String();
538 : }
539 :
540 7 : AsciiStringView XmlStreamReader::asciiText() const
541 : {
542 7 : if (m_xml->node) {
543 7 : pugi::xml_node_type t = m_xml->node.type();
544 7 : if (t == pugi::node_pcdata || t == pugi::node_cdata || t == pugi::node_comment) {
545 7 : return m_xml->node.value();
546 : }
547 : }
548 0 : return AsciiStringView();
549 : }
550 :
551 505575 : String XmlStreamReader::readText()
552 : {
553 505575 : if (isStartElement()) {
554 505575 : String result;
555 : while (1) {
556 976607 : switch (readNext()) {
557 471032 : case Characters:
558 471032 : result = nodeValue(m_xml);
559 471032 : break;
560 505575 : case EndElement:
561 505575 : return result;
562 0 : case Comment:
563 0 : break;
564 0 : case StartElement:
565 0 : break;
566 0 : default:
567 0 : break;
568 : }
569 : }
570 0 : }
571 0 : return String();
572 : }
573 :
574 368178 : AsciiStringView XmlStreamReader::readAsciiText()
575 : {
576 368178 : if (isStartElement()) {
577 368178 : AsciiStringView result;
578 : while (1) {
579 735874 : switch (readNext()) {
580 367696 : case Characters:
581 367696 : result = AsciiStringView(m_xml->node.value());
582 367696 : break;
583 368178 : case EndElement:
584 368178 : return result;
585 0 : case Comment:
586 0 : break;
587 0 : case StartElement:
588 0 : break;
589 0 : default:
590 0 : break;
591 : }
592 : }
593 : }
594 0 : return AsciiStringView();
595 : }
596 :
597 173172 : int XmlStreamReader::readInt(bool* ok, int base)
598 : {
599 173172 : AsciiStringView s = readAsciiText();
600 346344 : return s.toInt(ok, base);
601 : }
602 :
603 56236 : double XmlStreamReader::readDouble(bool* ok)
604 : {
605 56236 : AsciiStringView s = readAsciiText();
606 112472 : return s.toDouble(ok);
607 : }
608 :
609 3037 : int64_t XmlStreamReader::byteOffset() const
610 : {
611 3037 : if (!m_xml->node) {
612 0 : return 0;
613 : }
614 :
615 : // Use byte offset from start of document as a temporary proxy for line number
616 3037 : return static_cast<int64_t>(m_xml->node.offset_debug());
617 : }
618 :
619 3839 : XmlStreamReader::Error XmlStreamReader::error() const
620 : {
621 3839 : if (!m_xml->customErr.isEmpty()) {
622 0 : return CustomError;
623 : }
624 :
625 3839 : if (m_xml->result.status == pugi::status_ok) {
626 3838 : return NoError;
627 : }
628 :
629 1 : return NotWellFormedError;
630 : }
631 :
632 0 : bool XmlStreamReader::isError() const
633 : {
634 0 : return error() != NoError;
635 : }
636 :
637 1 : String XmlStreamReader::errorString() const
638 : {
639 1 : if (!m_xml->customErr.empty()) {
640 0 : return m_xml->customErr;
641 : }
642 1 : return String::fromUtf8(m_xml->result.description());
643 : }
644 :
645 0 : void XmlStreamReader::raiseError(const String& message)
646 : {
647 0 : m_xml->customErr = message;
648 0 : }
|