// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "extensions/browser/file_highlighter.h" #include <stack> #include "base/strings/utf_string_conversions.h" #include "base/values.h" namespace extensions { namespace { // Keys for a highlighted dictionary. const char kBeforeHighlightKey[] = "beforeHighlight"; const char kHighlightKey[] = "highlight"; const char kAfterHighlightKey[] = "afterHighlight"; // Increment |index| to the position of the next quote ('"') in |str|, skipping // over any escaped quotes. If no next quote is found, |index| is set to // std::string::npos. Assumes |index| currently points to a quote. void QuoteIncrement(const std::string& str, size_t* index) { size_t i = *index + 1; // Skip over the first quote. bool found = false; while (!found && i < str.size()) { if (str[i] == '\\') i += 2; // if we find an escaped character, skip it. else if (str[i] == '"') found = true; else ++i; } *index = found ? i : std::string::npos; } // Increment |index| by one if the next character is not a comment. Increment // index until the end of the comment if it is a comment. void CommentSafeIncrement(const std::string& str, size_t* index) { size_t i = *index; if (str[i] == '/' && i + 1 < str.size()) { // Eat a single-line comment. if (str[i + 1] == '/') { i += 2; // Eat the '//'. while (i < str.size() && str[i] != '\n' && str[i] != '\r') ++i; } else if (str[i + 1] == '*') { // Eat a multi-line comment. i += 3; // Advance to the first possible comment end. while (i < str.size() && !(str[i - 1] == '*' && str[i] == '/')) ++i; } } *index = i + 1; } // Increment index until the end of the current "chunk"; a "chunk" is a JSON- // style list, object, or string literal, without exceeding |end|. Assumes // |index| currently points to a chunk's starting character ('{', '[', or '"'). void ChunkIncrement(const std::string& str, size_t* index, size_t end) { char c = str[*index]; std::stack<char> stack; do { if (c == '"') QuoteIncrement(str, index); else if (c == '[') stack.push(']'); else if (c == '{') stack.push('}'); else if (!stack.empty() && c == stack.top()) stack.pop(); CommentSafeIncrement(str, index); c = str[*index]; } while (!stack.empty() && *index < end); } } // namespace FileHighlighter::FileHighlighter(const std::string& contents) : contents_(contents), start_(0u), end_(contents_.size()) { } FileHighlighter::~FileHighlighter() { } std::string FileHighlighter::GetBeforeFeature() const { return contents_.substr(0, start_); } std::string FileHighlighter::GetFeature() const { return contents_.substr(start_, end_ - start_); } std::string FileHighlighter::GetAfterFeature() const { return contents_.substr(end_); } void FileHighlighter::SetHighlightedRegions(base::DictionaryValue* dict) const { std::string before_feature = GetBeforeFeature(); if (!before_feature.empty()) dict->SetString(kBeforeHighlightKey, base::UTF8ToUTF16(before_feature)); std::string feature = GetFeature(); if (!feature.empty()) dict->SetString(kHighlightKey, base::UTF8ToUTF16(feature)); std::string after_feature = GetAfterFeature(); if (!after_feature.empty()) dict->SetString(kAfterHighlightKey, base::UTF8ToUTF16(after_feature)); } ManifestHighlighter::ManifestHighlighter(const std::string& manifest, const std::string& key, const std::string& specific) : FileHighlighter(manifest) { start_ = contents_.find('{') + 1; end_ = contents_.rfind('}'); Parse(key, specific); } ManifestHighlighter::~ManifestHighlighter() { } void ManifestHighlighter::Parse(const std::string& key, const std::string& specific) { // First, try to find the bounds of the full key. if (FindBounds(key, true) /* enforce at top level */ ) { // If we succeed, and we have a specific location, find the bounds of the // specific. if (!specific.empty()) FindBounds(specific, false /* don't enforce at top level */ ); // We may have found trailing whitespace. Don't use base::TrimWhitespace, // because we want to keep any whitespace we find - just not highlight it. size_t trim = contents_.find_last_not_of(" \t\n\r", end_ - 1); if (trim < end_ && trim > start_) end_ = trim + 1; } else { // If we fail, then we set start to end so that the highlighted portion is // empty. start_ = end_; } } bool ManifestHighlighter::FindBounds(const std::string& feature, bool enforce_at_top_level) { char c = '\0'; while (start_ < end_) { c = contents_[start_]; if (c == '"') { // The feature may be quoted. size_t quote_end = start_; QuoteIncrement(contents_, "e_end); if (contents_.substr(start_ + 1, quote_end - 1 - start_) == feature) { FindBoundsEnd(feature, quote_end + 1); return true; } else { // If it's not the feature, then we can skip the quoted section. start_ = quote_end + 1; } } else if (contents_.substr(start_, feature.size()) == feature) { FindBoundsEnd(feature, start_ + feature.size() + 1); return true; } else if (enforce_at_top_level && (c == '{' || c == '[')) { // If we don't have to be at the top level, then we can skip any chunks // we find. ChunkIncrement(contents_, &start_, end_); } else { CommentSafeIncrement(contents_, &start_); } } return false; } void ManifestHighlighter::FindBoundsEnd(const std::string& feature, size_t local_start) { char c = '\0'; while (local_start < end_) { c = contents_[local_start]; // We're done when we find a terminating character (i.e., either a comma or // an ending bracket. if (c == ',' || c == '}' || c == ']') { end_ = local_start; return; } // We can skip any chunks we find, since we are looking for the end of the // current feature, and don't want to go any deeper. if (c == '"' || c == '{' || c == '[') ChunkIncrement(contents_, &local_start, end_); else CommentSafeIncrement(contents_, &local_start); } } SourceHighlighter::SourceHighlighter(const std::string& contents, size_t line_number) : FileHighlighter(contents) { Parse(line_number); } SourceHighlighter::~SourceHighlighter() { } void SourceHighlighter::Parse(size_t line_number) { // If line 0 is requested, highlight nothing. if (line_number == 0) { start_ = contents_.size(); return; } for (size_t i = 1; i < line_number; ++i) { start_ = contents_.find('\n', start_); if (start_ == std::string::npos) break; start_ += 1; } end_ = contents_.find('\n', start_); // If we went off the end of the string (i.e., the line number was invalid), // then move start and end to the end of the string, so that the highlighted // portion is empty. start_ = start_ == std::string::npos ? contents_.size() : start_; end_ = end_ == std::string::npos ? contents_.size() : end_; } } // namespace extensions