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