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 "serialize.hh"
6 #include "serialize_stl.hh"
7 #include "memory.hh"
8 #include "xrange.hh"
9 #include <libxml/uri.h>
10 #include <cassert>
11 #include <algorithm>
12 
13 using std::unique_ptr;
14 using std::string;
15 
16 namespace openmsx {
17 
19  : name(name_.str())
20 {
21 }
22 
24  : name(name_.str())
25  , data(data_.str())
26 {
27 }
28 
30 {
31  // Mixed-content elements are not supported by this class. In the past
32  // we had a 'assert(data.empty())' here to enforce this, though that
33  // assert triggered when you started openMSX without a user (but with
34  // a system) settings.xml file (the deeper reason is a harmless comment
35  // in the system version of this file).
36  // When you add child nodes to a node with data, that data will be
37  // ignored when this node is later written to disk. In the case of
38  // settings.xml this behaviour is fine.
39  children.push_back(std::move(child));
40  return children.back();
41 }
42 
44 {
45  assert(std::count_if(children.begin(), children.end(),
46  [&](Children::value_type& v) { return &v == &child; }) == 1);
47  auto it = std::find_if(children.begin(), children.end(),
48  [&](Children::value_type& v) { return &v == &child; });
49  assert(it != children.end());
50  children.erase(it);
51 }
52 
53 XMLElement::Attributes::iterator XMLElement::findAttribute(string_ref name)
54 {
55  return find_if(attributes.begin(), attributes.end(),
56  [&](Attribute& a) { return a.first == name; });
57 }
58 XMLElement::Attributes::const_iterator XMLElement::findAttribute(string_ref name) const
59 {
60  return find_if(attributes.begin(), attributes.end(),
61  [&](const Attribute& a) { return a.first == name; });
62 }
63 
65 {
66  assert(findAttribute(name) == attributes.end());
67  attributes.push_back(make_pair(name.str(), value.str()));
68 }
69 
71 {
72  auto it = findAttribute(name);
73  if (it != attributes.end()) {
74  it->second = value.str();
75  } else {
76  attributes.push_back(make_pair(name.str(), value.str()));
77  }
78 }
79 
81 {
82  auto it = findAttribute(name);
83  if (it != attributes.end()) {
84  attributes.erase(it);
85  }
86 }
87 
89 {
91 }
92 
94 {
96 }
97 
99 {
101 }
102 
104 {
105  name = name_.str();
106 }
107 
109 {
110  name.clear();
111 }
112 
114 {
115  assert(children.empty()); // no mixed-content elements
116  data = data_.str();
117 }
118 
119 std::vector<const XMLElement*> XMLElement::getChildren(string_ref name) const
120 {
121  std::vector<const XMLElement*> result;
122  for (auto& c : children) {
123  if (c.getName() == name) {
124  result.push_back(&c);
125  }
126  }
127  return result;
128 }
129 
131 {
132  for (auto& c : children) {
133  if (c.getName() == name) {
134  return &c;
135  }
136  }
137  return nullptr;
138 }
140 {
141  return const_cast<XMLElement*>(this)->findChild(name);
142 }
143 
145  size_t& fromIndex) const
146 {
147  for (auto i : xrange(fromIndex, children.size())) {
148  if (children[i].getName() == name) {
149  fromIndex = i + 1;
150  return &children[i];
151  }
152  }
153  for (auto i : xrange(fromIndex)) {
154  if (children[i].getName() == name) {
155  fromIndex = i + 1;
156  return &children[i];
157  }
158  }
159  return nullptr;
160 }
161 
163  string_ref attName, string_ref attValue)
164 {
165  for (auto& c : children) {
166  if ((c.getName() == name) &&
167  (c.getAttribute(attName) == attValue)) {
168  return &c;
169  }
170  }
171  return nullptr;
172 }
173 
175  string_ref attName, string_ref attValue) const
176 {
177  return const_cast<XMLElement*>(this)->findChildWithAttribute(
178  name, attName, attValue);
179 }
180 
182 {
183  if (auto* elem = findChild(name)) {
184  return *elem;
185  }
187  "Missing tag \"" << name << "\".");
188 }
190 {
191  return const_cast<XMLElement*>(this)->getChild(name);
192 }
193 
195  string_ref defaultValue)
196 {
197  if (auto* result = findChild(name)) {
198  return *result;
199  }
200  return addChild(XMLElement(name, defaultValue));
201 }
202 
204  string_ref name, string_ref attName,
205  string_ref attValue)
206 {
207  if (auto* result = findChildWithAttribute(name, attName, attValue)) {
208  return *result;
209  }
210  auto& result = addChild(XMLElement(name));
211  result.addAttribute(attName, attValue);
212  return result;
213 }
214 
215 const string& XMLElement::getChildData(string_ref name) const
216 {
217  return getChild(name).getData();
218 }
219 
221  string_ref defaultValue) const
222 {
223  auto* child = findChild(name);
224  return child ? child->getData() : defaultValue;
225 }
226 
227 bool XMLElement::getChildDataAsBool(string_ref name, bool defaultValue) const
228 {
229  auto* child = findChild(name);
230  return child ? StringOp::stringToBool(child->getData()) : defaultValue;
231 }
232 
233 int XMLElement::getChildDataAsInt(string_ref name, int defaultValue) const
234 {
235  auto* child = findChild(name);
236  return child ? StringOp::stringToInt(child->getData()) : defaultValue;
237 }
238 
240 {
241  if (auto* child = findChild(name)) {
242  child->setData(value);
243  } else {
244  addChild(XMLElement(name, value));
245  }
246 }
247 
249 {
250  children.clear();
251 }
252 
254 {
255  return findAttribute(name) != attributes.end();
256 }
257 
258 const string& XMLElement::getAttribute(string_ref attName) const
259 {
260  auto it = findAttribute(attName);
261  if (it == attributes.end()) {
262  throw ConfigException("Missing attribute \"" +
263  attName + "\".");
264  }
265  return it->second;
266 }
267 
269  string_ref defaultValue) const
270 {
271  auto it = findAttribute(attName);
272  return (it == attributes.end()) ? defaultValue : it->second;
273 }
274 
276  bool defaultValue) const
277 {
278  auto it = findAttribute(attName);
279  return (it == attributes.end()) ? defaultValue
280  : StringOp::stringToBool(it->second);
281 }
282 
284  int defaultValue) const
285 {
286  auto it = findAttribute(attName);
287  return (it == attributes.end()) ? defaultValue
288  : StringOp::stringToInt(it->second);
289 }
290 
292  unsigned& result) const
293 {
294  auto it = findAttribute(attName);
295  if (it != attributes.end()) {
296  result = StringOp::stringToInt(it->second);
297  return true;
298  } else {
299  return false;
300  }
301 }
302 
303 string XMLElement::dump() const
304 {
305  StringOp::Builder result;
306  dump(result, 0);
307  return result;
308 }
309 
310 void XMLElement::dump(StringOp::Builder& result, unsigned indentNum) const
311 {
312  string indent(indentNum, ' ');
313  result << indent << '<' << getName();
314  for (auto& p : attributes) {
315  result << ' ' << p.first
316  << "=\"" << XMLEscape(p.second) << '"';
317  }
318  if (children.empty()) {
319  if (data.empty()) {
320  result << "/>\n";
321  } else {
322  result << '>' << XMLEscape(data) << "</"
323  << getName() << ">\n";
324  }
325  } else {
326  result << ">\n";
327  for (auto& c : children) {
328  c.dump(result, indentNum + 2);
329  }
330  result << indent << "</" << getName() << ">\n";
331  }
332 }
333 
334 string XMLElement::XMLEscape(const string& str)
335 {
336  xmlChar* buffer = xmlEncodeEntitiesReentrant(
337  nullptr, reinterpret_cast<const xmlChar*>(str.c_str()));
338  string result = reinterpret_cast<const char*>(buffer);
339  xmlFree(buffer);
340  return result;
341 }
342 
343 static unique_ptr<FileContext> lastSerializedFileContext;
344 unique_ptr<FileContext> XMLElement::getLastSerializedFileContext()
345 {
346  return std::move(lastSerializedFileContext); // this also sets value to nullptr;
347 }
348 // version 1: initial version
349 // version 2: removed 'context' tag
350 // also removed 'parent', but that was never serialized
351 // 2b: (no need to increase version) name and data members are
352 // serialized as normal members instead of constructor parameters
353 // 2c: (no need to increase version) attributes were initially stored as
354 // map<string, string>, later this was changed to
355 // vector<pair<string, string>>. To keep bw-compat the serialize()
356 // method converted between these two formats. Though (by luck) in
357 // the XML output both datastructures are serialized to the same
358 // format, so we can drop this conversion step without breaking
359 // bw-compat.
360 template<typename Archive>
361 void XMLElement::serialize(Archive& ar, unsigned version)
362 {
363  ar.serialize("name", name);
364  ar.serialize("data", data);
365  ar.serialize("attributes", attributes);
366  ar.serialize("children", children);
367 
368  if (ar.versionBelow(version, 2)) {
369  assert(ar.isLoader());
370  unique_ptr<FileContext> context;
371  ar.serialize("context", context);
372  if (context.get()) {
373  assert(!lastSerializedFileContext.get());
374  lastSerializedFileContext = std::move(context);
375  }
376  }
377 }
378 INSTANTIATE_SERIALIZE_METHODS(XMLElement);
379 
380 } // namespace openmsx