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