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::unique_ptr;
15 using std::string;
16 
17 namespace openmsx {
18 
20  : name(name_.str())
21 {
22 }
23 
25  : name(name_.str())
26  , data(data_.str())
27 {
28 }
29 
31 {
32  children.emplace_back(childName);
33  return children.back();
34 }
36 {
37  children.emplace_back(childName, childData);
38  return children.back();
39 }
40 
42 {
43  children.erase(rfind_if_unguarded(children,
44  [&](Children::value_type& v) { return &v == &child; }));
45 }
46 
47 XMLElement::Attributes::iterator XMLElement::findAttribute(string_view attrName)
48 {
49  return ranges::find_if(attributes,
50  [&](auto& a) { return a.first == attrName; });
51 }
52 XMLElement::Attributes::const_iterator XMLElement::findAttribute(string_view attrName) const
53 {
54  return ranges::find_if(attributes,
55  [&](auto& a) { return a.first == attrName; });
56 }
57 
59 {
60  assert(findAttribute(attrName) == end(attributes));
61  attributes.emplace_back(attrName.str(), value.str());
62 }
63 
65 {
66  auto it = findAttribute(attrName);
67  if (it != end(attributes)) {
68  it->second = value.str();
69  } else {
70  attributes.emplace_back(attrName.str(), value.str());
71  }
72 }
73 
75 {
76  auto it = findAttribute(attrName);
77  if (it != end(attributes)) {
78  attributes.erase(it);
79  }
80 }
81 
83 {
84  name = newName.str();
85 }
86 
88 {
89  name.clear();
90 }
91 
93 {
94  assert(children.empty()); // no mixed-content elements
95  data = newData.str();
96 }
97 
98 std::vector<const XMLElement*> XMLElement::getChildren(string_view childName) const
99 {
100  std::vector<const XMLElement*> result;
101  for (auto& c : children) {
102  if (c.getName() == childName) {
103  result.push_back(&c);
104  }
105  }
106  return result;
107 }
108 
110 {
111  auto it = ranges::find_if(
112  children, [&](auto& c) { return c.getName() == childName; });
113  return (it != end(children)) ? &*it : nullptr;
114 }
116 {
117  return const_cast<XMLElement*>(this)->findChild(childName);
118 }
119 
121  size_t& fromIndex) const
122 {
123  for (auto i : xrange(fromIndex, children.size())) {
124  if (children[i].getName() == childName) {
125  fromIndex = i + 1;
126  return &children[i];
127  }
128  }
129  for (auto i : xrange(fromIndex)) {
130  if (children[i].getName() == childName) {
131  fromIndex = i + 1;
132  return &children[i];
133  }
134  }
135  return nullptr;
136 }
137 
139  string_view attName, string_view attValue)
140 {
141  auto it = ranges::find_if(children, [&](auto& c) {
142  return (c.getName() == childName) &&
143  (c.getAttribute(attName) == attValue);
144  });
145  return (it != end(children)) ? &*it : nullptr;
146 }
147 
149  string_view attName, string_view attValue) const
150 {
151  return const_cast<XMLElement*>(this)->findChildWithAttribute(
152  childName, attName, attValue);
153 }
154 
156 {
157  if (auto* elem = findChild(childName)) {
158  return *elem;
159  }
160  throw ConfigException("Missing tag \"", childName, "\".");
161 }
163 {
164  return const_cast<XMLElement*>(this)->getChild(childName);
165 }
166 
168  string_view defaultValue)
169 {
170  if (auto* result = findChild(childName)) {
171  return *result;
172  }
173  return addChild(childName, defaultValue);
174 }
175 
177  string_view childName, string_view attName,
178  string_view attValue)
179 {
180  if (auto* result = findChildWithAttribute(childName, attName, attValue)) {
181  return *result;
182  }
183  auto& result = addChild(childName);
184  result.addAttribute(attName, attValue);
185  return result;
186 }
187 
188 const string& XMLElement::getChildData(string_view childName) const
189 {
190  return getChild(childName).getData();
191 }
192 
194  string_view defaultValue) const
195 {
196  auto* child = findChild(childName);
197  return child ? child->getData() : defaultValue;
198 }
199 
200 bool XMLElement::getChildDataAsBool(string_view childName, bool defaultValue) const
201 {
202  auto* child = findChild(childName);
203  return child ? StringOp::stringToBool(child->getData()) : defaultValue;
204 }
205 
206 int XMLElement::getChildDataAsInt(string_view childName, int defaultValue) const
207 {
208  auto* child = findChild(childName);
209  return child ? StringOp::stringToInt(child->getData()) : defaultValue;
210 }
211 
213 {
214  if (auto* child = findChild(childName)) {
215  child->setData(value);
216  } else {
217  addChild(childName, value);
218  }
219 }
220 
222 {
223  children.clear();
224 }
225 
227 {
228  return findAttribute(attrName) != end(attributes);
229 }
230 
231 const string& XMLElement::getAttribute(string_view attName) const
232 {
233  auto it = findAttribute(attName);
234  if (it == end(attributes)) {
235  throw ConfigException("Missing attribute \"", attName, "\".");
236  }
237  return it->second;
238 }
239 
241  string_view defaultValue) const
242 {
243  auto it = findAttribute(attName);
244  return (it == end(attributes)) ? defaultValue : it->second;
245 }
246 
248  bool defaultValue) const
249 {
250  auto it = findAttribute(attName);
251  return (it == end(attributes)) ? defaultValue
252  : StringOp::stringToBool(it->second);
253 }
254 
256  int defaultValue) const
257 {
258  auto it = findAttribute(attName);
259  return (it == end(attributes)) ? defaultValue
260  : StringOp::stringToInt(it->second);
261 }
262 
264  unsigned& result) const
265 {
266  auto it = findAttribute(attName);
267  if (it != end(attributes)) {
268  result = StringOp::stringToInt(it->second);
269  return true;
270  } else {
271  return false;
272  }
273 }
274 
275 string XMLElement::dump() const
276 {
277  string result;
278  dump(result, 0);
279  return result;
280 }
281 
282 void XMLElement::dump(string& result, unsigned indentNum) const
283 {
284  strAppend(result, spaces(indentNum), '<', getName());
285  for (auto& p : attributes) {
286  strAppend(result, ' ', p.first,
287  "=\"", XMLEscape(p.second), '"');
288  }
289  if (children.empty()) {
290  if (data.empty()) {
291  strAppend(result, "/>\n");
292  } else {
293  strAppend(result, '>', XMLEscape(data), "</",
294  getName(), ">\n");
295  }
296  } else {
297  strAppend(result, ">\n");
298  for (auto& c : children) {
299  c.dump(result, indentNum + 2);
300  }
301  strAppend(result, spaces(indentNum), "</", getName(), ">\n");
302  }
303 }
304 
305 // This routine does the following substitutions:
306 // & -> &amp; must always be done
307 // < -> &lt; must always be done
308 // > -> &gt; always allowed, but must be done when it appears as ]]>
309 // ' -> &apos; always allowed, but must be done inside quoted attribute
310 // " -> &quot; always allowed, but must be done inside quoted attribute
311 // So to simplify things we always do these 5 substitutions.
312 string XMLElement::XMLEscape(const string& s)
313 {
314  static const char* const CHARS = "<>&\"'";
315  size_t i = s.find_first_of(CHARS);
316  if (i == string::npos) return s; // common case, no substitutions
317 
318  string result;
319  result.reserve(s.size() + 10); // extra space for at least 2 substitutions
320  size_t pos = 0;
321  do {
322  strAppend(result, string_view(s).substr(pos, i - pos));
323  switch (s[i]) {
324  case '<' : result += "&lt;"; break;
325  case '>' : result += "&gt;"; break;
326  case '&' : result += "&amp;"; break;
327  case '"' : result += "&quot;"; break;
328  case '\'': result += "&apos;"; break;
329  default: UNREACHABLE;
330  }
331  pos = i + 1;
332  i = s.find_first_of(CHARS, pos);
333  } while (i != string::npos);
334  strAppend(result, string_view(s).substr(pos));
335  return result;
336 }
337 
338 static unique_ptr<FileContext> lastSerializedFileContext;
340 {
341  return std::move(lastSerializedFileContext); // this also sets value to nullptr;
342 }
343 // version 1: initial version
344 // version 2: removed 'context' tag
345 // also removed 'parent', but that was never serialized
346 // 2b: (no need to increase version) name and data members are
347 // serialized as normal members instead of constructor parameters
348 // 2c: (no need to increase version) attributes were initially stored as
349 // map<string, string>, later this was changed to
350 // vector<pair<string, string>>. To keep bw-compat the serialize()
351 // method converted between these two formats. Though (by luck) in
352 // the XML output both datastructures are serialized to the same
353 // format, so we can drop this conversion step without breaking
354 // bw-compat.
355 template<typename Archive>
356 void XMLElement::serialize(Archive& ar, unsigned version)
357 {
358  ar.serialize("name", name);
359  ar.serialize("data", data);
360  ar.serialize("attributes", attributes);
361  ar.serialize("children", children);
362 
363  if (ar.versionBelow(version, 2)) {
364  assert(ar.isLoader());
365  unique_ptr<FileContext> context;
366  ar.serialize("context", context);
367  if (context) {
368  assert(!lastSerializedFileContext);
369  lastSerializedFileContext = std::move(context);
370  }
371  }
372 }
374 
375 } // namespace openmsx
bool findAttributeInt(string_view attName, unsigned &result) const
Definition: XMLElement.cc:263
const std::string & getName() const
Definition: XMLElement.hh:28
auto xrange(T e)
Definition: xrange.hh:170
bool getAttributeAsBool(string_view attName, bool defaultValue=false) const
Definition: XMLElement.cc:247
void setName(string_view name)
Definition: XMLElement.cc:82
const XMLElement * findChild(string_view name) const
Definition: XMLElement.cc:115
void removeChild(const XMLElement &child)
Definition: XMLElement.cc:41
XMLElement & addChild(string_view name)
Definition: XMLElement.cc:30
void setAttribute(string_view name, string_view value)
Definition: XMLElement.cc:64
std::string dump() const
Definition: XMLElement.cc:275
XMLElement & getCreateChildWithAttribute(string_view name, string_view attName, string_view attValue)
Definition: XMLElement.cc:176
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:648
auto find_if(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:113
int getAttributeAsInt(string_view attName, int defaultValue=0) const
Definition: XMLElement.cc:255
const std::string & getData() const
Definition: XMLElement.hh:33
void serialize(Archive &ar, unsigned version)
Definition: XMLElement.cc:356
static std::unique_ptr< FileContext > getLastSerializedFileContext()
Definition: XMLElement.cc:339
void addAttribute(string_view name, string_view value)
Definition: XMLElement.cc:58
std::unique_ptr< Context > context
Definition: GLContext.cc:9
void setData(string_view data)
Definition: XMLElement.cc:92
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
const std::string & getChildData(string_view name) const
Definition: XMLElement.cc:188
const XMLElement * findChildWithAttribute(string_view name, string_view attName, string_view attValue) const
Definition: XMLElement.cc:148
void removeAttribute(string_view name)
Definition: XMLElement.cc:74
bool stringToBool(string_view str)
Definition: StringOp.cc:41
int getChildDataAsInt(string_view name, int defaultValue=0) const
Definition: XMLElement.cc:206
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
void setChildData(string_view name, string_view value)
Definition: XMLElement.cc:212
int stringToInt(const string &str)
Definition: StringOp.cc:14
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:840
std::string str() const
Definition: string_view.cc:12
XMLElement & getCreateChild(string_view name, string_view defaultValue={})
Definition: XMLElement.cc:167
static std::string XMLEscape(const std::string &str)
Definition: XMLElement.cc:312
strCatImpl::ConcatSpaces spaces(size_t n)
Definition: strCat.hh:679
string_view substr(string_view utf8, string_view::size_type first=0, string_view::size_type len=string_view::npos)
bool getChildDataAsBool(string_view name, bool defaultValue=false) const
Definition: XMLElement.cc:200
const std::string & getAttribute(string_view attName) const
Definition: XMLElement.cc:231
const Children & getChildren() const
Definition: XMLElement.hh:47
const XMLElement & getChild(string_view name) const
Definition: XMLElement.cc:162
auto rfind_if_unguarded(RANGE &range, PRED pred)
Definition: stl.hh:174
bool hasAttribute(string_view name) const
Definition: XMLElement.cc:226
const XMLElement * findNextChild(string_view name, size_t &fromIndex) const
Definition: XMLElement.cc:120
auto end(const string_view &x)
Definition: string_view.hh:152
#define UNREACHABLE
Definition: unreachable.hh:38