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); } } }
/** * Accepts a CSSPageNode and breaks down the node into different formatter nodes which should * represent it while rewriting the doc.<br> * The CSSPageNode will be broken down into several nodes of type FormatterCSSSelectorNode, * FormatterCSSBlockNode, and FormatterCSSDeclarationNode (if declarations are present). * * @param pageNode */ private void pushFormatterPageNode(CSSPageNode pageNode) { // Push an At-Node to control the lines separators. FormatterCSSAtRuleNode atNode = new FormatterCSSAtRuleNode(document); // +5 for @page length int pageNodeStart = pageNode.getStartingOffset(); atNode.setBegin(createTextNode(document, pageNodeStart, pageNodeStart + PAGE_AT_RULE_LENGTH)); push(atNode); checkedPop(atNode, -1); CSSPageSelectorNode selector = pageNode.getSelector(); CSSDeclarationNode[] declarations = pageNode.getDeclarations(); int blockStartOffset = getBlockStartOffset(pageNodeStart + 1, document); if (selector != null) { blockStartOffset = getBlockStartOffset(selector.getEndingOffset() + 1, document); FormatterBlockWithBeginNode formatterSelectorNode = new FormatterCSSSelectorNode(document, true, false); formatterSelectorNode.setBegin( createTextNode( document, getBeginWithoutWhiteSpaces(pageNodeStart, document), getEndWithoutWhiteSpaces(pageNodeStart + PAGE_AT_RULE_LENGTH, document) + 1)); push(formatterSelectorNode); checkedPop(formatterSelectorNode, -1); formatterSelectorNode = new FormatterCSSSelectorNode(document, false, false); // we do startingOffset - 1 to account for the ':' formatterSelectorNode.setBegin( createTextNode( document, getBeginWithoutWhiteSpaces(selector.getStartingOffset() - 1, document), getEndWithoutWhiteSpaces(selector.getEndingOffset() + 1, document) + 1)); push(formatterSelectorNode); checkedPop(formatterSelectorNode, -1); } FormatterBlockWithBeginEndNode formatterBlockNode = new FormatterCSSBlockNode(document, false); formatterBlockNode.setBegin(createTextNode(document, blockStartOffset, blockStartOffset + 1)); push(formatterBlockNode); // Don't create text nodes when there are no declarations, or only white space if (declarations != null && declarations.length != 0 && getBeginWithoutWhiteSpaces(blockStartOffset + 1, document) < declarations[0].getStartingOffset()) { formatterBlockNode.addChild( createTextNode(document, blockStartOffset + 1, declarations[0].getStartingOffset())); } pushFormatterDeclarationNodes(pageNode.getEndingOffset(), declarations, formatterBlockNode); checkedPop(formatterBlockNode, -1); formatterBlockNode.setEnd( createTextNode(document, pageNode.getEndingOffset(), pageNode.getEndingOffset() + 1)); }
/** * Pushes the selector nodes in a media node. (Also recursively handles nodes inside parenthesis) * * @param medias * @param startIndex * @return The index of the last medias that it was pushed */ private int pushFormatterMediaSelectorNodes(CSSTextNode[] medias, int startIndex) { boolean previousNodeIsPunctuation = startIndex > 0; int lastMediaIndex = startIndex; for (; lastMediaIndex < medias.length; lastMediaIndex++) { CSSTextNode mediaSelectorNode = medias[lastMediaIndex]; String selectorText = mediaSelectorNode.getText(); // For media nodes that are just punctuation, we skip it if (selectorText.length() == 1 && NON_LETTER_PATTERN.matcher(selectorText).matches()) { TypePunctuation punctuation = getTypePunctuationForChar(selectorText.charAt(0)); // push a punctuation node if it's part of the list if (punctuation != null) { findAndPushPunctuationNode(punctuation, mediaSelectorNode.getStartingOffset(), false); } else if (selectorText.charAt(0) == '(') { // This is the start of a parenthesis block int openParen = mediaSelectorNode.getStartingOffset(); FormatterBlockWithBeginEndNode parenthesisNode = new FormatterCSSParenthesesNode(document); parenthesisNode.setBegin(createTextNode(document, openParen, openParen + 1)); push(parenthesisNode); // push contents inside the parenthesis lastMediaIndex = pushFormatterMediaSelectorNodes(medias, lastMediaIndex + 1); checkedPop(parenthesisNode, -1); if (lastMediaIndex >= medias.length) { break; } mediaSelectorNode = medias[lastMediaIndex]; parenthesisNode.setEnd( createTextNode( document, mediaSelectorNode.getStartingOffset(), mediaSelectorNode.getStartingOffset() + 1)); } else if (selectorText.charAt(0) == ')') { // This is the end of a parenthesis block return lastMediaIndex; } previousNodeIsPunctuation = true; // otherwise, we still skip all other punctuation continue; } FormatterBlockWithBeginNode formatterSelectorNode = new FormatterCSSSelectorNode(document, false, previousNodeIsPunctuation); formatterSelectorNode.setBegin( createTextNode( document, getBeginWithoutWhiteSpaces(mediaSelectorNode.getStartingOffset(), document), getEndWithoutWhiteSpaces(mediaSelectorNode.getEndingOffset() + 1, document) + 1)); push(formatterSelectorNode); checkedPop(formatterSelectorNode, -1); previousNodeIsPunctuation = false; } return lastMediaIndex; }
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); } }
// 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); }
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); } }