/**
  * @param offset
  * @param document
  * @return
  */
 private int getBeginWithoutWhiteSpaces(int offset, FormatterDocument document) {
   int length = document.getLength();
   while (offset < length) {
     if (!Character.isWhitespace(document.charAt(offset))) {
       break;
     }
     offset++;
   }
   return offset;
 }
 private int getBlockStartOffset(int offset, FormatterDocument document) {
   int length = document.getLength();
   while (offset < length) {
     if (document.charAt(offset) == '{') {
       break;
     }
     offset++;
   }
   return offset;
 }
 /**
  * Scan for given character located at the same line. Return the given offset if non is found.
  *
  * @param offset
  * @param document
  * @return The offset of the character; The given offset if character not found.
  */
 private int locateCharacterInSameLine(char character, int offset, FormatterDocument document) {
   for (int i = offset; i < document.getLength(); i++) {
     char c = document.charAt(i);
     if (c == character) {
       return i;
     }
     if (c == '\n' || c == '\r') {
       break;
     }
   }
   return offset;
 }
  /**
   * Searchings backwards starting at 'offset' and continues until it finds a non-whitespace char
   * (ignores punctuation)
   *
   * @param offset
   * @param document
   * @return
   */
  private int getEndWithoutWhiteSpaces(int offset, FormatterDocument document) {

    while (offset > 0) {
      if (!Character.isWhitespace(document.charAt(offset))
          && document.charAt(offset) != '{'
          && !PUNCTUATION.contains(Character.toString(document.charAt(offset)))) {
        break;
      }
      offset--;
    }
    return offset;
  }
  private void pushFormatterSelectorNodes(CSSSelectorNode[] selectors) {

    for (int i = 0; i < selectors.length; i++) {
      CSSSelectorNode selectorNode = selectors[i];
      FormatterBlockWithBeginNode formatterSelectorNode =
          new FormatterCSSSelectorNode(document, i == 0, false);
      int selectorEndOffset =
          getEndWithoutWhiteSpaces(selectorNode.getEndingOffset() + 1, document) + 1;

      formatterSelectorNode.setBegin(
          createTextNode(
              document,
              getBeginWithoutWhiteSpaces(selectorNode.getStartingOffset(), document),
              selectorEndOffset));
      push(formatterSelectorNode);

      checkedPop(formatterSelectorNode, -1);

      int nextNonWhiteSpaceOffset = getBeginWithoutWhiteSpaces(selectorEndOffset, document);

      TypePunctuation punctuation =
          getTypePunctuationForChar(document.charAt(nextNonWhiteSpaceOffset));

      if (punctuation != null) {
        findAndPushPunctuationNode(punctuation, nextNonWhiteSpaceOffset, false);
      }
    }
  }
  // This is a temporary fix for custom at-rules. When the parser adds support to return the ruleID,
  // this will need to
  // be changed
  private void pushAtRuleNode(CSSNode atRuleNode) {

    int length = document.getLength();
    int selectorStartingOffset = atRuleNode.getStartingOffset();
    int selectEndingOffset = atRuleNode.getEndingOffset();

    // Locate first white space after the @rule
    while (selectorStartingOffset < length) {
      if (Character.isWhitespace(document.charAt(selectorStartingOffset))) {
        break;
      }
      selectorStartingOffset++;
    }
    // Find the starting offset for the selector
    selectorStartingOffset = getBeginWithoutWhiteSpaces(selectorStartingOffset, document);

    // Find the end offset for the selector
    while (selectEndingOffset >= selectorStartingOffset) {
      if (!Character.isWhitespace(document.charAt(selectEndingOffset - 1))) {
        break;
      }
      selectEndingOffset--;
    }
    // Push an At-Node to control the lines separators.
    FormatterCSSAtRuleNode atNode = new FormatterCSSAtRuleNode(document);
    atNode.setBegin(
        createTextNode(document, atRuleNode.getStartingOffset(), selectorStartingOffset));
    push(atNode);
    checkedPop(atNode, -1);

    // We use a selector node for now, we may want to create a new formatter node type for rule id
    FormatterBlockWithBeginNode formatterSelectorNode =
        new FormatterCSSSelectorNode(document, false, false);
    formatterSelectorNode.setBegin(
        createTextNode(
            document,
            getBeginWithoutWhiteSpaces(selectorStartingOffset, document),
            getEndWithoutWhiteSpaces(selectEndingOffset, document) + 1));
    push(formatterSelectorNode);

    findAndPushPunctuationNode(TypePunctuation.SEMICOLON, selectEndingOffset, false);

    checkedPop(formatterSelectorNode, -1);
  }
 /**
  * Locate and push a punctuation node.
  *
  * @param offsetToSearch - The offset that will be used as the start for the search of the
  *     punctuation characters.
  */
 private void findAndPushPunctuationNode(
     TypePunctuation type, int offsetToSearch, boolean isEndofDeclarationNode) {
   char punctuationType = type.toString().charAt(0);
   int punctuationOffset = locateCharForward(document, punctuationType, offsetToSearch);
   if (punctuationOffset != offsetToSearch
       || document.charAt(punctuationOffset) == punctuationType) {
     FormatterCSSPunctuationNode punctuationNode =
         new FormatterCSSPunctuationNode(document, type, isEndofDeclarationNode);
     punctuationNode.setBegin(createTextNode(document, punctuationOffset, punctuationOffset + 1));
     push(punctuationNode);
     checkedPop(punctuationNode, -1);
   }
 }
  private void pushDeclarationValueNode(
      CSSExpressionNode expressionNode, boolean isLastDeclaration) {

    int expressionEndOffset = expressionNode.getEndingOffset();
    int semicolonLocation =
        locateCharacterSkippingWhitespaces(document, expressionEndOffset + 1, ';', false);
    int commaLocation =
        locateCharacterSkippingWhitespaces(document, expressionEndOffset + 1, ',', false);
    int forwardSlashLocation =
        locateCharacterSkippingWhitespaces(document, expressionEndOffset + 1, '/', false);
    int LFLocation =
        locateCharacterSkippingWhitespaces(document, expressionEndOffset + 1, '\n', false);
    int CRLocation =
        locateCharacterSkippingWhitespaces(document, expressionEndOffset + 1, '\r', false);

    boolean endsWithSemicolon = false;
    boolean endsWithComma = false;
    boolean endsWithSlash = false;
    boolean isLastNodeInDeclaration = false;

    if (document.charAt(semicolonLocation) == ';') {
      endsWithSemicolon = true;
    }

    if (document.charAt(commaLocation) == ',') {
      endsWithComma = true;
    }

    if (document.charAt(forwardSlashLocation) == '/') {
      endsWithSlash = true;
    }

    if (document.charAt(forwardSlashLocation) == '/') {
      endsWithSlash = true;
    }

    if ((document.charAt(LFLocation) == '\n' || document.charAt(CRLocation) == '\r')
        && isLastDeclaration) {
      isLastNodeInDeclaration = true;
    }

    FormatterBlockWithBeginNode formatterDeclarationValueNode =
        new FormatterCSSDeclarationValueNode(
            document, isLastNodeInDeclaration, endsWithComma || endsWithSemicolon || endsWithSlash);

    formatterDeclarationValueNode.setBegin(
        createTextNode(
            document, expressionNode.getStartingOffset(), expressionNode.getEndingOffset() + 1));
    push(formatterDeclarationValueNode);
    checkedPop(formatterDeclarationValueNode, -1);

    if (endsWithComma) {
      findAndPushPunctuationNode(TypePunctuation.COMMA, commaLocation, false);
    }
  }
  /**
   * build
   *
   * @param parseResult
   * @param document
   * @return
   */
  public IFormatterContainerNode build(IParseNode parseResult, FormatterDocument document) {
    this._document = document;

    // create the formatter root node
    IFormatterContainerNode rootNode = new JSONRootFormatNode(document);

    // begin the transformation
    start(rootNode);

    JSONFormattingWalker walker = new JSONFormattingWalker();
    JSONParseRootNode jsonRootNode = (JSONParseRootNode) parseResult;
    jsonRootNode.accept(walker);

    // end the transformation
    checkedPop(rootNode, document.getLength());

    return rootNode;
  }
 /**
  * @param parseResult
  * @param document
  * @return
  */
 public IFormatterContainerNode build(IParseNode parseResult, FormatterDocument document) {
   this.document = document;
   final IFormatterContainerNode rootNode = new FormatterCSSRootNode(document);
   start(rootNode);
   IParseNode[] children = parseResult.getChildren();
   addNodes(children);
   checkedPop(rootNode, document.getLength());
   // Collect Off/On tags
   if (parseResult instanceof IParseRootNode) {
     setOffOnRegions(
         resolveOffOnRegions(
             (IParseRootNode) parseResult,
             document,
             CSSFormatterConstants.FORMATTER_OFF_ON_ENABLED,
             CSSFormatterConstants.FORMATTER_OFF,
             CSSFormatterConstants.FORMATTER_ON));
   }
   return rootNode;
 }
  private void pushFormatterDeclarationNodes(
      int parentEndOffset,
      CSSDeclarationNode[] declarations,
      FormatterBlockWithBeginEndNode formatterBlockNode) {
    for (int i = 0; i < declarations.length; ++i) {

      CSSDeclarationNode declarationNode = declarations[i];
      CSSExpressionNode expressionNode = declarationNode.getAssignedValue();

      // If we run into a CSSErrorDeclarationNode, we just create a text node and move on to the
      // next declaration
      // node
      if (declarationNode instanceof CSSErrorDeclarationNode) {
        formatterBlockNode.addChild(
            createTextNode(
                document,
                declarationNode.getStartingOffset(),
                declarationNode.getEndingOffset() + 1));
        // Create text nodes for comments between declaration
        findAndPushCommentsBetweenDeclarations(
            parentEndOffset, declarations, formatterBlockNode, i);
        continue;
      }

      // push the property
      FormatterBlockWithBeginNode formatterDeclarationPropertyNode =
          new FormatterCSSDeclarationPropertyNode(document);
      int propertyEndOffset =
          getEndWithoutWhiteSpaces(
              locateCharacterInSameLine(':', declarationNode.getStartingOffset(), document),
              document);

      formatterDeclarationPropertyNode.setBegin(
          createTextNode(document, declarationNode.getStartingOffset(), propertyEndOffset + 1));
      push(formatterDeclarationPropertyNode);
      checkedPop(formatterDeclarationPropertyNode, -1);

      // push the ':'
      findAndPushPunctuationNode(TypePunctuation.PROPERTY_COLON, propertyEndOffset, false);

      // push the value for the declaration
      if (expressionNode != null) {
        if (expressionNode instanceof CSSTermListNode) {
          pushTermListNode((CSSTermListNode) expressionNode, i == declarations.length - 1);
        } else {
          pushDeclarationValueNode(expressionNode, i == declarations.length - 1);
        }

        // Push a text node for status nodes: currently it's only !important
        IRange statusRange = declarationNode.getStatusRange();
        if (declarationNode.getStatus() != null) {
          formatterBlockNode.addChild(
              createTextNode(
                  document, statusRange.getStartingOffset(), statusRange.getEndingOffset() + 1));
        }
      }

      // Push a semicolon if the declaration ends with one
      if (document.charAt(declarationNode.getEndingOffset()) == ';') {
        findAndPushPunctuationNode(
            TypePunctuation.SEMICOLON, declarationNode.getEndingOffset(), true);
      }

      // Create text nodes for comments between declaration
      findAndPushCommentsBetweenDeclarations(parentEndOffset, declarations, formatterBlockNode, i);
    }
  }