/******************************************************************************* * Copyright (c) 2004, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Genuitec - fix for bug #203252 *******************************************************************************/ package org.eclipse.wst.html.core.internal.format; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Preferences; import org.eclipse.jface.text.BadLocationException; import org.eclipse.wst.html.core.internal.HTMLCorePlugin; import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames; import org.eclipse.wst.html.core.internal.provisional.HTMLCMProperties; import org.eclipse.wst.html.core.internal.provisional.HTMLFormatContraints; import org.eclipse.wst.sse.core.internal.format.IStructuredFormatContraints; import org.eclipse.wst.sse.core.internal.format.IStructuredFormatPreferences; import org.eclipse.wst.sse.core.internal.format.IStructuredFormatter; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration; import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery; import org.eclipse.wst.xml.core.internal.modelquery.ModelQueryUtil; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; import org.eclipse.wst.xml.core.internal.provisional.format.StructuredFormatPreferencesXML; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.Text; public class HTMLFormatter implements IStructuredFormatter { private static final String HTML_NAME = "html";//$NON-NLS-1$ private static final String HEAD_NAME = "head"; //$NON-NLS-1$ private static final String BODY_NAME = "BODY";//$NON-NLS-1$ // hidden jsp logic that should be removed when jsp formatter is created private static final String JSP = "jsp";//$NON-NLS-1$ private HTMLFormattingUtil formattingUtil; public HTMLFormatter() { formattingUtil = new HTMLFormattingUtil(); } /** */ protected void addWidth(HTMLFormatContraints contraints, int width) { if (contraints == null) return; if (!splitLines() || getLineWidth() < 0) return; int availableWidth = contraints.getAvailableLineWidth() - width; if (availableWidth < 0) availableWidth = 0; contraints.setAvailableLineWidth(availableWidth); } /** */ protected boolean canFormatChild(Node node) { while (node != null) { if (node.getNodeType() != Node.ELEMENT_NODE) return true; CMElementDeclaration decl = getElementDeclaration((Element) node); if (decl != null) { if (decl.getContentType() == CMElementDeclaration.CDATA) return false; if (decl.supports(HTMLCMProperties.SHOULD_KEEP_SPACE)) { boolean shouldKeepSpace = ((Boolean) decl.getProperty(HTMLCMProperties.SHOULD_KEEP_SPACE)).booleanValue(); if (shouldKeepSpace) return false; } } node = node.getParentNode(); } return false; } /** */ protected boolean canInsertBreakAfter(CMElementDeclaration decl) { if (decl == null) return false; if (!decl.supports(HTMLCMProperties.LINE_BREAK_HINT)) return false; String hint = (String) decl.getProperty(HTMLCMProperties.LINE_BREAK_HINT); if (hint == null) return false; return (hint.equals(HTMLCMProperties.Values.BREAK_BEFORE_START_AND_AFTER_END) || hint.equals(HTMLCMProperties.Values.BREAK_AFTER_START)); } /** */ protected boolean canInsertBreakAfter(Node node) { if (node == null) return false; Node parent = node.getParentNode(); if (parent == null) return false; Node next = node.getNextSibling(); // special exception if this node is a non-HTML tag (like JSP // elements) // BUG188093 - only preserve whitespace for jsp (not custom) tags String prefix = node.getPrefix(); if (prefix != null && JSP.equals(prefix)) { boolean canInsertBreakAfter = false; // if a whitespace does not exist after it, do not add one if (next != null && next.getNodeType() == Node.TEXT_NODE) { String theText = ((Text) next).getData(); if (theText != null && theText.length() > 0) { char theChar = theText.charAt(0); canInsertBreakAfter = Character.isWhitespace(theChar); } } // if cannot insert break, go ahead and return false (otherwise, // continue processing) if (!canInsertBreakAfter) return false; } // special exception if next node is a non-HTML tag (like JSP // elements) // BUG188093 - only preserve whitespace for jsp (not custom) tags if (next != null) { prefix = next.getPrefix(); if (prefix != null && JSP.equals(prefix)) { boolean canInsertBreakAfterPrevious = false; // if a whitespace does not exist before it, do not add one if (node.getNodeType() == Node.TEXT_NODE) { String theText = ((Text) node).getData(); if (theText != null && theText.length() > 0) { char theChar = theText.charAt(theText.length() - 1); canInsertBreakAfterPrevious = Character.isWhitespace(theChar); } } // if cannot insert break, go ahead and return false // (otherwise, // continue processing) if (!canInsertBreakAfterPrevious) return false; } } if (parent.getNodeType() == Node.DOCUMENT_NODE) { if (node.getNodeType() == Node.ELEMENT_NODE) { // do not insert break after unclosed tag if (!((IDOMElement) node).isClosed()) return false; } return true; } else if (parent.getNodeType() == Node.ELEMENT_NODE) { IDOMElement element = (IDOMElement) parent; // do not insert break before missing end tag if (next == null && element.getEndStructuredDocumentRegion() == null) return false; // insert line break under non-HTML elements including JSP // elements if (element.getPrefix() != null) return true; CMElementDeclaration decl = getElementDeclaration(element); if (decl != null) { if (decl.getContentType() == CMElementDeclaration.ELEMENT) return true; // causes all closing tags to wrap to a new line boolean allowsText = decl.getContentType() == CMElementDeclaration.MIXED || decl.getContentType() == CMElementDeclaration.PCDATA; if (allowsNewlineAfter(allowsText, node, element, decl)) return true; String tagName = element.getTagName(); // special for direct children under BODY if (tagName != null && tagName.equalsIgnoreCase(BODY_NAME)) return true; } } if (node.getNodeType() == Node.ELEMENT_NODE) { IDOMElement element = (IDOMElement) node; CMElementDeclaration decl = getElementDeclaration(element); if (canInsertBreakAfter(decl)) { // spcial for BR return canFormatChild(parent); } } if (next != null && next.getNodeType() == Node.ELEMENT_NODE) { CMElementDeclaration decl = getElementDeclaration((Element) next); if (canInsertBreakBefore(decl)) return true; } return false; } /** */ protected boolean canInsertBreakBefore(CMElementDeclaration decl) { if (decl == null) return false; if (!decl.supports(HTMLCMProperties.LINE_BREAK_HINT)) return false; String hint = (String) decl.getProperty(HTMLCMProperties.LINE_BREAK_HINT); if (hint == null) return false; return hint.equals(HTMLCMProperties.Values.BREAK_BEFORE_START_AND_AFTER_END); } /** */ protected boolean canInsertBreakBefore(Node node) { if (node == null) return false; Node parent = node.getParentNode(); if (parent == null) return false; Node prev = node.getPreviousSibling(); // special exception if this node is a non-HTML tag (like JSP // elements) // BUG188093 - only preserve whitespace for jsp (not custom) tags String prefix = node.getPrefix(); if (prefix != null && JSP.equals(prefix)) { boolean canInsertBreakBefore = false; // if a whitespace does not exist before it, do not add one if (prev != null && prev.getNodeType() == Node.TEXT_NODE) { String theText = ((Text) prev).getData(); if (theText != null && theText.length() > 0) { char theChar = theText.charAt(theText.length() - 1); canInsertBreakBefore = Character.isWhitespace(theChar); } } // if cannot insert break, go ahead and return false (otherwise, // continue processing) if (!canInsertBreakBefore) return false; } // special exception if previous node is a non-HTML tag (like JSP // elements) // BUG188093 - only preserve whitespace for jsp (not custom) tags if (prev != null) { prefix = prev.getPrefix(); if (prefix != null && JSP.equals(prefix)) { boolean canInsertBreakBeforeNext = false; // if a whitespace does not exist after it, do not add one if (node.getNodeType() == Node.TEXT_NODE) { String theText = ((Text) node).getData(); if (theText != null && theText.length() > 0) { char theChar = theText.charAt(0); canInsertBreakBeforeNext = Character.isWhitespace(theChar); } } // if cannot insert break, go ahead and return false // (otherwise, // continue processing) if (!canInsertBreakBeforeNext) return false; } } if (parent.getNodeType() == Node.DOCUMENT_NODE) { if (prev == null) return false; return true; } else if (parent.getNodeType() == Node.ELEMENT_NODE) { IDOMElement element = (IDOMElement) parent; // do not insert break after missing start tag if (prev == null && element.getStartStructuredDocumentRegion() == null) return false; // insert line break under non-HTML elements including JSP // elements if (element.getPrefix() != null) return true; CMElementDeclaration decl = getElementDeclaration(element); if (decl != null) { return allowNewlineBefore(node, element, decl); } } if (node.getNodeType() == Node.ELEMENT_NODE) { return true; } if (prev != null && prev.getNodeType() == Node.ELEMENT_NODE) { CMElementDeclaration decl = getElementDeclaration((Element) prev); if (canInsertBreakAfter(decl)) { // spcial for BR return canFormatChild(parent); } } return false; } /** */ public void format(Node node) { format(node, getFormatContraints()); } /** */ public void format(Node node, IStructuredFormatContraints contraints) { if (node instanceof IDOMNode && contraints instanceof HTMLFormatContraints) format((IDOMNode) node, (HTMLFormatContraints) contraints); } public void format(IDOMNode node, HTMLFormatContraints contraints) { if (node == null) return; if (node.getParentNode() == null) return; // do not format removed node setWidth(contraints, node); if (canInsertBreakBefore(node)) insertBreakBefore(node, contraints); formatNode(node, contraints); if (canInsertBreakAfter(node)) insertBreakAfter(node, contraints); } /** */ protected void formatChildNodes(IDOMNode node, HTMLFormatContraints contraints) { if (node == null) return; if (!node.hasChildNodes()) return; // concat adjacent texts node.normalize(); // disable sibling indent during formatting all the children boolean indent = false; if (contraints != null) { indent = contraints.getFormatWithSiblingIndent(); contraints.setFormatWithSiblingIndent(false); } boolean insertBreak = true; IDOMNode child = (IDOMNode) node.getFirstChild(); while (child != null) { if (child.getParentNode() != node) break; IDOMNode next = (IDOMNode) child.getNextSibling(); if (insertBreak && canInsertBreakBefore(child)) { insertBreakBefore(child, contraints); } IStructuredFormatter formatter = HTMLFormatterFactory.getInstance().createFormatter(child, getFormatPreferences()); if (formatter != null) { if (formatter instanceof HTMLFormatter) { HTMLFormatter htmlFormatter = (HTMLFormatter) formatter; htmlFormatter.formatNode(child, contraints); } else { formatter.format(child); } } if (canInsertBreakAfter(child)) { insertBreakAfter(child, contraints); insertBreak = false; // not to insert twice } else { insertBreak = true; } child = next; } if (contraints != null) contraints.setFormatWithSiblingIndent(indent); } /** */ protected void formatNode(IDOMNode node, HTMLFormatContraints contraints) { if (node == null) return; if (node.hasChildNodes()) { // container formatChildNodes(node, contraints); } else { // leaf IStructuredDocumentRegion flatNode = node.getStartStructuredDocumentRegion(); if (flatNode != null) { String source = flatNode.getText(); if (source != null && source.length() > 0) { setWidth(contraints, source); } } } } /** */ protected String getBreakSpaces(Node node) { if (node == null) return null; StringBuffer buffer = new StringBuffer(); String delim = ((IDOMNode) node).getModel().getStructuredDocument().getLineDelimiter(); if (delim != null && delim.length() > 0) buffer.append(delim); String indent = getIndent(); if (indent != null && indent.length() > 0) { for (Node parent = node.getParentNode(); parent != null; parent = parent.getParentNode()) { if (parent.getNodeType() != Node.ELEMENT_NODE) break; // ignore omitted tag if (((IDOMNode) parent).getStartStructuredDocumentRegion() == null) continue; IDOMElement element = (IDOMElement) parent; if (element.getPrefix() != null) { String localName = element.getLocalName(); // special for html:html if (localName != null && !localName.equals(HTML_NAME)) { buffer.append(indent); } continue; } else { String localName = element.getLocalName(); if (HTML_NAME.equalsIgnoreCase(localName) || HEAD_NAME.equalsIgnoreCase(localName)) break; } CMElementDeclaration decl = getElementDeclaration(element); if (decl != null && decl.supports(HTMLCMProperties.SHOULD_INDENT_CHILD_SOURCE)) { boolean shouldIndent = isIdentable(node, parent); if (shouldIndent) buffer.append(indent); } } } return buffer.toString(); } /** */ protected String getIndent() { return getFormatPreferences().getIndent(); } /** */ protected int getLineWidth() { return getFormatPreferences().getLineWidth(); } /** */ protected CMElementDeclaration getElementDeclaration(Element element) { if (element == null) return null; Document document = element.getOwnerDocument(); if (document == null) return null; ModelQuery modelQuery = ModelQueryUtil.getModelQuery(document); if (modelQuery == null) return null; return modelQuery.getCMElementDeclaration(element); } /** * Checks if the content of an inline node has been formatted in any way * @param parent the parent node to check for formatting * @return true if the parent node is inline and also has text formatting */ protected boolean isContentFormatted(Node parent) { final Node first = parent.getFirstChild(); if (first != null && first.getNodeType() == Node.TEXT_NODE) { final String content = first.getNodeValue(); final int length = content.length(); for (int i = 0; i < length; i++) { final char c = content.charAt(i); if (c == '\r' || c == '\n') { return true; } } } return false; } /** */ protected void insertBreakAfter(IDOMNode node, HTMLFormatContraints contraints) { if (node == null) return; if (node.getNodeType() == Node.TEXT_NODE) return; // don't insert break if node is on the last line int documentLength = node.getStructuredDocument().getLength(); if (documentLength < 1 || (node.getEndOffset() >= (documentLength - 1))) return; Node parent = node.getParentNode(); if (parent == null) return; Node next = node.getNextSibling(); String spaces = null; if (next == null) { // last spaces // If the parent is inline and its content is not formatted don't insert if (formattingUtil.isInline(parent) && !isContentFormatted(parent)) return; // use parent indent for the end tag spaces = getBreakSpaces(parent); } else if (next.getNodeType() == Node.TEXT_NODE) { if (contraints != null && contraints.getFormatWithSiblingIndent()) { IDOMNode text = (IDOMNode) next; IStructuredFormatter formatter = HTMLFormatterFactory.getInstance().createFormatter(text, getFormatPreferences()); if (formatter instanceof HTMLTextFormatter) { HTMLTextFormatter textFormatter = (HTMLTextFormatter) formatter; textFormatter.formatText(text, contraints, HTMLTextFormatter.FORMAT_HEAD); } } return; } else { spaces = getBreakSpaces(node); } if (spaces == null || spaces.length() == 0) return; replaceSource(node.getModel(), node.getEndOffset(), 0, spaces); setWidth(contraints, spaces); } /** */ protected void insertBreakBefore(IDOMNode node, HTMLFormatContraints contraints) { if (node == null) return; if (node.getNodeType() == Node.TEXT_NODE) return; Node parent = node.getParentNode(); if (parent == null) return; Node prev = node.getPreviousSibling(); String spaces = null; if (prev != null && prev.getNodeType() == Node.TEXT_NODE) { if (contraints != null && contraints.getFormatWithSiblingIndent()) { IDOMNode text = (IDOMNode) prev; IStructuredFormatter formatter = HTMLFormatterFactory.getInstance().createFormatter(text, getFormatPreferences()); if (formatter instanceof HTMLTextFormatter) { HTMLTextFormatter textFormatter = (HTMLTextFormatter) formatter; textFormatter.formatText(text, contraints, HTMLTextFormatter.FORMAT_TAIL); } } return; } else { spaces = getBreakSpaces(node); } if (spaces == null || spaces.length() == 0) return; replaceSource(node.getModel(), node.getStartOffset(), 0, spaces); setWidth(contraints, spaces); } /** */ protected boolean isWidthAvailable(HTMLFormatContraints contraints, int width) { if (contraints == null) return true; if (!splitLines() || getLineWidth() < 0) return true; return (contraints.getAvailableLineWidth() >= width); } /** */ protected boolean keepBlankLines(HTMLFormatContraints contraints) { if (contraints == null) return true; return (!contraints.getClearAllBlankLines()); } /** */ protected void replaceSource(IStructuredDocumentRegion flatNode, int offset, int length, String source) { if (flatNode == null) return; IStructuredDocument structuredDocument = flatNode.getParentDocument(); if (structuredDocument == null) return; if (source == null) source = new String(); int startOffset = flatNode.getStartOffset(); if (structuredDocument.containsReadOnly(startOffset + offset, length)) return; // We use 'structuredDocument' as the requester object just so this // and the other // format-related 'repalceText' (in replaceSource) can use the same // requester. // Otherwise, if requester is not identical, // the undo group gets "broken" into multiple pieces based // on the requesters being different. Technically, any unique, common // requester object would work. structuredDocument.replaceText(structuredDocument, startOffset + offset, length, source); } /** */ protected void replaceSource(IDOMModel model, int offset, int length, String source) { if (model == null) return; IStructuredDocument structuredDocument = model.getStructuredDocument(); if (structuredDocument == null) return; if (source == null) source = new String(); if (structuredDocument.containsReadOnly(offset, length)) return; // We use 'structuredDocument' as the requester object just so this // and the other // format-related 'repalceText' (in replaceSource) can use the same // requester. // Otherwise, if requester is not identical, // the undo group gets "broken" into multiple pieces based // on the requesters being different. Technically, any unique, common // requester object would work. structuredDocument.replaceText(structuredDocument, offset, length, source); } /** */ protected void setWidth(HTMLFormatContraints contraints, String source) { if (contraints == null) return; if (source == null) return; int length = source.length(); if (length == 0) return; if (!splitLines()) return; int lineWidth = getLineWidth(); if (lineWidth < 0) return; int offset = source.lastIndexOf('\n'); int offset2 = source.lastIndexOf('\r'); if (offset2 > offset) offset = offset2; if (offset >= 0) offset++; int availableWidth = 0; if (offset >= 0) { availableWidth = lineWidth - (length - offset); } else { availableWidth = contraints.getAvailableLineWidth() - length; } if (availableWidth < 0) availableWidth = 0; contraints.setAvailableLineWidth(availableWidth); } /** */ protected void setWidth(HTMLFormatContraints contraints, Node node) { if (contraints == null) return; if (node == null) return; IStructuredDocument structuredDocument = ((IDOMNode) node).getStructuredDocument(); if (structuredDocument == null) return; // error if (!splitLines()) return; int lineWidth = getLineWidth(); if (lineWidth < 0) return; int offset = ((IDOMNode) node).getStartOffset(); int line = structuredDocument.getLineOfOffset(offset); int lineOffset = 0; try { lineOffset = structuredDocument.getLineOffset(line); } catch (BadLocationException ex) { return; // error } if (lineOffset > offset) return; // error int availableWidth = lineWidth - (offset - lineOffset); if (availableWidth < 0) availableWidth = 0; contraints.setAvailableLineWidth(availableWidth); } /** */ protected boolean splitLines() { return true;// getFormatPreferences().getSplitLines(); } protected IStructuredFormatPreferences fFormatPreferences = null; protected HTMLFormatContraints fFormatContraints = null; protected IProgressMonitor fProgressMonitor = null; // public void format(XMLNode node, FormatContraints formatContraints) { // if (formatContraints.getFormatWithSiblingIndent()) // formatContraints.setCurrentIndent(getSiblingIndent(node)); // // formatNode(node, formatContraints); // } public void setFormatPreferences(IStructuredFormatPreferences formatPreferences) { fFormatPreferences = formatPreferences; } public IStructuredFormatPreferences getFormatPreferences() { if (fFormatPreferences == null) { fFormatPreferences = new StructuredFormatPreferencesXML(); Preferences preferences = HTMLCorePlugin.getDefault().getPluginPreferences(); if (preferences != null) { fFormatPreferences.setLineWidth(preferences.getInt(HTMLCorePreferenceNames.LINE_WIDTH)); ((StructuredFormatPreferencesXML) fFormatPreferences).setSplitMultiAttrs(preferences.getBoolean(HTMLCorePreferenceNames.SPLIT_MULTI_ATTRS)); ((StructuredFormatPreferencesXML) fFormatPreferences).setAlignEndBracket(preferences.getBoolean(HTMLCorePreferenceNames.ALIGN_END_BRACKET)); fFormatPreferences.setClearAllBlankLines(preferences.getBoolean(HTMLCorePreferenceNames.CLEAR_ALL_BLANK_LINES)); char indentChar = ' '; String indentCharPref = preferences.getString(HTMLCorePreferenceNames.INDENTATION_CHAR); if (HTMLCorePreferenceNames.TAB.equals(indentCharPref)) { indentChar = '\t'; } int indentationWidth = preferences.getInt(HTMLCorePreferenceNames.INDENTATION_SIZE); StringBuffer indent = new StringBuffer(); for (int i = 0; i < indentationWidth; i++) { indent.append(indentChar); } fFormatPreferences.setIndent(indent.toString()); } } return fFormatPreferences; } public IStructuredFormatContraints getFormatContraints() { if (fFormatContraints == null) { fFormatContraints = new HTMLFormatContraintsImpl(); fFormatContraints.setAvailableLineWidth(getFormatPreferences().getLineWidth()); fFormatContraints.setClearAllBlankLines(getFormatPreferences().getClearAllBlankLines()); } return fFormatContraints; } public void setProgressMonitor(IProgressMonitor progressMonitor) { fProgressMonitor = progressMonitor; } /* Check to see if current text Node is a child of an inline element. */ public boolean isInlinableTextNode(Node theNode, Element theParentElement) { return formattingUtil.isInline(theParentElement) && theNode.getNodeType() == Node.TEXT_NODE; } public boolean allowsNewlineAfter(boolean theBool, Node theNode, Element theParentElement) { return allowsNewlineAfter(theBool, theNode, theParentElement, null); } public boolean allowsNewlineAfter(boolean theBool, Node theNode, Element theParentElement, CMElementDeclaration decl) { boolean result = theBool; if ((theNode.getNodeType() == Node.TEXT_NODE) && formattingUtil.isInline(theParentElement)) { if (theParentElement.getChildNodes().getLength() == 1) { // Only child is a text node, no newline result = false; } else if (theNode == theParentElement.getLastChild()) { // theNode is the last, does formatting need corrected if (theParentElement.getFirstChild().getNodeType() == Node.ELEMENT_NODE) { result = false; } } else { result = false; } } else if (theNode.getNodeType() == Node.ELEMENT_NODE && formattingUtil.isInline(theNode.getNextSibling())) { result = false; } else if (theNode.getNodeType() == Node.TEXT_NODE) { Node next = theNode.getNextSibling(); if (next != null && formattingUtil.isInline(next) || theParentElement.getChildNodes().getLength() <= 1) { result = false; } } else if (decl != null && decl.getContentType() == CMElementDeclaration.PCDATA) { result = false; } return result; } public boolean allowNewlineBefore(Node theNode) { if (theNode.getNodeType() != Node.TEXT_NODE && theNode.getNodeType() != Node.ELEMENT_NODE) return false; return (formattingUtil.isInline(theNode.getParentNode()) || formattingUtil.isInline(theNode.getPreviousSibling())); } public boolean allowNewlineBefore(Node theNode, Element theParentElement) { return allowNewlineBefore(theNode, theParentElement, (CMElementDeclaration) null); } public boolean allowNewlineBefore(Node theNode, Element theParentElement, CMElementDeclaration decl) { boolean result = true; /* The calling method canInsertBreakBefore is checking if you can * insert a line break after the text node in the parentElement. We * need to check for the case with inline element because we don't want to * break before the closing </tag> */ if (isInlinableTextNode(theNode, theParentElement)) { result = false; /* Check to see if we need to not break the line because we are * a child of a inline element or a next sibling to an inline element*/ } else if (allowNewlineBefore(theNode)) { result = false; } else if (theNode.getNodeType() == Node.TEXT_NODE && theParentElement.getChildNodes().getLength() <= 1) { result = false; } else if (decl != null && decl.getContentType() == CMElementDeclaration.PCDATA) { result = false; } return result; } public boolean isIdentable(Node theNode, Node theParent) { boolean result = true; /* The first 2 cases where we don't want to break/indent or if the * node is a inlineText ELement or if we should skip it before its parent * is an inlineText element. * The last check is to make sure that the parent is actually the parent * of the node. This method is called when the formatter is formatting * the startTag and the wrap margin causes attributes to be indents on * mulitple lines. In this case where the parentNode doesn't match * theParent argument, we can allow the indent. */ if (formattingUtil.isInline(theNode) && formattingUtil.shouldSkipIndentForNode(theNode) && theParent == theNode.getParentNode()) { result = false; } return result; } }