| /******************************************************************************* |
| * Copyright (c) 2004 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.wst.css.core.internal.document; |
| |
| |
| |
| import org.eclipse.wst.css.core.internal.formatter.AttrChangeContext; |
| import org.eclipse.wst.css.core.internal.formatter.CSSSourceFormatterFactory; |
| import org.eclipse.wst.css.core.internal.formatter.CSSSourceGenerator; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSAttr; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode; |
| import org.eclipse.wst.css.core.internal.util.CSSUtil; |
| import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier; |
| import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent; |
| import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; |
| import org.eclipse.wst.sse.core.internal.text.BasicStructuredDocument; |
| import org.w3c.dom.DOMException; |
| |
| |
| /** |
| * |
| */ |
| class CSSModelUpdater { |
| |
| private CSSModelImpl fModel = null; |
| private CSSModelParser fParser = null; |
| |
| /** |
| * CSSModelUpdater constructor comment. |
| */ |
| CSSModelUpdater() { |
| super(); |
| } |
| |
| /** |
| * |
| */ |
| CSSModelUpdater(CSSModelImpl model) { |
| super(); |
| fModel = model; |
| } |
| |
| /** |
| * @param parentNode |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| * @param node |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| */ |
| private void attrInserted(CSSNodeImpl parentNode, CSSAttrImpl attr) { |
| CSSSourceGenerator formatter = CSSSourceFormatterFactory.getInstance().getSourceFormatter(parentNode); |
| |
| if (formatter == null) { |
| CSSUtil.debugOut("Cannot get format adapter : " + parentNode.getClass().toString());//$NON-NLS-1$ |
| return; |
| } |
| |
| short updateMode = CSSModelUpdateContext.UPDATE_INSERT_RCONTAINER; |
| fParser.setupUpdateContext(updateMode, parentNode, attr); |
| |
| // get formatted info |
| AttrChangeContext region = new AttrChangeContext(); |
| String text = new String(formatter.formatAttrChanged(parentNode, attr, true, region)); |
| |
| // set text |
| StructuredDocumentEvent result = insertText(region.start, region.end - region.start, text); |
| |
| int nRemains = fParser.cleanupUpdateContext(); |
| |
| if (0 < nRemains && !(result instanceof NoChangeEvent)) { |
| throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "");//$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * @param parentNode |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| * @param node |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| */ |
| private void attrRemoved(CSSNodeImpl parentNode, CSSAttrImpl attr) { |
| CSSSourceGenerator formatter = CSSSourceFormatterFactory.getInstance().getSourceFormatter(parentNode); |
| |
| if (formatter == null) { |
| CSSUtil.debugOut("Cannot get format adapter : " + parentNode.getClass().toString());//$NON-NLS-1$ |
| return; |
| } |
| |
| short updateMode = CSSModelUpdateContext.UPDATE_REMOVE_RCONTAINER; |
| fParser.setupUpdateContext(updateMode, parentNode, attr); |
| |
| // get formatted info |
| AttrChangeContext region = new AttrChangeContext(); |
| String text = new String(formatter.formatAttrChanged(parentNode, attr, false, region)); |
| |
| // set text |
| StructuredDocumentEvent result = insertText(region.start, region.end - region.start, text); |
| |
| int nRemains = fParser.cleanupUpdateContext(); |
| |
| if (0 < nRemains && !(result instanceof NoChangeEvent)) { |
| throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "");//$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * |
| */ |
| void attrReplaced(CSSNodeImpl parentNode, CSSNodeImpl newAttr, CSSNodeImpl oldAttr) { |
| if (parentNode == null) { |
| return; |
| } |
| |
| if (oldAttr != null) { |
| attrRemoved(parentNode, (CSSAttrImpl) oldAttr); |
| } |
| |
| if (newAttr != null) { |
| attrInserted(parentNode, (CSSAttrImpl) newAttr); |
| } |
| } |
| |
| /** |
| * |
| */ |
| private void childInserted(CSSNodeImpl parentNode, CSSNodeImpl node) { |
| short updateMode = CSSModelUpdateContext.UPDATE_IDLE; |
| |
| if (node instanceof CSSStructuredDocumentRegionContainer) { |
| updateMode = CSSModelUpdateContext.UPDATE_INSERT_FNCONTAINER; |
| } |
| else if (node instanceof CSSRegionContainer) { |
| updateMode = CSSModelUpdateContext.UPDATE_INSERT_RCONTAINER; |
| } |
| else { |
| CSSUtil.debugOut("What's this node? : " + node.getClass().toString());//$NON-NLS-1$ |
| return; |
| } |
| |
| fParser.setupUpdateContext(updateMode, parentNode, node); |
| |
| StructuredDocumentEvent result = defaultInserted(parentNode, node); |
| |
| int nRemains = fParser.cleanupUpdateContext(); |
| |
| if (0 < nRemains && !(result instanceof NoChangeEvent)) { |
| throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "");//$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * |
| */ |
| private void childRemoved(CSSNodeImpl parentNode, CSSNodeImpl node) { |
| short updateMode = CSSModelUpdateContext.UPDATE_IDLE; |
| |
| if (node instanceof CSSStructuredDocumentRegionContainer) { |
| updateMode = CSSModelUpdateContext.UPDATE_REMOVE_FNCONTAINER; |
| } |
| else if (node instanceof CSSRegionContainer) { |
| updateMode = CSSModelUpdateContext.UPDATE_REMOVE_RCONTAINER; |
| } |
| else { |
| CSSUtil.debugOut("What's this node? : " + node.getClass().toString());//$NON-NLS-1$ |
| return; |
| } |
| |
| fParser.setupUpdateContext(updateMode, parentNode, node); |
| |
| CSSNodeImpl prev = getOldPrevious(parentNode, node); |
| CSSNodeImpl next = getOldNext(parentNode, node); |
| int insertPos = -1, endPos = -1; |
| String source = "";//$NON-NLS-1$ |
| if (prev != null) { |
| insertPos = prev.getEndOffset(); |
| } |
| else { |
| insertPos = node.getStartOffset(); |
| insertPos -= nearestSpaceLengthBefore(parentNode, insertPos); |
| } |
| if (next != null) { |
| endPos = next.getStartOffset(); |
| } |
| else { |
| endPos = node.getEndOffset(); |
| endPos += nearestSpaceLengthAfter(parentNode, endPos); |
| } |
| source = getSpaceBefore(parentNode, next, node); |
| StructuredDocumentEvent result; |
| if (source.length() > 0) { |
| result = insertText(insertPos, endPos - insertPos, source); |
| } |
| else { |
| result = removeText(insertPos, endPos - insertPos); |
| } |
| |
| int nRemains = fParser.cleanupUpdateContext(); |
| |
| if (0 < nRemains && !(result instanceof NoChangeEvent)) { |
| throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "");//$NON-NLS-1$ |
| } |
| |
| /* |
| * int removeStart = -1; int removeEnd = -1; if (node instanceof |
| * CSSStructuredDocumentRegionContainer) { |
| * CSSStructuredDocumentRegionContainer container = |
| * (CSSStructuredDocumentRegionContainer)node; |
| * IStructuredDocumentRegion firstNode = |
| * container.getFirstStructuredDocumentRegion(); |
| * IStructuredDocumentRegion lastNode = |
| * container.getLastStructuredDocumentRegion(); if (firstNode != null && |
| * lastNode != null) { removeStart = firstNode.getStart(); removeEnd = |
| * lastNode.getEnd(); } } else if (node instanceof CSSRegionContainer) { |
| * CSSRegionContainer container = (CSSRegionContainer)node; |
| * ITextRegion firstRegion = container.getFirstRegion(); ITextRegion |
| * lastRegion = container.getLastRegion(); if (firstRegion != null && |
| * lastRegion != null) { removeStart = firstRegion.getStartOffset(); |
| * removeEnd = lastRegion.getEndOffset(); } } if (0 <= removeStart && |
| * 0 <= removeEnd) { removeText(removeStart, removeEnd - removeStart + |
| * 1); } |
| */ |
| } |
| |
| /** |
| * |
| */ |
| void childReplaced(CSSNodeImpl parentNode, CSSNodeImpl newChild, CSSNodeImpl oldChild) { |
| if (parentNode == null) { |
| return; |
| } |
| |
| if (oldChild != null) { |
| childRemoved(parentNode, oldChild); |
| } |
| |
| if (newChild != null) { |
| childInserted(parentNode, newChild); |
| } |
| } |
| |
| /** |
| * |
| */ |
| private StructuredDocumentEvent defaultInserted(CSSNodeImpl parentNode, CSSNodeImpl node) { |
| int insertPos = -1; |
| ICSSNode sibling; |
| String preSpace = "", postSpace = "";//$NON-NLS-2$//$NON-NLS-1$ |
| int length = 0; |
| |
| if (insertPos < 0) { |
| if ((sibling = node.getPreviousSibling()) != null) { |
| // after previous child |
| insertPos = getTextEnd(sibling); |
| } |
| } |
| |
| if (insertPos < 0) { |
| if ((sibling = node.getNextSibling()) != null) { |
| // before next child |
| insertPos = getTextStart(sibling); |
| } |
| } |
| |
| if (insertPos < 0) { |
| // position of parent |
| insertPos = getChildInsertPos(parentNode); |
| } |
| |
| if (insertPos < 0) { |
| // firsttime |
| insertPos = 0; |
| } |
| |
| // format previous spaces |
| length = nearestSpaceLengthBefore(parentNode, insertPos); |
| insertPos -= length; |
| preSpace = getSpaceBefore(parentNode, node, null); |
| // format post spaces |
| length += nearestSpaceLengthAfter(parentNode, insertPos + length); |
| postSpace = getSpaceBefore(parentNode, node.getNextSibling(), null); |
| |
| // set text |
| String text = preSpace + node.generateSource().trim() + postSpace; |
| return insertText(insertPos, length, text); |
| } |
| |
| /** |
| * @return int |
| * @param node |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| */ |
| private int getChildInsertPos(CSSNodeImpl node) { |
| CSSSourceGenerator formatter = CSSSourceFormatterFactory.getInstance().getSourceFormatter(node); |
| if (formatter != null) |
| return formatter.getChildInsertPos(node); |
| else |
| return node.getEndOffset(); |
| } |
| |
| /** |
| * @return org.eclipse.wst.css.core.model.CSSNodeImpl |
| * @param parentNode |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| * @param node |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| */ |
| private CSSNodeImpl getOldNext(CSSNodeImpl parentNode, CSSNodeImpl node) { |
| CSSNodeImpl child = (CSSNodeImpl) parentNode.getLastChild(); |
| CSSNodeImpl ret = null; |
| while (child != null) { |
| if (node.getEndOffset() < child.getEndOffset()) |
| ret = child; |
| else |
| break; |
| child = (CSSNodeImpl) child.getPreviousSibling(); |
| } |
| return ret; |
| } |
| |
| /** |
| * @return org.eclipse.wst.css.core.model.CSSNodeImpl |
| * @param parentNode |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| * @param node |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| */ |
| private CSSNodeImpl getOldPrevious(CSSNodeImpl parentNode, CSSNodeImpl node) { |
| CSSNodeImpl child = (CSSNodeImpl) parentNode.getFirstChild(); |
| CSSNodeImpl ret = null; |
| while (child != null) { |
| if (child.getStartOffset() < node.getStartOffset()) |
| ret = child; |
| else |
| break; |
| child = (CSSNodeImpl) child.getNextSibling(); |
| } |
| return ret; |
| } |
| |
| /** |
| * @return java.lang.String |
| * @param parentNode |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| * @param node |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| */ |
| private String getSpaceBefore(ICSSNode parentNode, ICSSNode node, ICSSNode toRemove) { |
| CSSSourceGenerator formatter = CSSSourceFormatterFactory.getInstance().getSourceFormatter((INodeNotifier) parentNode); |
| |
| if (formatter != null) { |
| org.eclipse.jface.text.IRegion exceptFor = null; |
| if (toRemove != null) { |
| CSSNodeImpl remove = (CSSNodeImpl) toRemove; |
| exceptFor = new org.eclipse.jface.text.Region(remove.getStartOffset(), remove.getEndOffset() - remove.getStartOffset()); |
| } |
| return formatter.formatBefore(parentNode, node, exceptFor).toString(); |
| } |
| else |
| return "";//$NON-NLS-1$ |
| } |
| |
| /** |
| * |
| */ |
| private int getTextEnd(ICSSNode node) { |
| int end = -1; |
| if (node != null) { |
| if (node instanceof CSSStructuredDocumentRegionContainer) { |
| end = ((CSSStructuredDocumentRegionContainer) node).getEndOffset(); |
| } |
| else if (node instanceof CSSRegionContainer) { |
| end = ((CSSRegionContainer) node).getEndOffset(); |
| } |
| } |
| return end; |
| } |
| |
| /** |
| * |
| */ |
| private int getTextStart(ICSSNode node) { |
| int start = -1; |
| if (node != null) { |
| if (node instanceof CSSStructuredDocumentRegionContainer) { |
| start = ((CSSStructuredDocumentRegionContainer) node).getStartOffset(); |
| } |
| else if (node instanceof CSSRegionContainer) { |
| start = ((CSSRegionContainer) node).getStartOffset(); |
| } |
| } |
| return start; |
| } |
| |
| /** |
| * |
| */ |
| private StructuredDocumentEvent insertText(int start, int oldLength, String text) { |
| StructuredDocumentEvent result = null; |
| BasicStructuredDocument structuredDocument = (BasicStructuredDocument) fModel.getStructuredDocument(); |
| if (structuredDocument != null) { |
| if (text != null && 0 < oldLength && start + oldLength <= structuredDocument.getLength()) { |
| // minimize text change |
| String delText = structuredDocument.get(start, oldLength); |
| int newLength = text.length(); |
| int shorterLen = Math.min(oldLength, newLength); |
| int stMatchLen; |
| for (stMatchLen = 0; stMatchLen < shorterLen && text.charAt(stMatchLen) == delText.charAt(stMatchLen); stMatchLen++) { |
| // |
| } |
| if (0 < stMatchLen && stMatchLen < shorterLen && text.charAt(stMatchLen - 1) == 0x000d && (text.charAt(stMatchLen) == 0x000a || delText.charAt(stMatchLen) == 0x000a)) { |
| // must not divide 0d->0a sequence |
| stMatchLen--; |
| } |
| if (stMatchLen == shorterLen) { |
| if (oldLength < newLength) { // just insert |
| oldLength = 0; |
| start += stMatchLen; |
| text = text.substring(stMatchLen); |
| } |
| else if (newLength < oldLength) { // just remove |
| oldLength -= stMatchLen; |
| start += stMatchLen; |
| text = null; |
| } |
| else { // nothing to do |
| oldLength = 0; |
| text = null; |
| } |
| } |
| else { |
| int edMatchLen; |
| for (edMatchLen = 0; stMatchLen + edMatchLen < shorterLen && text.charAt(newLength - edMatchLen - 1) == delText.charAt(oldLength - edMatchLen - 1); edMatchLen++) { |
| // |
| } |
| if (0 < edMatchLen && text.charAt(newLength - edMatchLen) == 0x000a && ((edMatchLen < newLength && text.charAt(newLength - edMatchLen - 1) == 0x000d) || (edMatchLen < oldLength && delText.charAt(oldLength - edMatchLen - 1) == 0x000d))) { |
| // must not divide 0d->0a sequence |
| edMatchLen--; |
| } |
| oldLength -= stMatchLen + edMatchLen; |
| start += stMatchLen; |
| if (stMatchLen + edMatchLen < newLength) { |
| text = text.substring(stMatchLen, newLength - edMatchLen); |
| } |
| else { |
| text = null; |
| } |
| } |
| } |
| if (0 < oldLength || text != null) { |
| // String delText = structuredDocument.get(start, oldLength); |
| result = structuredDocument.replaceText(fModel, start, oldLength, text); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * @return int |
| * @param insertPos |
| * int |
| */ |
| private int nearestSpaceLengthAfter(CSSNodeImpl node, int insertPos) { |
| CSSSourceGenerator formatter = CSSSourceFormatterFactory.getInstance().getSourceFormatter(node); |
| if (formatter != null) { |
| return formatter.getLengthToReformatAfter(node, insertPos); |
| } |
| else |
| return 0; |
| } |
| |
| /** |
| * @return int |
| * @param insertPos |
| * int |
| */ |
| private int nearestSpaceLengthBefore(CSSNodeImpl node, int insertPos) { |
| CSSSourceGenerator formatter = CSSSourceFormatterFactory.getInstance().getSourceFormatter(node); |
| if (formatter != null) { |
| return formatter.getLengthToReformatBefore(node, insertPos); |
| } |
| else |
| return 0; |
| } |
| |
| /** |
| * |
| */ |
| private StructuredDocumentEvent removeText(int start, int length) { |
| StructuredDocumentEvent result = null; |
| IStructuredDocument structuredDocument = fModel.getStructuredDocument(); |
| if (structuredDocument != null) { |
| result = structuredDocument.replaceText(fModel, start, length, new String(""));//$NON-NLS-1$ |
| } |
| return result; |
| } |
| |
| /** |
| * |
| */ |
| void setParser(CSSModelParser parser) { |
| fParser = parser; |
| } |
| |
| /** |
| * |
| */ |
| void valueChanged(CSSNodeImpl node, String oldValue) { |
| if (!(node instanceof CSSRegionContainer)) { |
| CSSUtil.debugOut("Too Bad.." + //$NON-NLS-1$ |
| ((node == null) ? "null" : node.getClass().toString()));//$NON-NLS-1$ |
| return; |
| } |
| |
| int start = node.getStartOffset(); |
| |
| if (node.getNodeType() == ICSSNode.ATTR_NODE) { |
| ICSSAttr attr = (ICSSAttr) node; |
| CSSSourceGenerator formatter = CSSSourceFormatterFactory.getInstance().getSourceFormatter((INodeNotifier) attr.getOwnerCSSNode()); |
| if (formatter != null) |
| start = formatter.getAttrInsertPos(attr.getOwnerCSSNode(), attr.getName()); |
| } |
| |
| int oldLength = (oldValue == null) ? 0 : oldValue.length(); |
| // flash old IStructuredDocumentRegion/ITextRegion |
| if (node instanceof CSSStructuredDocumentRegionContainer) { |
| ((CSSStructuredDocumentRegionContainer) node).setFirstStructuredDocumentRegion(null); |
| ((CSSStructuredDocumentRegionContainer) node).setLastStructuredDocumentRegion(null); |
| } |
| else if (node instanceof CSSRegionContainer) { |
| ((CSSRegionContainer) node).setRangeRegion(null, null, null); |
| } |
| // generate new source |
| String newValue = node.generateSource(); |
| |
| ICSSNode parent; |
| if (node.getNodeType() == ICSSNode.ATTR_NODE) { |
| parent = ((ICSSAttr) node).getOwnerCSSNode(); |
| } |
| else { |
| parent = node.getParentNode(); |
| } |
| |
| fParser.setupUpdateContext(CSSModelUpdateContext.UPDATE_CHANGE_RCONTAINER, parent, node); |
| |
| StructuredDocumentEvent result = insertText(start, oldLength, newValue); |
| |
| int nRemains = fParser.cleanupUpdateContext(); |
| |
| if (0 < nRemains && !(result instanceof NoChangeEvent)) { |
| throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "");//$NON-NLS-1$ |
| } |
| } |
| } |