blob: 06d25f80d6074cb322523dbbd1443088f7176c0c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2005 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.Enumeration;
import java.util.Iterator;
import java.util.Vector;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegionList;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.xml.core.internal.commentelement.impl.CommentElementConfiguration;
import org.eclipse.wst.xml.core.internal.commentelement.impl.CommentElementRegistry;
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.regions.DOMRegionContext;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
/**
* XMLModelParser
*/
public class XMLModelParser {
private XMLModelContext context = null;
private DOMModelImpl model = null;
/**
*/
protected XMLModelParser(DOMModelImpl model) {
super();
if (model != null) {
this.model = model;
}
}
/**
*/
protected boolean canBeImplicitTag(Element element) {
ModelParserAdapter adapter = getParserAdapter();
if (adapter != null) {
return adapter.canBeImplicitTag(element);
}
return false;
}
/**
*/
protected boolean canBeImplicitTag(Element element, Node child) {
ModelParserAdapter adapter = getParserAdapter();
if (adapter != null) {
return adapter.canBeImplicitTag(element, child);
}
return false;
}
/**
*/
protected boolean canContain(Element element, Node child) {
if (element == null || child == null)
return false;
ElementImpl impl = (ElementImpl) element;
if (impl.isEndTag())
return false; // invalid (floating) end tag
if (!impl.isContainer())
return false;
if (child.getNodeType() != Node.TEXT_NODE) {
if (impl.isJSPContainer() || impl.isCDATAContainer()) {
// accepts only Text child
return false;
}
}
ModelParserAdapter adapter = getParserAdapter();
if (adapter != null) {
return adapter.canContain(element, child);
}
return true;
}
/**
*/
private void changeAttrEqual(IStructuredDocumentRegion flatNode, ITextRegion region) {
int offset = flatNode.getStart();
if (offset < 0)
return;
NodeImpl root = (NodeImpl) this.context.getRootNode();
if (root == null)
return;
Node node = root.getNodeAt(offset);
if (node == null)
return;
if (node.getNodeType() != Node.ELEMENT_NODE) {
if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
// just notify the change instead of setting data
ProcessingInstructionImpl pi = (ProcessingInstructionImpl) node;
pi.notifyValueChanged();
}
return;
}
// actually, do nothing
}
/**
* changeAttrName method
*
*/
private void changeAttrName(IStructuredDocumentRegion flatNode, ITextRegion region) {
int offset = flatNode.getStart();
if (offset < 0)
return;
NodeImpl root = (NodeImpl) this.context.getRootNode();
if (root == null)
return;
Node node = root.getNodeAt(offset);
if (node == null)
return;
if (node.getNodeType() != Node.ELEMENT_NODE) {
if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
// just notify the change instead of setting data
ProcessingInstructionImpl pi = (ProcessingInstructionImpl) node;
pi.notifyValueChanged();
}
return;
}
ElementImpl element = (ElementImpl) node;
NamedNodeMap attributes = element.getAttributes();
if (attributes == null)
return;
int length = attributes.getLength();
for (int i = 0; i < length; i++) {
AttrImpl attr = (AttrImpl) attributes.item(i);
if (attr == null)
continue;
if (attr.getNameRegion() != region)
continue;
String name = flatNode.getText(region);
attr.setName(name);
break;
}
}
/**
* changeAttrValue method
*
*/
private void changeAttrValue(IStructuredDocumentRegion flatNode, ITextRegion region) {
int offset = flatNode.getStart();
if (offset < 0)
return;
NodeImpl root = (NodeImpl) this.context.getRootNode();
if (root == null)
return;
Node node = root.getNodeAt(offset);
if (node == null)
return;
if (node.getNodeType() != Node.ELEMENT_NODE) {
if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
// just notify the change instead of setting data
ProcessingInstructionImpl pi = (ProcessingInstructionImpl) node;
pi.notifyValueChanged();
}
return;
}
ElementImpl element = (ElementImpl) node;
NamedNodeMap attributes = element.getAttributes();
if (attributes == null)
return;
int length = attributes.getLength();
for (int i = 0; i < length; i++) {
AttrImpl attr = (AttrImpl) attributes.item(i);
if (attr == null)
continue;
if (attr.getValueRegion() != region)
continue;
// just notify the change instead of setting value
attr.notifyValueChanged();
break;
}
}
/**
* changeData method
*
*/
private void changeData(IStructuredDocumentRegion flatNode, ITextRegion region) {
int offset = flatNode.getStart();
if (offset < 0)
return;
NodeImpl root = (NodeImpl) this.context.getRootNode();
if (root == null)
return;
Node node = root.getNodeAt(offset);
if (node == null)
return;
switch (node.getNodeType()) {
case Node.TEXT_NODE : {
TextImpl text = (TextImpl) node;
if (text.isSharingStructuredDocumentRegion(flatNode)) {
// has consecutive text sharing IStructuredDocumentRegion
changeStructuredDocumentRegion(flatNode);
return;
}
this.context.setNextNode(node);
cleanupText();
break;
}
case Node.CDATA_SECTION_NODE :
case Node.PROCESSING_INSTRUCTION_NODE :
break;
case Node.COMMENT_NODE :
case Node.ELEMENT_NODE :
// comment tag
changeStructuredDocumentRegion(flatNode);
return;
default :
return;
}
// just notify the change instead of setting data
NodeImpl impl = (NodeImpl) node;
impl.notifyValueChanged();
}
/**
*/
private void changeEndTag(IStructuredDocumentRegion flatNode, ITextRegionList newRegions, ITextRegionList oldRegions) {
int offset = flatNode.getStart();
if (offset < 0)
return; // error
NodeImpl root = (NodeImpl) this.context.getRootNode();
if (root == null)
return; // error
Node node = root.getNodeAt(offset);
if (node == null)
return; // error
if (node.getNodeType() != Node.ELEMENT_NODE) {
changeStructuredDocumentRegion(flatNode);
return;
}
// check if change is only for close tag
if (newRegions != null) {
Iterator e = newRegions.iterator();
while (e.hasNext()) {
ITextRegion region = (ITextRegion) e.next();
String regionType = region.getType();
if (regionType == DOMRegionContext.XML_TAG_CLOSE)
continue;
// other region has changed
changeStructuredDocumentRegion(flatNode);
return;
}
}
if (oldRegions != null) {
Iterator e = oldRegions.iterator();
while (e.hasNext()) {
ITextRegion region = (ITextRegion) e.next();
String regionType = region.getType();
if (regionType == DOMRegionContext.XML_TAG_CLOSE)
continue;
// other region has changed
changeStructuredDocumentRegion(flatNode);
return;
}
}
// change for close tag has no impact
// do nothing
}
/**
* changeRegion method
*
*/
void changeRegion(IStructuredDocumentRegion flatNode, ITextRegion region) {
if (flatNode == null || region == null)
return;
if (this.model.getDocument() == null)
return;
this.context = new XMLModelContext(this.model.getDocument());
// optimize typical cases
String regionType = region.getType();
if (regionType == DOMRegionContext.XML_CONTENT || regionType == DOMRegionContext.XML_COMMENT_TEXT || regionType == DOMRegionContext.XML_CDATA_TEXT || regionType == DOMRegionContext.BLOCK_TEXT || isNestedContent(regionType)) {
changeData(flatNode, region);
}
else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
changeAttrName(flatNode, region);
}
else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) {
changeAttrValue(flatNode, region);
}
else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) {
changeAttrEqual(flatNode, region);
}
else if (regionType == DOMRegionContext.XML_TAG_NAME || isNestedTagName(regionType)) {
changeTagName(flatNode, region);
}
else {
changeStructuredDocumentRegion(flatNode);
}
}
/**
*/
private void changeStartTag(IStructuredDocumentRegion flatNode, ITextRegionList newRegions, ITextRegionList oldRegions) {
int offset = flatNode.getStart();
if (offset < 0)
return; // error
NodeImpl root = (NodeImpl) this.context.getRootNode();
if (root == null)
return; // error
Node node = root.getNodeAt(offset);
if (node == null)
return; // error
if (node.getNodeType() != Node.ELEMENT_NODE) {
changeStructuredDocumentRegion(flatNode);
return;
}
ElementImpl element = (ElementImpl) node;
// check if changes are only for attributes and close tag
boolean tagNameUnchanged = false;
if (newRegions != null) {
Iterator e = newRegions.iterator();
while (e.hasNext()) {
ITextRegion region = (ITextRegion) e.next();
String regionType = region.getType();
if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME || regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS || regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE)
continue;
if (regionType == DOMRegionContext.XML_TAG_CLOSE) {
// change from empty tag may have impact on structure
if (!element.isEmptyTag())
continue;
}
else if (regionType == DOMRegionContext.XML_TAG_NAME || isNestedTagName(regionType)) {
String oldTagName = element.getTagName();
String newTagName = flatNode.getText(region);
if (oldTagName != null && newTagName != null && oldTagName.equals(newTagName)) {
// the tag name is unchanged
tagNameUnchanged = true;
continue;
}
}
// other region has changed
changeStructuredDocumentRegion(flatNode);
return;
}
}
if (oldRegions != null) {
Iterator e = oldRegions.iterator();
while (e.hasNext()) {
ITextRegion region = (ITextRegion) e.next();
String regionType = region.getType();
if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME || regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS || regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE)
continue;
if (regionType == DOMRegionContext.XML_TAG_CLOSE) {
// change from empty tag may have impact on structure
if (!element.isEmptyTag())
continue;
}
else if (regionType == DOMRegionContext.XML_TAG_NAME || isNestedTagName(regionType)) {
// if new tag name is unchanged, it's OK
if (tagNameUnchanged)
continue;
}
// other region has changed
changeStructuredDocumentRegion(flatNode);
return;
}
}
// update attributes
ITextRegionList regions = flatNode.getRegions();
if (regions == null)
return; // error
NamedNodeMap attributes = element.getAttributes();
if (attributes == null)
return; // error
// first remove attributes
int regionIndex = 0;
int attrIndex = 0;
AttrImpl attr = null;
while (attrIndex < attributes.getLength()) {
attr = (AttrImpl) attributes.item(attrIndex);
if (attr == null) { // error
attrIndex++;
continue;
}
ITextRegion nameRegion = attr.getNameRegion();
if (nameRegion == null) { // error
element.removeAttributeNode(attr);
continue;
}
boolean found = false;
for (int i = regionIndex; i < regions.size(); i++) {
ITextRegion region = regions.get(i);
if (region == nameRegion) {
regionIndex = i + 1; // next region
found = true;
break;
}
}
if (found) {
attrIndex++;
}
else {
element.removeAttributeNode(attr);
}
}
// insert or update attributes
attrIndex = 0; // reset to first
AttrImpl newAttr = null;
ITextRegion oldValueRegion = null;
Iterator e = regions.iterator();
while (e.hasNext()) {
ITextRegion region = (ITextRegion) e.next();
String regionType = region.getType();
if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
if (newAttr != null) {
// insert deferred new attribute
element.insertAttributeNode(newAttr, attrIndex++);
newAttr = null;
}
else if (attr != null && oldValueRegion != null) {
// notify existing attribute value removal
attr.notifyValueChanged();
}
oldValueRegion = null;
attr = (AttrImpl) attributes.item(attrIndex);
if (attr != null && attr.getNameRegion() == region) {
// existing attribute
attrIndex++;
// clear other regions
oldValueRegion = attr.getValueRegion();
attr.setEqualRegion(null);
attr.setValueRegion(null);
}
else {
String name = flatNode.getText(region);
attr = (AttrImpl) this.model.getDocument().createAttribute(name);
if (attr != null)
attr.setNameRegion(region);
// defer insertion of new attribute
newAttr = attr;
}
}
else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) {
if (attr != null) {
attr.setEqualRegion(region);
}
}
else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) {
if (attr != null) {
attr.setValueRegion(region);
if (attr != newAttr && oldValueRegion != region) {
// notify existing attribute value changed
attr.notifyValueChanged();
}
oldValueRegion = null;
attr = null;
}
}
}
if (newAttr != null) {
// insert deferred new attribute
element.appendAttributeNode(newAttr);
}
else if (attr != null && oldValueRegion != null) {
// notify existing attribute value removal
attr.notifyValueChanged();
}
}
/**
* changeStructuredDocumentRegion method
*
*/
private void changeStructuredDocumentRegion(IStructuredDocumentRegion flatNode) {
if (flatNode == null)
return;
if (this.model.getDocument() == null)
return;
setupContext(flatNode);
removeStructuredDocumentRegion(flatNode);
// make sure the parent is set to deepest level
// when end tag has been removed
this.context.setLast();
insertStructuredDocumentRegion(flatNode);
cleanupText();
cleanupEndTag();
}
/**
*/
private void changeTagName(IStructuredDocumentRegion flatNode, ITextRegion region) {
int offset = flatNode.getStart();
if (offset < 0)
return; // error
NodeImpl root = (NodeImpl) this.context.getRootNode();
if (root == null)
return; // error
Node node = root.getNodeAt(offset);
if (node == null)
return; // error
if (node.getNodeType() != Node.ELEMENT_NODE) {
changeStructuredDocumentRegion(flatNode);
return;
}
ElementImpl element = (ElementImpl) node;
String newTagName = flatNode.getText(region);
if (newTagName == null || !element.matchTagName(newTagName)) {
// the tag name is changed
changeStructuredDocumentRegion(flatNode);
return;
}
// the tag name is unchanged
// this happens when typing spaces after the tag name
// do nothing, but...
// if it's not a change in the end tag of an element with the start
// tag,
// and case has been changed, set to element and notify
if (!element.hasStartTag() || StructuredDocumentRegionUtil.getFirstRegionType(flatNode) != DOMRegionContext.XML_END_TAG_OPEN) {
String tagName = element.getTagName();
if (tagName == null || !tagName.equals(newTagName)) {
element.setTagName(newTagName);
element.notifyValueChanged();
}
}
}
/**
* cleanupContext method
*/
private void cleanupEndTag() {
Node parent = this.context.getParentNode();
Node next = this.context.getNextNode();
while (parent != null) {
while (next != null) {
if (next.getNodeType() == Node.ELEMENT_NODE) {
ElementImpl element = (ElementImpl) next;
if (element.isEndTag()) {
// floating end tag
String tagName = element.getTagName();
String rootName = getFindRootName(tagName);
ElementImpl start = (ElementImpl) this.context.findStartTag(tagName, rootName);
if (start != null) {
insertEndTag(start);
// move the end tag from 'element' to 'start'
start.addEndTag(element);
removeNode(element);
parent = this.context.getParentNode();
next = this.context.getNextNode();
continue;
}
}
}
Node first = next.getFirstChild();
if (first != null) {
parent = next;
next = first;
this.context.setNextNode(next);
}
else {
next = next.getNextSibling();
this.context.setNextNode(next);
}
}
if (parent.getNodeType() == Node.ELEMENT_NODE) {
ElementImpl element = (ElementImpl) parent;
if (!element.hasEndTag() && element.hasStartTag() && element.getNextSibling() == null) {
String tagName = element.getTagName();
ElementImpl end = (ElementImpl) this.context.findEndTag(tagName);
if (end != null) {
// move the end tag from 'end' to 'element'
element.addEndTag(end);
removeEndTag(end);
this.context.setParentNode(parent); // reset context
continue;
}
}
}
next = parent.getNextSibling();
parent = parent.getParentNode();
if (next != null) {
this.context.setNextNode(next);
}
else {
this.context.setParentNode(parent);
}
}
}
/**
*/
private void cleanupText() {
Node parent = this.context.getParentNode();
if (parent == null)
return; // error
Node next = this.context.getNextNode();
Node prev = (next == null ? parent.getLastChild() : next.getPreviousSibling());
TextImpl nextText = null;
TextImpl prevText = null;
if (next != null && next.getNodeType() == Node.TEXT_NODE) {
nextText = (TextImpl) next;
}
if (prev != null && prev.getNodeType() == Node.TEXT_NODE) {
prevText = (TextImpl) prev;
}
if (nextText == null && prevText == null)
return;
if (nextText != null && prevText != null) {
// consecutive Text nodes created by setupContext(),
// concat them
IStructuredDocumentRegion flatNode = nextText.getStructuredDocumentRegion();
if (flatNode != null)
prevText.appendStructuredDocumentRegion(flatNode);
Node newNext = next.getNextSibling();
parent.removeChild(next);
next = null;
this.context.setNextNode(newNext);
}
TextImpl childText = (prevText != null ? prevText : nextText);
if (childText.getNextSibling() == null && childText.getPreviousSibling() == null) {
if (parent.getNodeType() == Node.ELEMENT_NODE) {
ElementImpl parentElement = (ElementImpl) parent;
if (!parentElement.hasStartTag() && !parentElement.hasEndTag()) {
if (childText.isWhitespace() || childText.isInvalid()) {
// implicit parent is not required
Node newParent = parent.getParentNode();
if (newParent != null) {
Node newNext = parent.getNextSibling();
newParent.removeChild(parent);
parent.removeChild(childText);
newParent.insertBefore(childText, newNext);
if (childText == next) {
this.context.setNextNode(childText);
}
else if (newNext != null) {
this.context.setNextNode(newNext);
}
else {
this.context.setParentNode(newParent);
}
// try again
cleanupText();
}
}
}
}
}
}
/**
* This routine create an Element from comment data for comment style
* elements, such as SSI and METADATA
*/
protected Element createCommentElement(String data, boolean isJSPTag) {
String trimmedData = data.trim();
CommentElementConfiguration[] configs = CommentElementRegistry.getInstance().getConfigurations();
for (int iConfig = 0; iConfig < configs.length; iConfig++) {
CommentElementConfiguration config = configs[iConfig];
if ((isJSPTag && !config.acceptJSPComment()) || (!isJSPTag && !config.acceptXMLComment())) {
continue;
}
String[] prefixes = config.getPrefix();
for (int iPrefix = 0; iPrefix < prefixes.length; iPrefix++) {
if (trimmedData.startsWith(prefixes[iPrefix])) {
return config.createElement(this.model.getDocument(), data, isJSPTag);
}
}
}
ModelParserAdapter adapter = getParserAdapter();
if (adapter != null) {
return adapter.createCommentElement(this.model.getDocument(), data, isJSPTag);
}
return null;
}
/**
* This routine create an implicit Element for given parent and child,
* such as HTML, BODY, HEAD, and TBODY for HTML document.
*/
protected Element createImplicitElement(Node parent, Node child) {
ModelParserAdapter adapter = getParserAdapter();
if (adapter != null) {
return adapter.createImplicitElement(this.model.getDocument(), parent, child);
}
return null;
}
/**
*/
private void demoteNodes(Node root, Node newParent, Node oldParent, Node next) {
if (newParent.getNodeType() != Node.ELEMENT_NODE)
return;
ElementImpl newElement = (ElementImpl) newParent;
// find next
while (next == null) {
if (oldParent.getNodeType() != Node.ELEMENT_NODE)
return;
ElementImpl oldElement = (ElementImpl) oldParent;
if (oldElement.hasEndTag())
return;
oldParent = oldElement.getParentNode();
if (oldParent == null)
return; // error
next = oldElement.getNextSibling();
}
while (next != null) {
boolean done = false;
if (next.getNodeType() == Node.ELEMENT_NODE) {
ElementImpl nextElement = (ElementImpl) next;
if (!nextElement.hasStartTag()) {
Node nextChild = nextElement.getFirstChild();
if (nextChild != null) {
// demote children
next = nextChild;
oldParent = nextElement;
continue;
}
if (nextElement.hasEndTag()) {
if (nextElement.matchEndTag(newElement)) {
// stop at the matched invalid end tag
next = nextElement.getNextSibling();
oldParent.removeChild(nextElement);
newElement.addEndTag(nextElement);
if (newElement == root)
return;
Node p = newElement.getParentNode();
// check if reached to top
if (p == null || p == oldParent || p.getNodeType() != Node.ELEMENT_NODE)
return;
newElement = (ElementImpl) p;
done = true;
}
}
else {
// remove implicit element
next = nextElement.getNextSibling();
oldParent.removeChild(nextElement);
done = true;
}
}
}
if (!done) {
if (!canContain(newElement, next)) {
if (newElement == root)
return;
Node p = newElement.getParentNode();
// check if reached to top
if (p == null || p == oldParent || p.getNodeType() != Node.ELEMENT_NODE)
return;
newElement = (ElementImpl) p;
continue;
}
Node child = next;
next = next.getNextSibling();
oldParent.removeChild(child);
insertNode(newElement, child, null);
Node childParent = child.getParentNode();
if (childParent != newElement) {
newElement = (ElementImpl) childParent;
}
}
// find next parent and sibling
while (next == null) {
if (oldParent.getNodeType() != Node.ELEMENT_NODE)
return;
ElementImpl oldElement = (ElementImpl) oldParent;
// dug parent must not have children at this point
if (!oldElement.hasChildNodes() && !oldElement.hasStartTag()) {
oldParent = oldElement.getParentNode();
if (oldParent == null)
return; // error
next = oldElement;
break;
}
if (oldElement.hasEndTag())
return;
oldParent = oldElement.getParentNode();
if (oldParent == null)
return; // error
next = oldElement.getNextSibling();
}
}
}
private ModelParserAdapter getParserAdapter() {
return (ModelParserAdapter) this.model.getDocument().getAdapterFor(ModelParserAdapter.class);
}
/**
*/
protected String getFindRootName(String tagName) {
ModelParserAdapter adapter = getParserAdapter();
if (adapter != null) {
return adapter.getFindRootName(tagName);
}
return null;
}
/**
*/
protected final IDOMModel getModel() {
return this.model;
}
/**
* insertCDATASection method
*
*/
private void insertCDATASection(IStructuredDocumentRegion flatNode) {
ITextRegionList regions = flatNode.getRegions();
if (regions == null)
return;
CDATASectionImpl cdata = null;
try {
cdata = (CDATASectionImpl) this.model.getDocument().createCDATASection(null);
}
catch (DOMException ex) {
}
if (cdata == null) { // CDATA section might not be supported
insertInvalidDecl(flatNode); // regard as invalid decl
return;
}
cdata.setStructuredDocumentRegion(flatNode);
insertNode(cdata);
}
/**
* insertComment method
*
*/
private void insertComment(IStructuredDocumentRegion flatNode) {
ITextRegionList regions = flatNode.getRegions();
if (regions == null)
return;
String data = null;
boolean isJSPTag = false;
Iterator e = regions.iterator();
while (e.hasNext()) {
ITextRegion region = (ITextRegion) e.next();
String regionType = region.getType();
if (isNestedCommentOpen(regionType)) {
isJSPTag = true;
}
else if (regionType == DOMRegionContext.XML_COMMENT_TEXT || isNestedCommentText(regionType)) {
if (data == null) {
data = flatNode.getText(region);
}
}
}
if (data != null) {
ElementImpl element = (ElementImpl) createCommentElement(data, isJSPTag);
if (element != null) {
if (!isEndTag(element)) {
element.setStartStructuredDocumentRegion(flatNode);
insertStartTag(element);
return;
}
// end tag
element.setEndStructuredDocumentRegion(flatNode);
String tagName = element.getTagName();
String rootName = getFindRootName(tagName);
ElementImpl start = (ElementImpl) this.context.findStartTag(tagName, rootName);
if (start != null) { // start tag found
insertEndTag(start);
start.addEndTag(element);
return;
}
// invalid end tag
insertNode(element);
return;
}
}
CommentImpl comment = (CommentImpl) this.model.getDocument().createComment(null);
if (comment == null)
return;
if (isJSPTag)
comment.setJSPTag(true);
comment.setStructuredDocumentRegion(flatNode);
insertNode(comment);
}
/**
* insertDecl method
*
*/
private void insertDecl(IStructuredDocumentRegion flatNode) {
ITextRegionList regions = flatNode.getRegions();
if (regions == null)
return;
boolean isDocType = false;
String name = null;
String publicId = null;
String systemId = null;
Iterator e = regions.iterator();
while (e.hasNext()) {
ITextRegion region = (ITextRegion) e.next();
String regionType = region.getType();
if (regionType == DOMRegionContext.XML_DOCTYPE_DECLARATION) {
isDocType = true;
}
else if (regionType == DOMRegionContext.XML_DOCTYPE_NAME) {
if (name == null)
name = flatNode.getText(region);
}
else if (regionType == DOMRegionContext.XML_DOCTYPE_EXTERNAL_ID_PUBREF) {
if (publicId == null)
publicId = StructuredDocumentRegionUtil.getAttrValue(flatNode, region);
}
else if (regionType == DOMRegionContext.XML_DOCTYPE_EXTERNAL_ID_SYSREF) {
if (systemId == null)
systemId = StructuredDocumentRegionUtil.getAttrValue(flatNode, region);
}
}
// invalid declaration
if (!isDocType) {
insertInvalidDecl(flatNode);
return;
}
DocumentTypeImpl docType = (DocumentTypeImpl) this.model.getDocument().createDoctype(name);
if (docType == null)
return;
if (publicId != null)
docType.setPublicId(publicId);
if (systemId != null)
docType.setSystemId(systemId);
docType.setStructuredDocumentRegion(flatNode);
insertNode(docType);
}
/**
* insertEndTag method can be used by subclasses, but not overrided.
*
* @param element
* org.w3c.dom.Element
*/
protected void insertEndTag(Element element) {
if (element == null)
return;
Node newParent = element.getParentNode();
if (newParent == null)
return; // error
if (!((ElementImpl) element).isContainer()) {
// just update context
Node elementNext = element.getNextSibling();
if (elementNext != null)
this.context.setNextNode(elementNext);
else
this.context.setParentNode(newParent);
return;
}
// promote children
Node newNext = element.getNextSibling();
Node oldParent = this.context.getParentNode();
if (oldParent == null)
return; // error
Node oldNext = this.context.getNextNode();
promoteNodes(element, newParent, newNext, oldParent, oldNext);
// update context
// re-check the next sibling
newNext = element.getNextSibling();
if (newNext != null)
this.context.setNextNode(newNext);
else
this.context.setParentNode(newParent);
}
/**
* insertEndTag method
*
*/
private void insertEndTag(IStructuredDocumentRegion flatNode) {
ITextRegionList regions = flatNode.getRegions();
if (regions == null)
return;
String tagName = null;
Iterator e = regions.iterator();
while (e.hasNext()) {
ITextRegion region = (ITextRegion) e.next();
String regionType = region.getType();
if (regionType == DOMRegionContext.XML_TAG_NAME || isNestedTagName(regionType)) {
if (tagName == null)
tagName = flatNode.getText(region);
}
}
if (tagName == null) { // invalid end tag
insertText(flatNode); // regard as invalid text
return;
}
String rootName = getFindRootName(tagName);
ElementImpl start = (ElementImpl) this.context.findStartTag(tagName, rootName);
if (start != null) { // start tag found
insertEndTag(start);
start.setEndStructuredDocumentRegion(flatNode);
return;
}
// invalid end tag
ElementImpl end = null;
try {
end = (ElementImpl) this.model.getDocument().createElement(tagName);
}
catch (DOMException ex) {
}
if (end == null) { // invalid end tag
insertText(flatNode); // regard as invalid text
return;
}
end.setEndStructuredDocumentRegion(flatNode);
insertNode(end);
}
/**
* insertEntityRef method
*
*/
private void insertEntityRef(IStructuredDocumentRegion flatNode) {
ITextRegionList regions = flatNode.getRegions();
if (regions == null)
return;
String name = null;
Iterator e = regions.iterator();
while (e.hasNext()) {
ITextRegion region = (ITextRegion) e.next();
String regionType = region.getType();
if (regionType == DOMRegionContext.XML_ENTITY_REFERENCE || regionType == DOMRegionContext.XML_CHAR_REFERENCE) {
if (name == null)
name = StructuredDocumentRegionUtil.getEntityRefName(flatNode, region);
}
}
if (name == null) { // invalid entity
insertText(flatNode);
return;
}
// ISSUE: avoid this cast
String value = ((DocumentImpl)this.model.getDocument()).getCharValue(name);
if (value != null) { // character entity
TextImpl text = (TextImpl) this.context.findPreviousText();
if (text != null) { // existing text found
// do not append data
text.appendStructuredDocumentRegion(flatNode);
// notify the change
text.notifyValueChanged();
return;
}
// new text
text = (TextImpl) this.model.getDocument().createTextNode(null);
if (text == null)
return;
text.setStructuredDocumentRegion(flatNode);
insertNode(text);
return;
}
// general entity reference
EntityReferenceImpl ref = null;
try {
ref = (EntityReferenceImpl) this.model.getDocument().createEntityReference(name);
}
catch (DOMException ex) {
}
if (ref == null) { // entity reference might not be supported
insertText(flatNode); // regard as invalid text
return;
}
ref.setStructuredDocumentRegion(flatNode);
insertNode(ref);
}
/**
* insertInvalidDecl method
*
*/
private void insertInvalidDecl(IStructuredDocumentRegion flatNode) {
ITextRegionList regions = flatNode.getRegions();
if (regions == null)
return;
ElementImpl element = null;
try {
element = (ElementImpl) this.model.getDocument().createElement("!");//$NON-NLS-1$
}
catch (DOMException ex) {
}
if (element == null) { // invalid tag
insertText(flatNode); // regard as invalid text
return;
}
element.setEmptyTag(true);
element.setStartStructuredDocumentRegion(flatNode);
insertNode(element);
}
/**
* insertJSPTag method
*
*/
private void insertNestedTag(IStructuredDocumentRegion flatNode) {
ITextRegionList regions = flatNode.getRegions();
if (regions == null)
return;
String tagName = null;
AttrImpl attr = null;
Vector attrNodes = null;
boolean isCloseTag = false;
Iterator e = regions.iterator();
while (e.hasNext()) {
ITextRegion region = (ITextRegion) e.next();
String regionType = region.getType();
if (isNestedTagOpen(regionType) || isNestedTagName(regionType)) {
tagName = computeNestedTag(regionType, tagName, flatNode, region);
}
else if (isNestedTagClose(regionType)) {
isCloseTag = true;
}
else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
String name = flatNode.getText(region);
attr = (AttrImpl) this.model.getDocument().createAttribute(name);
if (attr != null) {
attr.setNameRegion(region);
if (attrNodes == null)
attrNodes = new Vector();
attrNodes.addElement(attr);
}
}
else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) {
if (attr != null) {
attr.setEqualRegion(region);
}
}
else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) {
if (attr != null) {
attr.setValueRegion(region);
attr = null;
}
}
}
if (tagName == null) {
if (isCloseTag) {
// close JSP tag
Node parent = this.context.getParentNode();
if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) {
ElementImpl start = (ElementImpl) parent;
if (start.isJSPContainer()) {
insertEndTag(start);
start.setEndStructuredDocumentRegion(flatNode);
return;
}
}
}
// invalid JSP tag
insertText(flatNode); // regard as invalid text
return;
}
ElementImpl element = null;
try {
element = (ElementImpl) this.model.getDocument().createElement(tagName);
}
catch (DOMException ex) {
}
if (element == null) { // invalid tag
insertText(flatNode); // regard as invalid text
return;
}
if (attrNodes != null) {
Enumeration ae = attrNodes.elements();
while (ae.hasMoreElements()) {
Attr a = (Attr) ae.nextElement();
if (a == null)
continue;
element.appendAttributeNode(a);
}
}
element.setJSPTag(true);
element.setStartStructuredDocumentRegion(flatNode);
insertStartTag(element);
}
protected boolean isNestedTagClose(String regionType) {
boolean result = false;
return result;
}
protected boolean isNestedTagOpen(String regionType) {
boolean result = false;
return result;
}
protected String computeNestedTag(String regionType, String tagName, IStructuredDocumentRegion structuredDocumentRegion, ITextRegion region) {
return tagName;
}
/**
* insertNode method
*
* @param child
* org.w3c.dom.Node
*/
private void insertNode(Node node) {
if (node == null)
return;
if (this.context == null)
return;
Node parent = this.context.getParentNode();
if (parent == null)
return;
Node next = this.context.getNextNode();
while (parent.getNodeType() == Node.ELEMENT_NODE) {
ElementImpl element = (ElementImpl) parent;
if (canContain(element, node)) {
if (!element.hasStartTag() && next == element.getFirstChild()) {
// first child of implicit tag
// deletege to the parent
parent = element.getParentNode();
if (parent == null)
return;
next = element;
this.context.setNextNode(next);
continue;
}
break;
}
parent = element.getParentNode();
if (parent == null)
return;
// promote siblings
Node newNext = element.getNextSibling();
Node child = next;
while (child != null) {
Node nextChild = child.getNextSibling();
element.removeChild(child);
parent.insertBefore(child, newNext);
child = nextChild;
}
// leave the old end tag where it is
if (element.hasEndTag()) {
Element end = element.removeEndTag();
if (end != null) {
parent.insertBefore(end, newNext);
if (next == null)
next = end;
}
}
if (!element.hasStartTag()) {
// implicit element
if (!element.hasChildNodes()) {
parent.removeChild(element);
}
}
// update context
if (next == null)
next = newNext;
if (next != null)
this.context.setNextNode(next);
else
this.context.setParentNode(parent);
}
insertNode(parent, node, next);
next = node.getNextSibling();
if (next != null)
this.context.setNextNode(next);
else
this.context.setParentNode(node.getParentNode());
}
/**
*/
private void insertNode(Node parent, Node node, Node next) {
while (next != null && next.getNodeType() == Node.ELEMENT_NODE) {
ElementImpl nextElement = (ElementImpl) next;
if (nextElement.hasStartTag())
break;
if (!canBeImplicitTag(nextElement, node))
break;
parent = nextElement;
next = nextElement.getFirstChild();
}
Element implicitElement = createImplicitElement(parent, node);
if (implicitElement != null)
node = implicitElement;
parent.insertBefore(node, next);
}
/**
* insertPI method
*
*/
private void insertPI(IStructuredDocumentRegion flatNode) {
ITextRegionList regions = flatNode.getRegions();
if (regions == null)
return;
String target = null;
Iterator e = regions.iterator();
while (e.hasNext()) {
ITextRegion region = (ITextRegion) e.next();
String regionType = region.getType();
if (regionType == DOMRegionContext.XML_PI_OPEN || regionType == DOMRegionContext.XML_PI_CLOSE)
continue;
if (target == null)
target = flatNode.getText(region);
}
ProcessingInstructionImpl pi = (ProcessingInstructionImpl) this.model.getDocument().createProcessingInstruction(target, null);
if (pi == null)
return;
pi.setStructuredDocumentRegion(flatNode);
insertNode(pi);
}
/**
* insertStartTag method can be used by subclasses, but not overridden.
*
* @param element
* org.w3c.dom.Element
*/
protected void insertStartTag(Element element) {
if (element == null)
return;
if (this.context == null)
return;
insertNode(element);
ElementImpl newElement = (ElementImpl) element;
if (newElement.isEmptyTag() || !newElement.isContainer())
return;
// demote siblings
Node parent = this.context.getParentNode();
if (parent == null)
return; // error
Node next = this.context.getNextNode();
demoteNodes(element, element, parent, next);
// update context
Node firstChild = element.getFirstChild();
if (firstChild != null)
this.context.setNextNode(firstChild);
else
this.context.setParentNode(element);
}
/**
* insertStartTag method
*
*/
private void insertStartTag(IStructuredDocumentRegion flatNode) {
ITextRegionList regions = flatNode.getRegions();
if (regions == null)
return;
String tagName = null;
boolean isEmptyTag = false;
AttrImpl attr = null;
Vector attrNodes = null;
Iterator e = regions.iterator();
while (e.hasNext()) {
ITextRegion region = (ITextRegion) e.next();
String regionType = region.getType();
if (regionType == DOMRegionContext.XML_TAG_NAME || isNestedTagName(regionType)) {
if (tagName == null)
tagName = flatNode.getText(region);
}
else if (regionType == DOMRegionContext.XML_EMPTY_TAG_CLOSE) {
isEmptyTag = true;
}
else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
String name = flatNode.getText(region);
attr = (AttrImpl) this.model.getDocument().createAttribute(name);
if (attr != null) {
attr.setNameRegion(region);
if (attrNodes == null)
attrNodes = new Vector();
attrNodes.addElement(attr);
}
}
else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) {
if (attr != null) {
attr.setEqualRegion(region);
}
}
else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) {
if (attr != null) {
attr.setValueRegion(region);
attr = null;
}
}
}
if (tagName == null) { // invalid start tag
insertText(flatNode); // regard as invalid text
return;
}
ElementImpl element = null;
try {
element = (ElementImpl) this.model.getDocument().createElement(tagName);
}
catch (DOMException ex) {
// typically invalid name
}
if (element == null) { // invalid tag
insertText(flatNode); // regard as invalid text
return;
}
if (attrNodes != null) {
Enumeration ae = attrNodes.elements();
while (ae.hasMoreElements()) {
Attr a = (Attr) ae.nextElement();
if (a == null)
continue;
element.appendAttributeNode(a);
}
}
if (isEmptyTag)
element.setEmptyTag(true);
element.setStartStructuredDocumentRegion(flatNode);
insertStartTag(element);
}
/**
* insertStructuredDocumentRegion method
*
*/
protected void insertStructuredDocumentRegion(IStructuredDocumentRegion flatNode) {
String regionType = StructuredDocumentRegionUtil.getFirstRegionType(flatNode);
if (regionType == DOMRegionContext.XML_TAG_OPEN) {
insertStartTag(flatNode);
}
else if (regionType == DOMRegionContext.XML_END_TAG_OPEN) {
insertEndTag(flatNode);
}
else if (regionType == DOMRegionContext.XML_COMMENT_OPEN || isNestedCommentOpen(regionType)) {
insertComment(flatNode);
}
else if (regionType == DOMRegionContext.XML_ENTITY_REFERENCE || regionType == DOMRegionContext.XML_CHAR_REFERENCE) {
insertEntityRef(flatNode);
}
else if (regionType == DOMRegionContext.XML_DECLARATION_OPEN) {
insertDecl(flatNode);
}
else if (regionType == DOMRegionContext.XML_PI_OPEN) {
insertPI(flatNode);
}
else if (regionType == DOMRegionContext.XML_CDATA_OPEN) {
insertCDATASection(flatNode);
}
else if (isNestedTag(regionType)) {
insertNestedTag(flatNode);
}
else {
insertText(flatNode);
}
}
protected boolean isNestedTag(String regionType) {
boolean result = false;
return result;
}
protected boolean isNestedCommentText(String regionType) {
boolean result = false;
return result;
}
protected boolean isNestedCommentOpen(String regionType) {
boolean result = false;
return result;
}
protected boolean isNestedTagName(String regionType) {
boolean result = false;
return result;
}
protected boolean isNestedContent(String regionType) {
boolean result = false;
return result;
}
/**
* insertText method Can be called from subclasses, not to be overrided or
* re-implemented.
*
*/
protected void insertText(IStructuredDocumentRegion flatNode) {
TextImpl text = (TextImpl) this.context.findPreviousText();
if (text != null) { // existing text found
text.appendStructuredDocumentRegion(flatNode);
// notify the change
text.notifyValueChanged();
return;
}
// new text
text = (TextImpl) this.model.getDocument().createTextNode(null);
if (text == null)
return;
text.setStructuredDocumentRegion(flatNode);
insertNode(text);
}
/**
*/
protected boolean isEndTag(IDOMElement element) {
ModelParserAdapter adapter = getParserAdapter();
if (adapter != null) {
return adapter.isEndTag(element);
}
return element.isEndTag();
}
/**
*/
private void promoteNodes(Node root, Node newParent, Node newNext, Node oldParent, Node next) {
ElementImpl newElement = null;
if (newParent.getNodeType() == Node.ELEMENT_NODE) {
newElement = (ElementImpl) newParent;
}
Node rootParent = root.getParentNode();
while (oldParent != rootParent) {
while (next != null) {
boolean done = false;
boolean endTag = false;
if (next.getNodeType() == Node.ELEMENT_NODE) {
ElementImpl nextElement = (ElementImpl) next;
if (!nextElement.hasStartTag()) {
Node nextChild = nextElement.getFirstChild();
if (nextChild != null) {
// promote children
next = nextChild;
oldParent = nextElement;
continue;
}
if (nextElement.hasEndTag()) {
if (nextElement.matchEndTag(newElement)) {
endTag = true;
}
}
else {
// remove implicit element
next = nextElement.getNextSibling();
oldParent.removeChild(nextElement);
done = true;
}
}
}
if (!done) {
if (!endTag && newElement != null && !canContain(newElement, next)) {
newParent = newElement.getParentNode();
if (newParent == null)
return; // error
Node elementNext = newElement.getNextSibling();
// promote siblings
promoteNodes(newElement, newParent, elementNext, newElement, newNext);
newNext = newElement.getNextSibling();
if (newParent.getNodeType() == Node.ELEMENT_NODE) {
newElement = (ElementImpl) newParent;
}
else {
newElement = null;
}
continue;
}
Node child = next;
next = next.getNextSibling();
oldParent.removeChild(child);
insertNode(newParent, child, newNext);
Node childParent = child.getParentNode();
if (childParent != newParent) {
newParent = childParent;
newElement = (ElementImpl) newParent;
newNext = child.getNextSibling();
}
}
}
if (oldParent.getNodeType() != Node.ELEMENT_NODE)
return;
ElementImpl oldElement = (ElementImpl) oldParent;
oldParent = oldElement.getParentNode();
if (oldParent == null)
return; // error
next = oldElement.getNextSibling();
if (oldElement.hasEndTag()) {
Element end = null;
if (!oldElement.hasChildNodes() && !oldElement.hasStartTag()) {
oldParent.removeChild(oldElement);
end = oldElement;
}
else {
end = oldElement.removeEndTag();
}
if (end != null) {
insertNode(newParent, end, newNext);
Node endParent = end.getParentNode();
if (endParent != newParent) {
newParent = endParent;
newElement = (ElementImpl) newParent;
newNext = end.getNextSibling();
}
}
}
}
}
/**
* removeEndTag method
*
* @param element
* org.w3c.dom.Element
*/
private void removeEndTag(Element element) {
if (element == null)
return;
if (this.context == null)
return;
Node parent = element.getParentNode();
if (parent == null)
return; // error
if (!((ElementImpl) element).isContainer()) {
// just update context
Node elementNext = element.getNextSibling();
if (elementNext != null)
this.context.setNextNode(elementNext);
else
this.context.setParentNode(parent);
return;
}
// demote siblings
Node next = element.getNextSibling();
ElementImpl newElement = (ElementImpl) element;
// find new parent
for (Node last = newElement.getLastChild(); last != null; last = last.getLastChild()) {
if (last.getNodeType() != Node.ELEMENT_NODE)
break;
ElementImpl lastElement = (ElementImpl) last;
if (lastElement.hasEndTag() || lastElement.isEmptyTag() || !lastElement.isContainer())
break;
newElement = lastElement;
}
Node lastChild = newElement.getLastChild();
demoteNodes(element, newElement, parent, next);
// update context
Node newNext = null;
if (lastChild != null)
newNext = lastChild.getNextSibling();
else
newNext = newElement.getFirstChild();
if (newNext != null)
this.context.setNextNode(newNext);
else
this.context.setParentNode(newElement);
}
/**
* Remove the specified node if it is no longer required implicit tag with
* remaining child nodes promoted.
*/
private Element removeImplicitElement(Node parent) {
if (parent == null)
return null;
if (parent.getNodeType() != Node.ELEMENT_NODE)
return null;
ElementImpl element = (ElementImpl) parent;
if (!element.isImplicitTag())
return null;
if (canBeImplicitTag(element))
return null;
Node elementParent = element.getParentNode();
if (elementParent == null)
return null; // error
Node firstChild = element.getFirstChild();
Node child = firstChild;
Node elementNext = element.getNextSibling();
while (child != null) {
Node nextChild = child.getNextSibling();
element.removeChild(child);
elementParent.insertBefore(child, elementNext);
child = nextChild;
}
// reset context
if (this.context.getParentNode() == element) {
Node oldNext = this.context.getNextNode();
if (oldNext != null) {
this.context.setNextNode(oldNext);
}
else {
if (elementNext != null) {
this.context.setNextNode(elementNext);
}
else {
this.context.setParentNode(elementParent);
}
}
}
else if (this.context.getNextNode() == element) {
if (firstChild != null) {
this.context.setNextNode(firstChild);
}
else {
if (elementNext != null) {
this.context.setNextNode(elementNext);
}
else {
this.context.setParentNode(elementParent);
}
}
}
removeNode(element);
return element;
}
/**
* removeNode method
*
* @param node
* org.w3c.dom.Node
*/
private void removeNode(Node node) {
if (node == null)
return;
if (this.context == null)
return;
Node parent = node.getParentNode();
if (parent == null)
return;
Node next = node.getNextSibling();
Node prev = node.getPreviousSibling();
// update context
Node oldParent = this.context.getParentNode();
if (node == oldParent) {
if (next != null)
this.context.setNextNode(next);
else
this.context.setParentNode(parent);
}
else {
Node oldNext = this.context.getNextNode();
if (node == oldNext) {
this.context.setNextNode(next);
}
}
parent.removeChild(node);
if (removeImplicitElement(parent) != null)
return;
// demote sibling
if (prev != null && prev.getNodeType() == Node.ELEMENT_NODE) {
ElementImpl newElement = (ElementImpl) prev;
if (!newElement.hasEndTag() && !newElement.isEmptyTag() && newElement.isContainer()) {
// find new parent
for (Node last = newElement.getLastChild(); last != null; last = last.getLastChild()) {
if (last.getNodeType() != Node.ELEMENT_NODE)
break;
ElementImpl lastElement = (ElementImpl) last;
if (lastElement.hasEndTag() || lastElement.isEmptyTag() || !lastElement.isContainer())
break;
newElement = lastElement;
}
Node lastChild = newElement.getLastChild();
demoteNodes(prev, newElement, parent, next);
// update context
Node newNext = null;
if (lastChild != null)
newNext = lastChild.getNextSibling();
else
newNext = newElement.getFirstChild();
if (newNext != null)
this.context.setNextNode(newNext);
else
this.context.setParentNode(newElement);
}
}
}
/**
* removeStartTag method
*
* @param element
* org.w3c.dom.Element
*/
private void removeStartTag(Element element) {
if (element == null)
return;
if (this.context == null)
return;
// for implicit tag
ElementImpl oldElement = (ElementImpl) element;
if (canBeImplicitTag(oldElement)) {
Node newParent = null;
Node prev = oldElement.getPreviousSibling();
if (prev != null && prev.getNodeType() == Node.ELEMENT_NODE) {
ElementImpl prevElement = (ElementImpl) prev;
if (!prevElement.hasEndTag()) {
if (prevElement.hasStartTag() || prevElement.matchTagName(oldElement.getTagName())) {
newParent = prevElement;
}
}
}
if (newParent == null) {
// this element should stay as implicit tag
// just remove all attributes
oldElement.removeStartTag();
// update context
Node child = oldElement.getFirstChild();
if (child != null) {
this.context.setNextNode(child);
}
else if (oldElement.hasEndTag()) {
this.context.setParentNode(oldElement);
}
return;
}
}
// for comment tag
if (oldElement.isCommentTag())
oldElement.removeStartTag();
// promote children
Node elementParent = element.getParentNode();
Node parent = elementParent;
if (parent == null)
return;
Node first = element.getFirstChild();
Node firstElement = null; // for the case first is removed as end
// tag
if (first != null) {
// find new parent for children
ElementImpl newElement = null;
for (Node last = element.getPreviousSibling(); last != null; last = last.getLastChild()) {
if (last.getNodeType() != Node.ELEMENT_NODE)
break;
ElementImpl lastElement = (ElementImpl) last;
if (lastElement.hasEndTag() || lastElement.isEmptyTag() || !lastElement.isContainer())
break;
newElement = lastElement;
}
Node next = first;
if (newElement != null) {
while (next != null) {
if (!newElement.hasEndTag() && newElement.hasStartTag() && next.getNodeType() == Node.ELEMENT_NODE) {
ElementImpl nextElement = (ElementImpl) next;
if (!nextElement.hasStartTag() && nextElement.hasEndTag() && nextElement.matchEndTag(newElement)) {
// stop at the matched invalid end tag
Node elementChild = nextElement.getFirstChild();
while (elementChild != null) {
Node nextChild = elementChild.getNextSibling();
nextElement.removeChild(elementChild);
newElement.appendChild(elementChild);
elementChild = nextChild;
}
next = nextElement.getNextSibling();
element.removeChild(nextElement);
newElement.addEndTag(nextElement);
if (nextElement == first)
firstElement = newElement;
Node newParent = newElement.getParentNode();
if (newParent == parent)
break;
if (newParent == null || newParent.getNodeType() != Node.ELEMENT_NODE)
break; // error
newElement = (ElementImpl) newParent;
continue;
}
}
if (!canContain(newElement, next)) {
Node newParent = newElement.getParentNode();
if (newParent == parent)
break;
if (newParent == null || newParent.getNodeType() != Node.ELEMENT_NODE)
break; // error
newElement = (ElementImpl) newParent;
continue;
}
Node child = next;
next = next.getNextSibling();
element.removeChild(child);
newElement.appendChild(child);
}
newElement = null;
}
if (parent.getNodeType() == Node.ELEMENT_NODE) {
newElement = (ElementImpl) parent;
}
while (next != null) {
if (newElement == null || canContain(newElement, next)) {
Node child = next;
next = next.getNextSibling();
element.removeChild(child);
parent.insertBefore(child, element);
continue;
}
parent = newElement.getParentNode();
if (parent == null)
return;
// promote siblings
Node newNext = newElement.getNextSibling();
Node child = element;
while (child != null) {
Node nextChild = child.getNextSibling();
newElement.removeChild(child);
parent.insertBefore(child, newNext);
child = nextChild;
}
// leave the old end tag where it is
if (newElement.hasEndTag()) {
Element end = newElement.removeEndTag();
if (end != null) {
parent.insertBefore(end, newNext);
}
}
if (!newElement.hasStartTag()) {
// implicit element
if (!newElement.hasChildNodes()) {
parent.removeChild(newElement);
}
}
if (parent.getNodeType() == Node.ELEMENT_NODE) {
newElement = (ElementImpl) parent;
}
else {
newElement = null;
}
}
}
Node newNext = element;
Node startElement = null; // for the case element is removed as end
// tag
if (oldElement.hasEndTag()) {
// find new parent for invalid end tag and siblings
ElementImpl newElement = null;
for (Node last = element.getPreviousSibling(); last != null; last = last.getLastChild()) {
if (last.getNodeType() != Node.ELEMENT_NODE)
break;
ElementImpl lastElement = (ElementImpl) last;
if (lastElement.hasEndTag() || lastElement.isEmptyTag() || !lastElement.isContainer())
break;
newElement = lastElement;
}
if (newElement != null) {
// demote invalid end tag and sibling
Node next = element;
while (next != null) {
if (!newElement.hasEndTag() && newElement.hasStartTag() && next.getNodeType() == Node.ELEMENT_NODE) {
ElementImpl nextElement = (ElementImpl) next;
if (!nextElement.hasStartTag() && nextElement.hasEndTag() && nextElement.matchEndTag(newElement)) {
// stop at the matched invalid end tag
Node elementChild = nextElement.getFirstChild();
while (elementChild != null) {
Node nextChild = elementChild.getNextSibling();
nextElement.removeChild(elementChild);
newElement.appendChild(elementChild);
elementChild = nextChild;
}
next = nextElement.getNextSibling();
parent.removeChild(nextElement);
newElement.addEndTag(nextElement);
if (nextElement == newNext)
startElement = newElement;
Node newParent = newElement.getParentNode();
if (newParent == parent)
break;
if (newParent == null || newParent.getNodeType() != Node.ELEMENT_NODE)
break; // error
newElement = (ElementImpl) newParent;
continue;
}
}
if (!canContain(newElement, next)) {
Node newParent = newElement.getParentNode();
if (newParent == parent)
break;
if (newParent == null || newParent.getNodeType() != Node.ELEMENT_NODE)
break; // error
newElement = (ElementImpl) newParent;
continue;
}
Node child = next;
next = next.getNextSibling();
parent.removeChild(child);
if (child == oldElement) {
if (!oldElement.isCommentTag()) {
// clone (re-create) end tag
Element end = oldElement.removeEndTag();
if (end != null) {
child = end;
newNext = end;
}
}
}
newElement.appendChild(child);
}
}
else {
if (!oldElement.isCommentTag()) {
// clone (re-create) end tag
Element end = oldElement.removeEndTag();
if (end != null) {
parent.insertBefore(end, oldElement);
parent.removeChild(oldElement);
newNext = end;
}
}
}
}
else {
newNext = oldElement.getNextSibling();
parent.removeChild(oldElement);
}
// update context
Node oldParent = this.context.getParentNode();
Node oldNext = this.context.getNextNode();
if (element == oldParent) {
if (oldNext != null) {
this.context.setNextNode(oldNext); // reset for new parent
}
else if (newNext != null) {
this.context.setNextNode(newNext);
}
else {
this.context.setParentNode(parent);
}
}
else if (element == oldNext) {
if (firstElement != null) {
this.context.setParentNode(firstElement);
}
else if (first != null) {
this.context.setNextNode(first);
}
else if (startElement != null) {
this.context.setParentNode(startElement);
}
else {
this.context.setNextNode(newNext);
}
}
removeImplicitElement(elementParent);
}
/**
* removeStructuredDocumentRegion method
*
*/
private void removeStructuredDocumentRegion(IStructuredDocumentRegion oldStructuredDocumentRegion) {
NodeImpl next = (NodeImpl) this.context.getNextNode();
if (next != null) {
short nodeType = next.getNodeType();
if (nodeType != Node.ELEMENT_NODE) {
IStructuredDocumentRegion flatNode = next.getStructuredDocumentRegion();
if (flatNode == oldStructuredDocumentRegion) {
removeNode(next);
return;
}
if (nodeType != Node.TEXT_NODE) {
throw new StructuredDocumentRegionManagementException();
}
if (flatNode == null) {
// this is the case for empty Text
// remove and continue
removeNode(next);
removeStructuredDocumentRegion(oldStructuredDocumentRegion);
return;
}
TextImpl text = (TextImpl) next;
boolean isShared = text.isSharingStructuredDocumentRegion(oldStructuredDocumentRegion);
if (isShared) {
// make sure there is next Text node sharing this
TextImpl nextText = (TextImpl) this.context.findNextText();
if (nextText == null || !nextText.hasStructuredDocumentRegion(oldStructuredDocumentRegion)) {
isShared = false;
}
}
oldStructuredDocumentRegion = text.removeStructuredDocumentRegion(oldStructuredDocumentRegion);
if (oldStructuredDocumentRegion == null) {
throw new StructuredDocumentRegionManagementException();
}
if (text.getStructuredDocumentRegion() == null) {
// this is the case partial IStructuredDocumentRegion is
// removed
removeNode(text);
}
else {
// notify the change
text.notifyValueChanged();
}
// if shared, continue to remove IStructuredDocumentRegion
// from them
if (isShared)
removeStructuredDocumentRegion(oldStructuredDocumentRegion);
return;
}
ElementImpl element = (ElementImpl) next;
if (element.hasStartTag()) {
IStructuredDocumentRegion flatNode = element.getStartStructuredDocumentRegion();
if (flatNode != oldStructuredDocumentRegion) {
throw new StructuredDocumentRegionManagementException();
}
if (element.hasEndTag() || element.hasChildNodes()) {
element.setStartStructuredDocumentRegion(null);
removeStartTag(element);
}
else {
removeNode(element);
}
}
else {
Node child = element.getFirstChild();
if (child != null) {
this.context.setNextNode(child);
removeStructuredDocumentRegion(oldStructuredDocumentRegion);
return;
}
if (!element.hasEndTag()) {
// implicit element
removeNode(element);
removeStructuredDocumentRegion(oldStructuredDocumentRegion);
return;
}
IStructuredDocumentRegion flatNode = element.getEndStructuredDocumentRegion();
if (flatNode != oldStructuredDocumentRegion) {
throw new StructuredDocumentRegionManagementException();
}
removeNode(element);
}
return;
}
Node parent = this.context.getParentNode();
if (parent == null || parent.getNodeType() != Node.ELEMENT_NODE) {
throw new StructuredDocumentRegionManagementException();
}
ElementImpl end = (ElementImpl) parent;
if (end.hasEndTag()) {
IStructuredDocumentRegion flatNode = end.getEndStructuredDocumentRegion();
if (flatNode != oldStructuredDocumentRegion) {
throw new StructuredDocumentRegionManagementException();
}
if (!end.hasStartTag() && !end.hasChildNodes()) {
this.context.setNextNode(end);
removeNode(end);
}
else {
end.setEndStructuredDocumentRegion(null);
removeEndTag(end);
}
return;
}
next = (NodeImpl) end.getNextSibling();
if (next != null) {
this.context.setNextNode(next);
removeStructuredDocumentRegion(oldStructuredDocumentRegion);
return;
}
parent = end.getParentNode();
if (parent != null) {
this.context.setParentNode(parent);
removeStructuredDocumentRegion(oldStructuredDocumentRegion);
return;
}
}
/**
* replaceRegions method
*
* @param newRegions
* java.util.Vector
* @param oldRegions
* java.util.Vector
*/
void replaceRegions(IStructuredDocumentRegion flatNode, ITextRegionList newRegions, ITextRegionList oldRegions) {
if (flatNode == null)
return;
if (this.model.getDocument() == null)
return;
this.context = new XMLModelContext(this.model.getDocument());
// optimize typical cases
String regionType = StructuredDocumentRegionUtil.getFirstRegionType(flatNode);
if (regionType == DOMRegionContext.XML_TAG_OPEN) {
changeStartTag(flatNode, newRegions, oldRegions);
}
else if (regionType == DOMRegionContext.XML_END_TAG_OPEN) {
changeEndTag(flatNode, newRegions, oldRegions);
}
else {
changeStructuredDocumentRegion(flatNode);
}
}
/**
* replaceStructuredDocumentRegions method
*
*/
void replaceStructuredDocumentRegions(IStructuredDocumentRegionList newStructuredDocumentRegions, IStructuredDocumentRegionList oldStructuredDocumentRegions) {
if (this.model.getDocument() == null)
return;
this.context = new XMLModelContext(this.model.getDocument());
int newCount = (newStructuredDocumentRegions != null ? newStructuredDocumentRegions.getLength() : 0);
int oldCount = (oldStructuredDocumentRegions != null ? oldStructuredDocumentRegions.getLength() : 0);
if (oldCount > 0) {
setupContext(oldStructuredDocumentRegions.item(0));
// Node startParent = this.context.getParentNode();
Enumeration e = oldStructuredDocumentRegions.elements();
while (e.hasMoreElements()) {
IStructuredDocumentRegion flatNode = (IStructuredDocumentRegion) e.nextElement();
if (flatNode == null)
continue;
removeStructuredDocumentRegion(flatNode);
}
}
else {
if (newCount == 0)
return;
setupContext(newStructuredDocumentRegions.item(0));
}
// make sure the parent is set to deepest level
// when end tag has been removed
this.context.setLast();
if (newCount > 0) {
Enumeration e = newStructuredDocumentRegions.elements();
while (e.hasMoreElements()) {
IStructuredDocumentRegion flatNode = (IStructuredDocumentRegion) e.nextElement();
if (flatNode == null)
continue;
insertStructuredDocumentRegion(flatNode);
}
}
cleanupText();
cleanupEndTag();
}
/**
* setupContext method
*
*/
private void setupContext(IStructuredDocumentRegion startStructuredDocumentRegion) {
int offset = startStructuredDocumentRegion.getStart();
if (offset < 0)
return;
NodeImpl root = (NodeImpl) this.context.getRootNode();
if (root == null)
return;
if (offset == 0) {
// at the beggining of document
Node child = root.getFirstChild();
if (child != null)
this.context.setNextNode(child);
else
this.context.setParentNode(root);
return;
}
NodeImpl node = (NodeImpl) root.getNodeAt(offset);
if (node == null) {
// might be at the end of document
this.context.setParentNode(root);
this.context.setLast();
return;
}
if (offset == node.getStartOffset()) {
this.context.setNextNode(node);
return;
}
if (node.getNodeType() == Node.TEXT_NODE) {
TextImpl text = (TextImpl) node;
Text nextText = text.splitText(startStructuredDocumentRegion);
// notify the change
text.notifyValueChanged();
if (nextText == null)
return; // error
this.context.setNextNode(nextText);
return;
}
for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
if (offset >= ((NodeImpl) child).getEndOffset())
continue;
this.context.setNextNode(child);
return;
}
this.context.setParentNode(node);
this.context.setLast();
}
protected XMLModelContext getContext() {
return context;
}
}