LCOV - code coverage report
Current view: top level - muse/framework/global/serialization - xmlstreamreader.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 75.4 % 345 260
Test Date: 2026-11-05 04:07:02

            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 : }
        

Generated by: LCOV version 2.0-1