1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 #include "rgw/rgw_xml.h"
5 #include <gtest/gtest.h>
14 // intrusive XML decoding API
15 bool decode_xml(XMLObj
*obj
) {
16 if (!RGWXMLDecoder::decode_xml("Name", name
, obj
, true)) {
20 if (!RGWXMLDecoder::decode_xml("Status", status
, obj
, false)) {
21 // status is optional and defaults to True
30 NameAndStatus name_and_status
;
34 // these are attributes
38 // intrusive XML decoding API
39 bool decode_xml(XMLObj
*obj
) {
40 if (!RGWXMLDecoder::decode_xml("NameAndStatus", name_and_status
, obj
, true)) {
41 // name amd status are mandatory
44 if (!RGWXMLDecoder::decode_xml("Value", value
, obj
, true)) {
48 if (!RGWXMLDecoder::decode_xml("ExtraValue", extra_value
, obj
, false)) {
49 // extra value is optional and defaults to zero
53 // date attribute is optional
54 if (!obj
->get_attr("Date", date
)) {
57 // comment attribute is optional
58 if (!obj
->get_attr("Comment", comment
)) {
59 comment
= "no comment";
68 std::list
<Item
> item_list
;
70 // intrusive XML decoding API
71 bool decode_xml(XMLObj
*obj
) {
72 do_decode_xml_obj(item_list
, "Item", obj
);
77 // in case of non-intrusive decoding class
78 // hierarchy should reflect the XML hierarchy
80 class NameXMLObj
: public XMLObj
{
82 void xml_handle_data(const char *s
, int len
) override
{
83 // no need to set "data", setting "name" directly
89 ~NameXMLObj() override
= default;
92 class StatusXMLObj
: public XMLObj
{
94 void xml_handle_data(const char *s
, int len
) override
{
95 std::istringstream
is(std::string(s
, len
));
96 is
>> std::boolalpha
>> value
;
101 ~StatusXMLObj() override
= default;
104 class NameAndStatusXMLObj
: public NameAndStatus
, public XMLObj
{
106 ~NameAndStatusXMLObj() override
= default;
108 bool xml_end(const char *el
) override
{
109 XMLObjIter iter
= find("Name");
110 NameXMLObj
* _name
= static_cast<NameXMLObj
*>(iter
.get_next());
116 iter
= find("Status");
117 StatusXMLObj
* _status
= static_cast<StatusXMLObj
*>(iter
.get_next());
119 // status is optional and defaults to True
122 status
= _status
->value
;
128 class ItemXMLObj
: public Item
, public XMLObj
{
130 ~ItemXMLObj() override
= default;
132 bool xml_end(const char *el
) override
{
133 XMLObjIter iter
= find("NameAndStatus");
134 NameAndStatusXMLObj
* _name_and_status
= static_cast<NameAndStatusXMLObj
*>(iter
.get_next());
135 if (!_name_and_status
) {
136 // name and status are mandatory
139 name_and_status
= *static_cast<NameAndStatus
*>(_name_and_status
);
140 iter
= find("Value");
141 XMLObj
* _value
= iter
.get_next();
143 // value is mandatory
147 value
= std::stoi(_value
->get_data());
148 } catch (const std::exception
& e
) {
151 iter
= find("ExtraValue");
152 XMLObj
* _extra_value
= iter
.get_next();
154 // extra value is optional but cannot contain garbage
156 extra_value
= std::stoi(_extra_value
->get_data());
157 } catch (const std::exception
& e
) {
161 // if not set, it defaults to zero
165 // date attribute is optional
166 if (!get_attr("Date", date
)) {
169 // comment attribute is optional
170 if (!get_attr("Comment", comment
)) {
171 comment
= "no comment";
178 class ItemsXMLObj
: public Items
, public XMLObj
{
180 ~ItemsXMLObj() override
= default;
182 bool xml_end(const char *el
) override
{
183 XMLObjIter iter
= find("Item");
184 ItemXMLObj
* item_ptr
= static_cast<ItemXMLObj
*>(iter
.get_next());
185 // mandatory to have at least one item
186 bool item_found
= false;
188 item_list
.push_back(*static_cast<Item
*>(item_ptr
));
189 item_ptr
= static_cast<ItemXMLObj
*>(iter
.get_next());
196 class ItemsXMLParser
: public RGWXMLParser
{
197 static const int MAX_NAME_LEN
= 16;
199 XMLObj
*alloc_obj(const char *el
) override
{
200 if (strncmp(el
, "Items", MAX_NAME_LEN
) == 0) {
201 items
= new ItemsXMLObj
;
203 } else if (strncmp(el
, "Item", MAX_NAME_LEN
) == 0) {
204 return new ItemXMLObj
;
205 } else if (strncmp(el
, "NameAndStatus", MAX_NAME_LEN
) == 0) {
206 return new NameAndStatusXMLObj
;
207 } else if (strncmp(el
, "Name", MAX_NAME_LEN
) == 0) {
208 return new NameXMLObj
;
209 } else if (strncmp(el
, "Status", MAX_NAME_LEN
) == 0) {
210 return new StatusXMLObj
;
214 // this is a pointer to the parsed results
218 static const char* good_input
= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
220 "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>"
221 "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>"
222 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>"
223 "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>"
226 static const char* expected_output
= "((hello,1),1,0),((world,1),2,99),((foo,1),3,0),((bar,0),4,42),";
228 std::string
to_string(const Items
& items
) {
229 std::stringstream ss
;
230 for (const auto& item
: items
.item_list
) {
231 ss
<< "((" << item
.name_and_status
.name
<< "," << item
.name_and_status
.status
<< ")," << item
.value
<< "," << item
.extra_value
<< ")" << ",";
236 std::string
to_string_with_attributes(const Items
& items
) {
237 std::stringstream ss
;
238 for (const auto& item
: items
.item_list
) {
239 ss
<< "(" << item
.date
<< "," << item
.comment
<< ",(" << item
.name_and_status
.name
<< "," << item
.name_and_status
.status
<< "),"
240 << item
.value
<< "," << item
.extra_value
<< ")" << ",";
245 TEST(TestParser
, BasicParsing
)
247 ItemsXMLParser parser
;
248 ASSERT_TRUE(parser
.init());
249 ASSERT_TRUE(parser
.parse(good_input
, strlen(good_input
), 1));
250 ASSERT_EQ(parser
.items
->item_list
.size(), 4U);
251 ASSERT_STREQ(to_string(*parser
.items
).c_str(), expected_output
);
254 static const char* malformed_input
= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
256 "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value><Item>"
257 "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>"
258 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>"
259 "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>"
262 TEST(TestParser
, MalformedInput
)
264 ItemsXMLParser parser
;
265 ASSERT_TRUE(parser
.init());
266 ASSERT_FALSE(parser
.parse(good_input
, strlen(malformed_input
), 1));
269 static const char* missing_value_input
= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
271 "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>"
272 "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>"
273 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>"
274 "<Item><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>"
277 TEST(TestParser
, MissingMandatoryTag
)
279 ItemsXMLParser parser
;
280 ASSERT_TRUE(parser
.init());
281 ASSERT_FALSE(parser
.parse(missing_value_input
, strlen(missing_value_input
), 1));
284 static const char* unknown_tag_input
= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
286 "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>"
287 "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>"
288 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus><Kaboom>0</Kaboom></Item>"
289 "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>"
293 TEST(TestParser
, UnknownTag
)
295 ItemsXMLParser parser
;
296 ASSERT_TRUE(parser
.init());
297 ASSERT_TRUE(parser
.parse(unknown_tag_input
, strlen(unknown_tag_input
), 1));
298 ASSERT_EQ(parser
.items
->item_list
.size(), 4U);
299 ASSERT_STREQ(to_string(*parser
.items
).c_str(), expected_output
);
302 static const char* invalid_value_input
= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
304 "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>"
305 "<Item><ExtraValue>kaboom</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value></Item>"
306 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>"
307 "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>"
310 TEST(TestParser
, InvalidValue
)
312 ItemsXMLParser parser
;
313 ASSERT_TRUE(parser
.init());
314 ASSERT_FALSE(parser
.parse(invalid_value_input
, strlen(invalid_value_input
), 1));
317 static const char* good_input1
= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
319 "<Item><NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value></Item>"
320 "<Item><ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name>";
322 static const char* good_input2
= "</NameAndStatus><Value>2</Value></Item>"
323 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>"
324 "<Item><Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus></Item>"
327 TEST(TestParser
, MultipleChunks
)
329 ItemsXMLParser parser
;
330 ASSERT_TRUE(parser
.init());
331 ASSERT_TRUE(parser
.parse(good_input1
, strlen(good_input1
), 0));
332 ASSERT_TRUE(parser
.parse(good_input2
, strlen(good_input2
), 1));
333 ASSERT_EQ(parser
.items
->item_list
.size(), 4U);
334 ASSERT_STREQ(to_string(*parser
.items
).c_str(), expected_output
);
337 static const char* input_with_attributes
= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
339 "<Item Date=\"Tue Dec 27 17:21:29 2011\" Kaboom=\"just ignore\">"
340 "<NameAndStatus><Name>hello</Name></NameAndStatus><Value>1</Value>"
342 "<Item Comment=\"hello world\">"
343 "<ExtraValue>99</ExtraValue><NameAndStatus><Name>world</Name></NameAndStatus><Value>2</Value>"
345 "<Item><Value>3</Value><NameAndStatus><Name>foo</Name></NameAndStatus></Item>"
346 "<Item Comment=\"goodbye\" Date=\"Thu Feb 28 10:00:18 UTC 2019 \">"
347 "<Value>4</Value><ExtraValue>42</ExtraValue><NameAndStatus><Name>bar</Name><Status>False</Status></NameAndStatus>"
351 static const char* expected_output_with_attributes
= "(Tue Dec 27 17:21:29 2011,no comment,(hello,1),1,0),"
352 "(no date,hello world,(world,1),2,99),"
353 "(no date,no comment,(foo,1),3,0),"
354 "(Thu Feb 28 10:00:18 UTC 2019 ,goodbye,(bar,0),4,42),";
356 TEST(TestParser
, Attributes
)
358 ItemsXMLParser parser
;
359 ASSERT_TRUE(parser
.init());
360 ASSERT_TRUE(parser
.parse(input_with_attributes
, strlen(input_with_attributes
), 1));
361 ASSERT_EQ(parser
.items
->item_list
.size(), 4U);
362 ASSERT_STREQ(to_string_with_attributes(*parser
.items
).c_str(),
363 expected_output_with_attributes
);
366 TEST(TestDecoder
, BasicParsing
)
368 RGWXMLDecoder::XMLParser parser
;
369 ASSERT_TRUE(parser
.init());
370 ASSERT_TRUE(parser
.parse(good_input
, strlen(good_input
), 1));
373 ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result
, &parser
, true));
375 ASSERT_EQ(result
.item_list
.size(), 4U);
376 ASSERT_STREQ(to_string(result
).c_str(), expected_output
);
379 TEST(TestDecoder
, MalfomedInput
)
381 RGWXMLDecoder::XMLParser parser
;
382 ASSERT_TRUE(parser
.init());
383 ASSERT_FALSE(parser
.parse(good_input
, strlen(malformed_input
), 1));
386 TEST(TestDecoder
, MissingMandatoryTag
)
388 RGWXMLDecoder::XMLParser parser
;
389 ASSERT_TRUE(parser
.init());
390 ASSERT_TRUE(parser
.parse(missing_value_input
, strlen(missing_value_input
), 1));
393 ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result
, &parser
, true));
397 TEST(TestDecoder
, InvalidValue
)
399 RGWXMLDecoder::XMLParser parser
;
400 ASSERT_TRUE(parser
.init());
401 ASSERT_TRUE(parser
.parse(invalid_value_input
, strlen(invalid_value_input
), 1));
404 ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result
, &parser
, true));
408 TEST(TestDecoder
, MultipleChunks
)
410 RGWXMLDecoder::XMLParser parser
;
411 ASSERT_TRUE(parser
.init());
412 ASSERT_TRUE(parser
.parse(good_input1
, strlen(good_input1
), 0));
413 ASSERT_TRUE(parser
.parse(good_input2
, strlen(good_input2
), 1));
416 ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result
, &parser
, true));
418 ASSERT_EQ(result
.item_list
.size(), 4U);
419 ASSERT_STREQ(to_string(result
).c_str(), expected_output
);
422 TEST(TestDecoder
, Attributes
)
424 RGWXMLDecoder::XMLParser parser
;
425 ASSERT_TRUE(parser
.init());
426 ASSERT_TRUE(parser
.parse(input_with_attributes
, strlen(input_with_attributes
), 1));
429 ASSERT_TRUE(RGWXMLDecoder::decode_xml("Items", result
, &parser
, true));
431 ASSERT_EQ(result
.item_list
.size(), 4U);
432 ASSERT_STREQ(to_string_with_attributes(result
).c_str(),
433 expected_output_with_attributes
);