| /******************************************************************************* |
| * Copyright (c) 2004, 2008 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 java.util.Iterator; |
| |
| import org.eclipse.wst.css.core.internal.formatter.CSSSourceFormatter; |
| import org.eclipse.wst.css.core.internal.formatter.CSSSourceFormatterFactory; |
| import org.eclipse.wst.css.core.internal.provisional.adapters.IStyleDeclarationAdapter; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSModel; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode; |
| import org.eclipse.wst.html.core.internal.provisional.HTMLFormatContraints; |
| import org.eclipse.wst.sse.core.internal.provisional.INodeAdapter; |
| import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; |
| 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.format.StructuredFormatPreferencesXML; |
| import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| |
| // nakamori_TODO: check and remove CSS formatting |
| |
| public class HTMLElementFormatter extends HTMLFormatter { |
| |
| /** |
| */ |
| protected HTMLElementFormatter() { |
| super(); |
| } |
| |
| /** |
| */ |
| private void compressTailingSpaces(IStructuredDocumentRegion flatNode, ITextRegion region) { |
| int offset = region.getTextEnd(); |
| int count = region.getEnd() - offset; |
| if (count == 1) { |
| String source = flatNode.getFullText(region); |
| int start = region.getStart(); |
| if (source != null && source.charAt(offset - start) == ' ') { |
| // nothing to do |
| return; |
| } |
| } |
| replaceSource(flatNode, offset, count, " ");//$NON-NLS-1$ |
| } |
| |
| /** |
| */ |
| private void formatEndTag(IDOMElement element, HTMLFormatContraints contraints) { |
| Node lastChild = element.getLastChild(); |
| |
| if (lastChild != null && lastChild instanceof IDOMElement && lastChild.getNodeName().equals("jsp:scriptlet")) { //$NON-NLS-1$ |
| insertBreakAfter((IDOMElement) lastChild, contraints); |
| return; |
| } |
| |
| |
| IStructuredDocumentRegion endStructuredDocumentRegion = element.getEndStructuredDocumentRegion(); |
| if (endStructuredDocumentRegion == null) |
| return; |
| |
| if (element.isJSPTag() || element.isCommentTag()) { |
| String endTag = endStructuredDocumentRegion.getText(); |
| if (endTag != null && endTag.length() > 0) { |
| setWidth(contraints, endTag); |
| } |
| return; |
| } |
| |
| ITextRegion prevRegion = null; |
| ITextRegionList regions = endStructuredDocumentRegion.getRegions(); |
| Iterator e = regions.iterator(); |
| while (e.hasNext()) { |
| ITextRegion region = (ITextRegion) e.next(); |
| if (region == null) |
| continue; |
| String regionType = region.getType(); |
| if (regionType == DOMRegionContext.XML_TAG_NAME || isNestedTag(regionType)) { |
| if (prevRegion != null && prevRegion.getType() == DOMRegionContext.XML_END_TAG_OPEN) { |
| removeTailingSpaces(endStructuredDocumentRegion, prevRegion); |
| } |
| } |
| else if (regionType == DOMRegionContext.XML_TAG_CLOSE) { |
| if (prevRegion != null && (prevRegion.getType() == DOMRegionContext.XML_TAG_NAME || isNestedRootTag(prevRegion.getType()))) { |
| removeTailingSpaces(endStructuredDocumentRegion, prevRegion); |
| } |
| } |
| prevRegion = region; |
| } |
| if (prevRegion != null && (prevRegion.getType() == DOMRegionContext.XML_TAG_NAME || isNestedRootTag(prevRegion.getType()))) { |
| removeTailingSpaces(endStructuredDocumentRegion, prevRegion); |
| } |
| |
| // BUG123890 (end tag length was already prefactored into |
| // formatStartTag so no need to do it here) |
| // String newEndTag = endStructuredDocumentRegion.getText(); |
| // if (newEndTag != null && newEndTag.length() > 0) { |
| // setWidth(contraints, newEndTag); |
| // } |
| } |
| |
| /** |
| */ |
| protected void formatNode(IDOMNode node, HTMLFormatContraints contraints) { |
| if (node == null) |
| return; |
| IDOMElement element = (IDOMElement) node; |
| |
| formatStartTag(element, contraints); |
| |
| formatChildNodes(element, contraints); |
| |
| formatEndTag(element, contraints); |
| } |
| |
| /** |
| */ |
| private void formatStartTag(IDOMElement element, HTMLFormatContraints contraints) { |
| |
| if (element.getNodeName().equals("jsp:scriptlet")) { //$NON-NLS-1$ |
| insertBreakBefore(element, contraints); |
| return; |
| } |
| |
| IStructuredDocumentRegion startStructuredDocumentRegion = element.getStartStructuredDocumentRegion(); |
| if (startStructuredDocumentRegion == null) |
| return; |
| |
| // We should format attributes in JSPTag? |
| // if (element.isJSPTag() || element.isCommentTag()) { |
| if (element.isCommentTag()) { |
| String startTag = startStructuredDocumentRegion.getText(); |
| if (startTag != null && startTag.length() > 0) { |
| setWidth(contraints, startTag); |
| } |
| return; |
| } |
| |
| // first process style attribute |
| if (element.isGlobalTag()) { |
| Attr attr = element.getAttributeNode("style");//$NON-NLS-1$ |
| if (attr != null) |
| formatStyleAttr(attr); |
| } |
| boolean insertBreak = false; |
| insertBreak = ((StructuredFormatPreferencesXML) getFormatPreferences()).getSplitMultiAttrs(); |
| boolean alignEndBracket = ((StructuredFormatPreferencesXML) getFormatPreferences()).isAlignEndBracket(); |
| boolean attributesSplitted = false; |
| |
| if (insertBreak) { |
| NamedNodeMap attributes = element.getAttributes(); |
| if (attributes == null || attributes.getLength() < 2) |
| insertBreak = false; |
| } |
| String breakSpaces = getBreakSpaces(element); |
| String originalBreakSpaces = breakSpaces; |
| String indent = getIndent(); |
| if (indent != null && indent.length() > 0) { |
| breakSpaces += indent; |
| } |
| ITextRegion lastBreakRegion = null; |
| |
| ITextRegion prevRegion = null; |
| ITextRegionList regions = startStructuredDocumentRegion.getRegions(); |
| Iterator e = regions.iterator(); |
| while (e.hasNext()) { |
| ITextRegion region = (ITextRegion) e.next(); |
| if (region == null) |
| continue; |
| |
| ITextRegion breakRegion = null; |
| |
| String regionType = region.getType(); |
| if (regionType == DOMRegionContext.XML_TAG_NAME || isNestedTag(regionType)) { |
| if (prevRegion != null && prevRegion.getType() == DOMRegionContext.XML_TAG_OPEN) { |
| removeTailingSpaces(startStructuredDocumentRegion, prevRegion); |
| } |
| breakRegion = region; |
| } |
| else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) { |
| if (prevRegion != null && (prevRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME || prevRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS)) { |
| // attribute name without value |
| breakRegion = prevRegion; |
| } |
| } |
| else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) { |
| if (prevRegion != null && prevRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) { |
| removeTailingSpaces(startStructuredDocumentRegion, prevRegion); |
| } |
| } |
| else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) { |
| if (prevRegion != null && prevRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) { |
| removeTailingSpaces(startStructuredDocumentRegion, prevRegion); |
| } |
| breakRegion = region; |
| } |
| else if (regionType == DOMRegionContext.XML_TAG_CLOSE || regionType == DOMRegionContext.XML_EMPTY_TAG_CLOSE) { |
| if (prevRegion != null && (prevRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME || prevRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS)) { |
| // attribute name without value |
| breakRegion = prevRegion; |
| } |
| } |
| |
| if (breakRegion != null) { |
| int end = breakRegion.getTextEnd(); |
| if (lastBreakRegion != null) { |
| int offset = lastBreakRegion.getEnd(); |
| int count = end - offset; |
| if (insertBreak || !isWidthAvailable(contraints, count + 1)) { |
| replaceTailingSpaces(startStructuredDocumentRegion, lastBreakRegion, breakSpaces); |
| setWidth(contraints, breakSpaces); |
| attributesSplitted = true; |
| } |
| else { |
| compressTailingSpaces(startStructuredDocumentRegion, lastBreakRegion); |
| addWidth(contraints, 1); |
| } |
| addWidth(contraints, count); |
| } |
| else { |
| addWidth(contraints, end); |
| } |
| lastBreakRegion = breakRegion; |
| } |
| |
| prevRegion = region; |
| } |
| if (prevRegion != null && (prevRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME || prevRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS)) { |
| // attribute name without value |
| int end = prevRegion.getTextEnd(); |
| if (lastBreakRegion != null) { |
| int offset = lastBreakRegion.getEnd(); |
| int count = end - offset; |
| if (insertBreak || !isWidthAvailable(contraints, count + 1)) { |
| replaceTailingSpaces(startStructuredDocumentRegion, lastBreakRegion, breakSpaces); |
| setWidth(contraints, breakSpaces); |
| attributesSplitted = true; |
| } |
| else { |
| compressTailingSpaces(startStructuredDocumentRegion, lastBreakRegion); |
| addWidth(contraints, 1); |
| } |
| addWidth(contraints, count); |
| } |
| else { |
| addWidth(contraints, end); |
| } |
| lastBreakRegion = prevRegion; |
| } |
| |
| if (lastBreakRegion != null) { |
| int offset = lastBreakRegion.getTextEnd(); |
| int count = startStructuredDocumentRegion.getLength() - offset; |
| if (prevRegion != null && prevRegion.getType() == DOMRegionContext.XML_EMPTY_TAG_CLOSE) { |
| compressTailingSpaces(startStructuredDocumentRegion, lastBreakRegion); |
| count++; |
| } |
| else { |
| removeTailingSpaces(startStructuredDocumentRegion, lastBreakRegion); |
| // BUG123890 (pre-factor in end tag) |
| count += element.getTagName().length() + 3; |
| } |
| addWidth(contraints, count); |
| } |
| else { |
| addWidth(contraints, startStructuredDocumentRegion.getLength()); |
| } |
| // BUG113584 - align last bracket |
| if (alignEndBracket && attributesSplitted) { |
| removeTailingSpaces(startStructuredDocumentRegion, lastBreakRegion); |
| replaceTailingSpaces(startStructuredDocumentRegion, lastBreakRegion, originalBreakSpaces); |
| contraints.setAvailableLineWidth(getLineWidth() - originalBreakSpaces.length() - 1); |
| } |
| } |
| |
| /** |
| * 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 isNestedTag(String regionType) { |
| final String JSP_ROOT_TAG_NAME = "JSP_ROOT_TAG_NAME"; //$NON-NLS-1$ |
| final String JSP_DIRECTIVE_NAME = "JSP_DIRECTIVE_NAME"; //$NON-NLS-1$ |
| boolean result = regionType.equals(JSP_ROOT_TAG_NAME) || regionType.equals(JSP_DIRECTIVE_NAME); |
| return result; |
| } |
| |
| /** |
| * 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 isNestedRootTag(String regionType) { |
| final String JSP_ROOT_TAG_NAME = "JSP_ROOT_TAG_NAME"; //$NON-NLS-1$ |
| boolean result = regionType.equals(JSP_ROOT_TAG_NAME); |
| return result; |
| } |
| |
| |
| /** |
| */ |
| private void formatStyleAttr(Attr attr) { |
| if (attr == null) |
| return; |
| // if someone's made it a container somehow, CSS can't format it |
| if (((IDOMNode) attr).getValueRegion() instanceof ITextRegionContainer) |
| return; |
| String value = getCSSValue(attr); |
| if (value == null) |
| return; |
| String oldValue = ((IDOMNode) attr).getValueSource(); |
| if (oldValue != null && value.equals(oldValue)) |
| return; |
| attr.setValue(value); |
| } |
| |
| /** |
| */ |
| private ICSSModel getCSSModel(Attr attr) { |
| if (attr == null) |
| return null; |
| INodeNotifier notifier = (INodeNotifier) attr.getOwnerElement(); |
| if (notifier == null) |
| return null; |
| INodeAdapter adapter = notifier.getAdapterFor(IStyleDeclarationAdapter.class); |
| if (adapter == null) |
| return null; |
| if (!(adapter instanceof IStyleDeclarationAdapter)) |
| return null; |
| IStyleDeclarationAdapter styleAdapter = (IStyleDeclarationAdapter) adapter; |
| return styleAdapter.getModel(); |
| } |
| |
| /** |
| */ |
| private String getCSSValue(Attr attr) { |
| ICSSModel model = getCSSModel(attr); |
| if (model == null) |
| return null; |
| ICSSNode document = model.getDocument(); |
| if (document == null) |
| return null; |
| INodeNotifier notifier = (INodeNotifier) document; |
| CSSSourceFormatter formatter = (CSSSourceFormatter) notifier.getAdapterFor(CSSSourceFormatter.class); |
| // try another way to get formatter |
| if (formatter == null) |
| formatter = CSSSourceFormatterFactory.getInstance().getSourceFormatter(notifier); |
| if (formatter == null) |
| return null; |
| StringBuffer buffer = formatter.format(document); |
| if (buffer == null) |
| return null; |
| return buffer.toString(); |
| } |
| |
| /** |
| */ |
| private void removeTailingSpaces(IStructuredDocumentRegion flatNode, ITextRegion region) { |
| int offset = region.getTextEnd(); |
| int count = region.getEnd() - offset; |
| if (count <= 0) |
| return; |
| replaceSource(flatNode, offset, count, null); |
| } |
| |
| /** |
| */ |
| private void replaceTailingSpaces(IStructuredDocumentRegion flatNode, ITextRegion region, String spaces) { |
| int offset = region.getTextEnd(); |
| int count = region.getEnd() - offset; |
| if (count == spaces.length()) { |
| String source = flatNode.getFullText(region); |
| if (source != null && source.endsWith(spaces)) { |
| // nothing to do |
| return; |
| } |
| } |
| replaceSource(flatNode, offset, count, spaces); |
| } |
| } |