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