| 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;
|
| +})();
|
| +
|
|
|