Index: netlog_viewer/source_filter_parser.js |
diff --git a/netlog_viewer/source_filter_parser.js b/netlog_viewer/source_filter_parser.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e8e148e7d8e0b2beb92a299425fe05b6d85b5c98 |
--- /dev/null |
+++ b/netlog_viewer/source_filter_parser.js |
@@ -0,0 +1,221 @@ |
+// Copyright (c) 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. |
+ |
+var SourceFilterParser = (function() { |
+ 'use strict'; |
+ |
+ /** |
+ * Parses |filterText|, extracting a sort method, a list of filters, and a |
+ * copy of |filterText| with all sort parameters removed. |
+ */ |
+ function SourceFilterParser(filterText) { |
+ // Final output will be stored here. |
+ this.filter = null; |
+ this.sort = {}; |
+ this.filterTextWithoutSort = ''; |
+ var filterList = parseFilter_(filterText); |
+ |
+ // Text filters are stored here as strings and then added as a function at |
+ // the end, for performance reasons. |
+ var textFilters = []; |
+ |
+ // Filter functions are first created individually, and then merged. |
+ var filterFunctions = []; |
+ |
+ for (var i = 0; i < filterList.length; ++i) { |
+ var filterElement = filterList[i].parsed; |
+ var negated = filterList[i].negated; |
+ |
+ var sort = parseSortDirective_(filterElement, negated); |
+ if (sort) { |
+ this.sort = sort; |
+ continue; |
+ } |
+ |
+ this.filterTextWithoutSort += filterList[i].original; |
+ |
+ var filter = parseRestrictDirective_(filterElement, negated); |
+ if (!filter) |
+ filter = parseStringDirective_(filterElement, negated); |
+ if (filter) { |
+ if (negated) { |
+ filter = (function(func, sourceEntry) { |
+ return !func(sourceEntry); |
+ }).bind(null, filter); |
+ } |
+ filterFunctions.push(filter); |
+ continue; |
+ } |
+ textFilters.push({ text: filterElement, negated: negated }); |
+ } |
+ |
+ // Create a single filter for all text filters, so they can share a |
+ // TabePrinter. |
+ filterFunctions.push(textFilter_.bind(null, textFilters)); |
+ |
+ // Create function to go through all the filters. |
+ this.filter = function(sourceEntry) { |
+ for (var i = 0; i < filterFunctions.length; ++i) { |
+ if (!filterFunctions[i](sourceEntry)) |
+ return false; |
+ } |
+ return true; |
+ }; |
+ } |
+ |
+ /** |
+ * Parses a single "sort:" directive, and returns a dictionary containing |
+ * the sort function and direction. Returns null on failure, including |
+ * the case when no such sort function exists. |
+ */ |
+ function parseSortDirective_(filterElement, backwards) { |
+ var match = /^sort:(.*)$/.exec(filterElement); |
+ if (!match) |
+ return null; |
+ return { method: match[1], backwards: backwards }; |
+ } |
+ |
+ /** |
+ * Tries to parses |filterElement| as a single "is:" directive, and returns a |
+ * new filter function. Returns null on failure. |
+ */ |
+ function parseRestrictDirective_(filterElement) { |
+ var match = /^is:(.*)$/.exec(filterElement); |
+ if (!match) |
+ return null; |
+ if (match[1] == 'active') { |
+ return function(sourceEntry) { return !sourceEntry.isInactive(); }; |
+ } |
+ if (match[1] == 'error') { |
+ return function(sourceEntry) { return sourceEntry.isError(); }; |
+ } |
+ return null; |
+ } |
+ |
+ /** |
+ * Tries to parse |filterElement| as a single filter of a type that takes |
+ * arbitrary strings as input, and returns a new filter function on success. |
+ * Returns null on failure. |
+ */ |
+ function parseStringDirective_(filterElement) { |
+ var match = RegExp('^([^:]*):(.*)$').exec(filterElement); |
+ if (!match) |
+ return null; |
+ |
+ // Split parameters around commas and remove empty elements. |
+ var parameters = match[2].split(','); |
+ parameters = parameters.filter(function(string) { |
+ return string.length > 0; |
+ }); |
+ |
+ if (match[1] == 'type') { |
+ return function(sourceEntry) { |
+ var i; |
+ var sourceType = sourceEntry.getSourceTypeString().toLowerCase(); |
+ for (i = 0; i < parameters.length; ++i) { |
+ if (sourceType.search(parameters[i]) != -1) |
+ return true; |
+ } |
+ return false; |
+ }; |
+ } |
+ |
+ if (match[1] == 'id') { |
+ return function(sourceEntry) { |
+ return parameters.indexOf(sourceEntry.getSourceId() + '') != -1; |
+ }; |
+ } |
+ |
+ return null; |
+ } |
+ |
+ /** |
+ * Takes in the text of a filter and returns a list of |
+ * {parsed, original, negated} values that correspond to substrings of the |
+ * filter before and after filtering, and whether or not it started with a |
+ * '-'. Extra whitespace other than a single character after each element is |
+ * ignored. Parsed strings are all lowercase. |
+ */ |
+ function parseFilter_(filterText) { |
+ // Assemble a list of quoted and unquoted strings in the filter. |
+ var filterList = []; |
+ var position = 0; |
+ while (position < filterText.length) { |
+ var inQuote = false; |
+ var filterElement = ''; |
+ var negated = false; |
+ var startPosition = position; |
+ while (position < filterText.length) { |
+ var nextCharacter = filterText[position]; |
+ ++position; |
+ if (nextCharacter == '\\' && |
+ position < filterText.length) { |
+ // If there's a backslash, skip the backslash and add the next |
+ // character to the element. |
+ filterElement += filterText[position]; |
+ ++position; |
+ continue; |
+ } else if (nextCharacter == '"') { |
+ // If there's an unescaped quote character, toggle |inQuote| without |
+ // modifying the element. |
+ inQuote = !inQuote; |
+ } else if (!inQuote && /\s/.test(nextCharacter)) { |
+ // If not in a quote and have a whitespace character, that's the |
+ // end of the element. |
+ break; |
+ } else if (nextCharacter == '-' && startPosition == position - 1) { |
+ // If this is the first character, and it's a '-', this entry is |
+ // negated. |
+ negated = true; |
+ } else { |
+ // Otherwise, add the next character to the element. |
+ filterElement += nextCharacter; |
+ } |
+ } |
+ |
+ if (filterElement.length > 0) { |
+ var filter = { |
+ parsed: filterElement.toLowerCase(), |
+ original: filterText.substring(startPosition, position), |
+ negated: negated, |
+ }; |
+ filterList.push(filter); |
+ } |
+ } |
+ return filterList; |
+ } |
+ |
+ /** |
+ * Takes in a list of text filters and a SourceEntry. Each filter has |
+ * "text" and "negated" fields. Returns true if the SourceEntry matches all |
+ * filters in the (possibly empty) list. |
+ */ |
+ function textFilter_(textFilters, sourceEntry) { |
+ var tablePrinter = null; |
+ for (var i = 0; i < textFilters.length; ++i) { |
+ var text = textFilters[i].text; |
+ var negated = textFilters[i].negated; |
+ var match = false; |
+ // The description is often not contained in one of the log entries. |
+ // The source type almost never is, so check for them directly. |
+ var description = sourceEntry.getDescription().toLowerCase(); |
+ var type = sourceEntry.getSourceTypeString().toLowerCase(); |
+ if (description.indexOf(text) != -1 || type.indexOf(text) != -1) { |
+ match = true; |
+ } else { |
+ if (!tablePrinter) |
+ tablePrinter = sourceEntry.createTablePrinter(); |
+ match = tablePrinter.search(text); |
+ } |
+ if (negated) |
+ match = !match; |
+ if (!match) |
+ return false; |
+ } |
+ return true; |
+ } |
+ |
+ return SourceFilterParser; |
+})(); |
+ |