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 attrName) const
47 {
48  auto it = getAttributeIter(attrName);
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  if (auto it = getAttributeIter(attrName); it != end(attributes)) {
71  attributes.erase(it);
72  }
73 }
74 
75 std::vector<const XMLElement*> XMLElement::getChildren(string_view childName) const
76 {
77  std::vector<const XMLElement*> result;
78  for (auto& c : children) {
79  if (c.getName() == childName) {
80  result.push_back(&c);
81  }
82  }
83  return result;
84 }
85 
86 XMLElement* XMLElement::findChild(string_view childName)
87 {
88  auto it = ranges::find_if(
89  children, [&](auto& c) { return c.getName() == childName; });
90  return (it != end(children)) ? &*it : nullptr;
91 }
92 const XMLElement* XMLElement::findChild(string_view childName) const
93 {
94  return const_cast<XMLElement*>(this)->findChild(childName);
95 }
96 
97 const XMLElement* XMLElement::findNextChild(string_view childName,
98  size_t& fromIndex) const
99 {
100  for (auto i : xrange(fromIndex, children.size())) {
101  if (children[i].getName() == childName) {
102  fromIndex = i + 1;
103  return &children[i];
104  }
105  }
106  for (auto i : xrange(fromIndex)) {
107  if (children[i].getName() == childName) {
108  fromIndex = i + 1;
109  return &children[i];
110  }
111  }
112  return nullptr;
113 }
114 
116  string_view attrName, string_view attValue)
117 {
118  auto it = ranges::find_if(children, [&](auto& c) {
119  if (c.getName() != childName) return false;
120  auto* value = c.findAttribute(attrName);
121  if (!value) return false;
122  return *value == attValue;
123  });
124  return (it != end(children)) ? &*it : nullptr;
125 }
126 
127 const XMLElement* XMLElement::findChildWithAttribute(string_view childName,
128  string_view attrName, string_view attValue) const
129 {
130  return const_cast<XMLElement*>(this)->findChildWithAttribute(
131  childName, attrName, attValue);
132 }
133 
134 XMLElement& XMLElement::getChild(string_view childName)
135 {
136  if (auto* elem = findChild(childName)) {
137  return *elem;
138  }
139  throw ConfigException("Missing tag \"", childName, "\".");
140 }
141 const XMLElement& XMLElement::getChild(string_view childName) const
142 {
143  return const_cast<XMLElement*>(this)->getChild(childName);
144 }
145 
146 XMLElement& XMLElement::getCreateChild(string_view childName,
147  string_view defaultValue)
148 {
149  if (auto* result = findChild(childName)) {
150  return *result;
151  }
152  return addChild(string(childName), string(defaultValue));
153 }
154 
156  string_view childName, string_view attrName,
157  string_view attValue)
158 {
159  if (auto* result = findChildWithAttribute(childName, attrName, attValue)) {
160  return *result;
161  }
162  auto& result = addChild(string(childName));
163  result.addAttribute(string(attrName), string(attValue));
164  return result;
165 }
166 
167 const string& XMLElement::getChildData(string_view childName) const
168 {
169  return getChild(childName).getData();
170 }
171 
172 string_view XMLElement::getChildData(string_view childName,
173  string_view defaultValue) const
174 {
175  auto* child = findChild(childName);
176  return child ? child->getData() : defaultValue;
177 }
178 
179 bool XMLElement::getChildDataAsBool(string_view childName, bool defaultValue) const
180 {
181  auto* child = findChild(childName);
182  return child ? StringOp::stringToBool(child->getData()) : defaultValue;
183 }
184 
185 int XMLElement::getChildDataAsInt(string_view childName, int defaultValue) const
186 {
187  auto* child = findChild(childName);
188  return child ? StringOp::stringToInt(child->getData()) : defaultValue;
189 }
190 
191 void XMLElement::setChildData(string_view childName, string value)
192 {
193  if (auto* child = findChild(childName)) {
194  child->setData(std::move(value));
195  } else {
196  addChild(string(childName), std::move(value));
197  }
198 }
199 
201 {
202  children.clear();
203 }
204 
205 bool XMLElement::hasAttribute(string_view attrName) const
206 {
207  return findAttribute(attrName);
208 }
209 
210 const string& XMLElement::getAttribute(string_view attrName) const
211 {
212  if (auto* value = findAttribute(attrName)) {
213  return *value;
214  }
215  throw ConfigException("Missing attribute \"", attrName, "\".");
216 }
217 
218 string_view XMLElement::getAttribute(string_view attrName,
219  string_view defaultValue) const
220 {
221  auto* value = findAttribute(attrName);
222  return value ? *value : defaultValue;
223 }
224 
225 bool XMLElement::getAttributeAsBool(string_view attrName,
226  bool defaultValue) const
227 {
228  auto* value = findAttribute(attrName);
229  return value ? StringOp::stringToBool(*value) : defaultValue;
230 }
231 
232 int XMLElement::getAttributeAsInt(string_view attrName,
233  int defaultValue) const
234 {
235  auto* value = findAttribute(attrName);
236  return value ? StringOp::stringToInt(*value) : defaultValue;
237 }
238 
239 bool XMLElement::findAttributeInt(string_view attrName,
240  unsigned& result) const
241 {
242  if (auto* value = findAttribute(attrName)) {
243  result = StringOp::stringToInt(*value);
244  return true;
245  } else {
246  return false;
247  }
248 }
249 
250 string XMLElement::dump() const
251 {
252  string result;
253  dump(result, 0);
254  return result;
255 }
256 
257 void XMLElement::dump(string& result, unsigned indentNum) const
258 {
259  strAppend(result, spaces(indentNum), '<', getName());
260  for (const auto& [attrName, value] : attributes) {
261  strAppend(result, ' ', attrName, "=\"", XMLEscape(value), '"');
262  }
263  if (children.empty()) {
264  if (data.empty()) {
265  strAppend(result, "/>\n");
266  } else {
267  strAppend(result, '>', XMLEscape(data), "</",
268  getName(), ">\n");
269  }
270  } else {
271  strAppend(result, ">\n");
272  for (auto& c : children) {
273  c.dump(result, indentNum + 2);
274  }
275  strAppend(result, spaces(indentNum), "</", getName(), ">\n");
276  }
277 }
278 
279 // This routine does the following substitutions:
280 // & -> &amp; must always be done
281 // < -> &lt; must always be done
282 // > -> &gt; always allowed, but must be done when it appears as ]]>
283 // ' -> &apos; always allowed, but must be done inside quoted attribute
284 // " -> &quot; always allowed, but must be done inside quoted attribute
285 // all chars less than 32 -> &#xnn;
286 // So to simplify things we always do these 5+32 substitutions.
287 string XMLElement::XMLEscape(string_view s)
288 {
289  string result;
290  result.reserve(s.size()); // By default assume no substitution will be needed
291  for (char c : s) {
292  if (auto uc = unsigned(c); uc < 32) {
293  auto hex = [](unsigned x) { return (x < 10) ? char(x + '0') : char(x - 10 + 'a'); };
294  result += "&#x";
295  result += hex(uc / 16);
296  result += hex(uc % 16);
297  result += ';';
298  } else if (c == '<') {
299  result += "&lt;";
300  } else if (c == '>') {
301  result += "&gt;";
302  } else if (c == '&') {
303  result += "&amp;";
304  } else if (c == '"') {
305  result += "&quot;";
306  } else if (c == '\'') {
307  result += "&apos;";
308  } else {
309  result += c;
310  }
311  }
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
openmsx::XMLElement::findAttribute
const std::string * findAttribute(std::string_view attrName) const
Definition: XMLElement.cc:46
ConfigException.hh
openmsx::XMLElement::getChildDataAsBool
bool getChildDataAsBool(std::string_view childName, bool defaultValue=false) const
Definition: XMLElement.cc:179
xrange
auto xrange(T e)
Definition: xrange.hh:170
openmsx::XMLElement::setChildData
void setChildData(std::string_view childName, std::string value)
Definition: XMLElement.cc:191
serialize.hh
openmsx::XMLElement::hasAttribute
bool hasAttribute(std::string_view name) const
Definition: XMLElement.cc:205
openmsx::XMLElement::getAttribute
const std::string & getAttribute(std::string_view attrName) const
Definition: XMLElement.cc:210
openmsx::XMLElement::findChildWithAttribute
const XMLElement * findChildWithAttribute(std::string_view childName, std::string_view attrName, std::string_view attValue) const
Definition: XMLElement.cc:127
openmsx::ConfigException
Definition: ConfigException.hh:9
openmsx::XMLElement::getChildData
const std::string & getChildData(std::string_view childName) const
Definition: XMLElement.cc:167
ranges.hh
openmsx::XMLElement::removeChild
void removeChild(const XMLElement &child)
Definition: XMLElement.cc:29
openmsx::XMLElement::getCreateChildWithAttribute
XMLElement & getCreateChildWithAttribute(std::string_view childName, std::string_view attrName, std::string_view attValue)
Definition: XMLElement.cc:155
XMLElement.hh
openmsx::XMLElement::addChild
XMLElement & addChild(std::string name)
Definition: XMLElement.cc:20
strAppend
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:644
openmsx::XMLElement::removeAllChildren
void removeAllChildren()
Definition: XMLElement.cc:200
openmsx::XMLElement::findNextChild
const XMLElement * findNextChild(std::string_view name, size_t &fromIndex) const
Definition: XMLElement.cc:97
openmsx::XMLElement::serialize
void serialize(Archive &ar, unsigned version)
Definition: XMLElement.cc:333
openmsx::XMLElement::findChild
const XMLElement * findChild(std::string_view childName) const
Definition: XMLElement.cc:92
openmsx::XMLElement::findAttributeInt
bool findAttributeInt(std::string_view attrName, unsigned &result) const
Definition: XMLElement.cc:239
StringOp::stringToBool
bool stringToBool(string_view str)
Definition: StringOp.cc:43
openmsx::XMLElement
Definition: XMLElement.hh:16
openmsx::XMLElement::getLastSerializedFileContext
static std::unique_ptr< FileContext > getLastSerializedFileContext()
Definition: XMLElement.cc:316
openmsx::XMLElement::removeAttribute
void removeAttribute(std::string_view name)
Definition: XMLElement.cc:68
INSTANTIATE_SERIALIZE_METHODS
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
serialize_stl.hh
openmsx::XMLElement::addAttribute
void addAttribute(std::string name, std::string value)
Definition: XMLElement.cc:52
FileContext.hh
spaces
strCatImpl::ConcatSpaces spaces(size_t n)
Definition: strCat.hh:675
openmsx::XMLElement::getCreateChild
XMLElement & getCreateChild(std::string_view childName, std::string_view defaultValue={})
Definition: XMLElement.cc:146
openmsx::XMLElement::dump
std::string dump() const
Definition: XMLElement.cc:250
openmsx::XMLElement::getName
const std::string & getName() const
Definition: XMLElement.hh:31
StringOp::stringToInt
int stringToInt(const string &str)
Definition: StringOp.cc:16
openmsx::x
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:1419
gl::context
std::unique_ptr< Context > context
Definition: GLContext.cc:9
StringOp.hh
strCat.hh
openmsx::XMLElement::getData
const std::string & getData() const
Definition: XMLElement.hh:36
openmsx::XMLElement::getAttributeAsInt
int getAttributeAsInt(std::string_view attrName, int defaultValue=0) const
Definition: XMLElement.cc:232
openmsx::XMLElement::getChildDataAsInt
int getChildDataAsInt(std::string_view childName, int defaultValue=0) const
Definition: XMLElement.cc:185
openmsx::XMLElement::getAttributeAsBool
bool getAttributeAsBool(std::string_view attrName, bool defaultValue=false) const
Definition: XMLElement.cc:225
stl.hh
openmsx::XMLElement::XMLEscape
static std::string XMLEscape(std::string_view str)
Definition: XMLElement.cc:287
ranges::find_if
auto find_if(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:113
unreachable.hh
openmsx::XMLElement::getChildren
const Children & getChildren() const
Definition: XMLElement.hh:59
openmsx::XMLElement::setAttribute
void setAttribute(std::string_view name, std::string value)
Definition: XMLElement.cc:58
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
xrange.hh
rfind_if_unguarded
auto rfind_if_unguarded(RANGE &range, PRED pred)
Definition: stl.hh:160
openmsx::XMLElement::getChild
const XMLElement & getChild(std::string_view childName) const
Definition: XMLElement.cc:141