/******************************************************************************* * 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 *******************************************************************************/ package org.eclipse.wst.html.core.internal.format; 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.provisional.text.IStructuredDocumentRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer; import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMText; import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; import org.w3c.dom.Element; import org.w3c.dom.Node; public class HTMLTextFormatter extends HTMLFormatter { public static int FORMAT_ALL = 0; public static int FORMAT_HEAD = 1; public static int FORMAT_TAIL = 2; private IStructuredDocumentRegion textRegion; /** */ protected HTMLTextFormatter() { super(); } /** */ private boolean canFormatText(IDOMText text) { if (text == null) return false; IStructuredDocumentRegion flatNode = text.getFirstStructuredDocumentRegion(); if (flatNode != null) { String type = flatNode.getType(); if (isUnparsedRegion(type)) return false; } Node parent = text.getParentNode(); if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) { IDOMElement element = (IDOMElement) parent; if (!element.isGlobalTag() && !text.isElementContentWhitespace()) return false; } return canFormatChild(parent); } private boolean isUnparsedRegion(String type) { boolean result = isNestedScannedRegion(type) || isBlockScannedRegion(type); return result; } private boolean isBlockScannedRegion(String type) { return type == DOMRegionContext.BLOCK_TEXT; } /** * ISSUE: this is a bit of hidden JSP knowledge that was implemented this * way for expedency. Should be evolved in future to depend on * "nestedContext". */ private boolean isNestedScannedRegion(String type) { final String JSP_CONTENT = "JSP_CONTENT"; //$NON-NLS-1$ return type.equals(JSP_CONTENT); } /** */ private boolean canRemoveHeadingSpaces(IDOMNode node) { if (node == null) return false; if (node.getPreviousSibling() != null) return false; Node parent = node.getParentNode(); if (parent == null || parent.getNodeType() != Node.ELEMENT_NODE) return false; CMElementDeclaration decl = getElementDeclaration((Element) parent); if (decl == null || (!decl.supports(HTMLCMProperties.LINE_BREAK_HINT))) return false; String hint = (String) decl.getProperty(HTMLCMProperties.LINE_BREAK_HINT); return hint.equals(HTMLCMProperties.Values.BREAK_BEFORE_START_AND_AFTER_END); } /** */ private boolean canRemoveTailingSpaces(IDOMNode node) { if (node == null) return false; if (node.getNextSibling() != null) return false; Node parent = node.getParentNode(); if (parent == null || parent.getNodeType() != Node.ELEMENT_NODE) return false; CMElementDeclaration decl = getElementDeclaration((Element) parent); if (decl == null || (!decl.supports(HTMLCMProperties.LINE_BREAK_HINT))) return false; String hint = (String) decl.getProperty(HTMLCMProperties.LINE_BREAK_HINT); return hint.equals(HTMLCMProperties.Values.BREAK_BEFORE_START_AND_AFTER_END); } /** */ protected void formatNode(IDOMNode node, HTMLFormatContraints contraints) { formatText(node, contraints, FORMAT_ALL); // full format } /** */ protected void formatText(IDOMNode node, HTMLFormatContraints contraints, int mode) { if (node == null) return; Node parent = node.getParentNode(); if (parent == null) return; IDOMText text = (IDOMText) node; String source = text.getSource(); textRegion = text.getFirstStructuredDocumentRegion(); if (!canFormatText(text)) { setWidth(contraints, source); return; } int offset = text.getStartOffset(); int length = text.getEndOffset() - offset; // format adjacent text at once if (mode == FORMAT_HEAD) { Node next = node.getNextSibling(); while (next != null && next.getNodeType() == Node.TEXT_NODE) { IDOMText nextText = (IDOMText) next; length += (nextText.getEndOffset() - nextText.getStartOffset()); String nextSource = nextText.getSource(); if (nextSource != null && nextSource.length() > 0) { if (source == null) source = nextSource; else source += nextSource; } next = next.getNextSibling(); } } else if (mode == FORMAT_TAIL) { Node prev = node.getPreviousSibling(); while (prev != null && prev.getNodeType() == Node.TEXT_NODE) { IDOMText prevText = (IDOMText) prev; offset = prevText.getStartOffset(); length += (prevText.getEndOffset() - offset); String prevSource = prevText.getSource(); if (prevSource != null && prevSource.length() > 0) { if (source == null) source = prevSource; else source = prevSource + source; } prev = prev.getPreviousSibling(); } } SpaceConverter converter = new SpaceConverter(source, keepBlankLines(contraints)); int wordLength = getNextWord(converter, contraints, text); if (wordLength == 0) { // only spaces if (!converter.hasSpaces()) return; // empty boolean removeSpaces = false; if (parent.getNodeType() == Node.ELEMENT_NODE) { // check if tags are omitted IDOMNode element = (IDOMNode) parent; if (node.getPreviousSibling() == null && element.getStartStructuredDocumentRegion() == null) { removeSpaces = true; } else if (node.getNextSibling() == null && element.getEndStructuredDocumentRegion() == null) { removeSpaces = true; } } if (removeSpaces) { converter.replaceSpaces(null); } else if (!isWidthAvailable(contraints, 2) || canInsertBreakAfter(node) || canInsertBreakBefore(node)) { String spaces = null; if (node.getNextSibling() == null) { // last spaces // use parent indent for the end tag spaces = getBreakSpaces(parent); } else { spaces = getBreakSpaces(node); } converter.replaceSpaces(spaces); setWidth(contraints, spaces); } else if (canRemoveHeadingSpaces(node) || canRemoveTailingSpaces(node)) { converter.replaceSpaces(null); } else { converter.compressSpaces(); addWidth(contraints, 1); } } else { String breakSpaces = null; // format heading spaces boolean hasSpaces = converter.hasSpaces(); if (mode == FORMAT_TAIL) { // keep spaces as is addWidth(contraints, converter.getSpaceCount()); } else if ((hasSpaces && !isWidthAvailable(contraints, wordLength + 1)) || canInsertBreakBefore(node)) { breakSpaces = getBreakSpaces(node); converter.replaceSpaces(breakSpaces); setWidth(contraints, breakSpaces); } else { if (hasSpaces) { if (canRemoveHeadingSpaces(node)) { converter.replaceSpaces(null); } else { converter.compressSpaces(); addWidth(contraints, 1); } } } addWidth(contraints, wordLength); // format middle wordLength = getNextWord(converter, contraints, text); while (wordLength > 0) { if (mode != FORMAT_ALL) { // keep spaces as is addWidth(contraints, converter.getSpaceCount()); } else if (!isWidthAvailable(contraints, wordLength + 1)) { if (breakSpaces == null) breakSpaces = getBreakSpaces(node); converter.replaceSpaces(breakSpaces); setWidth(contraints, breakSpaces); } else { converter.compressSpaces(); addWidth(contraints, 1); } addWidth(contraints, wordLength); wordLength = getNextWord(converter, contraints, text); } // format tailing spaces hasSpaces = converter.hasSpaces(); if (mode == FORMAT_HEAD) { // keep spaces as is addWidth(contraints, converter.getSpaceCount()); } else if ((hasSpaces && !isWidthAvailable(contraints, 2)) || canInsertBreakAfter(node)) { if (node.getNextSibling() == null) { // last test // use parent indent for the end tag breakSpaces = getBreakSpaces(parent); } else { if (breakSpaces == null) breakSpaces = getBreakSpaces(node); } converter.replaceSpaces(breakSpaces); setWidth(contraints, breakSpaces); } else { if (hasSpaces) { if (canRemoveTailingSpaces(node)) { converter.replaceSpaces(null); } else { converter.compressSpaces(); addWidth(contraints, 1); } } } } if (converter.isModified()) { source = converter.getSource(); replaceSource(text.getModel(), offset, length, source); } } /** * Gets the next word in the text node, but it will skip over any words that are in a complex * regions (like EL within text nodes) * @return the next word */ private int getNextWord(SpaceConverter converter, HTMLFormatContraints contraints, IDOMText text) { int wordLength = converter.nextWord(); while (isWordInComplexRegion(converter, text)) { addWidth(contraints, wordLength + converter.getSpaceCount()); if (!converter.hasMoreWords()) { return converter.nextWord(); } wordLength = converter.nextWord(); } return wordLength; } private boolean isWordInComplexRegion(SpaceConverter converter, IDOMText text) { if (textRegion == null) { return false; } final int offset = text.getFirstStructuredDocumentRegion().getStartOffset(); while (textRegion != null && (converter.getWordOffset() + offset >= textRegion.getEnd()) && textRegion != text.getLastStructuredDocumentRegion()) { textRegion = textRegion.getNext(); } return textRegion != null && textRegion.getFirstRegion() instanceof ITextRegionContainer && ((ITextRegionContainer) textRegion.getFirstRegion()).getRegions() != null; } }