Coverage Report

Created: 2026-04-01 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/include/assimp/XmlParser.h
Line
Count
Source
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2026, assimp team
6
7
All rights reserved.
8
9
Redistribution and use of this software in source and binary forms,
10
with or without modification, are permitted provided that the
11
following conditions are met:
12
13
* Redistributions of source code must retain the above
14
copyright notice, this list of conditions and the
15
following disclaimer.
16
17
* Redistributions in binary form must reproduce the above
18
copyright notice, this list of conditions and the
19
following disclaimer in the documentation and/or other
20
materials provided with the distribution.
21
22
* Neither the name of the assimp team, nor the names of its
23
contributors may be used to endorse or promote products
24
derived from this software without specific prior
25
written permission of the assimp team.
26
27
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39
----------------------------------------------------------------------
40
*/
41
42
#ifndef INCLUDED_AI_IRRXML_WRAPPER
43
#define INCLUDED_AI_IRRXML_WRAPPER
44
45
#include <assimp/ai_assert.h>
46
#include <assimp/StringUtils.h>
47
#include <assimp/DefaultLogger.hpp>
48
49
#include "BaseImporter.h"
50
#include "IOStream.hpp"
51
52
#include <pugixml.hpp>
53
#include <istream>
54
#include <utility>
55
#include <vector>
56
57
namespace Assimp {
58
59
/// @brief  Will find a node by its name.
60
struct find_node_by_name_predicate {
61
    /// @brief The default constructor.
62
    find_node_by_name_predicate() = default;
63
64
    /// @brief Constructor with the predicate name
65
    /// @param name    The name.
66
0
    explicit find_node_by_name_predicate(const std::string &name) : mName(name) {
67
        // empty
68
0
    }
69
70
    std::string mName; ///< The name to find.
71
72
0
    bool operator()(pugi::xml_node node) const {
73
0
        return node.name() == mName;
74
0
    }
75
};
76
77
/// @brief  Will convert an attribute to its int value.
78
/// @tparam[in] TNodeType  The node type.
79
template <class TNodeType>
80
struct NodeConverter {
81
public:
82
    /// @brief Will convert the attribute from the node to an int.
83
    /// @param node            The XML-node.
84
    /// @param attribName      The name of the attribute.
85
    static int to_int(TNodeType &node, const char *attribName) {
86
        ai_assert(nullptr != attribName);
87
        return node.attribute(attribName).to_int();
88
    }
89
};
90
91
using XmlNode = pugi::xml_node;
92
using XmlAttribute = pugi::xml_attribute;
93
94
/// @brief The Xml-Parser class.
95
///
96
/// Use this parser if you have to import any kind of xml-format.
97
///
98
/// An example:
99
/// @code
100
/// TXmlParser<XmlNode> theParser;
101
/// if (theParser.parse(fileStream)) {
102
///     auto node = theParser.getRootNode();
103
///     for ( auto currentNode : node.children()) {
104
///         // Will loop over all children
105
///     }
106
/// }
107
/// @endcode
108
/// @tparam TNodeType
109
template <class TNodeType>
110
class TXmlParser {
111
public:
112
    /// @brief The default class constructor.
113
    TXmlParser();
114
115
    /// @brief  The class destructor.
116
    ~TXmlParser();
117
118
    /// @brief  Will clear the parsed xml-file.
119
    void clear();
120
121
    /// @brief  Will search for a child-node by its name
122
    /// @param[in] name     The name of the child-node.
123
    /// @return The node instance or nullptr, if nothing was found.
124
    TNodeType *findNode(const std::string &name);
125
126
    /// @brief  Will return true, if the node is a child-node.
127
    /// @param[in]  name    The name of the child node to look for.
128
    /// @return true, if the node is a child-node or false if not.
129
    bool hasNode(const std::string &name);
130
131
    /// @brief  Will parse an xml-file from a given stream.
132
    /// @param[in] stream      The input stream.
133
    /// @return true, if the parsing was successful, false if not.
134
    bool parse(IOStream *stream);
135
136
    /// @brief  Will parse an xml-file from a stringstream.
137
    /// @param[in] str      The input istream (note: not "const" to match pugixml param)
138
    /// @return true, if the parsing was successful, false if not.
139
    bool parse(std::istream &inStream);
140
141
    /// @brief  Will return true if a root node is there.
142
    /// @return true in case of an existing root.
143
    bool hasRoot() const;
144
145
    /// @brief  Will return the document pointer, is nullptr if no xml-file was parsed.
146
    /// @return The pointer showing to the document.
147
    pugi::xml_document *getDocument() const;
148
149
    /// @brief  Will return the root node, const version.
150
    /// @return The root node.
151
    const TNodeType getRootNode() const;
152
153
    /// @brief  Will return the root node, non-const version.
154
    /// @return The root node.
155
    TNodeType getRootNode();
156
157
    /// @brief Will check if a node with the given name is in.
158
    /// @param[in] node     The node to look in.
159
    /// @param[in] name     The name of the child-node.
160
    /// @return true, if node was found, false if not.
161
    static inline bool hasNode(XmlNode &node, const char *name);
162
163
    /// @brief Will check if an attribute is part of the XmlNode.
164
    /// @param[in] xmlNode  The node to search in.
165
    /// @param[in] name     The attribute name to look for.
166
    /// @return true, if the was found, false if not.
167
    static inline bool hasAttribute(XmlNode &xmlNode, const char *name);
168
169
    /// @brief Will try to get an unsigned int attribute value.
170
    /// @param[in] xmlNode  The node to search in.
171
    /// @param[in] name     The attribute name to look for.
172
    /// @param[out] val     The unsigned int value from the attribute.
173
    /// @return true, if the node contains an attribute with the given name and if the value is an unsigned int.
174
    static inline bool getUIntAttribute(XmlNode &xmlNode, const char *name, unsigned int &val);
175
176
    /// @brief Will try to get an int attribute value.
177
    /// @param[in] xmlNode  The node to search in.
178
    /// @param[in] name     The attribute name to look for.
179
    /// @param[out] val     The int value from the attribute.
180
    /// @return true, if the node contains an attribute with the given name and if the value is an int.
181
    static inline bool getIntAttribute(XmlNode &xmlNode, const char *name, int &val);
182
183
    /// @brief Will try to get a real attribute value.
184
    /// @param[in] xmlNode  The node to search in.
185
    /// @param[in] name     The attribute name to look for.
186
    /// @param[out] val     The real value from the attribute.
187
    /// @return true, if the node contains an attribute with the given name and if the value is a real.
188
    static inline bool getRealAttribute(XmlNode &xmlNode, const char *name, ai_real &val);
189
190
    /// @brief Will try to get a float attribute value.
191
    /// @param[in] xmlNode  The node to search in.
192
    /// @param[in] name     The attribute name to look for.
193
    /// @param[out] val     The float value from the attribute.
194
    /// @return true, if the node contains an attribute with the given name and if the value is a float.
195
    static inline bool getFloatAttribute(XmlNode &xmlNode, const char *name, float &val);
196
197
    /// @brief Will try to get a double attribute value.
198
    /// @param[in] xmlNode  The node to search in.
199
    /// @param[in] name     The attribute name to look for.
200
    /// @param[out] val     The double value from the attribute.
201
    /// @return true, if the node contains an attribute with the given name and if the value is a double.
202
    static inline bool getDoubleAttribute(XmlNode &xmlNode, const char *name, double &val);
203
204
    /// @brief Will try to get a std::string attribute value.
205
    /// @param[in] xmlNode  The node to search in.
206
    /// @param[in] name     The attribute name to look for.
207
    /// @param[out] val     The std::string value from the attribute.
208
    /// @return true, if the node contains an attribute with the given name and if the value is a std::string.
209
    static inline bool getStdStrAttribute(XmlNode &xmlNode, const char *name, std::string &val);
210
211
    /// @brief Will try to get a bool attribute value.
212
    /// @param[in] xmlNode  The node to search in.
213
    /// @param[in] name     The attribute name to look for.
214
    /// @param[out] val     The bool value from the attribute.
215
    /// @return true, if the node contains an attribute with the given name and if the value is a bool.
216
    static inline bool getBoolAttribute(XmlNode &xmlNode, const char *name, bool &val);
217
218
    /// @brief Will try to get the value of the node as a string.
219
    /// @param[in] node     The node to search in.
220
    /// @param[out] text    The value as a text.
221
    /// @return true, if the value can be read out.
222
    static inline bool getValueAsString(XmlNode &node, std::string &text);
223
224
    /// @brief Will try to get the value of the node as a real.
225
    /// @param[in]  node   The node to search in.
226
    /// @param[out] v      The value as a ai_real.
227
    /// @return true, if the value can be read out.
228
    static inline bool getValueAsReal(XmlNode &node, ai_real &v);
229
230
    /// @brief Will try to get the value of the node as a float.
231
    /// @param[in] node     The node to search in.
232
    /// @param[out]v        The value as a float.
233
    /// @return true, if the value can be read out.
234
    static inline bool getValueAsFloat(XmlNode &node, float &v);
235
236
    /// @brief Will try to get the value of the node as an integer.
237
    /// @param[in]  node    The node to search in.
238
    /// @param[out] i       The value as a int.
239
    /// @return true, if the value can be read out.
240
    static inline bool getValueAsInt(XmlNode &node, int &v);
241
242
    /// @brief Will try to get the value of the node as an bool.
243
    /// @param[in]  node    The node to search in.
244
    /// @param[out] v       The value as a bool.
245
    /// @return true, if the value can be read out.
246
    static inline bool getValueAsBool(XmlNode &node, bool &v);
247
248
private:
249
    pugi::xml_document *mDoc;
250
    TNodeType mCurrent;
251
    std::vector<char> mData;
252
};
253
254
template <class TNodeType>
255
inline TXmlParser<TNodeType>::TXmlParser() :
256
9.25k
        mDoc(nullptr),
257
9.25k
        mData() {
258
    // empty
259
9.25k
}
260
261
template <class TNodeType>
262
9.25k
inline TXmlParser<TNodeType>::~TXmlParser() {
263
9.25k
    clear();
264
9.25k
}
265
266
template <class TNodeType>
267
9.25k
inline void TXmlParser<TNodeType>::clear() {
268
9.25k
    if (mData.empty()) {
269
9.25k
        if (mDoc) {
270
0
            delete mDoc;
271
0
        }
272
9.25k
        mDoc = nullptr;
273
9.25k
        return;
274
9.25k
    }
275
276
0
    mData.clear();
277
0
    delete mDoc;
278
0
    mDoc = nullptr;
279
0
}
280
281
template <class TNodeType>
282
0
inline TNodeType *TXmlParser<TNodeType>::findNode(const std::string &name) {
283
0
    if (name.empty()) {
284
0
        return nullptr;
285
0
    }
286
287
0
    if (nullptr == mDoc) {
288
0
        return nullptr;
289
0
    }
290
291
0
    find_node_by_name_predicate predicate(name);
292
0
    mCurrent = mDoc->find_node(std::move(predicate));
293
0
    if (mCurrent.empty()) {
294
0
        return nullptr;
295
0
    }
296
297
0
    return &mCurrent;
298
0
}
299
300
template <class TNodeType>
301
0
bool TXmlParser<TNodeType>::hasNode(const std::string &name) {
302
0
    return nullptr != findNode(name);
303
0
}
304
305
template <class TNodeType>
306
0
bool TXmlParser<TNodeType>::parse(IOStream *stream) {
307
0
    if (hasRoot()) {
308
0
        clear();
309
0
    }
310
311
0
    if (nullptr == stream) {
312
0
        ASSIMP_LOG_DEBUG("Stream is nullptr.");
313
0
        return false;
314
0
    }
315
316
0
    const size_t len = stream->FileSize();
317
0
    mData.resize(len + 1);
318
0
    memset(&mData[0], '\0', len + 1);
319
0
    stream->Read(&mData[0], 1, len);
320
321
0
    mDoc = new pugi::xml_document();
322
    // load_string assumes native encoding (aka always utf-8 per build options)
323
    //pugi::xml_parse_result parse_result = mDoc->load_string(&mData[0], pugi::parse_full);
324
0
    pugi::xml_parse_result parse_result = mDoc->load_buffer(&mData[0], mData.size(), pugi::parse_full);
325
0
    if (parse_result.status == pugi::status_ok) {
326
0
        return true;
327
0
    }
328
329
0
    ASSIMP_LOG_DEBUG("Error while parse xml.", std::string(parse_result.description()), " @ ", parse_result.offset);
330
331
0
    return false;
332
0
}
333
334
template <class TNodeType>
335
0
bool TXmlParser<TNodeType>::parse(std::istream &inStream) {
336
0
    if (hasRoot()) {
337
0
        clear();
338
0
    }
339
0
    mDoc = new pugi::xml_document();
340
0
    pugi::xml_parse_result parse_result = mDoc->load(inStream);
341
0
    if (parse_result.status == pugi::status_ok) {
342
0
        return true;
343
0
    }
344
345
0
    ASSIMP_LOG_DEBUG("Error while parse xml.", std::string(parse_result.description()), " @ ", parse_result.offset);
346
347
0
    return false;
348
0
}
349
350
template <class TNodeType>
351
0
bool TXmlParser<TNodeType>::hasRoot() const {
352
0
    return nullptr != mDoc;
353
0
}
354
355
template <class TNodeType>
356
pugi::xml_document *TXmlParser<TNodeType>::getDocument() const {
357
    return mDoc;
358
}
359
360
template <class TNodeType>
361
const TNodeType TXmlParser<TNodeType>::getRootNode() const {
362
    static pugi::xml_node none;
363
    if (nullptr == mDoc) {
364
        return none;
365
    }
366
    return mDoc->root();
367
}
368
369
template <class TNodeType>
370
0
TNodeType TXmlParser<TNodeType>::getRootNode() {
371
0
    static pugi::xml_node none;
372
0
    if (nullptr == mDoc) {
373
0
        return none;
374
0
    }
375
376
0
    return mDoc->root();
377
0
}
378
379
template <class TNodeType>
380
inline bool TXmlParser<TNodeType>::hasNode(XmlNode &node, const char *name) {
381
    pugi::xml_node child = node.find_child(find_node_by_name_predicate(name));
382
    return !child.empty();
383
}
384
385
template <class TNodeType>
386
0
inline bool TXmlParser<TNodeType>::hasAttribute(XmlNode &xmlNode, const char *name) {
387
0
    pugi::xml_attribute attr = xmlNode.attribute(name);
388
0
    return !attr.empty();
389
0
}
390
391
template <class TNodeType>
392
0
inline bool TXmlParser<TNodeType>::getUIntAttribute(XmlNode &xmlNode, const char *name, unsigned int &val) {
393
0
    pugi::xml_attribute attr = xmlNode.attribute(name);
394
0
    if (attr.empty()) {
395
0
        return false;
396
0
    }
397
398
0
    val = attr.as_uint();
399
0
    return true;
400
0
}
401
402
template <class TNodeType>
403
0
inline bool TXmlParser<TNodeType>::getIntAttribute(XmlNode &xmlNode, const char *name, int &val) {
404
0
    pugi::xml_attribute attr = xmlNode.attribute(name);
405
0
    if (attr.empty()) {
406
0
        return false;
407
0
    }
408
409
0
    val = attr.as_int();
410
0
    return true;
411
0
}
412
413
template <class TNodeType>
414
0
inline bool TXmlParser<TNodeType>::getRealAttribute(XmlNode &xmlNode, const char *name, ai_real &val) {
415
0
    pugi::xml_attribute attr = xmlNode.attribute(name);
416
0
    if (attr.empty()) {
417
0
        return false;
418
0
    }
419
#ifdef ASSIMP_DOUBLE_PRECISION
420
    val = attr.as_double();
421
#else
422
0
    val = attr.as_float();
423
0
#endif
424
0
    return true;
425
0
}
426
427
template <class TNodeType>
428
0
inline bool TXmlParser<TNodeType>::getFloatAttribute(XmlNode &xmlNode, const char *name, float &val) {
429
0
    pugi::xml_attribute attr = xmlNode.attribute(name);
430
0
    if (attr.empty()) {
431
0
        return false;
432
0
    }
433
434
0
    val = attr.as_float();
435
436
0
    return true;
437
0
}
438
439
template <class TNodeType>
440
0
inline bool TXmlParser<TNodeType>::getDoubleAttribute(XmlNode &xmlNode, const char *name, double &val) {
441
0
    pugi::xml_attribute attr = xmlNode.attribute(name);
442
0
    if (attr.empty()) {
443
0
        return false;
444
0
    }
445
446
0
    val = attr.as_double();
447
448
0
    return true;
449
0
}
450
451
template <class TNodeType>
452
0
inline bool TXmlParser<TNodeType>::getStdStrAttribute(XmlNode &xmlNode, const char *name, std::string &val) {
453
0
    pugi::xml_attribute attr = xmlNode.attribute(name);
454
0
    if (attr.empty()) {
455
0
        return false;
456
0
    }
457
458
0
    val = attr.as_string();
459
460
0
    return true;
461
0
}
462
463
template <class TNodeType>
464
0
inline bool TXmlParser<TNodeType>::getBoolAttribute(XmlNode &xmlNode, const char *name, bool &val) {
465
0
    pugi::xml_attribute attr = xmlNode.attribute(name);
466
0
    if (attr.empty()) {
467
0
        return false;
468
0
    }
469
470
0
    val = attr.as_bool();
471
472
0
    return true;
473
0
}
474
475
template <class TNodeType>
476
0
inline bool TXmlParser<TNodeType>::getValueAsString(XmlNode &node, std::string &text) {
477
0
    text = std::string();
478
0
    if (node.empty()) {
479
0
        return false;
480
0
    }
481
482
0
    text = node.text().as_string();
483
0
    text = ai_trim(text);
484
485
0
    return true;
486
0
}
487
488
template <class TNodeType>
489
0
inline bool TXmlParser<TNodeType>::getValueAsReal(XmlNode& node, ai_real& v) {
490
0
    if (node.empty()) {
491
0
        return false;
492
0
    }
493
494
0
    v = node.text().as_float();
495
496
0
    return true;
497
0
}
498
499
500
template <class TNodeType>
501
0
inline bool TXmlParser<TNodeType>::getValueAsFloat(XmlNode &node, float &v) {
502
0
    if (node.empty()) {
503
0
        return false;
504
0
    }
505
506
0
    v = node.text().as_float();
507
508
0
    return true;
509
0
}
510
511
template <class TNodeType>
512
inline bool TXmlParser<TNodeType>::getValueAsInt(XmlNode &node, int &v) {
513
    if (node.empty()) {
514
        return false;
515
    }
516
517
    v = node.text().as_int();
518
519
    return true;
520
}
521
522
template <class TNodeType>
523
0
inline bool TXmlParser<TNodeType>::getValueAsBool(XmlNode &node, bool &v) {
524
0
    if (node.empty()) {
525
0
        return false;
526
0
    }
527
528
0
    v = node.text().as_bool();
529
530
0
    return true;
531
0
}
532
533
using XmlParser = TXmlParser<pugi::xml_node>;
534
535
/// @brief  This class declares an iterator to loop through all children of the root node.
536
class XmlNodeIterator {
537
public:
538
    /// @brief The iteration mode.
539
    enum IterationMode {
540
        PreOrderMode, ///< Pre-ordering, get the values, continue the iteration.
541
        PostOrderMode ///< Post-ordering, continue the iteration, get the values.
542
    };
543
    /// @brief  The class constructor
544
    /// @param  parent      [in] The xml parent to to iterate through.
545
    /// @param  mode        [in] The iteration mode.
546
    explicit XmlNodeIterator(XmlNode &parent, IterationMode mode) :
547
0
            mParent(parent),
548
0
            mNodes(),
549
0
            mIndex(0) {
550
0
        if (mode == PreOrderMode) {
551
0
            collectChildrenPreOrder(parent);
552
0
        } else {
553
0
            collectChildrenPostOrder(parent);
554
0
        }
555
0
    }
556
557
    /// @brief  The class destructor, default implementation.
558
0
    ~XmlNodeIterator() = default;
559
560
    /// @brief  Will iterate through all children in pre-order iteration.
561
    /// @param  node    [in] The nod to iterate through.
562
0
    void collectChildrenPreOrder(XmlNode &node) {
563
0
        if (node != mParent && node.type() == pugi::node_element) {
564
0
            mNodes.push_back(node);
565
0
        }
566
0
        for (XmlNode currentNode : node.children()) {
567
0
            collectChildrenPreOrder(currentNode);
568
0
        }
569
0
    }
570
571
    /// @brief  Will iterate through all children in post-order iteration.
572
    /// @param  node    [in] The nod to iterate through.
573
0
    void collectChildrenPostOrder(XmlNode &node) {
574
0
        for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) {
575
0
            collectChildrenPostOrder(currentNode);
576
0
        }
577
0
        if (node != mParent) {
578
0
            mNodes.push_back(node);
579
0
        }
580
0
    }
581
582
    /// @brief  Will iterate through all collected nodes.
583
    /// @param  next    The next node, if there is any.
584
    /// @return true, if there is a node left.
585
0
    bool getNext(XmlNode &next) {
586
0
        if (mIndex == mNodes.size()) {
587
0
            return false;
588
0
        }
589
590
0
        next = mNodes[mIndex];
591
0
        ++mIndex;
592
593
0
        return true;
594
0
    }
595
596
    /// @brief  Will return the number of collected nodes.
597
    /// @return The number of collected nodes.
598
0
    size_t size() const {
599
0
        return mNodes.size();
600
0
    }
601
602
    /// @brief  Returns true, if the node is empty.
603
    /// @return true, if the node is empty, false if not.
604
0
    bool isEmpty() const {
605
0
        return mNodes.empty();
606
0
    }
607
608
    /// @brief  Will clear all collected nodes.
609
0
    void clear() {
610
0
        if (mNodes.empty()) {
611
0
            return;
612
0
        }
613
0
614
0
        mNodes.clear();
615
0
        mIndex = 0;
616
0
    }
617
618
private:
619
    XmlNode &mParent;
620
    std::vector<XmlNode> mNodes;
621
    size_t mIndex;
622
};
623
624
} // namespace Assimp
625
626
#endif // !! INCLUDED_AI_IRRXML_WRAPPER