OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 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. |
| 4 |
| 5 var SourceFilterParser = (function() { |
| 6 'use strict'; |
| 7 |
| 8 /** |
| 9 * Parses |filterText|, extracting a sort method, a list of filters, and a |
| 10 * copy of |filterText| with all sort parameters removed. |
| 11 */ |
| 12 function SourceFilterParser(filterText) { |
| 13 // Final output will be stored here. |
| 14 this.filter = null; |
| 15 this.sort = {}; |
| 16 this.filterTextWithoutSort = ''; |
| 17 var filterList = parseFilter_(filterText); |
| 18 |
| 19 // Text filters are stored here as strings and then added as a function at |
| 20 // the end, for performance reasons. |
| 21 var textFilters = []; |
| 22 |
| 23 // Filter functions are first created individually, and then merged. |
| 24 var filterFunctions = []; |
| 25 |
| 26 for (var i = 0; i < filterList.length; ++i) { |
| 27 var filterElement = filterList[i].parsed; |
| 28 var negated = filterList[i].negated; |
| 29 |
| 30 var sort = parseSortDirective_(filterElement, negated); |
| 31 if (sort) { |
| 32 this.sort = sort; |
| 33 continue; |
| 34 } |
| 35 |
| 36 this.filterTextWithoutSort += filterList[i].original; |
| 37 |
| 38 var filter = parseRestrictDirective_(filterElement, negated); |
| 39 if (!filter) |
| 40 filter = parseStringDirective_(filterElement, negated); |
| 41 if (filter) { |
| 42 if (negated) { |
| 43 filter = (function(func, sourceEntry) { |
| 44 return !func(sourceEntry); |
| 45 }).bind(null, filter); |
| 46 } |
| 47 filterFunctions.push(filter); |
| 48 continue; |
| 49 } |
| 50 textFilters.push({ text: filterElement, negated: negated }); |
| 51 } |
| 52 |
| 53 // Create a single filter for all text filters, so they can share a |
| 54 // TabePrinter. |
| 55 filterFunctions.push(textFilter_.bind(null, textFilters)); |
| 56 |
| 57 // Create function to go through all the filters. |
| 58 this.filter = function(sourceEntry) { |
| 59 for (var i = 0; i < filterFunctions.length; ++i) { |
| 60 if (!filterFunctions[i](sourceEntry)) |
| 61 return false; |
| 62 } |
| 63 return true; |
| 64 }; |
| 65 } |
| 66 |
| 67 /** |
| 68 * Parses a single "sort:" directive, and returns a dictionary containing |
| 69 * the sort function and direction. Returns null on failure, including |
| 70 * the case when no such sort function exists. |
| 71 */ |
| 72 function parseSortDirective_(filterElement, backwards) { |
| 73 var match = /^sort:(.*)$/.exec(filterElement); |
| 74 if (!match) |
| 75 return null; |
| 76 return { method: match[1], backwards: backwards }; |
| 77 } |
| 78 |
| 79 /** |
| 80 * Tries to parses |filterElement| as a single "is:" directive, and returns a |
| 81 * new filter function. Returns null on failure. |
| 82 */ |
| 83 function parseRestrictDirective_(filterElement) { |
| 84 var match = /^is:(.*)$/.exec(filterElement); |
| 85 if (!match) |
| 86 return null; |
| 87 if (match[1] == 'active') { |
| 88 return function(sourceEntry) { return !sourceEntry.isInactive(); }; |
| 89 } |
| 90 if (match[1] == 'error') { |
| 91 return function(sourceEntry) { return sourceEntry.isError(); }; |
| 92 } |
| 93 return null; |
| 94 } |
| 95 |
| 96 /** |
| 97 * Tries to parse |filterElement| as a single filter of a type that takes |
| 98 * arbitrary strings as input, and returns a new filter function on success. |
| 99 * Returns null on failure. |
| 100 */ |
| 101 function parseStringDirective_(filterElement) { |
| 102 var match = RegExp('^([^:]*):(.*)$').exec(filterElement); |
| 103 if (!match) |
| 104 return null; |
| 105 |
| 106 // Split parameters around commas and remove empty elements. |
| 107 var parameters = match[2].split(','); |
| 108 parameters = parameters.filter(function(string) { |
| 109 return string.length > 0; |
| 110 }); |
| 111 |
| 112 if (match[1] == 'type') { |
| 113 return function(sourceEntry) { |
| 114 var i; |
| 115 var sourceType = sourceEntry.getSourceTypeString().toLowerCase(); |
| 116 for (i = 0; i < parameters.length; ++i) { |
| 117 if (sourceType.search(parameters[i]) != -1) |
| 118 return true; |
| 119 } |
| 120 return false; |
| 121 }; |
| 122 } |
| 123 |
| 124 if (match[1] == 'id') { |
| 125 return function(sourceEntry) { |
| 126 return parameters.indexOf(sourceEntry.getSourceId() + '') != -1; |
| 127 }; |
| 128 } |
| 129 |
| 130 return null; |
| 131 } |
| 132 |
| 133 /** |
| 134 * Takes in the text of a filter and returns a list of |
| 135 * {parsed, original, negated} values that correspond to substrings of the |
| 136 * filter before and after filtering, and whether or not it started with a |
| 137 * '-'. Extra whitespace other than a single character after each element is |
| 138 * ignored. Parsed strings are all lowercase. |
| 139 */ |
| 140 function parseFilter_(filterText) { |
| 141 // Assemble a list of quoted and unquoted strings in the filter. |
| 142 var filterList = []; |
| 143 var position = 0; |
| 144 while (position < filterText.length) { |
| 145 var inQuote = false; |
| 146 var filterElement = ''; |
| 147 var negated = false; |
| 148 var startPosition = position; |
| 149 while (position < filterText.length) { |
| 150 var nextCharacter = filterText[position]; |
| 151 ++position; |
| 152 if (nextCharacter == '\\' && |
| 153 position < filterText.length) { |
| 154 // If there's a backslash, skip the backslash and add the next |
| 155 // character to the element. |
| 156 filterElement += filterText[position]; |
| 157 ++position; |
| 158 continue; |
| 159 } else if (nextCharacter == '"') { |
| 160 // If there's an unescaped quote character, toggle |inQuote| without |
| 161 // modifying the element. |
| 162 inQuote = !inQuote; |
| 163 } else if (!inQuote && /\s/.test(nextCharacter)) { |
| 164 // If not in a quote and have a whitespace character, that's the |
| 165 // end of the element. |
| 166 break; |
| 167 } else if (nextCharacter == '-' && startPosition == position - 1) { |
| 168 // If this is the first character, and it's a '-', this entry is |
| 169 // negated. |
| 170 negated = true; |
| 171 } else { |
| 172 // Otherwise, add the next character to the element. |
| 173 filterElement += nextCharacter; |
| 174 } |
| 175 } |
| 176 |
| 177 if (filterElement.length > 0) { |
| 178 var filter = { |
| 179 parsed: filterElement.toLowerCase(), |
| 180 original: filterText.substring(startPosition, position), |
| 181 negated: negated, |
| 182 }; |
| 183 filterList.push(filter); |
| 184 } |
| 185 } |
| 186 return filterList; |
| 187 } |
| 188 |
| 189 /** |
| 190 * Takes in a list of text filters and a SourceEntry. Each filter has |
| 191 * "text" and "negated" fields. Returns true if the SourceEntry matches all |
| 192 * filters in the (possibly empty) list. |
| 193 */ |
| 194 function textFilter_(textFilters, sourceEntry) { |
| 195 var tablePrinter = null; |
| 196 for (var i = 0; i < textFilters.length; ++i) { |
| 197 var text = textFilters[i].text; |
| 198 var negated = textFilters[i].negated; |
| 199 var match = false; |
| 200 // The description is often not contained in one of the log entries. |
| 201 // The source type almost never is, so check for them directly. |
| 202 var description = sourceEntry.getDescription().toLowerCase(); |
| 203 var type = sourceEntry.getSourceTypeString().toLowerCase(); |
| 204 if (description.indexOf(text) != -1 || type.indexOf(text) != -1) { |
| 205 match = true; |
| 206 } else { |
| 207 if (!tablePrinter) |
| 208 tablePrinter = sourceEntry.createTablePrinter(); |
| 209 match = tablePrinter.search(text); |
| 210 } |
| 211 if (negated) |
| 212 match = !match; |
| 213 if (!match) |
| 214 return false; |
| 215 } |
| 216 return true; |
| 217 } |
| 218 |
| 219 return SourceFilterParser; |
| 220 })(); |
| 221 |
OLD | NEW |