1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "extensions/browser/file_highlighter.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "base/values.h"
12 namespace extensions
{
16 // Keys for a highlighted dictionary.
17 const char kBeforeHighlightKey
[] = "beforeHighlight";
18 const char kHighlightKey
[] = "highlight";
19 const char kAfterHighlightKey
[] = "afterHighlight";
21 // Increment |index| to the position of the next quote ('"') in |str|, skipping
22 // over any escaped quotes. If no next quote is found, |index| is set to
23 // std::string::npos. Assumes |index| currently points to a quote.
24 void QuoteIncrement(const std::string
& str
, size_t* index
) {
25 size_t i
= *index
+ 1; // Skip over the first quote.
27 while (!found
&& i
< str
.size()) {
29 i
+= 2; // if we find an escaped character, skip it.
30 else if (str
[i
] == '"')
35 *index
= found
? i
: std::string::npos
;
38 // Increment |index| by one if the next character is not a comment. Increment
39 // index until the end of the comment if it is a comment.
40 void CommentSafeIncrement(const std::string
& str
, size_t* index
) {
42 if (str
[i
] == '/' && i
+ 1 < str
.size()) {
43 // Eat a single-line comment.
44 if (str
[i
+ 1] == '/') {
45 i
+= 2; // Eat the '//'.
46 while (i
< str
.size() && str
[i
] != '\n' && str
[i
] != '\r')
48 } else if (str
[i
+ 1] == '*') { // Eat a multi-line comment.
49 i
+= 3; // Advance to the first possible comment end.
50 while (i
< str
.size() && !(str
[i
- 1] == '*' && str
[i
] == '/'))
57 // Increment index until the end of the current "chunk"; a "chunk" is a JSON-
58 // style list, object, or string literal, without exceeding |end|. Assumes
59 // |index| currently points to a chunk's starting character ('{', '[', or '"').
60 void ChunkIncrement(const std::string
& str
, size_t* index
, size_t end
) {
62 std::stack
<char> stack
;
65 QuoteIncrement(str
, index
);
70 else if (!stack
.empty() && c
== stack
.top())
72 CommentSafeIncrement(str
, index
);
74 } while (!stack
.empty() && *index
< end
);
79 FileHighlighter::FileHighlighter(const std::string
& contents
)
80 : contents_(contents
), start_(0u), end_(contents_
.size()) {
83 FileHighlighter::~FileHighlighter() {
86 std::string
FileHighlighter::GetBeforeFeature() const {
87 return contents_
.substr(0, start_
);
90 std::string
FileHighlighter::GetFeature() const {
91 return contents_
.substr(start_
, end_
- start_
);
94 std::string
FileHighlighter::GetAfterFeature() const {
95 return contents_
.substr(end_
);
98 void FileHighlighter::SetHighlightedRegions(base::DictionaryValue
* dict
) const {
99 std::string before_feature
= GetBeforeFeature();
100 if (!before_feature
.empty())
101 dict
->SetString(kBeforeHighlightKey
, base::UTF8ToUTF16(before_feature
));
103 std::string feature
= GetFeature();
104 if (!feature
.empty())
105 dict
->SetString(kHighlightKey
, base::UTF8ToUTF16(feature
));
107 std::string after_feature
= GetAfterFeature();
108 if (!after_feature
.empty())
109 dict
->SetString(kAfterHighlightKey
, base::UTF8ToUTF16(after_feature
));
112 ManifestHighlighter::ManifestHighlighter(const std::string
& manifest
,
113 const std::string
& key
,
114 const std::string
& specific
)
115 : FileHighlighter(manifest
) {
116 start_
= contents_
.find('{') + 1;
117 end_
= contents_
.rfind('}');
118 Parse(key
, specific
);
121 ManifestHighlighter::~ManifestHighlighter() {
125 void ManifestHighlighter::Parse(const std::string
& key
,
126 const std::string
& specific
) {
127 // First, try to find the bounds of the full key.
128 if (FindBounds(key
, true) /* enforce at top level */ ) {
129 // If we succeed, and we have a specific location, find the bounds of the
131 if (!specific
.empty())
132 FindBounds(specific
, false /* don't enforce at top level */ );
134 // We may have found trailing whitespace. Don't use base::TrimWhitespace,
135 // because we want to keep any whitespace we find - just not highlight it.
136 size_t trim
= contents_
.find_last_not_of(" \t\n\r", end_
- 1);
137 if (trim
< end_
&& trim
> start_
)
140 // If we fail, then we set start to end so that the highlighted portion is
146 bool ManifestHighlighter::FindBounds(const std::string
& feature
,
147 bool enforce_at_top_level
) {
149 while (start_
< end_
) {
150 c
= contents_
[start_
];
152 // The feature may be quoted.
153 size_t quote_end
= start_
;
154 QuoteIncrement(contents_
, "e_end
);
155 if (contents_
.substr(start_
+ 1, quote_end
- 1 - start_
) == feature
) {
156 FindBoundsEnd(feature
, quote_end
+ 1);
159 // If it's not the feature, then we can skip the quoted section.
160 start_
= quote_end
+ 1;
162 } else if (contents_
.substr(start_
, feature
.size()) == feature
) {
163 FindBoundsEnd(feature
, start_
+ feature
.size() + 1);
165 } else if (enforce_at_top_level
&& (c
== '{' || c
== '[')) {
166 // If we don't have to be at the top level, then we can skip any chunks
168 ChunkIncrement(contents_
, &start_
, end_
);
170 CommentSafeIncrement(contents_
, &start_
);
176 void ManifestHighlighter::FindBoundsEnd(const std::string
& feature
,
177 size_t local_start
) {
179 while (local_start
< end_
) {
180 c
= contents_
[local_start
];
181 // We're done when we find a terminating character (i.e., either a comma or
182 // an ending bracket.
183 if (c
== ',' || c
== '}' || c
== ']') {
187 // We can skip any chunks we find, since we are looking for the end of the
188 // current feature, and don't want to go any deeper.
189 if (c
== '"' || c
== '{' || c
== '[')
190 ChunkIncrement(contents_
, &local_start
, end_
);
192 CommentSafeIncrement(contents_
, &local_start
);
196 SourceHighlighter::SourceHighlighter(const std::string
& contents
,
198 : FileHighlighter(contents
) {
202 SourceHighlighter::~SourceHighlighter() {
205 void SourceHighlighter::Parse(size_t line_number
) {
206 // If line 0 is requested, highlight nothing.
207 if (line_number
== 0) {
208 start_
= contents_
.size();
212 for (size_t i
= 1; i
< line_number
; ++i
) {
213 start_
= contents_
.find('\n', start_
);
214 if (start_
== std::string::npos
)
219 end_
= contents_
.find('\n', start_
);
221 // If we went off the end of the string (i.e., the line number was invalid),
222 // then move start and end to the end of the string, so that the highlighted
224 start_
= start_
== std::string::npos
? contents_
.size() : start_
;
225 end_
= end_
== std::string::npos
? contents_
.size() : end_
;
228 } // namespace extensions