"Fossies" - the Fresh Open Source Software Archive 
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "add_ids_filter.cc" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
1.13.35.2_vs_1.14.36.1.
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20
21 #include "net/instaweb/rewriter/public/add_ids_filter.h"
22
23 #include <algorithm>
24
25 #include "base/logging.h"
26 #include "pagespeed/kernel/base/basictypes.h"
27 #include "pagespeed/kernel/base/string.h"
28 #include "pagespeed/kernel/base/string_util.h"
29 #include "pagespeed/kernel/html/html_element.h"
30 #include "pagespeed/kernel/html/html_name.h"
31
32 namespace net_instaweb {
33
34 namespace {
35
36 // TODO(jmaessen): perhaps this should go somewhere central? It needs to be a
37 // subset of the tags considered divlike by mobilize_label_filter at least.
38 const HtmlName::Keyword kDivLikeTags[] = {
39 HtmlName::kArticle,
40 HtmlName::kAside,
41 HtmlName::kContent,
42 HtmlName::kDiv,
43 HtmlName::kFooter,
44 HtmlName::kForm,
45 HtmlName::kHeader,
46 HtmlName::kMain,
47 HtmlName::kMenu,
48 HtmlName::kNav,
49 HtmlName::kSection,
50 HtmlName::kTable,
51 HtmlName::kTr,
52 HtmlName::kUl
53 };
54
55 #ifndef NDEBUG
56 // For invariant-checking the static data above.
57 void CheckKeywordsSorted(const HtmlName::Keyword* list, int len) {
58 for (int i = 1; i < len; ++i) {
59 DCHECK_LT(list[i - 1], list[i]);
60 }
61 }
62 #endif // #ifndef NDEBUG
63
64 bool IsDivLike(HtmlName::Keyword tag) {
65 return std::binary_search(
66 kDivLikeTags, kDivLikeTags + arraysize(kDivLikeTags), tag);
67 }
68
69 bool NeedsExplicitId(HtmlName::Keyword tag) {
70 return IsDivLike(tag);
71 }
72
73 bool IsIgnored(HtmlName::Keyword tag) {
74 return (tag == HtmlName::kHtml || tag == HtmlName::kBody);
75 }
76
77 } // namespace
78
79 // We don't want this to conflict with another id name, and length
80 // also matters (shorter is better).
81 const char AddIdsFilter::kIdPrefix[] = "PageSpeed";
82
83 const int AddIdsFilter::kIsId = -1;
84
85 AddIdsFilter::AddIdsFilter(RewriteDriver* driver)
86 : driver_(driver) {}
87
88 AddIdsFilter::~AddIdsFilter() {}
89
90 void AddIdsFilter::StartDocument() {
91 // Push an initial top-level count.
92 div_count_stack_.clear();
93 div_count_stack_.push_back(0);
94 id_stack_.clear();
95 #ifndef NDEBUG
96 CheckKeywordsSorted(kDivLikeTags, arraysize(kDivLikeTags));
97 #endif // #ifndef NDEBUG
98 }
99
100 // As we parse outside head we maintain a stack of tag locations, and at each
101 // tag for which TagRequiresId we add an encoded version of the stack as a query
102 // param. Note that the stack is incremented immediately after its encoded
103 // value is added as a query param.
104 //
105 // An explicit id adds a kIsId entry to the stack before the entry for that
106 // tag's children, and pushes the id onto the id_stack_.
107 //
108 // Example HTML: | Stack as we go:
109 // |
110 // <html> | 0
111 // <head> | 0
112 // </head> | 0
113 // <body> | 0
114 // <div> | 0, 0 (id="...-0")
115 // <p>Toolbar link 1.</p> | 0, 0
116 // <p>Toolbar link 2.</p> | 0, 1
117 // </div> | 1 id stack
118 // <div id=foo> | 1, -1, 0 foo
119 // <div> | 1, -1, 0, 0 foo (id="...-foo-0")
120 // <p>Main page link.</p> | 1, -1, 0, 0 foo
121 // </div> | 1, 1
122 // <div>Secondary link. | 1, -1, 1, 0 foo (id="...-foo-1")
123 // </div> | 1, -1, 2 foo
124 // </div> | 2
125 // </body> | 2
126 // </html> | 2
127 void AddIdsFilter::StartElement(HtmlElement* element) {
128 HtmlName::Keyword tag = element->keyword();
129 const HtmlElement::Attribute* id =
130 element->FindAttribute(HtmlName::kId);
131 if (id != NULL) {
132 id_stack_.push_back(id);
133 div_count_stack_.push_back(kIsId);
134 } else if (IsIgnored(tag)) {
135 // Don't touch stack in this case.
136 return;
137 } else if (NeedsExplicitId(tag) ||
138 element->FindAttribute(HtmlName::kClass) != NULL) {
139 driver_->AddAttribute(element, HtmlName::kId, GetDivCountStackEncoding());
140 }
141 div_count_stack_.push_back(0);
142 }
143
144 void AddIdsFilter::EndElement(HtmlElement* element) {
145 DCHECK(!div_count_stack_.empty());
146 DCHECK_NE(kIsId, div_count_stack_.back());
147 if (!id_stack_.empty() &&
148 id_stack_.back() == element->FindAttribute(HtmlName::kId)) {
149 DCHECK_LT(2, div_count_stack_.size());
150 // For an element with an id the stack looks like:
151 // ... my_count_in_parent kIsId child_count
152 // If so, pop both along with the back of id_stack_.
153 div_count_stack_.pop_back();
154 id_stack_.pop_back();
155 // Now stack is ... my_count_in_parent kIsId
156 } else if (IsIgnored(element->keyword())) {
157 // Again, don't touch the stack in this case.
158 return;
159 } else {
160 // stack is:
161 // ... my_count_in_parent child_count
162 }
163 div_count_stack_.pop_back();
164 // Stack is ... my_count_in_parent
165 div_count_stack_.back()++;
166 // Stack is ... my_count_in_parent+1
167 DCHECK(!div_count_stack_.empty());
168 DCHECK_NE(kIsId, div_count_stack_.back());
169 }
170
171 GoogleString AddIdsFilter::GetDivCountStackEncoding() {
172 DCHECK(!div_count_stack_.empty());
173 DCHECK_NE(kIsId, div_count_stack_.back());
174 GoogleString result(kIdPrefix);
175 if (!id_stack_.empty()) {
176 // Note: we make use of StringPiece(NULL) -> "" in this call.
177 StrAppend(&result, "-", id_stack_.back()->escaped_value());
178 }
179 int size = div_count_stack_.size();
180 int count_index = size - 1;
181 while (count_index > 0 && div_count_stack_[count_index - 1] != kIsId) {
182 --count_index;
183 }
184 for (; count_index < size; ++count_index) {
185 StrAppend(&result, "-", IntegerToString(div_count_stack_[count_index]));
186 }
187 return result;
188 }
189
190 } // namespace net_instaweb