1 // Copyright 2009, 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 StyleInliner class.
27 
28 #include "kml/engine/style_inliner.h"
29 #include "kml/engine/style_inliner_internal.h"
30 #include "gtest/gtest.h"
31 #include "kml/base/file.h"
32 
33 // The following define is a convenience for testing inside Google.
34 #ifdef GOOGLE_INTERNAL
35 #include "kml/base/google_internal_test.h"
36 #endif
37 
38 #ifndef DATADIR
39 #error *** DATADIR must be defined! ***
40 #endif
41 
42 using kmlbase::File;
43 using kmldom::DocumentPtr;
44 using kmldom::ElementPtr;
45 using kmldom::FieldPtr;
46 using kmldom::KmlFactory;
47 using kmldom::PlacemarkPtr;
48 using kmldom::StylePtr;
49 using kmldom::Type_styleUrl;
50 using kmldom::UpdatePtr;
51 
52 namespace kmlengine {
53 
54 class StyleInlinerTest : public testing::Test {
55  protected:
SetUp()56   virtual void SetUp() {
57     kml_factory_ = KmlFactory::GetFactory();
58     style_inliner_.reset(new StyleInliner);
59   }
60 
61   KmlFactory* kml_factory_;
62   boost::scoped_ptr<StyleInliner> style_inliner_;
63 };
64 
TEST_F(StyleInlinerTest,CallStyleInlinerMethodsInTypicalUsage)65 TEST_F(StyleInlinerTest, CallStyleInlinerMethodsInTypicalUsage) {
66   // Call the StyleInliner methods involved in parsing this:
67   // <Document>
68   //   <Style id="_0"/>
69   //   <Placemark>
70   //     <styleUrl>#_0</styleUrl>
71   //   </Placemark>
72   // </Document>
73   // ...to generate this:
74   const string kExpectedKml(
75     "<Document>\n"
76     "  <Placemark>\n"
77     "    <StyleMap>\n"
78     "      <Pair>\n"
79     "        <key>normal</key>\n"
80     "        <Style/>\n"
81     "      </Pair>\n"
82     "      <Pair>\n"
83     "        <key>highlight</key>\n"
84     "        <Style/>\n"
85     "      </Pair>\n"
86     "    </StyleMap>\n"
87     "  </Placemark>\n"
88     "</Document>\n");
89 
90   // 0) Initial conditions.
91   ASSERT_FALSE(style_inliner_->get_document());
92   ASSERT_EQ(static_cast<size_t>(0),
93             style_inliner_->get_shared_styles().size());
94   ASSERT_FALSE(style_inliner_->in_update());
95 
96   // 1) <Document>
97   DocumentPtr document = kml_factory_->CreateDocument();
98   ASSERT_TRUE(style_inliner_->NewElement(document));
99   ASSERT_TRUE(style_inliner_->get_document());
100 
101   // 2) <Style id="_0">
102   StylePtr style = kml_factory_->CreateStyle();
103   const string kStyleId("_0");
104   style->set_id(kStyleId);
105   ASSERT_TRUE(style_inliner_->NewElement(style));
106 
107   // 3) </Style>
108   ASSERT_FALSE(style_inliner_->EndElement(document, style));
109 
110   // 4) <Placemark>
111   PlacemarkPtr placemark = kml_factory_->CreatePlacemark();
112   ASSERT_TRUE(style_inliner_->NewElement(placemark));
113 
114   // 5) <styleUrl>#_0</styleUrl>
115   FieldPtr styleurl = kml_factory_->CreateFieldById(Type_styleUrl);
116   styleurl->set_char_data(string("#") + kStyleId);
117   ASSERT_TRUE(style_inliner_->NewElement(styleurl));
118   ASSERT_FALSE(style_inliner_->EndElement(placemark, styleurl));
119   // StyleInliner::EndElement() gives the placemark a style selector
120   // representing that which the styleUrl pointed to.
121   ASSERT_TRUE(placemark->has_styleselector());
122   // The local style should have no ids.
123   ASSERT_FALSE(placemark->get_styleselector()->has_id());
124 
125   // 6) </Placemark>
126   ASSERT_TRUE(style_inliner_->EndElement(document, placemark));
127   document->add_feature(placemark);
128 
129   // Verify the shared style was captured.
130   const SharedStyleMap& shared_styles = style_inliner_->get_shared_styles();
131   ASSERT_EQ(static_cast<size_t>(1), shared_styles.size());
132   ASSERT_EQ(kStyleId, shared_styles.find(kStyleId)->second->get_id());
133 
134   // Serialize the document and verify a proper overall result.
135   ASSERT_EQ(kExpectedKml, SerializePretty(document));
136 }
137 
TEST_F(StyleInlinerTest,VerifyRemoteStyleUrlNotInlined)138 TEST_F(StyleInlinerTest, VerifyRemoteStyleUrlNotInlined) {
139   PlacemarkPtr placemark = kml_factory_->CreatePlacemark();
140   FieldPtr styleurl = kml_factory_->CreateFieldById(Type_styleUrl);
141   styleurl->set_char_data("http://example.com/style.kml#cool-style");
142   // true == "proceed to add this styleurl to its feature"
143   ASSERT_TRUE(style_inliner_->EndElement(placemark, styleurl));
144 
145   // true == "proceed to add this styleurl to its feature"
146   styleurl->set_char_data("#non-existent-local-reference");
147   ASSERT_TRUE(style_inliner_->EndElement(placemark, styleurl));
148 }
149 
TEST_F(StyleInlinerTest,VerifyNoInliningWithinUpdate)150 TEST_F(StyleInlinerTest, VerifyNoInliningWithinUpdate) {
151   // <Update>
152   UpdatePtr update = kml_factory_->CreateUpdate();
153   ASSERT_TRUE(style_inliner_->NewElement(update));
154   ASSERT_TRUE(style_inliner_->in_update());
155 
156   // Since this is somewhere inside an <Update> the <Style> will be added
157   // to the <Document> as normal.
158   DocumentPtr document = kml_factory_->CreateDocument();
159   StylePtr style = kml_factory_->CreateStyle();
160   const string kStyleId("style0");
161   style->set_id(kStyleId);
162   ASSERT_TRUE(style_inliner_->EndElement(document, style));
163 
164   // </Update>
165   ASSERT_TRUE(style_inliner_->AddChild(kml_factory_->CreateNetworkLinkControl(),
166                                        update));
167   ASSERT_FALSE(style_inliner_->in_update());
168 
169   // Now that we're no longer within an <Update> a <Style> with an id _will_
170   // be captured into the SharedStyleMap and _not_ parented to a <Document>.
171   ASSERT_FALSE(style_inliner_->EndElement(document, style));
172   ASSERT_EQ(static_cast<size_t>(1), style_inliner_->get_shared_styles().size());
173   SharedStyleMap::const_iterator find =
174       style_inliner_->get_shared_styles().find(kStyleId);
175   ASSERT_EQ(kStyleId, find->second->get_id());
176 }
177 
178 // This is a utility function to read a file relative to the testdata directory.
ReadDataDirFileToString(const string & dir,const string & filename,string * content)179 static bool ReadDataDirFileToString(const string& dir,
180                                     const string& filename,
181                                     string* content) {
182   const string pathname =
183     File::JoinPaths(File::JoinPaths(string(DATADIR), dir), filename);
184   return File::ReadFileToString(pathname, content);
185 }
186 
187 // This is a table of test cases of input files with shared styles checked
188 // against the expected inline result.
189 static const struct {
190   const char* source_subdir_;
191   const char* source_file_;
192   const char* check_subdir_;
193   const char* check_file_;
194 } kTestCases [] = {
195   { "kml", "kmlsamples.kml", "style", "kmlsamples-inline-style-check.kml" },
196   { "kml", "ge-point.kml", "style", "ge-point-inline-style-check.kml" }
197 };
198 
TEST_F(StyleInlinerTest,TestFiles)199 TEST_F(StyleInlinerTest, TestFiles) {
200   const size_t size = sizeof(kTestCases)/sizeof(kTestCases[0]);
201   for (size_t i = 0; i < size; ++i) {
202     string input;
203     ASSERT_TRUE(ReadDataDirFileToString(kTestCases[i].source_subdir_,
204                                         kTestCases[i].source_file_, &input))
205       << kTestCases[i].source_subdir_ << "/" << kTestCases[i].source_file_;
206     string errors;
207 
208     // Call the function under test.
209     ElementPtr root = InlineStyles(input, &errors);
210     ASSERT_TRUE(root);
211     ASSERT_TRUE(errors.empty());
212     string check;
213     ASSERT_TRUE(ReadDataDirFileToString(kTestCases[i].check_subdir_,
214                                         kTestCases[i].check_file_, &check))
215       << kTestCases[i].check_subdir_ << "/" << kTestCases[i].check_file_;
216     ASSERT_EQ(check, kmldom::SerializePretty(root));
217   }
218 }
219 
220 }  // end namespace kmlengine
221