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