| /******************************************************************************* |
| * Copyright (c) 2001, 2004 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 |
| * Jens Lukowski/Innoopract - initial renaming/restructuring |
| * |
| *******************************************************************************/ |
| package org.eclipse.wst.xml.core.internal.document; |
| |
| |
| |
| |
| import java.util.Iterator; |
| |
| 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.contentmodel.CMAttributeDeclaration; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap; |
| import org.eclipse.wst.xml.core.internal.provisional.IXMLCharEntity; |
| import org.eclipse.wst.xml.core.internal.provisional.IXMLNamespace; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr; |
| import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.DOMException; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| |
| |
| /** |
| * AttrImpl class |
| */ |
| public class AttrImpl extends NodeImpl implements IDOMAttr { |
| private ITextRegion equalRegion = null; |
| |
| private String name = null; |
| private ITextRegion nameRegion = null; |
| private String namespaceURI = null; |
| private ElementImpl ownerElement = null; |
| private ITextRegion valueRegion = null; |
| private String valueSource = null; |
| |
| /** |
| * AttrImpl constructor |
| */ |
| protected AttrImpl() { |
| super(); |
| } |
| |
| /** |
| * AttrImpl constructor |
| * |
| * @param that |
| * AttrImpl |
| */ |
| protected AttrImpl(AttrImpl that) { |
| super(that); |
| |
| if (that != null) { |
| this.name = that.name; |
| this.valueSource = that.getValueSource(); |
| } |
| } |
| |
| /** |
| * cloneNode method |
| * |
| * @return org.w3c.dom.Node |
| */ |
| public Node cloneNode(boolean deep) { |
| AttrImpl cloned = new AttrImpl(this); |
| return cloned; |
| } |
| |
| /** |
| */ |
| protected CMAttributeDeclaration getDeclaration() { |
| ElementImpl element = (ElementImpl) getOwnerElement(); |
| if (element == null) |
| return null; |
| CMElementDeclaration elementDecl = element.getDeclaration(); |
| if (elementDecl == null) |
| return null; |
| CMNamedNodeMap attributes = elementDecl.getAttributes(); |
| if (attributes == null) |
| return null; |
| return (CMAttributeDeclaration) attributes.getNamedItem(getName()); |
| } |
| |
| /** |
| * getEndOffset method |
| * |
| * @return int |
| */ |
| public int getEndOffset() { |
| if (this.ownerElement == null) |
| return 0; |
| int offset = this.ownerElement.getStartOffset(); |
| if (this.valueRegion != null) { |
| return (offset + this.valueRegion.getEnd()); |
| } |
| if (this.equalRegion != null) { |
| return (offset + this.equalRegion.getEnd()); |
| } |
| if (this.nameRegion != null) { |
| return (offset + this.nameRegion.getEnd()); |
| } |
| return 0; |
| } |
| |
| |
| public ITextRegion getEqualRegion() { |
| return this.equalRegion; |
| } |
| |
| /** |
| */ |
| public String getLocalName() { |
| if (this.name == null) |
| return null; |
| int index = this.name.indexOf(':'); |
| if (index < 0) |
| return this.name; |
| return this.name.substring(index + 1); |
| } |
| |
| /** |
| * getName method |
| * |
| * @return java.lang.String |
| */ |
| public String getName() { |
| if (this.name == null) |
| return new String(); |
| return this.name; |
| } |
| |
| |
| public ITextRegion getNameRegion() { |
| return this.nameRegion; |
| } |
| |
| public int getNameRegionEndOffset() { |
| if (this.ownerElement == null) |
| return 0; |
| // assuming the firstStructuredDocumentRegion is the one that contains |
| // attributes |
| IStructuredDocumentRegion flatNode = this.ownerElement.getFirstStructuredDocumentRegion(); |
| if (flatNode == null) |
| return 0; |
| return flatNode.getEndOffset(this.nameRegion); |
| } |
| |
| public int getNameRegionStartOffset() { |
| if (this.ownerElement == null) |
| return 0; |
| // assuming the firstStructuredDocumentRegion is the one that contains |
| // attributes |
| IStructuredDocumentRegion flatNode = this.ownerElement.getFirstStructuredDocumentRegion(); |
| if (flatNode == null) |
| return 0; |
| return flatNode.getStartOffset(this.nameRegion); |
| } |
| |
| public String getNameRegionText() { |
| if (this.ownerElement == null) |
| return null; |
| // assuming the firstStructuredDocumentRegion is the one that contains |
| // attributes |
| IStructuredDocumentRegion flatNode = this.ownerElement.getFirstStructuredDocumentRegion(); |
| if (flatNode == null) |
| return null; |
| return flatNode.getText(this.nameRegion); |
| } |
| |
| public int getNameRegionTextEndOffset() { |
| if (this.ownerElement == null) |
| return 0; |
| // assuming the firstStructuredDocumentRegion is the one that contains |
| // attributes |
| IStructuredDocumentRegion flatNode = this.ownerElement.getFirstStructuredDocumentRegion(); |
| if (flatNode == null) |
| return 0; |
| return flatNode.getTextEndOffset(this.nameRegion); |
| } |
| |
| /** |
| */ |
| public String getNamespaceURI() { |
| String nsAttrName = null; |
| String prefix = getPrefix(); |
| if (prefix != null && prefix.length() > 0) { |
| if (prefix.equals(IXMLNamespace.XMLNS)) { |
| // fixed URI |
| return IXMLNamespace.XMLNS_URI; |
| } |
| nsAttrName = IXMLNamespace.XMLNS_PREFIX + prefix; |
| } |
| else { |
| String name = getName(); |
| if (name != null && name.equals(IXMLNamespace.XMLNS)) { |
| // fixed URI |
| return IXMLNamespace.XMLNS_URI; |
| } |
| // does not inherit namespace from owner element |
| // if (this.ownerElement != null) return |
| // this.ownerElement.getNamespaceURI(); |
| return this.namespaceURI; |
| } |
| |
| for (Node node = this.ownerElement; node != null; node = node.getParentNode()) { |
| if (node.getNodeType() != ELEMENT_NODE) |
| break; |
| Element element = (Element) node; |
| Attr attr = element.getAttributeNode(nsAttrName); |
| if (attr != null) |
| return attr.getValue(); |
| } |
| |
| return this.namespaceURI; |
| } |
| |
| /** |
| * getNodeName method |
| * |
| * @return java.lang.String |
| */ |
| public String getNodeName() { |
| return getName(); |
| } |
| |
| /** |
| * getNodeType method |
| * |
| * @return short |
| */ |
| public short getNodeType() { |
| return ATTRIBUTE_NODE; |
| } |
| |
| /** |
| * getNodeValue method |
| * |
| * @return java.lang.String |
| */ |
| public String getNodeValue() { |
| return getValue(); |
| } |
| |
| /** |
| * getOwnerElement method |
| * |
| * @return org.w3c.dom.Element |
| */ |
| public Element getOwnerElement() { |
| return this.ownerElement; |
| } |
| |
| /** |
| */ |
| public String getPrefix() { |
| if (this.name == null) |
| return null; |
| int index = this.name.indexOf(':'); |
| if (index <= 0) |
| return null; |
| // exclude JSP tag in name |
| if (this.name.charAt(0) == '<') |
| return null; |
| return this.name.substring(0, index); |
| } |
| |
| /** |
| * getSpecified method |
| * |
| * @return boolean |
| */ |
| public boolean getSpecified() { |
| return true; |
| } |
| |
| /** |
| * getStartOffset method |
| * |
| * @return int |
| */ |
| public int getStartOffset() { |
| if (this.ownerElement == null) |
| return 0; |
| int offset = this.ownerElement.getStartOffset(); |
| if (this.nameRegion != null) { |
| return (offset + this.nameRegion.getStart()); |
| } |
| if (this.equalRegion != null) { |
| return (offset + this.equalRegion.getStart()); |
| } |
| if (this.valueRegion != null) { |
| return (offset + this.valueRegion.getStart()); |
| } |
| return 0; |
| } |
| |
| /** |
| * getValue method |
| * |
| * @return java.lang.String |
| */ |
| public String getValue() { |
| return getValue(getValueSource()); |
| } |
| |
| /** |
| * Returns value for the source |
| */ |
| private String getValue(String source) { |
| if (source == null) |
| return new String(); |
| if (source.length() == 0) |
| return source; |
| StringBuffer buffer = null; |
| int offset = 0; |
| int length = source.length(); |
| int ref = source.indexOf('&'); |
| while (ref >= 0) { |
| int end = source.indexOf(';', ref + 1); |
| if (end > ref + 1) { |
| String name = source.substring(ref + 1, end); |
| String value = getCharValue(name); |
| if (value != null) { |
| if (buffer == null) |
| buffer = new StringBuffer(length); |
| if (ref > offset) |
| buffer.append(source.substring(offset, ref)); |
| buffer.append(value); |
| offset = end + 1; |
| ref = end; |
| } |
| } |
| ref = source.indexOf('&', ref + 1); |
| } |
| if (buffer == null) |
| return source; |
| if (length > offset) |
| buffer.append(source.substring(offset)); |
| return buffer.toString(); |
| } |
| |
| public ITextRegion getValueRegion() { |
| return this.valueRegion; |
| } |
| |
| public int getValueRegionStartOffset() { |
| if (this.ownerElement == null) |
| return 0; |
| // assuming the firstStructuredDocumentRegion is the one that contains |
| // attributes |
| IStructuredDocumentRegion flatNode = this.ownerElement.getFirstStructuredDocumentRegion(); |
| if (flatNode == null) |
| return 0; |
| return flatNode.getStartOffset(this.valueRegion); |
| } |
| |
| public String getValueRegionText() { |
| if (this.ownerElement == null) |
| return null; |
| // assuming the firstStructuredDocumentRegion is the one that contains |
| // attributes |
| IStructuredDocumentRegion flatNode = this.ownerElement.getFirstStructuredDocumentRegion(); |
| if (flatNode == null) |
| return null; |
| if (this.valueRegion == null) |
| return null; |
| return flatNode.getText(this.valueRegion); |
| } |
| |
| /** |
| */ |
| public String getValueSource() { |
| if (this.valueSource != null) |
| return this.valueSource; |
| // DW: 4/16/2003 due to change in structuredDocument ... we need a |
| // flatnode to |
| // get at region values. For now I'll assume this is always the first |
| // flatnode .. may need to make smarter later (e.g. to search for |
| // the flatnode that this.valueRegion belongs to. |
| // DW: 4/30/2003 For some reason, this method is getting called a lot |
| // Not sure if its a threading problem, or a fundamental error |
| // elsewhere. |
| // It needs more investigation, but in the use cases I've seen, |
| // doesn't |
| // seem to hurt to simply return null in those cases. I saw this null |
| // case, |
| // when tryint go format an XML file. |
| if (this.ownerElement == null) |
| return null; |
| IStructuredDocumentRegion ownerRegion = this.ownerElement.getFirstStructuredDocumentRegion(); |
| if (ownerRegion == null) |
| return null; |
| if (this.valueRegion != null) |
| return StructuredDocumentRegionUtil.getAttrValue(ownerRegion, this.valueRegion); |
| return new String(); |
| } |
| |
| private String getValueSource(ElementImpl ownerElement) { |
| if (this.valueSource != null) |
| return this.valueSource; |
| // DW: 4/16/2003 due to change in structuredDocument ... we need a |
| // flatnode to |
| // get at region values. For now I'll assume this is always the first |
| // flatnode .. may need to make smarter later (e.g. to search for |
| // the flatnode that this.valueRegion belongs to. |
| if (this.valueRegion != null) |
| return StructuredDocumentRegionUtil.getAttrValue(ownerElement.getStructuredDocumentRegion(), this.valueRegion); |
| return new String(); |
| } |
| |
| /** |
| */ |
| private String getValueSource(String value) { |
| if (value == null) |
| return null; |
| if (value.length() == 0) |
| return value; |
| StringBuffer buffer = null; |
| int offset = 0; |
| int length = value.length(); |
| int amp = value.indexOf('&'); |
| while (amp >= 0) { |
| if (buffer == null) |
| buffer = new StringBuffer(length + 4); |
| if (amp > offset) |
| buffer.append(value.substring(offset, amp)); |
| buffer.append(IXMLCharEntity.AMP_REF); |
| offset = amp + 1; |
| amp = value.indexOf('&', offset); |
| } |
| if (buffer == null) |
| return value; |
| if (length > offset) |
| buffer.append(value.substring(offset)); |
| return buffer.toString(); |
| } |
| |
| /** |
| * Check if Attr has JSP in value |
| */ |
| public boolean hasNestedValue() { |
| if (this.valueRegion == null) |
| return false; |
| if (!(this.valueRegion instanceof ITextRegionContainer)) |
| return false; |
| ITextRegionList regions = ((ITextRegionContainer) this.valueRegion).getRegions(); |
| if (regions == null) |
| return false; |
| 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_OPEN || isNestedLanguageOpening(regionType)) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Check if Attr has only name but not equal sign nor value |
| */ |
| public boolean hasNameOnly() { |
| return (this.nameRegion != null && this.equalRegion == null && this.valueRegion == null); |
| } |
| |
| /** |
| */ |
| protected final boolean hasPrefix() { |
| if (this.name == null) |
| return false; |
| if (this.name.indexOf(':') <= 0) |
| return false; |
| // exclude JSP tag in name |
| if (this.name.charAt(0) == '<') |
| return false; |
| return true; |
| } |
| |
| /** |
| */ |
| protected final boolean ignoreCase() { |
| if (this.ownerElement != null) { |
| if (this.ownerElement.ignoreCase()) { |
| return !hasPrefix(); |
| } |
| } |
| else { |
| DocumentImpl document = (DocumentImpl) getOwnerDocument(); |
| if (document != null && document.ignoreCase()) { |
| // even in case insensitive document, if having prefix, it's |
| // case sensitive |
| return !hasPrefix(); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| */ |
| public boolean isGlobalAttr() { |
| if (hasPrefix()) |
| return false; |
| if (this.ownerElement == null) |
| return false; |
| return this.ownerElement.isGlobalTag(); |
| } |
| |
| /** |
| */ |
| public final boolean isXMLAttr() { |
| if (this.ownerElement != null) { |
| if (!this.ownerElement.isXMLTag()) { |
| return hasPrefix(); |
| } |
| } |
| else { |
| DocumentImpl document = (DocumentImpl) getOwnerDocument(); |
| if (document != null && !document.isXMLType()) { |
| // even in non-XML document, if having prefix, it's XML tag |
| return hasPrefix(); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * matchName method |
| * |
| * @return boolean |
| * @param name |
| * java.lang.String |
| */ |
| protected boolean matchName(String name) { |
| if (name == null) |
| return (this.name == null); |
| if (this.name == null) |
| return false; |
| if (!ignoreCase()) |
| return this.name.equals(name); |
| return this.name.equalsIgnoreCase(name); |
| } |
| |
| /** |
| * notifyValueChanged method |
| */ |
| protected void notifyNameChanged() { |
| if (this.ownerElement == null) |
| return; |
| DocumentImpl document = (DocumentImpl) this.ownerElement.getContainerDocument(); |
| if (document == null) |
| return; |
| DOMModelImpl model = (DOMModelImpl) document.getModel(); |
| if (model == null) |
| return; |
| model.nameChanged(this); |
| } |
| |
| /** |
| * notifyValueChanged method |
| */ |
| protected void notifyValueChanged() { |
| if (this.ownerElement == null) |
| return; |
| DocumentImpl document = (DocumentImpl) this.ownerElement.getContainerDocument(); |
| if (document == null) |
| return; |
| DOMModelImpl model = (DOMModelImpl) document.getModel(); |
| if (model == null) |
| return; |
| model.valueChanged(this); |
| } |
| |
| /** |
| * removeRegions method |
| */ |
| void removeRegions() { |
| this.nameRegion = null; |
| this.valueRegion = null; |
| this.equalRegion = null; |
| } |
| |
| /** |
| */ |
| void resetRegions() { |
| this.valueSource = getValueSource(); |
| removeRegions(); |
| } |
| |
| /** |
| */ |
| void resetRegions(ElementImpl ownerElement) { |
| this.valueSource = getValueSource(ownerElement); |
| removeRegions(); |
| } |
| |
| void setEqualRegion(ITextRegion equalRegion) { |
| this.equalRegion = equalRegion; |
| } |
| |
| /** |
| * setName method |
| * |
| * @param name |
| * java.lang.String |
| */ |
| protected void setName(String name) { |
| String value = null; |
| int startOffset = 0; |
| if (this.ownerElement != null) { |
| value = getValue(); |
| startOffset = this.ownerElement.getStartOffset(); |
| this.ownerElement.notify(CHANGE, this, value, null, startOffset); |
| } |
| this.name = name; |
| if (this.ownerElement != null) { |
| this.ownerElement.notify(CHANGE, this, null, value, startOffset); |
| } |
| } |
| |
| void setNameRegion(ITextRegion nameRegion) { |
| this.nameRegion = nameRegion; |
| } |
| |
| protected void setNamespaceURI(String namespaceURI) { |
| this.namespaceURI = namespaceURI; |
| } |
| |
| /** |
| * setNodeValue method |
| * |
| * @param nodeValue |
| * java.lang.String |
| */ |
| public void setNodeValue(String nodeValue) throws DOMException { |
| setValue(nodeValue); |
| } |
| |
| /** |
| * setOwnerElement method |
| * |
| * @param ownerElement |
| * org.w3c.dom.Element |
| */ |
| protected void setOwnerElement(Element ownerElement) { |
| this.ownerElement = (ElementImpl) ownerElement; |
| } |
| |
| /** |
| */ |
| public void setPrefix(String prefix) throws DOMException { |
| if (this.ownerElement != null && !this.ownerElement.isDataEditable()) { |
| throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, new String()); |
| } |
| int prefixLength = (prefix != null ? prefix.length() : 0); |
| String localName = getLocalName(); |
| if (prefixLength == 0) { |
| setName(localName); |
| return; |
| } |
| if (localName == null) |
| localName = new String(); |
| int localLength = localName.length(); |
| StringBuffer buffer = new StringBuffer(prefixLength + 1 + localLength); |
| buffer.append(prefix); |
| buffer.append(':'); |
| buffer.append(localName); |
| setName(buffer.toString()); |
| |
| notifyNameChanged(); |
| } |
| |
| /** |
| * setValue method |
| * |
| * @param value |
| * java.lang.String |
| */ |
| public void setValue(String value) { |
| // Remember: as we account for "floaters" in |
| // future, remember that some are created |
| // in the natural process of implementing |
| // DOM apis. |
| // this "self notification" of about/changed, |
| // is added for this case, because it known to |
| // be called from properties pages. Should be a |
| // added to all DOM Modifiying APIs eventually. |
| try { |
| getModel().aboutToChangeModel(); |
| setValueSource(getValueSource(value)); |
| } |
| finally { |
| getModel().changedModel(); |
| } |
| } |
| |
| void setValueRegion(ITextRegion valueRegion) { |
| this.valueRegion = valueRegion; |
| if (valueRegion != null) |
| this.valueSource = null; |
| } |
| |
| public void setValueSource(String source) { |
| if (this.ownerElement != null && !this.ownerElement.isDataEditable()) { |
| throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, new String()); |
| } |
| this.valueSource = source; |
| |
| notifyValueChanged(); |
| } |
| |
| /** |
| * Subclasses must override |
| * @param regionType |
| * @return |
| */ |
| protected boolean isNestedLanguageOpening(String regionType) { |
| boolean result = false; |
| return result; |
| } |
| } |