openMSX
XMLElement.cc
Go to the documentation of this file.
1 #include "XMLElement.hh"
2 #include "StringOp.hh"
3 #include "FileContext.hh" // for bwcompat
4 #include "ConfigException.hh"
5 #include "ranges.hh"
6 #include "serialize.hh"
7 #include "serialize_stl.hh"
8 #include "stl.hh"
9 #include "strCat.hh"
10 #include "unreachable.hh"
11 #include "xrange.hh"
12 #include <cassert>
13 
14 using std::string;
15 using std::string_view;
16 using std::unique_ptr;
17 
18 namespace openmsx {
19 
21 {
22  children.erase(rfind_if_unguarded(children,
23  [&](auto& v) { return &v == &child; }));
24 }
25 
26 XMLElement::Attributes::iterator XMLElement::getAttributeIter(string_view attrName)
27 {
28  return ranges::find_if(attributes,
29  [&](auto& a) { return a.first == attrName; });
30 }
31 XMLElement::Attributes::const_iterator XMLElement::getAttributeIter(string_view attrName) const
32 {
33  return ranges::find_if(attributes,
34  [&](auto& a) { return a.first == attrName; });
35 }
36 
37 const string* XMLElement::findAttribute(string_view attrName) const
38 {
39  auto it = getAttributeIter(attrName);
40  return (it != end(attributes)) ? &it->second : nullptr;
41 }
42 
43 void XMLElement::removeAttribute(string_view attrName)
44 {
45  if (auto it = getAttributeIter(attrName); it != end(attributes)) {
46  attributes.erase(it);
47  }
48 }
49 
50 std::vector<const XMLElement*> XMLElement::getChildren(string_view childName) const
51 {
52  std::vector<const XMLElement*> result;
53  for (const auto& c : children) {
54  if (c.getName() == childName) {
55  result.push_back(&c);
56  }
57  }
58  return result;
59 }
60 
61 XMLElement* XMLElement::findChild(string_view childName)
62 {
63  auto it = ranges::find_if(
64  children, [&](auto& c) { return c.getName() == childName; });
65  return (it != end(children)) ? &*it : nullptr;
66 }
67 const XMLElement* XMLElement::findChild(string_view childName) const
68 {
69  return const_cast<XMLElement*>(this)->findChild(childName);
70 }
71 
72 const XMLElement* XMLElement::findNextChild(string_view childName,
73  size_t& fromIndex) const
74 {
75  for (auto i : xrange(fromIndex, children.size())) {
76  if (children[i].getName() == childName) {
77  fromIndex = i + 1;
78  return &children[i];
79  }
80  }
81  for (auto i : xrange(fromIndex)) {
82  if (children[i].getName() == childName) {
83  fromIndex = i + 1;
84  return &children[i];
85  }
86  }
87  return nullptr;
88 }
89 
91  string_view attrName, string_view attValue)
92 {
93  auto it = ranges::find_if(children, [&](auto& c) {
94  if (c.getName() != childName) return false;
95  auto* value = c.findAttribute(attrName);
96  if (!value) return false;
97  return *value == attValue;
98  });
99  return (it != end(children)) ? &*it : nullptr;
100 }
101 
102 const XMLElement* XMLElement::findChildWithAttribute(string_view childName,
103  string_view attrName, string_view attValue) const
104 {
105  return const_cast<XMLElement*>(this)->findChildWithAttribute(
106  childName, attrName, attValue);
107 }
108 
109 XMLElement& XMLElement::getChild(string_view childName)
110 {
111  if (auto* elem = findChild(childName)) {
112  return *elem;
113  }
114  throw ConfigException("Missing tag \"", childName, "\".");
115 }
116 const XMLElement& XMLElement::getChild(string_view childName) const
117 {
118  return const_cast<XMLElement*>(this)->getChild(childName);
119 }
120 
121 const string& XMLElement::getChildData(string_view childName) const
122 {
123  return getChild(childName).getData();
124 }
125 
126 string_view XMLElement::getChildData(string_view childName,
127  string_view defaultValue) const
128 {
129  const auto* child = findChild(childName);
130  return child ? child->getData() : defaultValue;
131 }
132 
133 bool XMLElement::getChildDataAsBool(string_view childName, bool defaultValue) const
134 {
135  const auto* child = findChild(childName);
136  return child ? StringOp::stringToBool(child->getData()) : defaultValue;
137 }
138 
139 int XMLElement::getChildDataAsInt(string_view childName, int defaultValue) const
140 {
141  const auto* child = findChild(childName);
142  if (!child) return defaultValue;
143  auto r = StringOp::stringTo<int>(child->getData());
144  return r ? *r : defaultValue;
145 }
146 
148 {
149  children.clear();
150 }
151 
152 bool XMLElement::hasAttribute(string_view attrName) const
153 {
154  return findAttribute(attrName);
155 }
156 
157 const string& XMLElement::getAttribute(string_view attrName) const
158 {
159  if (const auto* value = findAttribute(attrName)) {
160  return *value;
161  }
162  throw ConfigException("Missing attribute \"", attrName, "\".");
163 }
164 
165 string_view XMLElement::getAttribute(string_view attrName,
166  string_view defaultValue) const
167 {
168  const auto* value = findAttribute(attrName);
169  return value ? *value : defaultValue;
170 }
171 
172 bool XMLElement::getAttributeAsBool(string_view attrName,
173  bool defaultValue) const
174 {
175  const auto* value = findAttribute(attrName);
176  return value ? StringOp::stringToBool(*value) : defaultValue;
177 }
178 
179 int XMLElement::getAttributeAsInt(string_view attrName,
180  int defaultValue) const
181 {
182  const auto* value = findAttribute(attrName);
183  if (!value) return defaultValue;
184  auto r = StringOp::stringTo<int>(*value);
185  return r ? *r : defaultValue;
186 }
187 
188 bool XMLElement::findAttributeInt(string_view attrName,
189  unsigned& result) const
190 {
191  if (const auto* value = findAttribute(attrName)) {
192  if (auto r = StringOp::stringTo<int>(*value)) {
193  result = *r;
194  return true;
195  }
196  }
197  return false;
198 }
199 
200 string XMLElement::dump() const
201 {
202  string result;
203  dump(result, 0);
204  return result;
205 }
206 
207 void XMLElement::dump(string& result, unsigned indentNum) const
208 {
209  strAppend(result, spaces(indentNum), '<', getName());
210  for (const auto& [attrName, value] : attributes) {
211  strAppend(result, ' ', attrName, "=\"", XMLEscape(value), '"');
212  }
213  if (children.empty()) {
214  if (data.empty()) {
215  strAppend(result, "/>\n");
216  } else {
217  strAppend(result, '>', XMLEscape(data), "</",
218  getName(), ">\n");
219  }
220  } else {
221  strAppend(result, ">\n");
222  for (const auto& c : children) {
223  c.dump(result, indentNum + 2);
224  }
225  strAppend(result, spaces(indentNum), "</", getName(), ">\n");
226  }
227 }
228 
229 // This routine does the following substitutions:
230 // & -> &amp; must always be done
231 // < -> &lt; must always be done
232 // > -> &gt; always allowed, but must be done when it appears as ]]>
233 // ' -> &apos; always allowed, but must be done inside quoted attribute
234 // " -> &quot; always allowed, but must be done inside quoted attribute
235 // all chars less than 32 -> &#xnn;
236 // So to simplify things we always do these 5+32 substitutions.
237 string XMLElement::XMLEscape(string_view s)
238 {
239  string result;
240  result.reserve(s.size()); // By default assume no substitution will be needed
241  for (char c : s) {
242  if (auto uc = static_cast<unsigned char>(c); uc < 32) {
243  auto hex = [](unsigned x) { return (x < 10) ? char(x + '0') : char(x - 10 + 'a'); };
244  result += "&#x";
245  result += hex(uc / 16);
246  result += hex(uc % 16);
247  result += ';';
248  } else if (c == '<') {
249  result += "&lt;";
250  } else if (c == '>') {
251  result += "&gt;";
252  } else if (c == '&') {
253  result += "&amp;";
254  } else if (c == '"') {
255  result += "&quot;";
256  } else if (c == '\'') {
257  result += "&apos;";
258  } else {
259  result += c;
260  }
261  }
262  return result;
263 }
264 
265 static unique_ptr<FileContext> lastSerializedFileContext;
267 {
268  return std::move(lastSerializedFileContext); // this also sets value to nullptr;
269 }
270 // version 1: initial version
271 // version 2: removed 'context' tag
272 // also removed 'parent', but that was never serialized
273 // 2b: (no need to increase version) name and data members are
274 // serialized as normal members instead of constructor parameters
275 // 2c: (no need to increase version) attributes were initially stored as
276 // map<string, string>, later this was changed to
277 // vector<pair<string, string>>. To keep bw-compat the serialize()
278 // method converted between these two formats. Though (by luck) in
279 // the XML output both datastructures are serialized to the same
280 // format, so we can drop this conversion step without breaking
281 // bw-compat.
282 template<typename Archive>
283 void XMLElement::serialize(Archive& ar, unsigned version)
284 {
285  ar.serialize("name", name,
286  "data", data,
287  "attributes", attributes,
288  "children", children);
289 
290  if (ar.versionBelow(version, 2)) {
291  assert(Archive::IS_LOADER);
292  unique_ptr<FileContext> context;
293  ar.serialize("context", context);
294  if (context) {
295  assert(!lastSerializedFileContext);
296  lastSerializedFileContext = std::move(context);
297  }
298  }
299 }
301 
302 } // namespace openmsx
static std::string XMLEscape(std::string_view str)
Definition: XMLElement.cc:237
static std::unique_ptr< FileContext > getLastSerializedFileContext()
Definition: XMLElement.cc:266
void serialize(Archive &ar, unsigned version)
Definition: XMLElement.cc:283
void removeAttribute(std::string_view name)
Definition: XMLElement.cc:43
const XMLElement * findChild(std::string_view childName) const
Definition: XMLElement.cc:67
const std::string & getName() const
Definition: XMLElement.hh:47
const Children & getChildren() const
Definition: XMLElement.hh:105
const XMLElement * findNextChild(std::string_view name, size_t &fromIndex) const
Definition: XMLElement.cc:72
int getAttributeAsInt(std::string_view attrName, int defaultValue=0) const
Definition: XMLElement.cc:179
const std::string & getData() const
Definition: XMLElement.hh:54
bool findAttributeInt(std::string_view attrName, unsigned &result) const
Definition: XMLElement.cc:188
bool hasAttribute(std::string_view name) const
Definition: XMLElement.cc:152
const std::string & getChildData(std::string_view childName) const
Definition: XMLElement.cc:121
void removeChild(const XMLElement &child)
Definition: XMLElement.cc:20
const XMLElement & getChild(std::string_view childName) const
Definition: XMLElement.cc:116
const XMLElement * findChildWithAttribute(std::string_view childName, std::string_view attrName, std::string_view attValue) const
Definition: XMLElement.cc:102
bool getAttributeAsBool(std::string_view attrName, bool defaultValue=false) const
Definition: XMLElement.cc:172
int getChildDataAsInt(std::string_view childName, int defaultValue=0) const
Definition: XMLElement.cc:139
std::string dump() const
Definition: XMLElement.cc:200
bool getChildDataAsBool(std::string_view childName, bool defaultValue=false) const
Definition: XMLElement.cc:133
const std::string & getAttribute(std::string_view attrName) const
Definition: XMLElement.cc:157
const std::string * findAttribute(std::string_view attrName) const
Definition: XMLElement.cc:37
bool stringToBool(string_view str)
Definition: StringOp.cc:16
std::unique_ptr< Context > context
Definition: GLContext.cc:9
This file implemented 3 utility functions:
Definition: Autofire.cc:5
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:124
auto find_if(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:113
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:983
constexpr auto rfind_if_unguarded(RANGE &range, PRED pred)
Definition: stl.hh:165
strCatImpl::ConcatSpaces spaces(size_t n)
Definition: strCat.hh:700
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:669
constexpr auto xrange(T e)
Definition: xrange.hh:155
constexpr auto end(const zstring_view &x)
Definition: zstring_view.hh:83