libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
xml.h
Go to the documentation of this file.
1
9#pragma once
10
11
12#include <promeki/config.h>
13#if PROMEKI_ENABLE_CORE
14#include <functional>
15#include <utility>
16#include <promeki/function.h>
17#include <promeki/namespace.h>
18#include <promeki/string.h>
19#include <promeki/list.h>
20#include <promeki/map.h>
21#include <promeki/error.h>
22#include <promeki/sharedptr.h>
23#include <promeki/datastream.h>
24#include <pugixml.hpp>
25
26PROMEKI_NAMESPACE_BEGIN
27
28class XmlName;
29class XmlAttribute;
30class XmlElement;
31class XmlDocument;
32class XmlNode;
33class XmlParseError;
34
60class XmlName {
61 public:
63 XmlName() = default;
64
66 explicit XmlName(const String &local) : _local(local) {}
67
69 XmlName(const String &uri, const String &local) : _uri(uri), _local(local) {}
70
72 XmlName(const String &uri, const String &prefix, const String &local)
73 : _uri(uri), _prefix(prefix), _local(local) {}
74
81 static XmlName parseClark(const String &clark);
82
84 bool isValid() const { return !_local.isEmpty(); }
85
87 const String &uri() const { return _uri; }
88
90 const String &prefix() const { return _prefix; }
91
93 const String &local() const { return _local; }
94
96 bool hasNamespace() const { return !_uri.isEmpty(); }
97
99 String qualified() const;
100
102 String clark() const;
103
105 String toString() const { return clark(); }
106
108 bool operator==(const XmlName &other) const {
109 return _uri == other._uri && _local == other._local;
110 }
111
113 bool operator!=(const XmlName &other) const { return !(*this == other); }
114
115 private:
116 String _uri;
117 String _prefix;
118 String _local;
119};
120
147class XmlParseError {
148 public:
150 XmlParseError() = default;
151
153 bool ok() const { return _status == pugi::status_ok; }
154
156 bool isError() const { return !ok(); }
157
159 explicit operator bool() const { return ok(); }
160
162 pugi::xml_parse_status pugiStatus() const { return _status; }
163
165 const String &message() const { return _message; }
166
168 size_t offset() const { return _offset; }
169
171 int line() const { return _line; }
172
174 int column() const { return _column; }
175
185 Error toError() const;
186
188 String toString() const;
189
191 bool operator==(const XmlParseError &other) const {
192 return _status == other._status && _offset == other._offset &&
193 _message == other._message;
194 }
195
197 bool operator!=(const XmlParseError &other) const { return !(*this == other); }
198
199 private:
200 friend class XmlDocument;
201 friend class XmlElement;
202
210 void setFromPugi(const pugi::xml_parse_result &result, const String &source);
211
212 pugi::xml_parse_status _status = pugi::status_ok;
213 String _message;
214 size_t _offset = 0;
215 int _line = 0;
216 int _column = 0;
217};
218
244struct XmlData {
245 PROMEKI_SHARED_FINAL(XmlData)
246 pugi::xml_document doc;
247
248 XmlData() = default;
249 XmlData(const XmlData &other);
250 XmlData &operator=(const XmlData &other);
251 XmlData(XmlData &&) = delete;
252 XmlData &operator=(XmlData &&) = delete;
253};
254
266class XmlAttribute {
267 public:
269 XmlAttribute() = default;
270
272 XmlAttribute(String rawName, String value)
273 : _name(std::move(rawName)), _value(std::move(value)) {}
274
276 XmlAttribute(XmlName qname, String value);
277
279 XmlAttribute(String rawName, XmlName qname, String value)
280 : _name(std::move(rawName)), _qname(std::move(qname)), _value(std::move(value)) {}
281
283 const String &name() const { return _name; }
284
286 const XmlName &qname() const { return _qname; }
287
289 const String &value() const { return _value; }
290
292 void setName(const String &name) { _name = name; }
293
295 void setQName(const XmlName &qname) { _qname = qname; }
296
298 void setValue(const String &value) { _value = value; }
299
301 bool isValid() const { return !_name.isEmpty(); }
302
304 bool operator==(const XmlAttribute &other) const {
305 return _name == other._name && _value == other._value;
306 }
307
309 bool operator!=(const XmlAttribute &other) const { return !(*this == other); }
310
311 private:
312 String _name;
313 XmlName _qname;
314 String _value;
315};
316
350class XmlElement {
351 public:
363 static XmlElement parse(const String &str, XmlParseError *err = nullptr);
364
366 XmlElement() = default;
367
369 explicit XmlElement(const String &name);
370
372 bool isValid() const;
373
375 String name() const;
376
384 XmlName qname() const;
385
387 void setName(const String &name);
388
397 Map<String, String> namespaces() const;
398
399 // --- Attributes ---
400
402 bool hasAttribute(const String &name) const;
403
405 bool hasAttribute(const XmlName &qname) const;
406
413 String attribute(const String &name, Error *err = nullptr) const;
414
424 String attribute(const XmlName &qname, Error *err = nullptr) const;
425
427 void setAttribute(const String &name, const String &value);
428
440 void setAttribute(const XmlName &qname, const String &value);
441
446 bool removeAttribute(const String &name);
447
452 bool removeAttribute(const XmlName &qname);
453
455 List<XmlAttribute> attributes() const;
456
457 // --- Element children ---
458
460 bool hasChild(const String &name) const;
461
463 bool hasChild(const XmlName &qname) const;
464
468 XmlElement child(const String &name) const;
469
476 XmlElement child(const XmlName &qname) const;
477
479 List<XmlElement> elements() const;
480
482 List<XmlElement> elementsNamed(const String &name) const;
483
485 List<XmlElement> elementsNamed(const XmlName &qname) const;
486
487 // --- XPath ---
488
500 XmlElement selectFirst(const String &query, Error *err = nullptr) const;
501
508 List<XmlElement> selectAll(const String &query, Error *err = nullptr) const;
509
518 XmlAttribute selectFirstAttribute(const String &query, Error *err = nullptr) const;
519
521 List<XmlAttribute> selectAllAttributes(const String &query, Error *err = nullptr) const;
522
523 // --- Path navigation (lightweight) ---
524
537 XmlElement elementByPath(const String &path) const;
538
540 List<XmlNode> children() const;
541
542 // --- Text content ---
543
551 String text() const;
552
558 void setText(const String &text);
559
560 // --- Mutation: append children ---
561
572 void appendElement(const String &name);
573
575 void appendElement(const String &name, const String &text);
576
586 void appendElement(const XmlName &qname);
587
589 void appendElement(const XmlName &qname, const String &text);
590
592 void appendText(const String &text);
593
595 void appendCData(const String &text);
596
598 void appendComment(const String &text);
599
601 void appendProcessingInstruction(const String &target, const String &data);
602
604 void appendChild(const XmlElement &child);
605
607 void appendChild(const XmlNode &node);
608
618 void appendChild(const XmlDocument &doc);
619
631 XmlDocument toDocument() const;
632
633 // --- Prepend / insert at position ---
634
636 void prependElement(const String &name);
637
639 void prependElement(const String &name, const String &text);
640
642 void prependChild(const XmlElement &child);
643
645 void prependChild(const XmlNode &node);
646
648 void prependChild(const XmlDocument &doc);
649
659 void insertChildAt(size_t index, const XmlElement &child);
660
662 void insertChildAt(size_t index, const XmlNode &node);
663
664 // --- Targeted removal ---
665
670 bool removeChild(const String &name);
671
676 bool removeChild(const XmlName &qname);
677
682 bool removeChildAt(size_t index);
683
688 size_t removeAllNamed(const String &name);
689
694 size_t removeAllNamed(const XmlName &qname);
695
697 size_t childCount() const;
698
699 // --- Attribute order primitives ---
700
702 void prependAttribute(const String &name, const String &value);
703
705 void prependAttribute(const XmlName &qname, const String &value);
706
712 void insertAttributeAt(size_t index, const String &name, const String &value);
713
715 void insertAttributeAt(size_t index, const XmlName &qname, const String &value);
716
718 size_t attributeCount() const;
719
721 void clear();
722
727 String toString(unsigned int indent = 0) const;
728
730 void forEachElement(Function<void(const XmlElement &)> func) const;
731
733 bool operator==(const XmlElement &other) const;
734
736 bool operator!=(const XmlElement &other) const { return !(*this == other); }
737
738 private:
739 friend class XmlDocument;
740 friend class XmlNode;
741
742 SharedPtr<XmlData> _d = SharedPtr<XmlData>::create();
743
744 explicit XmlElement(const SharedPtr<XmlData> &d) : _d(d) {}
745
747 pugi::xml_node rootNode() const;
748
750 void resetTo(const pugi::xml_node &node);
751};
752
781class XmlDocument {
782 public:
783 PROMEKI_DATATYPE(XmlDocument, DataTypeXmlDocument, 1)
784
785
794 Error writeToStream(DataStream &s) const;
795
800 template <uint32_t V> static Result<XmlDocument> readFromStream(DataStream &s);
801
813 static XmlDocument parse(const String &str, XmlParseError *err = nullptr);
814
826 static Result<XmlDocument> fromString(const String &str);
827
837 static XmlDocument loadFromPath(const String &path, XmlParseError *err = nullptr);
838
851 Error saveToPath(const String &path, unsigned int indent = 0) const;
852
854 XmlDocument() = default;
855
862 explicit XmlDocument(const XmlElement &root);
863
865 bool isValid() const;
866
867 // --- Declaration ---
868
870 String declarationVersion() const;
871
873 String declarationEncoding() const;
874
876 String declarationStandalone() const;
877
884 void setDeclaration(const String &version = "1.0",
885 const String &encoding = "UTF-8",
886 const String &standalone = String());
887
888 // --- Doctype ---
889
891 String doctype() const;
892
894 void setDoctype(const String &doctype);
895
896 // --- Root element ---
897
903 XmlElement root() const;
904
906 XmlElement selectFirst(const String &query, Error *err = nullptr) const {
907 return root().selectFirst(query, err);
908 }
909
911 List<XmlElement> selectAll(const String &query, Error *err = nullptr) const {
912 return root().selectAll(query, err);
913 }
914
916 XmlElement elementByPath(const String &path) const { return root().elementByPath(path); }
917
924 void setRoot(const XmlElement &elem);
925
927 void appendComment(const String &text);
928
930 void appendProcessingInstruction(const String &target, const String &data);
931
933 void clear();
934
939 String toString(unsigned int indent = 0) const;
940
942 bool operator==(const XmlDocument &other) const;
943
945 bool operator!=(const XmlDocument &other) const { return !(*this == other); }
946
947 private:
948 friend class XmlElement;
949 friend class XmlNode;
950
951 SharedPtr<XmlData> _d = SharedPtr<XmlData>::create();
952
953 explicit XmlDocument(const SharedPtr<XmlData> &d) : _d(d) {}
954};
955
972class XmlNode {
973 public:
975 enum Type {
976 Null,
977 Element,
978 Text,
979 CData,
980 Comment,
981 ProcessingInstruction,
982 Undefined
983 };
984
986 XmlNode() = default;
987
989 static XmlNode undefined();
990
992 XmlNode(const XmlElement &elem);
993
995 static XmlNode makeText(const String &body);
996
998 static XmlNode makeCData(const String &body);
999
1001 static XmlNode makeComment(const String &body);
1002
1004 static XmlNode makeProcessingInstruction(const String &target, const String &data);
1005
1007 Type type() const { return _type; }
1008
1010 bool isNull() const { return _type == Null; }
1012 bool isUndefined() const { return _type == Undefined; }
1014 bool isElement() const { return _type == Element; }
1016 bool isText() const { return _type == Text; }
1018 bool isCData() const { return _type == CData; }
1020 bool isComment() const { return _type == Comment; }
1022 bool isProcessingInstruction() const { return _type == ProcessingInstruction; }
1023
1025 XmlElement toElement() const;
1026
1035 String text() const;
1036
1038 String piTarget() const;
1039
1041 String toString() const;
1042
1044 bool operator==(const XmlNode &other) const;
1045
1047 bool operator!=(const XmlNode &other) const { return !(*this == other); }
1048
1049 private:
1050 Type _type = Null;
1051 XmlElement _element;
1052 String _content;
1053 String _piTarget;
1054};
1055
1056// ============================================================================
1057// DataStream serialization (text-form, length-prefixed, matching JSON pattern)
1058// ============================================================================
1059
1063inline DataStream &operator<<(DataStream &stream, const XmlElement &elem) {
1064 stream.beginFrame(DataTypeXmlElement, 1);
1065 stream << elem.toString(0);
1066 stream.endFrame();
1067 return stream;
1068}
1069
1073inline DataStream &operator>>(DataStream &stream, XmlElement &elem) {
1074 if (!stream.readFrame(DataTypeXmlElement)) {
1075 elem = XmlElement();
1076 return stream;
1077 }
1078 String text;
1079 stream >> text;
1080 if (stream.status() != DataStream::Ok) {
1081 elem = XmlElement();
1082 return stream;
1083 }
1084 // A default-constructed XmlElement serializes to an empty
1085 // string; round-trip it as a default rather than feeding the
1086 // parser an empty buffer.
1087 if (text.isEmpty()) {
1088 elem = XmlElement();
1089 return stream;
1090 }
1091 XmlParseError perr;
1092 elem = XmlElement::parse(text, &perr);
1093 if (!perr) {
1094 stream.setError(DataStream::ReadCorruptData,
1095 String("XmlElement::parse failed: ") + perr.toString());
1096 }
1097 return stream;
1098}
1099
1100PROMEKI_NAMESPACE_END
1101
1102PROMEKI_FORMAT_VIA_TOSTRING(promeki::XmlDocument);
1103PROMEKI_FORMAT_VIA_TOSTRING(promeki::XmlElement);
1104
1105#endif // PROMEKI_ENABLE_CORE