| /******************************************************************************* |
| * Copyright (c) 2004, 2010 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.document; |
| |
| |
| |
| import org.eclipse.wst.html.core.internal.contentmodel.HTMLElementDeclaration; |
| import org.eclipse.wst.html.core.internal.contentmodel.HTMLPropertyDeclaration; |
| import org.eclipse.wst.html.core.internal.provisional.HTML40Namespace; |
| import org.eclipse.wst.html.core.internal.provisional.HTMLCMProperties; |
| import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMContent; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMGroup; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMNode; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMNodeList; |
| import org.eclipse.wst.xml.core.internal.document.CMNodeUtil; |
| import org.eclipse.wst.xml.core.internal.document.ModelParserAdapter; |
| import org.eclipse.wst.xml.core.internal.document.ModelParserAdapterExtension; |
| import org.eclipse.wst.xml.core.internal.document.TagAdapter; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMText; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| |
| /** |
| * HTMLDocumentImpl class |
| */ |
| public class HTMLModelParserAdapter implements ModelParserAdapter, ModelParserAdapterExtension { |
| /** |
| * note: I made public, temparily, so could be used by JSPLoader |
| */ |
| protected HTMLModelParserAdapter() { |
| super(); |
| } |
| |
| private boolean shouldTerminateAt(CMElementDeclaration parent, CMElementDeclaration child) { |
| if (!parent.supports(HTMLCMProperties.TERMINATORS)) |
| return false; |
| java.util.Iterator i = (java.util.Iterator) parent.getProperty(HTMLCMProperties.TERMINATORS); |
| if (i == null) |
| return false; |
| String nextName = child.getElementName(); |
| while (i.hasNext()) { |
| // NOTE: CMElementDeclaration of child is not always HTMLCMElementDeclaration. |
| // It might be one of based on DTD (for XHTML element). So, comparison must |
| // be performed ignoring case. |
| // -- 3/20/2002 |
| String terminator = (String) i.next(); |
| if (terminator == null) |
| continue; |
| if (nextName.equalsIgnoreCase(terminator)) |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean isEndTagOmissible(Element element) { |
| CMElementDeclaration dec = CMNodeUtil.getElementDeclaration(element); |
| if (dec == null || !(dec instanceof HTMLPropertyDeclaration)) |
| return false; |
| int type = ((HTMLPropertyDeclaration)dec ).getOmitType(); |
| return type == HTMLElementDeclaration.OMIT_BOTH || type == HTMLElementDeclaration.OMIT_END || type == HTMLElementDeclaration.OMIT_END_DEFAULT || type == HTMLElementDeclaration.OMIT_END_MUST; |
| } |
| |
| /** |
| */ |
| public boolean canContain(Element element, Node child) { |
| if (element == null || child == null) |
| return false; |
| IDOMElement impl = (IDOMElement) element; |
| |
| if (child.getNodeType() == Node.ELEMENT_NODE) { |
| if (!impl.isGlobalTag()) |
| return true; // non HTML tag |
| IDOMElement childElement = (IDOMElement) child; |
| |
| CMElementDeclaration myDec = CMNodeUtil.getElementDeclaration(element); |
| if (myDec == null) |
| return true; |
| //if (!(myDec instanceof HTMLElementDeclaration)) return true; |
| if (myDec.getContentType() == CMElementDeclaration.EMPTY) |
| return false; |
| |
| if (!childElement.isGlobalTag()) |
| return true; // non HTML tag |
| CMElementDeclaration childDec = CMNodeUtil.getElementDeclaration(childElement); |
| if (childDec == null) |
| return true; |
| //if (!(childDec instanceof HTMLElementDeclaration)) return true; |
| |
| if (myDec instanceof HTMLElementDeclaration) { |
| if (((Boolean) ((HTMLElementDeclaration) myDec).getProperty(HTMLCMProperties.IS_JSP)).booleanValue()) |
| return true; |
| } |
| if (shouldTerminateAt(myDec, childDec) && !isValidChild(myDec, childDec)) { |
| return false; |
| } |
| |
| String tagName = impl.getTagName(); |
| if (tagName == null) |
| return true; |
| String childName = childElement.getTagName(); |
| if (childName == null) |
| return true; |
| if (!impl.hasStartTag() && !impl.hasEndTag()) { |
| // implicit element |
| if (tagName.equalsIgnoreCase(childElement.getTagName())) |
| return false; |
| if (tagName.equalsIgnoreCase(HTML40Namespace.ElementName.HEAD)) { |
| if (!childName.equalsIgnoreCase(HTML40Namespace.ElementName.META) && !childName.equalsIgnoreCase(HTML40Namespace.ElementName.TITLE) && !childName.equalsIgnoreCase(HTML40Namespace.ElementName.LINK) && !childName.equalsIgnoreCase(HTML40Namespace.ElementName.STYLE) && !childName.equalsIgnoreCase(HTML40Namespace.ElementName.BASE) && !childName.equalsIgnoreCase(HTML40Namespace.ElementName.ISINDEX)) { |
| return false; |
| } |
| } |
| |
| Node parent = element.getParentNode(); |
| if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) { |
| IDOMElement parentElement = (IDOMElement) parent; |
| if (!parentElement.hasStartTag() && !parentElement.hasEndTag()) { |
| if (!canContain(parentElement, child)) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // contexual termination for TABLE content tags |
| boolean isTableContent = false; |
| if (childName.equalsIgnoreCase(HTML40Namespace.ElementName.TBODY) || childName.equalsIgnoreCase(HTML40Namespace.ElementName.THEAD) || childName.equalsIgnoreCase(HTML40Namespace.ElementName.TFOOT)) { |
| if (tagName.equalsIgnoreCase(HTML40Namespace.ElementName.TABLE)) |
| return true; |
| isTableContent = true; |
| } |
| else if (childName.equalsIgnoreCase(HTML40Namespace.ElementName.TR)) { |
| if (tagName.equalsIgnoreCase(HTML40Namespace.ElementName.TBODY) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.THEAD) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.TFOOT) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.TABLE)) |
| return true; |
| isTableContent = true; |
| } |
| else if (childName.equalsIgnoreCase(HTML40Namespace.ElementName.TD) || childName.equalsIgnoreCase(HTML40Namespace.ElementName.TH)) { |
| if (tagName.equalsIgnoreCase(HTML40Namespace.ElementName.TR) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.TBODY) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.THEAD) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.TFOOT) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.TABLE)) |
| return true; |
| isTableContent = true; |
| } |
| if (isTableContent) { |
| // TABLE content tags should terminate non TABLE content tags, |
| // if in TABLE |
| for (Node parent = element.getParentNode(); parent != null; parent = parent.getParentNode()) { |
| if (parent.getNodeType() != Node.ELEMENT_NODE) |
| break; |
| IDOMElement parentElement = (IDOMElement) parent; |
| String parentName = parentElement.getTagName(); |
| if (parentName == null) |
| continue; |
| if (parentName.equalsIgnoreCase(HTML40Namespace.ElementName.TABLE)) |
| return false; |
| } |
| } |
| if (tagName.equalsIgnoreCase(HTML40Namespace.ElementName.EMBED)) { |
| if (!childName.equalsIgnoreCase(HTML40Namespace.ElementName.NOEMBED)) |
| return false; |
| } |
| } |
| else if (child.getNodeType() == Node.TEXT_NODE) { |
| String tagName = impl.getTagName(); |
| if (tagName != null && tagName.equalsIgnoreCase(HTML40Namespace.ElementName.EMBED)) { |
| IDOMText text = (IDOMText) child; |
| if (!text.isElementContentWhitespace()) |
| return false; |
| } |
| } |
| else if (child.getNodeType() == Node.DOCUMENT_TYPE_NODE) { |
| if (impl.isImplicitTag()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| */ |
| public boolean canBeImplicitTag(Element element) { |
| return false; |
| } |
| |
| /** |
| */ |
| public boolean canBeImplicitTag(Element element, Node child) { |
| return false; |
| } |
| |
| /** |
| */ |
| public Element createCommentElement(Document document, String data, boolean isJSPTag) { |
| if (document == null || data == null || data.length() == 0) |
| return null; |
| |
| return createMetaElement(document, data, isJSPTag); |
| } |
| |
| /** |
| * This routine create an implicit Element for given parent and child, |
| * such as HTML, BODY, HEAD, and TBODY for HTML document. |
| */ |
| public Element createImplicitElement(Document document, Node parent, Node child) { |
| return null; |
| } |
| |
| /** |
| */ |
| private Element createMetaElement(Document document, String data, boolean isJSPTag) { |
| if (data == null || data.length() == 0) |
| return null; |
| |
| TagScanner scanner = new TagScanner(data, 0, true); // one line |
| String name = scanner.nextName(); |
| if (name == null || !name.equalsIgnoreCase(MetaData.METADATA)) |
| return null; |
| |
| String type = null; |
| boolean isStartSpan = false; |
| boolean isEndSpan = false; |
| name = scanner.nextName(); |
| while (name != null) { |
| String value = scanner.nextValue(); |
| if (name.equalsIgnoreCase(MetaData.TYPE)) { |
| if (value == null) |
| return null; |
| if (value.equalsIgnoreCase(MetaData.DESIGNER_CONTROL)) { |
| type = MetaData.DESIGNER_CONTROL; |
| } |
| else if (value.equalsIgnoreCase(MetaData.DYNAMIC_DATA)) { |
| type = MetaData.DYNAMIC_DATA; |
| } |
| else if (value.equalsIgnoreCase(MetaData.AUTHOR_TIME_VISUAL)) { |
| type = MetaData.AUTHOR_TIME_VISUAL; |
| } |
| else if (value.equalsIgnoreCase(MetaData.ANNOTATION)) { |
| type = MetaData.ANNOTATION; |
| } |
| else { |
| return null; |
| } |
| } |
| else if (name.equalsIgnoreCase(MetaData.STARTSPAN)) { |
| isStartSpan = true; |
| } |
| else if (name.equalsIgnoreCase(MetaData.ENDSPAN)) { |
| if (!isStartSpan) |
| isEndSpan = true; |
| } |
| name = scanner.nextName(); |
| } |
| if (type == null) |
| return null; |
| if (!isStartSpan && !isEndSpan) |
| return null; |
| String metaData = null; |
| int offset = scanner.getNextOffset(); // skip new line |
| if (offset < data.length()) |
| metaData = data.substring(offset); |
| if (metaData == null) |
| metaData = new String(); |
| |
| IDOMElement element = (IDOMElement) document.createElement(MetaData.PREFIX + type); |
| |
| MetaDataAdapter adapter = new MetaDataAdapter(type); |
| if (isStartSpan) { |
| if (metaData != null) |
| adapter.setData(metaData); |
| } |
| else { |
| if (metaData != null) |
| adapter.setEndData(metaData); |
| } |
| element.addAdapter(adapter); |
| adapter.setElement(element); |
| element.setJSPTag(isJSPTag); |
| |
| return element; |
| } |
| |
| /** |
| */ |
| public String getFindRootName(String tagName) { |
| if (tagName == null) |
| return null; |
| // tag matching should not beyond TABLE tag except BODY tag |
| if (tagName.equalsIgnoreCase(HTML40Namespace.ElementName.BODY)) |
| return null; |
| return HTML40Namespace.ElementName.TABLE; |
| } |
| |
| /** |
| */ |
| public boolean isAdapterForType(Object type) { |
| return (type == ModelParserAdapter.class); |
| } |
| |
| /** |
| */ |
| public boolean isEndTag(IDOMElement element) { |
| TagAdapter adapter = (TagAdapter) element.getExistingAdapter(TagAdapter.class); |
| if (adapter != null) |
| return adapter.isEndTag(); |
| return element.isEndTag(); |
| } |
| |
| /** |
| */ |
| public void notifyChanged(INodeNotifier notifier, int eventType, Object changedFeature, Object oldValue, Object newValue, int pos) { |
| // do nothing on notifiy change |
| // TODO: this means good candidate for regular platform adapter |
| } |
| |
| private static boolean isValidChild(CMElementDeclaration parent, CMElementDeclaration child) { |
| if (parent == null || child == null) |
| return false; |
| CMContent content = parent.getContent(); |
| if (content == null) |
| return false; |
| return isChild(content, child); |
| } |
| |
| /** |
| */ |
| private static boolean isChild(CMContent content, CMElementDeclaration target) { |
| switch (content.getNodeType()) { |
| case CMNode.ELEMENT_DECLARATION : |
| return isSameDeclaration((CMElementDeclaration) content, target); |
| case CMNode.GROUP : |
| CMNodeList children = ((CMGroup) content).getChildNodes(); |
| for (int i = 0; i < children.getLength(); i++) { |
| CMNode child = children.item(i); |
| switch (child.getNodeType()) { |
| case CMNode.ELEMENT_DECLARATION : |
| if (isSameDeclaration((CMElementDeclaration) child, target)) |
| return true; |
| continue; // Go next child. |
| case CMNode.GROUP : |
| if (isChild((CMContent) child, target)) |
| return true; |
| continue; // Go next child. |
| default : |
| continue; // Go next child. |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| */ |
| private static boolean isSameDeclaration(CMElementDeclaration aDec, CMElementDeclaration otherDec) { |
| return aDec.getElementName() == otherDec.getElementName(); |
| } |
| |
| } |