コード例 #1
0
    let traverseForward = check => {
      let location;
      // Backward loop to determine the beginning location of the selector.
      do {
        let lineText = sourceArray[line];
        if (line == caret.line) {
          lineText = lineText.substring(caret.ch);
        }

        let prevToken = undefined;
        let tokens = cssTokenizer(lineText);
        let found = false;
        let ech = line == caret.line ? caret.ch : 0;
        for (let token of tokens) {
          // If the line is completely spaces, handle it differently
          if (lineText.trim() == "") {
            limitedSource += lineText;
          } else {
            limitedSource += sourceArray[line]
                              .substring(ech + token.startOffset,
                                         ech + token.endOffset);
          }

          // Whitespace cannot change state.
          if (token.tokenType == "whitespace") {
            prevToken = token;
            continue;
          }

          let state = this.resolveState(limitedSource, {
            line: line,
            ch: token.endOffset + ech
          });
          if (check(state)) {
            if (prevToken && prevToken.tokenType == "whitespace") {
              token = prevToken;
            }
            location = {
              line: line,
              ch: token.startOffset + ech
            };
            found = true;
            break;
          }
          prevToken = token;
        }
        limitedSource += "\n";
        if (found) {
          break;
        }
      } while (line++ < sourceArray.length);
      return location;
    };
コード例 #2
0
/**
 * Tokenizes a CSS Filter value and returns an array of {name, value} pairs.
 *
 * @param {String} css CSS Filter value to be parsed
 * @return {Array} An array of {name, value} pairs
 */
function tokenizeFilterValue(css) {
  let filters = [];
  let depth = 0;

  if (SPECIAL_VALUES.has(css)) {
    return filters;
  }

  let state = "initial";
  let name;
  let contents;
  for (let token of cssTokenizer(css)) {
    switch (state) {
      case "initial":
        if (token.tokenType === "function") {
          name = token.text;
          contents = "";
          state = "function";
          depth = 1;
        } else if (token.tokenType === "url" || token.tokenType === "bad_url") {
          // Extract the quoting style from the url.
          let originalText = css.substring(token.startOffset, token.endOffset);
          let [, quote] = /^url\([ \t\r\n\f]*(["']?)/i.exec(originalText);

          filters.push({name: "url", value: token.text.trim(), quote: quote});
          // Leave state as "initial" because the URL token includes
          // the trailing close paren.
        }
        break;

      case "function":
        if (token.tokenType === "symbol" && token.text === ")") {
          --depth;
          if (depth === 0) {
            filters.push({name: name, value: contents.trim()});
            state = "initial";
            break;
          }
        }
        contents += css.substring(token.startOffset, token.endOffset);
        if (token.tokenType === "function" ||
            (token.tokenType === "symbol" && token.text === "(")) {
          ++depth;
        }
        break;
    }
  }

  return filters;
}
コード例 #3
0
    let traverseBackwards = (check, isValue) => {
      let location;
      // Backward loop to determine the beginning location of the selector.
      do {
        let lineText = sourceArray[line];
        if (line == caret.line) {
          lineText = lineText.substring(0, caret.ch);
        }

        let tokens = Array.from(cssTokenizer(lineText));
        let found = false;
        for (let i = tokens.length - 1; i >= 0; i--) {
          let token = tokens[i];
          // If the line is completely spaces, handle it differently
          if (lineText.trim() == "") {
            limitedSource = limitedSource.slice(0, -1 * lineText.length);
          } else {
            let length = token.endOffset - token.startOffset;
            limitedSource = limitedSource.slice(0, -1 * length);
          }

          // Whitespace cannot change state.
          if (token.tokenType == "whitespace") {
            continue;
          }

          let state = this.resolveState(limitedSource, {
            line: line,
            ch: token.startOffset
          });
          if (check(state)) {
            if (tokens[i + 1] && tokens[i + 1].tokenType == "whitespace") {
              token = tokens[i + 1];
            }
            location = {
              line: line,
              ch: isValue ? token.endOffset : token.startOffset
            };
            found = true;
            break;
          }
        }
        limitedSource = limitedSource.slice(0, -1);
        if (found) {
          break;
        }
      } while (line-- >= 0);
      return location;
    };
コード例 #4
0
ファイル: FilterWidget.js プロジェクト: eggpi/gecko-dev
/**
 * Tokenizes a CSS Filter value and returns an array of {name, value} pairs.
 *
 * @param {String} css CSS Filter value to be parsed
 * @return {Array} An array of {name, value} pairs
 */
function tokenizeFilterValue(css) {
  let filters = [];
  let depth = 0;

  if (css === "none") {
    return filters;
  }

  let state = "initial";
  let name;
  let contents;
  for (let token of cssTokenizer(css)) {
    switch (state) {
      case "initial":
        if (token.tokenType === "function") {
          name = token.text;
          contents = "";
          state = "function";
          depth = 1;
        } else if (token.tokenType === "url" || token.tokenType === "bad_url") {
          filters.push({name: "url", value: token.text.trim()});
          // Leave state as "initial" because the URL token includes
          // the trailing close paren.
        }
        break;

      case "function":
        if (token.tokenType === "symbol" && token.text === ")") {
          --depth;
          if (depth === 0) {
            filters.push({name: name, value: contents.trim()});
            state = "initial";
            break;
          }
        }
        contents += css.substring(token.startOffset, token.endOffset);
        if (token.tokenType === "function" ||
            (token.tokenType === "symbol" && token.text === "(")) {
          ++depth;
        }
        break;
    }
  }

  return filters;
}
コード例 #5
0
  getInfoAt: function (source, caret) {
    // Limits the input source till the {line, ch} caret position
    function limit(source, {line, ch}) {
      line++;
      let list = source.split("\n");
      if (list.length < line) {
        return source;
      }
      if (line == 1) {
        return list[0].slice(0, ch);
      }
      return [...list.slice(0, line - 1),
              list[line - 1].slice(0, ch)].join("\n");
    }

    // Get the state at the given line, ch
    let state = this.resolveState(limit(source, caret), caret);
    let propertyName = this.propertyName;
    let {line, ch} = caret;
    let sourceArray = source.split("\n");
    let limitedSource = limit(source, caret);

    /**
     * Method to traverse forwards from the caret location to figure out the
     * ending point of a selector or css value.
     *
     * @param {function} check
     *        A method which takes the current state as an input and determines
     *        whether the state changed or not.
     */
    let traverseForward = check => {
      let location;
      // Backward loop to determine the beginning location of the selector.
      do {
        let lineText = sourceArray[line];
        if (line == caret.line) {
          lineText = lineText.substring(caret.ch);
        }

        let prevToken = undefined;
        let tokens = cssTokenizer(lineText);
        let found = false;
        let ech = line == caret.line ? caret.ch : 0;
        for (let token of tokens) {
          // If the line is completely spaces, handle it differently
          if (lineText.trim() == "") {
            limitedSource += lineText;
          } else {
            limitedSource += sourceArray[line]
                              .substring(ech + token.startOffset,
                                         ech + token.endOffset);
          }

          // Whitespace cannot change state.
          if (token.tokenType == "whitespace") {
            prevToken = token;
            continue;
          }

          let state = this.resolveState(limitedSource, {
            line: line,
            ch: token.endOffset + ech
          });
          if (check(state)) {
            if (prevToken && prevToken.tokenType == "whitespace") {
              token = prevToken;
            }
            location = {
              line: line,
              ch: token.startOffset + ech
            };
            found = true;
            break;
          }
          prevToken = token;
        }
        limitedSource += "\n";
        if (found) {
          break;
        }
      } while (line++ < sourceArray.length);
      return location;
    };

    /**
     * Method to traverse backwards from the caret location to figure out the
     * starting point of a selector or css value.
     *
     * @param {function} check
     *        A method which takes the current state as an input and determines
     *        whether the state changed or not.
     * @param {boolean} isValue
     *        true if the traversal is being done for a css value state.
     */
    let traverseBackwards = (check, isValue) => {
      let location;
      // Backward loop to determine the beginning location of the selector.
      do {
        let lineText = sourceArray[line];
        if (line == caret.line) {
          lineText = lineText.substring(0, caret.ch);
        }

        let tokens = Array.from(cssTokenizer(lineText));
        let found = false;
        for (let i = tokens.length - 1; i >= 0; i--) {
          let token = tokens[i];
          // If the line is completely spaces, handle it differently
          if (lineText.trim() == "") {
            limitedSource = limitedSource.slice(0, -1 * lineText.length);
          } else {
            let length = token.endOffset - token.startOffset;
            limitedSource = limitedSource.slice(0, -1 * length);
          }

          // Whitespace cannot change state.
          if (token.tokenType == "whitespace") {
            continue;
          }

          let state = this.resolveState(limitedSource, {
            line: line,
            ch: token.startOffset
          });
          if (check(state)) {
            if (tokens[i + 1] && tokens[i + 1].tokenType == "whitespace") {
              token = tokens[i + 1];
            }
            location = {
              line: line,
              ch: isValue ? token.endOffset : token.startOffset
            };
            found = true;
            break;
          }
        }
        limitedSource = limitedSource.slice(0, -1);
        if (found) {
          break;
        }
      } while (line-- >= 0);
      return location;
    };

    if (state == CSS_STATES.selector) {
      // For selector state, the ending and starting point of the selector is
      // either when the state changes or the selector becomes empty and a
      // single selector can span multiple lines.
      // Backward loop to determine the beginning location of the selector.
      let start = traverseBackwards(state => {
        return (state != CSS_STATES.selector ||
               (this.selector == "" && this.selectorBeforeNot == null));
      });

      line = caret.line;
      limitedSource = limit(source, caret);
      // Forward loop to determine the ending location of the selector.
      let end = traverseForward(state => {
        return (state != CSS_STATES.selector ||
               (this.selector == "" && this.selectorBeforeNot == null));
      });

      // Since we have start and end positions, figure out the whole selector.
      let selector = source.split("\n").slice(start.line, end.line + 1);
      selector[selector.length - 1] =
        selector[selector.length - 1].substring(0, end.ch);
      selector[0] = selector[0].substring(start.ch);
      selector = selector.join("\n");
      return {
        state: state,
        selector: selector,
        loc: {
          start: start,
          end: end
        }
      };
    } else if (state == CSS_STATES.property) {
      // A property can only be a single word and thus very easy to calculate.
      let tokens = cssTokenizer(sourceArray[line]);
      for (let token of tokens) {
        // Note that, because we're tokenizing a single line, the
        // token's offset is also the column number.
        if (token.startOffset <= ch && token.endOffset >= ch) {
          return {
            state: state,
            propertyName: token.text,
            selectors: this.selectors,
            loc: {
              start: {
                line: line,
                ch: token.startOffset
              },
              end: {
                line: line,
                ch: token.endOffset
              }
            }
          };
        }
      }
    } else if (state == CSS_STATES.value) {
      // CSS value can be multiline too, so we go forward and backwards to
      // determine the bounds of the value at caret
      let start = traverseBackwards(state => state != CSS_STATES.value, true);

      line = caret.line;
      limitedSource = limit(source, caret);
      let end = traverseForward(state => state != CSS_STATES.value);

      let value = source.split("\n").slice(start.line, end.line + 1);
      value[value.length - 1] = value[value.length - 1].substring(0, end.ch);
      value[0] = value[0].substring(start.ch);
      value = value.join("\n");
      return {
        state: state,
        propertyName: propertyName,
        selectors: this.selectors,
        value: value,
        loc: {
          start: start,
          end: end
        }
      };
    }
    return null;
  }