private void printCloseElementTag(int depth, Node node) { Element element = (Element) node; if (isEmptyTag(element)) { // Empty tag: Already handled as part of opening tag return; } // Put the closing declaration on its own line - unless it's a compact // resource file format // If the element had element children, separate the end tag from them if (newlineBeforeElementClose(element, depth)) { mOut.append(mLineSeparator); } if (indentBeforeElementClose(element, depth)) { indent(depth); } mOut.append('<').append('/'); mOut.append(node.getNodeName()); mOut.append('>'); if (newlineAfterElementClose(element, depth)) { mOut.append(mLineSeparator); } }
private void printOpenElementTag(int depth, Node node) { Element element = (Element) node; if (newlineBeforeElementOpen(element, depth)) { mOut.append(mLineSeparator); } if (indentBeforeElementOpen(element, depth)) { indent(depth); } mOut.append('<').append(element.getTagName()); NamedNodeMap attributes = element.getAttributes(); int attributeCount = attributes.getLength(); if (attributeCount > 0) { // Sort the attributes List<Attr> attributeList = new ArrayList<Attr>(); for (int i = 0; i < attributeCount; i++) { attributeList.add((Attr) attributes.item(i)); } Comparator<Attr> comparator = mPrefs.getAttributeComparator(); if (comparator != null) { Collections.sort(attributeList, comparator); } // Put the single attribute on the same line as the element tag? boolean singleLine = mPrefs.oneAttributeOnFirstLine && attributeCount == 1 // In resource files we always put all the attributes (which is // usually just zero, one or two) on the same line || mStyle == XmlFormatStyle.RESOURCE; // We also place the namespace declaration on the same line as the root element, // but this doesn't also imply singleLine handling; subsequent attributes end up // on their own lines boolean indentNextAttribute; if (singleLine || (depth == 0 && XMLNS.equals(attributeList.get(0).getPrefix()))) { mOut.append(' '); indentNextAttribute = false; } else { mOut.append(mLineSeparator); indentNextAttribute = true; } Attr last = attributeList.get(attributeCount - 1); for (Attr attribute : attributeList) { if (indentNextAttribute) { indent(depth + 1); } mOut.append(attribute.getName()); mOut.append('=').append('"'); XmlUtils.appendXmlAttributeValue(mOut, attribute.getValue()); mOut.append('"'); // Don't add a newline at the last attribute line; the > should // immediately follow the last attribute if (attribute != last) { mOut.append(singleLine ? " " : mLineSeparator); // $NON-NLS-1$ indentNextAttribute = !singleLine; } } } boolean isClosed = isEmptyTag(element); // Add a space before the > or /> ? In resource files, only do this when closing the // element if (mPrefs.spaceBeforeClose && (mStyle != XmlFormatStyle.RESOURCE || isClosed) // in <selector> files etc still treat the <item> entries as in resource files && !TAG_ITEM.equals(element.getTagName()) && (isClosed || element.getAttributes().getLength() > 0)) { mOut.append(' '); } if (isClosed) { mOut.append('/'); } mOut.append('>'); if (newlineAfterElementOpen(element, depth, isClosed)) { mOut.append(mLineSeparator); } }
private void printComment(int depth, Node node) { String comment = node.getNodeValue(); boolean multiLine = comment.indexOf('\n') != -1; String trimmed = comment.trim(); // See if this is an "end-of-the-line" comment, e.g. it is not a multi-line // comment and it appears on the same line as an opening or closing element tag; // if so, continue to place it as a suffix comment boolean isSuffixComment = false; if (!multiLine) { Node previous = node.getPreviousSibling(); isSuffixComment = true; if (previous == null && node.getParentNode().getNodeType() == Node.DOCUMENT_NODE) { isSuffixComment = false; } while (previous != null) { short type = previous.getNodeType(); if (type == Node.COMMENT_NODE) { isSuffixComment = false; break; } else if (type == Node.TEXT_NODE) { if (previous.getNodeValue().indexOf('\n') != -1) { isSuffixComment = false; break; } } else { break; } previous = previous.getPreviousSibling(); } if (isSuffixComment) { // Remove newline added by element open tag or element close tag if (endsWithLineSeparator()) { removeLastLineSeparator(); } mOut.append(' '); } } // Put the comment on a line on its own? Only if it was separated by a blank line // in the previous version of the document. In other words, if the document // adds blank lines between comments this formatter will preserve that fact, and vice // versa for a tightly formatted document it will preserve that convention as well. if (!mPrefs.removeEmptyLines && !isSuffixComment) { Node curr = node.getPreviousSibling(); if (curr == null) { if (mOut.length() > 0 && !endsWithLineSeparator()) { mOut.append(mLineSeparator); } } else if (curr.getNodeType() == Node.TEXT_NODE) { String text = curr.getNodeValue(); // Count how many newlines we find in the trailing whitespace of the // text node int newLines = 0; for (int i = text.length() - 1; i >= 0; i--) { char c = text.charAt(i); if (Character.isWhitespace(c)) { if (c == '\n') { newLines++; if (newLines == 2) { break; } } } else { break; } } if (newLines >= 2) { mOut.append(mLineSeparator); } else if (text.trim().isEmpty() && curr.getPreviousSibling() == null) { // Comment before first child in node mOut.append(mLineSeparator); } } } // TODO: Reformat the comment text? if (!multiLine) { if (!isSuffixComment) { indent(depth); } mOut.append(XML_COMMENT_BEGIN).append(' '); mOut.append(trimmed); mOut.append(' ').append(XML_COMMENT_END); mOut.append(mLineSeparator); } else { // Strip off blank lines at the beginning and end of the comment text. // Find last newline at the beginning of the text: int index = 0; int end = comment.length(); int recentNewline = -1; while (index < end) { char c = comment.charAt(index); if (c == '\n') { recentNewline = index; } if (!Character.isWhitespace(c)) { break; } index++; } int start = recentNewline + 1; // Find last newline at the end of the text index = end - 1; recentNewline = -1; while (index > start) { char c = comment.charAt(index); if (c == '\n') { recentNewline = index; } if (!Character.isWhitespace(c)) { break; } index--; } end = recentNewline == -1 ? index + 1 : recentNewline; if (start >= end) { // It's a blank comment like <!-- \n\n--> - just clean it up if (!isSuffixComment) { indent(depth); } mOut.append(XML_COMMENT_BEGIN).append(' ').append(XML_COMMENT_END); mOut.append(mLineSeparator); return; } trimmed = comment.substring(start, end); // When stripping out prefix and suffix blank lines we might have ended up // with a single line comment again so check and format single line comments // without newlines inside the <!-- --> delimiters multiLine = trimmed.indexOf('\n') != -1; if (multiLine) { indent(depth); mOut.append(XML_COMMENT_BEGIN); mOut.append(mLineSeparator); // See if we need to add extra spacing to keep alignment. Consider a comment // like this: // <!-- Deprecated strings - Move the identifiers to this section, // and remove the actual text. --> // This String will be // " Deprecated strings - Move the identifiers to this section,\n" + // " and remove the actual text. -->" // where the left side column no longer lines up. // To fix this, we need to insert some extra whitespace into the first line // of the string; in particular, the exact number of characters that the // first line of the comment was indented with! // However, if the comment started like this: // <!-- // /** Copyright // --> // then obviously the align-indent is 0, so we only want to compute an // align indent when we don't find a newline before the content boolean startsWithNewline = false; for (int i = 0; i < start; i++) { if (comment.charAt(i) == '\n') { startsWithNewline = true; break; } } if (!startsWithNewline) { Node previous = node.getPreviousSibling(); if (previous != null && previous.getNodeType() == Node.TEXT_NODE) { String prevText = previous.getNodeValue(); int indentation = XML_COMMENT_BEGIN.length(); for (int i = prevText.length() - 1; i >= 0; i--) { char c = prevText.charAt(i); if (c == '\n') { break; } else { indentation += (c == '\t') ? mPrefs.getTabWidth() : 1; } } // See if the next line after the newline has indentation; if it doesn't, // leave things alone. This fixes a case like this: // <!-- This is the // comment block --> // such that it doesn't turn it into // <!-- // This is the // comment block // --> // In this case we instead want // <!-- // This is the // comment block // --> int minIndent = Integer.MAX_VALUE; String[] lines = trimmed.split("\n"); // $NON-NLS-1$ // Skip line 0 since we know that it doesn't start with a newline for (int i = 1; i < lines.length; i++) { int indent = 0; String line = lines[i]; for (int j = 0; j < line.length(); j++) { char c = line.charAt(j); if (!Character.isWhitespace(c)) { // Only set minIndent if there's text content on the line; // blank lines can exist in the comment without affecting // the overall minimum indentation boundary. if (indent < minIndent) { minIndent = indent; } break; } else { indent += (c == '\t') ? mPrefs.getTabWidth() : 1; } } } if (minIndent < indentation) { indentation = minIndent; // Subtract any indentation that is already present on the line String line = lines[0]; for (int j = 0; j < line.length(); j++) { char c = line.charAt(j); if (!Character.isWhitespace(c)) { break; } else { indentation -= (c == '\t') ? mPrefs.getTabWidth() : 1; } } } for (int i = 0; i < indentation; i++) { mOut.append(' '); } if (indentation < 0) { boolean prefixIsSpace = true; for (int i = 0; i < -indentation && i < trimmed.length(); i++) { if (!Character.isWhitespace(trimmed.charAt(i))) { prefixIsSpace = false; break; } } if (prefixIsSpace) { trimmed = trimmed.substring(-indentation); } } } } mOut.append(trimmed); mOut.append(mLineSeparator); indent(depth); mOut.append(XML_COMMENT_END); mOut.append(mLineSeparator); } else { mOut.append(XML_COMMENT_BEGIN).append(' '); mOut.append(trimmed); mOut.append(' ').append(XML_COMMENT_END); mOut.append(mLineSeparator); } } // Preserve whitespace after comment: See if the original document had two or // more newlines after the comment, and if so have a blank line between this // comment and the next Node next = node.getNextSibling(); if (!mPrefs.removeEmptyLines && (next != null) && (next.getNodeType() == Node.TEXT_NODE)) { String text = next.getNodeValue(); int newLinesBeforeText = 0; for (int i = 0, n = text.length(); i < n; i++) { char c = text.charAt(i); if (c == '\n') { newLinesBeforeText++; if (newLinesBeforeText == 2) { // Yes mOut.append(mLineSeparator); break; } } else if (!Character.isWhitespace(c)) { break; } } } }