/******************************************************************************* * Copyright (c) 2001, 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 * Jens Lukowski/Innoopract - initial renaming/restructuring * *******************************************************************************/ package org.eclipse.wst.xml.core.internal.document; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent; 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; private TextImpl lastTextNode = 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(RegionChangedEvent change, IStructuredDocumentRegion flatNode, ITextRegion region) { if (flatNode == null || region == null) return; if (this.model.getDocument() == null) return; this.context = new XMLModelContext(this.model.getDocument()); //determine if change was whitespace only change boolean isWhitespaceChange = false; if(change.getText() != null && change.getText().length() > 0) { isWhitespaceChange = Character.isWhitespace(change.getText().charAt(0)); } else if(change.getDeletedText() != null && change.getDeletedText().length() > 0) { isWhitespaceChange = Character.isWhitespace(change.getDeletedText().charAt(0)); } // 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) { if (isWhitespaceChange && (change.getOffset() >= flatNode.getStartOffset() + region.getTextEnd())) { // change is entirely in white-space return; } changeAttrName(flatNode, region); } else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) { if (isWhitespaceChange && (change.getOffset() >= flatNode.getStartOffset() + region.getTextEnd())) { // change is entirely in white-space return; } changeAttrValue(flatNode, region); } else if (regionType == DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS) { if (isWhitespaceChange && (change.getOffset() >= flatNode.getStartOffset() + region.getTextEnd())) { // change is entirely in white-space return; } changeAttrEqual(flatNode, region); } else if (regionType == DOMRegionContext.XML_TAG_NAME || isNestedTagName(regionType)) { if (isWhitespaceChange && (change.getOffset() >= flatNode.getStartOffset() + region.getTextEnd())) { // change is entirely in white-space return; } 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() { if (lastTextNode != null) { lastTextNode.notifyValueChanged(); lastTextNode = null; } 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; StringBuffer 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 = new StringBuffer(flatNode.getText(region)); } else { data.append(flatNode.getText(region)); } } } if (data != null) { ElementImpl element = (ElementImpl) createCommentElement(data.toString(), 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); // Adjacent text nodes, where changes were queued if (lastTextNode != null && lastTextNode != text) lastTextNode.notifyValueChanged(); lastTextNode = text; 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; List 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 ArrayList(); attrNodes.add(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) { for (int i = 0; i < attrNodes.size(); i++) { element.appendAttributeNode((Attr) attrNodes.get(i)); } } 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 && this.context != null) { Node parent = this.context.getParentNode(); if(parent != null) { Node next = this.context.getNextNode(); // Reset parents which are closed container elements; should not be parents if(parent.getNodeType() == Node.ELEMENT_NODE) { String type = ((ElementImpl)parent).getStartStructuredDocumentRegion().getLastRegion().getType(); if(((ElementImpl)parent).isContainer() && type == DOMRegionContext.XML_EMPTY_TAG_CLOSE) { next = parent.getNextSibling(); parent = parent.getParentNode(); } else { ModelParserAdapter adapter = getParserAdapter(); if (adapter != null) { while (parent.getNodeType() == Node.ELEMENT_NODE && !adapter.canContain( (Element) parent, node) && adapter.isEndTagOmissible((Element) parent)) { next = parent.getNextSibling(); parent = parent.getParentNode(); } } } } 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; // Ignore container tags that have been closed String type = newElement.getStartStructuredDocumentRegion().getLastRegion().getType(); if(newElement.isContainer() && type == DOMRegionContext.XML_EMPTY_TAG_CLOSE) 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; List 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 ArrayList(); attrNodes.add(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) { for (int i = 0; i < attrNodes.size(); i++) { element.appendAttributeNode((Attr) attrNodes.get(i)); } } if (isEmptyTag) element.setEmptyTag(true); element.setStartStructuredDocumentRegion(flatNode); insertStartTag(element); } /** * insertStructuredDocumentRegion method * */ protected void insertStructuredDocumentRegion(IStructuredDocumentRegion flatNode) { String regionType = StructuredDocumentRegionUtil.getFirstRegionType(flatNode); boolean isTextNode = false; 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); isTextNode = true; } 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); isTextNode = true; } // Changes to text regions are queued up, and once the value is done changing a notification is sent if (!isTextNode && lastTextNode != null) { lastTextNode.notifyValueChanged(); lastTextNode = null; } } 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); // Adjacent text nodes, where changes were queued if (lastTextNode != null && lastTextNode != text) lastTextNode.notifyValueChanged(); lastTextNode = text; 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(); for (int i = 0; i < oldCount; i++) { IStructuredDocumentRegion documentRegion = oldStructuredDocumentRegions.item(i); removeStructuredDocumentRegion(documentRegion); } } 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) { for (int i = 0; i < newCount; i++) { IStructuredDocumentRegion documentRegion = newStructuredDocumentRegions.item(i); insertStructuredDocumentRegion(documentRegion); } } 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 beginning 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; } }