1 // Copyright 2008, Google Inc. All rights reserved.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are met:
5 //
6 // 1. Redistributions of source code must retain the above copyright notice,
7 // this list of conditions and the following disclaimer.
8 // 2. Redistributions in binary form must reproduce the above copyright notice,
9 // this list of conditions and the following disclaimer in the documentation
10 // and/or other materials provided with the distribution.
11 // 3. Neither the name of Google Inc. nor the names of its contributors may be
12 // used to endorse or promote products derived from this software without
13 // specific prior written permission.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
16 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
18 // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26 // This file contains the unit tests for the KmlHandler class.
27
28 #include "kml/dom/kml_handler.h"
29 #include <stdlib.h> // For calloc() and free().
30 #include "boost/scoped_ptr.hpp"
31 #include "kml/base/file.h"
32 #include "kml/dom/element.h"
33 #include "kml/dom/kml_cast.h"
34 #include "kml/dom/kml_funcs.h"
35 #include "kml/dom/kml_ptr.h"
36 #include "kml/dom/placemark.h"
37 #include "kml/dom/parser.h"
38 #include "kml/dom/parser_observer.h"
39 #include "gtest/gtest.h"
40
41 // The following define is a convenience for testing inside Google.
42 #ifdef GOOGLE_INTERNAL
43 #include "kml/base/google_internal_test.h"
44 #endif
45
46 #ifndef DATADIR
47 #error *** DATADIR must be defined! ***
48 #endif
49
50 namespace kmldom {
51
52 typedef std::vector<ElementPtr> element_vector_t;
53
54 // This class is the unit test fixture for the KmlHandler class.
55 class KmlHandlerTest : public testing::Test {
56 protected:
SetUp()57 virtual void SetUp() {
58 kml_handler_.reset(new KmlHandler(observers_));
59 }
60
TearDown()61 virtual void TearDown() {
62 }
63
64 kmlbase::StringVector atts_;
65 parser_observer_vector_t observers_;
66 boost::scoped_ptr<KmlHandler> kml_handler_;
67 void VerifyFolderParse(const ElementPtr& root) const;
68 void VerifyElementTypes(const KmlDomType* types_array,
69 const element_vector_t& element_vector) const;
70 void MultipleObserverTestCommon(size_t max_elements,
71 size_t expected_elements,
72 size_t expected_pairs) const;
73 };
74
75 // This verifies the initial state of a freshly constructed KmlHandler.
TEST_F(KmlHandlerTest,TestInitialState)76 TEST_F(KmlHandlerTest, TestInitialState) {
77 // No elements have been processed, but the PopRoot() method should
78 // be well behaved.
79 ASSERT_TRUE(NULL == kml_handler_->PopRoot());
80 }
81
82 // This is a test of the StartElement() method for a known simple element.
TEST_F(KmlHandlerTest,TestStartSimpleElement)83 TEST_F(KmlHandlerTest, TestStartSimpleElement) {
84 // This is what expat sends to StartElement() on "<name>".
85 kml_handler_->StartElement("name", atts_);
86
87 // Since "name" is known we will find it as the root element.
88 ElementPtr root = kml_handler_->PopRoot();
89 ASSERT_EQ(root->Type(), Type_name);
90
91 // PopRoot() is destructive so now there is nothing.
92 ASSERT_TRUE(NULL == kml_handler_->PopRoot());
93 }
94
95 // This is a test of the EndElement() method for a known simple element.
TEST_F(KmlHandlerTest,TestEndSimpleElement)96 TEST_F(KmlHandlerTest, TestEndSimpleElement) {
97 // This is what expat sends to KmlHandler on "<name/>":
98 kml_handler_->StartElement("name", atts_);
99 kml_handler_->EndElement("name");
100
101 ElementPtr root = kml_handler_->PopRoot();
102 ASSERT_EQ(root->Type(), Type_name);
103
104 // PopRoot() is destructive so now there is nothing.
105 ASSERT_TRUE(NULL == kml_handler_->PopRoot());
106 }
107
108 // This is a test of the CharData() method for a known simple element.
TEST_F(KmlHandlerTest,TestBasicCharData)109 TEST_F(KmlHandlerTest, TestBasicCharData) {
110 // This is what expat sends to KmlHandler on "<name>what is in a</name>":
111 const char* kTagName = "name";
112 const char* kContent = "what is in a name";
113
114 kml_handler_->StartElement(kTagName, atts_);
115 kml_handler_->CharData(kContent);
116 kml_handler_->EndElement(kTagName);
117
118 ElementPtr root = kml_handler_->PopRoot();
119 ASSERT_EQ(root->Type(), Type_name);
120 ASSERT_TRUE(NULL == kml_handler_->PopRoot());
121 ASSERT_EQ(kContent, root->get_char_data());
122 }
123
124 // This is a test of the StartElement() method for a known complex element.
TEST_F(KmlHandlerTest,TestStartComplexElement)125 TEST_F(KmlHandlerTest, TestStartComplexElement) {
126 kml_handler_->StartElement("Placemark", atts_);
127 ElementPtr root = kml_handler_->PopRoot();
128 ASSERT_EQ(root->Type(), Type_Placemark);
129 ASSERT_TRUE(NULL == kml_handler_->PopRoot());
130 }
131
132 // This is a test of the EndElement() method for a known complex element.
TEST_F(KmlHandlerTest,TestEndComplexElement)133 TEST_F(KmlHandlerTest, TestEndComplexElement) {
134 // This is what expat does for "<Placemark/>".
135 kml_handler_->StartElement("Placemark", atts_);
136 kml_handler_->EndElement("Placemark");
137 ElementPtr root = kml_handler_->PopRoot();
138 ASSERT_EQ(root->Type(), Type_Placemark);
139 ASSERT_TRUE(NULL == kml_handler_->PopRoot());
140 }
141
142 // This is a test of StartElement() for a known complex element with known
143 // attributes.
TEST_F(KmlHandlerTest,TestStartComplexElementWithAtts)144 TEST_F(KmlHandlerTest, TestStartComplexElementWithAtts) {
145 const string kAttrName("id");
146 const string kAttrVal("foo");
147 atts_.push_back(kAttrName);
148 atts_.push_back(kAttrVal);
149 kml_handler_->StartElement("Placemark", atts_);
150 ElementPtr root = kml_handler_->PopRoot();
151 ASSERT_EQ(root->Type(), Type_Placemark);
152 ASSERT_TRUE(NULL == kml_handler_->PopRoot());
153 PlacemarkPtr placemark = AsPlacemark(root);
154 ASSERT_EQ(kAttrVal, placemark->get_id());
155 }
156
157 // This ParserObserver simply appends each Element passed to its NewElement
158 // to the vector passed to the constructor. The max_elements constructor arg
159 // terminates the parse if the specified number of elements have been parsed.
160 class SimpleNewElementObserver : public ParserObserver {
161 public:
SimpleNewElementObserver(element_vector_t * new_element_vector,size_t max_elements)162 SimpleNewElementObserver(element_vector_t* new_element_vector,
163 size_t max_elements)
164 : new_element_vector_(new_element_vector), max_elements_(max_elements) {
165 }
166
167 // ParserObserver::NewElement(). Append the new element to our vector.
NewElement(const ElementPtr & element)168 virtual bool NewElement(const ElementPtr& element) {
169 if (new_element_vector_->size() == max_elements_) {
170 return false; // Terminates parse.
171 }
172 new_element_vector_->push_back(element);
173 return true; // Keep parsing.
174 }
175
176 // Default implementation of AddChild() returns true.
177
178 private:
179 element_vector_t* new_element_vector_;
180 size_t max_elements_;
181 };
182
183 // This ParserObserver appends each parent-child pair to the supplied vectors.
184 // Plain vectors are used to simplify testing which is based on
185 // VerifyElementTypes(). The max_elements constructor arg specifies to
186 // terminate the parse if the specified number of pairs have been parsed.
187 class SimpleAddChildObserver : public ParserObserver {
188 public:
SimpleAddChildObserver(element_vector_t * parent_vector,element_vector_t * child_vector,size_t max_elements)189 SimpleAddChildObserver(element_vector_t* parent_vector,
190 element_vector_t* child_vector, size_t max_elements)
191 : parent_vector_(parent_vector),
192 child_vector_(child_vector),
193 max_elements_(max_elements) {
194 }
195
196 // Default implementation of NewElement() returns true.
197
AddChild(const ElementPtr & parent,const ElementPtr & child)198 virtual bool AddChild(const ElementPtr& parent,
199 const ElementPtr& child) {
200 if (parent_vector_->size() == max_elements_) {
201 return false; // Terminate parse.
202 }
203 parent_vector_->push_back(parent);
204 child_vector_->push_back(child);
205 return true; // Keep parsing.
206 }
207
208 private:
209 element_vector_t* parent_vector_;
210 element_vector_t* child_vector_;
211 size_t max_elements_;
212 };
213
214 // This KML document and test are kept here together.
215 static const char kKmlFolder[] =
216 "<kml>"
217 "<Folder><name/><description/><Region/>"
218 "<Placemark><Point/></Placemark>"
219 "</Folder>"
220 "</kml>";
221
222 static const size_t kNumElements = 7; // Number of elements in kKmlFolder.
223
224 // This is the order of the elements from kKmlFolder see in NewElement().
225 static const KmlDomType kKmlFolderNewElementOrder[] = {
226 Type_kml, Type_Folder, Type_name, Type_description, Type_Region,
227 Type_Placemark, Type_Point };
228
229 // This is the order of the elements from kKmlFolder seen in AddChild().
230 static const KmlDomType kKmlFolderParentOrder[] = {
231 Type_Folder, Type_Folder, Type_Folder, Type_Placemark, Type_Folder, Type_kml
232 };
233 static const KmlDomType kKmlFolderChildOrder[] = {
234 Type_name, Type_description, Type_Region, Type_Point, Type_Placemark,
235 Type_Folder
236 };
237
238 // Verify that each element in the vector is of the corresponding type
239 // in the types_array.
VerifyElementTypes(const KmlDomType * types_array,const element_vector_t & element_vector) const240 void KmlHandlerTest::VerifyElementTypes(
241 const KmlDomType* types_array,
242 const element_vector_t& element_vector) const {
243 for (size_t i = 0; i < element_vector.size(); ++i) {
244 ASSERT_EQ(*(types_array+i), element_vector[i]->Type());
245 }
246 }
247
248 // This helper function verifies the proper state of kKmlFolder's DOM.
VerifyFolderParse(const ElementPtr & root) const249 void KmlHandlerTest::VerifyFolderParse(const ElementPtr& root) const {
250 KmlPtr kml = AsKml(root);
251 ASSERT_TRUE(kml);
252 FolderPtr folder = AsFolder(kml->get_feature());
253 ASSERT_TRUE(folder);
254 ASSERT_TRUE(folder->has_name());
255 ASSERT_FALSE(folder->has_visibility());
256 ASSERT_FALSE(folder->has_open());
257 ASSERT_TRUE(folder->has_description());
258 ASSERT_TRUE(folder->has_region());
259 ASSERT_EQ(static_cast<size_t>(1), folder->get_feature_array_size());
260 PlacemarkPtr placemark = AsPlacemark(folder->get_feature_array_at(0));
261 PointPtr point = AsPoint(placemark->get_geometry());
262 ASSERT_FALSE(point->has_coordinates());
263 }
264
265 // This is a simple test of the NewElement() for an observer which does not
266 // terminate the parse.
TEST_F(KmlHandlerTest,SimpleNewElementObserverTest)267 TEST_F(KmlHandlerTest, SimpleNewElementObserverTest) {
268 Parser parser;
269 element_vector_t element_vector;
270 // This specifies to let the parse complete all 7 elements which are saved
271 // in the order encountered to element_vector.
272 SimpleNewElementObserver simple_new_element_observer(&element_vector,
273 kNumElements);
274 parser.AddObserver(&simple_new_element_observer);
275 ElementPtr root = parser.Parse(kKmlFolder, NULL);
276
277 // Verify that the entire document parsed properly.
278 VerifyFolderParse(root);
279
280 // Verify that the observer's NewElement() saw the expected elements in
281 // the expected order.
282 VerifyElementTypes(kKmlFolderNewElementOrder, element_vector);
283 }
284
285 // This verifies that an observer returning false from NewElement() terminates
286 // the parse.
TEST_F(KmlHandlerTest,NewElementObserverTerminationTest)287 TEST_F(KmlHandlerTest, NewElementObserverTerminationTest) {
288 Parser parser;
289 element_vector_t element_vector;
290 // This specifies to stop parsing after 2 elements.
291 SimpleNewElementObserver simple_new_element_observer(&element_vector, 2);
292 parser.AddObserver(&simple_new_element_observer);
293 string errors;
294 ElementPtr root = parser.Parse(kKmlFolder, &errors);
295
296 // Verify that the parse was terminated.
297 ASSERT_FALSE(root);
298 ASSERT_FALSE(errors.empty());
299
300 // Verify that exactly the first 2 elements were gathered.
301 ASSERT_EQ(static_cast<size_t>(2), element_vector.size());
302 ASSERT_EQ(Type_kml, element_vector[0]->Type());
303 ASSERT_EQ(Type_Folder, element_vector[1]->Type());
304 }
305
306 // This is a simple test of the AddChild() for an observer which does not
307 // terminate the parse.
TEST_F(KmlHandlerTest,SimpleAddChildObserverTest)308 TEST_F(KmlHandlerTest, SimpleAddChildObserverTest) {
309 Parser parser;
310 element_vector_t parent_vector;
311 element_vector_t child_vector;
312 // This specifies to let the parse complete all 7 elements which are saved
313 // in the order encountered to element_vector.
314 SimpleAddChildObserver simple_add_child_observer(&parent_vector,
315 &child_vector,
316 kNumElements);
317 parser.AddObserver(&simple_add_child_observer);
318 ElementPtr root = parser.Parse(kKmlFolder, NULL);
319
320 // Verify that the observer did not interfere with the parse as normal.
321 VerifyFolderParse(root);
322
323 // Verify that the observer's AddChild() saw the expected elements in
324 // the expected order.
325 VerifyElementTypes(kKmlFolderParentOrder, parent_vector);
326 VerifyElementTypes(kKmlFolderChildOrder, child_vector);
327 }
328
329 // This verifies that an observer returning false from AddChild() terminates
330 // the parse.
TEST_F(KmlHandlerTest,AddChildObserverTerminationTest)331 TEST_F(KmlHandlerTest, AddChildObserverTerminationTest) {
332 Parser parser;
333 element_vector_t parent_vector;
334 element_vector_t child_vector;
335 // This specifies to stop parsing after 4 parent-child pairs.
336 SimpleAddChildObserver simple_add_child_observer(&parent_vector,
337 &child_vector,
338 4);
339 parser.AddObserver(&simple_add_child_observer);
340 string errors;
341 ElementPtr root = parser.Parse(kKmlFolder, &errors);
342
343 // Verify that the parse was terminated.
344 ASSERT_FALSE(root);
345 ASSERT_FALSE(errors.empty());
346
347 // Verify that exactly the first 4 parent-child pairs were gathered.
348 ASSERT_EQ(static_cast<size_t>(4),parent_vector.size());
349 ASSERT_EQ(static_cast<size_t>(4), child_vector.size());
350 VerifyElementTypes(kKmlFolderParentOrder, parent_vector);
351 VerifyElementTypes(kKmlFolderChildOrder, child_vector);
352 }
353
354 // This verifies that multiple ParserObservers function properly and that
355 // the expected number of new elements and element pairs are seen for the
356 // given value of max_elements.
MultipleObserverTestCommon(size_t max_elements,size_t expected_element_count,size_t expected_pair_count) const357 void KmlHandlerTest::MultipleObserverTestCommon(size_t max_elements,
358 size_t expected_element_count,
359 size_t expected_pair_count)
360 const {
361 element_vector_t element_vector;
362 SimpleNewElementObserver simple_new_element_observer(&element_vector,
363 max_elements);
364 element_vector_t parent_vector;
365 element_vector_t child_vector;
366 SimpleAddChildObserver simple_parent_child_observer(&parent_vector,
367 &child_vector,
368 max_elements);
369 ParserObserver null_observer;
370
371 Parser parser;
372 parser.AddObserver(&null_observer);
373 parser.AddObserver(&simple_new_element_observer);
374 parser.AddObserver(&simple_parent_child_observer);
375 string errors;
376 ElementPtr root = parser.Parse(kKmlFolder, &errors);
377
378 if (expected_element_count >= kNumElements) {
379 // Verify that the observers did not interfere with the parse as normal.
380 ASSERT_TRUE(errors.empty());
381 VerifyFolderParse(root);
382 } else {
383 // Verify that an observer teminated the parse.
384 ASSERT_FALSE(root);
385 ASSERT_FALSE(errors.empty());
386 }
387
388 // Verify that the observers functioned properly.
389 ASSERT_TRUE(expected_element_count == element_vector.size());
390 ASSERT_TRUE(expected_pair_count == parent_vector.size());
391 ASSERT_TRUE(expected_pair_count == child_vector.size());
392 VerifyElementTypes(kKmlFolderNewElementOrder, element_vector);
393 VerifyElementTypes(kKmlFolderParentOrder, parent_vector);
394 VerifyElementTypes(kKmlFolderChildOrder, child_vector);
395 }
396
397 // Verify proper operation with multiple ParseObservers when no observer
398 // terminates the parse.
TEST_F(KmlHandlerTest,MultipleObserverNormalTest)399 TEST_F(KmlHandlerTest, MultipleObserverNormalTest) {
400 KmlHandlerTest::MultipleObserverTestCommon(kNumElements, kNumElements,
401 kNumElements-1);
402 }
403
404 // Verify proper operation with multiple ParseObservers when an observer
405 // terminates the parse.
TEST_F(KmlHandlerTest,MultipleObserverTerminationTest)406 TEST_F(KmlHandlerTest, MultipleObserverTerminationTest) {
407 KmlHandlerTest::MultipleObserverTestCommon(0, 0, 0);
408 // Accepting just one element results in seeing no pairs.
409 KmlHandlerTest::MultipleObserverTestCommon(1, 1, 0);
410 // These are highly dependent on the exact form of kKmlFolder!
411 KmlHandlerTest::MultipleObserverTestCommon(2, 2, 1);
412 KmlHandlerTest::MultipleObserverTestCommon(6, 6, 4);
413 }
414
415 // This ParserObserver collects all Features in the parse.
416 class FeatureCollector : public ParserObserver {
417 public:
FeatureCollector(element_vector_t * element_vector)418 FeatureCollector(element_vector_t* element_vector)
419 : element_vector_(element_vector) {
420 }
421 // This EndElement saves each non-Container Feature and returns false to
422 // request that the parser not give this feature to the given parent.
423 // All other parent-child relationships are preserved (such as all children
424 // of the collected feature).
EndElement(const ElementPtr & parent,const ElementPtr & child)425 virtual bool EndElement(const ElementPtr& parent,
426 const ElementPtr& child) {
427 if (child->IsA(Type_Feature) && !child->IsA(Type_Container)) {
428 element_vector_->push_back(child);
429 return false;
430 }
431 return true;
432 }
433 private:
434 element_vector_t* element_vector_;
435 };
436
TEST_F(KmlHandlerTest,InhibitingEndElement)437 TEST_F(KmlHandlerTest, InhibitingEndElement) {
438 element_vector_t features;
439 FeatureCollector feature_collector(&features);
440 observers_.push_back(&feature_collector);
441 KmlHandler kml_handler(observers_);
442 kml_handler.StartElement("kml", atts_);
443 kml_handler.StartElement("Document", atts_);
444 kml_handler.StartElement("Placemark", atts_);
445 kml_handler.StartElement("name", atts_);
446 kml_handler.EndElement("name");
447 kml_handler.StartElement("Point", atts_);
448 kml_handler.StartElement("coordinates", atts_);
449 kml_handler.EndElement("coordinates");
450 kml_handler.EndElement("Point");
451 kml_handler.EndElement("Placemark");
452 kml_handler.EndElement("Document");
453 kml_handler.StartElement("NetworkLinkControl", atts_);
454 kml_handler.EndElement("NetworkLinkControl");
455 kml_handler.EndElement("kml");
456 ElementPtr root = kml_handler.PopRoot();
457 ASSERT_TRUE(root);
458 KmlPtr kml = AsKml(root);
459 ASSERT_TRUE(kml);
460 // Document is a Container and is not collected.
461 ASSERT_TRUE(kml->has_feature());
462 ASSERT_TRUE(AsDocument(kml->get_feature()));
463 // NetworkLinkControl is not a Feature is not collected.
464 ASSERT_TRUE(kml->has_networklinkcontrol());
465 // One non-Container Feature is collected.
466 ASSERT_EQ(static_cast<size_t>(1), features.size());
467 PlacemarkPtr placemark = AsPlacemark(features[0]);
468 ASSERT_TRUE(placemark);
469 // Verify the collected feature has all expected children.
470 ASSERT_TRUE(placemark->has_name());
471 ASSERT_TRUE(placemark->has_geometry());
472 PointPtr point = AsPoint(placemark->get_geometry());
473 ASSERT_TRUE(point);
474 ASSERT_TRUE(point->has_coordinates());
475 }
476
TEST_F(KmlHandlerTest,TestParserHandlesGrossDescriptions)477 TEST_F(KmlHandlerTest, TestParserHandlesGrossDescriptions) {
478 // HTML markup in <description> MUST be wrapped with CDATA elements like so:
479 // <description><![CDATA[<h1>title</h1>]]></description>
480 // However, the web has files with markup like this:
481 // <description><table><tr>...</tr><table></description>
482 // Historically, Google Earth has preserved the author's intent with this
483 // type of invalid markup. And hence, we try to as well.
484 const string kInvalidDescriptions(
485 kmlbase::File::JoinPaths(DATADIR, kmlbase::File::JoinPaths(
486 "kml", "invalid_descriptions.kml")));
487 string data;
488 ASSERT_TRUE(kmlbase::File::ReadFileToString(kInvalidDescriptions, &data));
489 ElementPtr root = Parse(data, NULL);
490 ASSERT_TRUE(root);
491 KmlPtr kml = AsKml(root);
492 ASSERT_TRUE(kml);
493 DocumentPtr document = AsDocument(kml->get_feature());
494 ASSERT_TRUE(document);
495 ASSERT_EQ(static_cast<size_t>(3), document->get_feature_array_size());
496
497 PlacemarkPtr placemark0 = AsPlacemark(document->get_feature_array_at(0));
498 const string kExpected0("<b>bold</b>");
499 ASSERT_EQ(kExpected0, placemark0->get_description());
500
501 PlacemarkPtr placemark1 = AsPlacemark(document->get_feature_array_at(1));
502 const string kExpected1("foo<b>bold</b>bar");
503 ASSERT_EQ(kExpected1, placemark1->get_description());
504
505 PlacemarkPtr placemark2 = AsPlacemark(document->get_feature_array_at(2));
506 const string kExpected2("<description>foo<b>bold</b>bar</description>");
507 ASSERT_EQ(kExpected2, placemark2->get_description());
508 }
509
TEST_F(KmlHandlerTest,TestParserHandlesBoolWhitespace)510 TEST_F(KmlHandlerTest, TestParserHandlesBoolWhitespace) {
511 const string kOutlineSpace(
512 kmlbase::File::JoinPaths(DATADIR, kmlbase::File::JoinPaths(
513 "kml", "outline_space.kml")));
514 string data;
515 ASSERT_TRUE(kmlbase::File::ReadFileToString(kOutlineSpace, &data));
516 ElementPtr root = Parse(data, NULL);
517 ASSERT_TRUE(root);
518 DocumentPtr document = AsDocument(AsKml(root)->get_feature());
519 StylePtr style = AsStyle(document->get_styleselector_array_at(0));
520 PolyStylePtr polystyle = style->get_polystyle();
521 ASSERT_FALSE(polystyle->get_fill());
522 ASSERT_TRUE(polystyle->get_outline());
523 PlacemarkPtr placemark = AsPlacemark(document->get_feature_array_at(0));
524 polystyle = AsStyle(placemark->get_styleselector())->get_polystyle();
525 ASSERT_FALSE(polystyle->get_fill());
526 ASSERT_TRUE(polystyle->get_outline());
527 }
528
529 // 100 nested folders is equal to our default nesting limit.
TEST_F(KmlHandlerTest,TestMaxNestingOf100Folders)530 TEST_F(KmlHandlerTest, TestMaxNestingOf100Folders) {
531 const string k100Folders(
532 kmlbase::File::JoinPaths(DATADIR, kmlbase::File::JoinPaths(
533 "kml", "100_nested_folders.kml")));
534 string data;
535 ASSERT_TRUE(kmlbase::File::ReadFileToString(k100Folders, &data));
536 ElementPtr root = Parse(data, NULL);
537 ASSERT_TRUE(root); // Parse succeeded.
538 }
539
540 // 101 nested folders exceeds our default nesting limit of 100.
TEST_F(KmlHandlerTest,TestMaxNestingOf101Folders)541 TEST_F(KmlHandlerTest, TestMaxNestingOf101Folders) {
542 const string k101Folders(
543 kmlbase::File::JoinPaths(DATADIR, kmlbase::File::JoinPaths(
544 "kml", "101_nested_folders.kml")));
545 string data;
546 ASSERT_TRUE(kmlbase::File::ReadFileToString(k101Folders, &data));
547 ElementPtr root = Parse(data, NULL);
548 ASSERT_FALSE(root); // Parse was stopped.
549 }
550
551 // 101 nested elements exceeds our default nesting limit of 100.
TEST_F(KmlHandlerTest,TestMaxNestingOf101Elements)552 TEST_F(KmlHandlerTest, TestMaxNestingOf101Elements) {
553 const string k101Elements(
554 kmlbase::File::JoinPaths(DATADIR, kmlbase::File::JoinPaths(
555 "kml", "101_nested_elements.kml")));
556 string data;
557 ASSERT_TRUE(kmlbase::File::ReadFileToString(k101Elements, &data));
558 ElementPtr root = Parse(data, NULL);
559 ASSERT_FALSE(root); // Parse was stopped.
560 }
561
562 // KML 2.0 and 2.1 permitted the extension of Placemark by defining a
563 // substitution element and possible children. This didn't make it to
564 // OGC KML 2.2, but these files exist in surprising numbers, so we try our
565 // best to parse it sanely into standard KML.
566 //
567 // This is a test of turning testdata/kml/old_schema_example.kml into this:
568 //
569 // <?xml version="1.0" encoding="utf-8"?>
570 // <kml xmlns="http://www.opengis.net/kml/2.2">
571 // <Document>
572 // <Schema id="S_521_525_SSSSS_id" name="S_521_525_SSSSS">
573 // <SimpleField name="Foo" type="string"/>
574 // <SimpleField name="Bar" type="string"/>
575 // </Schema>
576 // <Placemark>
577 // <name>1</name>
578 // <ExtendedData>
579 // <SchemaData schemaUrl="S_521_525_SSSSS_id">
580 // <SimpleData name="Foo">foo 1</SimpleData>
581 // <SimpleData name="Bar">bar 1</SimpleData>
582 // </SchemaData>
583 // </ExtendedData>
584 // <Point>
585 // <coordinates>
586 // -122,37,0
587 // </coordinates>
588 // </Point>
589 // </Placemark>
590 // <Placemark>
591 // <name>2</name>
592 // <ExtendedData>
593 // <SchemaData schemaUrl="S_521_525_SSSSS_id">
594 // <SimpleData name="Foo">foo 2</SimpleData>
595 // <SimpleData name="Bar">bar 2</SimpleData>
596 // </SchemaData>
597 // </ExtendedData>
598 // </Placemark>
599 // </Document>
600 // </kml>
TEST_F(KmlHandlerTest,TestHandlesOldSchemaUsage)601 TEST_F(KmlHandlerTest, TestHandlesOldSchemaUsage) {
602 const string kOldSchemaKml(
603 kmlbase::File::JoinPaths(DATADIR, kmlbase::File::JoinPaths(
604 "kml", "old_schema_example.kml")));
605 string data;
606 ASSERT_TRUE(kmlbase::File::ReadFileToString(kOldSchemaKml, &data));
607 string errors;
608 ElementPtr root = Parse(data, &errors);
609 ASSERT_TRUE(root);
610 ASSERT_TRUE(errors.empty());
611 const KmlPtr kml = AsKml(root);
612 ASSERT_TRUE(kml);
613 ASSERT_TRUE(kml->has_feature());
614 const DocumentPtr document = AsDocument(kml->get_feature());
615 ASSERT_TRUE(document);
616 ASSERT_EQ(static_cast<size_t>(1), document->get_schema_array_size());
617 const SchemaPtr schema = AsSchema(document->get_schema_array_at(0));
618 ASSERT_TRUE(schema);
619 ASSERT_EQ("S_521_525_SSSSS_id", schema->get_id());
620 ASSERT_EQ("S_521_525_SSSSS", schema->get_name());
621 ASSERT_EQ(static_cast<size_t>(2), schema->get_simplefield_array_size());
622 const SimpleFieldPtr simplefield0 =
623 AsSimpleField(schema->get_simplefield_array_at(0));
624 ASSERT_TRUE(simplefield0);
625 ASSERT_EQ("Foo", simplefield0->get_name());
626 ASSERT_EQ("string", simplefield0->get_type());
627 const SimpleFieldPtr simplefield1 =
628 AsSimpleField(schema->get_simplefield_array_at(1));
629 ASSERT_TRUE(simplefield1);
630 ASSERT_EQ("Bar", simplefield1->get_name());
631 ASSERT_EQ("string", simplefield1->get_type());
632 ASSERT_EQ(static_cast<size_t>(2), document->get_feature_array_size());
633
634 const PlacemarkPtr placemark0 =
635 AsPlacemark(document->get_feature_array_at(0));
636 ASSERT_EQ("1", placemark0->get_name());
637 ASSERT_TRUE(placemark0->has_extendeddata());
638 const ExtendedDataPtr extendeddata0 =
639 AsExtendedData(placemark0->get_extendeddata());
640 ASSERT_TRUE(extendeddata0);
641 ASSERT_EQ(static_cast<size_t>(1), extendeddata0->get_schemadata_array_size());
642 const SchemaDataPtr schemadata0 =
643 AsSchemaData(extendeddata0->get_schemadata_array_at(0));
644 ASSERT_TRUE(schemadata0);
645 ASSERT_EQ("S_521_525_SSSSS_id", schemadata0->get_schemaurl());
646 ASSERT_EQ(static_cast<size_t>(2), schemadata0->get_simpledata_array_size());
647 const SimpleDataPtr simpledata00 =
648 AsSimpleData(schemadata0->get_simpledata_array_at(0));
649 ASSERT_TRUE(simpledata00);
650 ASSERT_EQ("Foo", simpledata00->get_name());
651 ASSERT_EQ("foo 1", simpledata00->get_text());
652 const SimpleDataPtr simpledata01 =
653 AsSimpleData(schemadata0->get_simpledata_array_at(1));
654 ASSERT_TRUE(simpledata01);
655 ASSERT_EQ("Bar", simpledata01->get_name());
656 ASSERT_EQ("bar 1", simpledata01->get_text());
657
658 const PlacemarkPtr placemark1 =
659 AsPlacemark(document->get_feature_array_at(1));
660 ASSERT_EQ("2", placemark1->get_name());
661 ASSERT_TRUE(placemark1->has_extendeddata());
662 const ExtendedDataPtr extendeddata1 =
663 AsExtendedData(placemark1->get_extendeddata());
664 ASSERT_TRUE(extendeddata1);
665 ASSERT_EQ(static_cast<size_t>(1), extendeddata1->get_schemadata_array_size());
666 const SchemaDataPtr schemadata1 =
667 AsSchemaData(extendeddata1->get_schemadata_array_at(0));
668 ASSERT_TRUE(schemadata1);
669 ASSERT_EQ("S_521_525_SSSSS_id", schemadata1->get_schemaurl());
670 ASSERT_EQ(static_cast<size_t>(2), schemadata1->get_simpledata_array_size());
671 const SimpleDataPtr simpledata10 =
672 AsSimpleData(schemadata1->get_simpledata_array_at(0));
673 ASSERT_TRUE(simpledata10);
674 ASSERT_EQ("Foo", simpledata10->get_name());
675 ASSERT_EQ("foo 2", simpledata10->get_text());
676 const SimpleDataPtr simpledata11 =
677 AsSimpleData(schemadata1->get_simpledata_array_at(1));
678 ASSERT_TRUE(simpledata11);
679 ASSERT_EQ("Bar", simpledata11->get_name());
680 ASSERT_EQ("bar 2", simpledata11->get_text());
681 }
682
683 // This verifies that a ParserObsever sees a <Placemark> when old
684 // KML 2.0/2.1 <Schema> usages is parsed.
TEST_F(KmlHandlerTest,TestOldSchemaParserObserver)685 TEST_F(KmlHandlerTest, TestOldSchemaParserObserver) {
686 const string kOldSchemaKml = (
687 "<Document>"
688 "<Schema parent=\"Placemark\" name=\"S_521_525_SSSSS\">"
689 "<SimpleField type=\"string\" name=\"Foo\"></SimpleField>"
690 "</Schema>"
691 "<S_521_525_SSSSS>"
692 "<Foo>foo 1</Foo>"
693 "</S_521_525_SSSSS>"
694 "</Document>");
695
696 element_vector_t element_vector;
697 size_t max_elements = 100;
698 SimpleNewElementObserver simple_new_element_observer(&element_vector,
699 max_elements);
700 Parser parser;
701 parser.AddObserver(&simple_new_element_observer);
702 string errors;
703 ElementPtr root = parser.Parse(kOldSchemaKml, &errors);
704 ASSERT_TRUE(root);
705 ASSERT_TRUE(errors.empty());
706 // NewElement() is called only 4 times; The logic that handles the old
707 // <Schema> knows to look for <Foo> as a child, and the handing there is
708 // special-cased; StartElement() returns before the observer for <Foo> is
709 // called.
710 ASSERT_EQ(static_cast<size_t>(4), element_vector.size());
711 ASSERT_EQ(Type_Placemark, element_vector.at(3)->Type());
712 }
713
714 // Verify the handling of old-style <Schema> parsing directly in
715 // StartElement and EndElement.
TEST_F(KmlHandlerTest,TestOldSchemaHandling)716 TEST_F(KmlHandlerTest, TestOldSchemaHandling) {
717 kml_handler_->StartElement("Document", atts_);
718 atts_.push_back("parent");
719 atts_.push_back("Placemark");
720 atts_.push_back("name");
721 const string kOldStyleSchemaName("OldStyleSchemaName");
722 atts_.push_back(kOldStyleSchemaName);
723 kml_handler_->StartElement("Schema", atts_);
724 atts_.clear();
725 atts_.push_back("type");
726 atts_.push_back("string");
727 atts_.push_back("name");
728 const string kOldStyleSchemaChild("OldStyleSchemaChild");
729 atts_.push_back(kOldStyleSchemaChild);
730 kml_handler_->StartElement("SimpleField", atts_);
731 kml_handler_->EndElement("SimpleField");
732 kml_handler_->EndElement("Schema");
733 atts_.clear();
734 kml_handler_->StartElement(kOldStyleSchemaName, atts_);
735 kml_handler_->StartElement(kOldStyleSchemaChild, atts_);
736 const string kOldStyleSchemaChildCharData("char data");
737 kml_handler_->CharData(kOldStyleSchemaChildCharData);
738 kml_handler_->EndElement(kOldStyleSchemaChild);
739 kml_handler_->EndElement(kOldStyleSchemaName);
740 ElementPtr root = kml_handler_->PopRoot();
741 ASSERT_TRUE(root);
742 ASSERT_EQ(Type_Document, root->Type());
743 // A Placemark was created from OldStyleSchemaName.
744 ASSERT_EQ(Type_Placemark, AsDocument(root)->get_feature_array_at(0)->Type());
745 PlacemarkPtr placemark =
746 AsPlacemark(AsDocument(root)->get_feature_array_at(0));
747 // The OldStyleSchemaChild was converted into an ExtendedData structure.
748 ASSERT_TRUE(placemark->has_extendeddata());
749 ExtendedDataPtr extendeddata = AsExtendedData(placemark->get_extendeddata());
750 ASSERT_EQ(static_cast<size_t>(1), extendeddata->get_schemadata_array_size());
751 SchemaDataPtr schemadata = extendeddata->get_schemadata_array_at(0);
752 ASSERT_EQ(kOldStyleSchemaName + "_id", schemadata->get_schemaurl());
753 ASSERT_EQ(static_cast<size_t>(1), schemadata->get_simpledata_array_size());
754 SimpleDataPtr simpledata = schemadata->get_simpledata_array_at(0);
755 ASSERT_EQ(kOldStyleSchemaChild, simpledata->get_name());
756 ASSERT_EQ(kOldStyleSchemaChildCharData, simpledata->get_text());
757 }
758
759 } // end namespace kmldom
760