| /******************************************************************************* |
| * 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 java.util.Collection; |
| import java.util.Enumeration; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| |
| import org.eclipse.wst.css.core.internal.parserz.CSSRegionContexts; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSCharsetRule; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSImportRule; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSMediaRule; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSModel; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSPageRule; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSRuleContainer; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSStyleDeclItem; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSStyleRule; |
| import org.eclipse.wst.css.core.internal.text.StructuredDocumentWalker; |
| import org.eclipse.wst.css.core.internal.util.CSSUtil; |
| 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.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.sse.core.internal.text.BasicStructuredDocumentRegion; |
| import org.eclipse.wst.sse.core.internal.text.CoreNodeList; |
| import org.eclipse.wst.sse.core.internal.text.TextRegionListImpl; |
| import org.eclipse.wst.sse.core.internal.util.Assert; |
| import org.w3c.dom.css.CSSFontFaceRule; |
| import org.w3c.dom.css.CSSRule; |
| import org.w3c.dom.css.CSSStyleDeclaration; |
| |
| |
| /** |
| * |
| */ |
| class CSSModelParser { |
| |
| private CSSDocumentImpl fDocument = null; |
| private CSSModelCreationContext fCreationContext = null; |
| private CSSModelDeletionContext fDeletionContext = null; |
| private CSSModelUpdateContext fUpdateContext = null; |
| private CSSModelNodeFeeder fFeeder = null; |
| private StructuredDocumentWalker fStructuredDocumentWalker = null; |
| private boolean fParseFloating = false; |
| |
| /** |
| * CSSModelParser constructor comment. |
| */ |
| private CSSModelParser() { |
| super(); |
| } |
| |
| /** |
| * |
| */ |
| CSSModelParser(CSSDocumentImpl doc) { |
| super(); |
| fDocument = doc; |
| fCreationContext = new CSSModelCreationContext(doc); |
| fDeletionContext = new CSSModelDeletionContext(doc); |
| fUpdateContext = new CSSModelUpdateContext(); |
| fFeeder = new CSSModelNodeFeeder(doc, fUpdateContext); |
| } |
| |
| /** |
| * |
| */ |
| void changeStructuredDocumentRegion(IStructuredDocumentRegion flatNode) { |
| replaceStructuredDocumentRegions(new CoreNodeList(flatNode, flatNode), new CoreNodeList(flatNode, flatNode)); |
| } |
| |
| /** |
| * |
| */ |
| void changeRegion(IStructuredDocumentRegion flatNode, ITextRegion region) { |
| if (flatNode == null || region == null) { |
| return; |
| } |
| if (fDocument == null) { |
| return; |
| } |
| |
| changeStructuredDocumentRegion(flatNode); |
| } |
| |
| /** |
| * |
| */ |
| private void checkNextNode(IStructuredDocumentRegion flatNode, String type) { |
| IStructuredDocumentRegion next = CSSUtil.findNextSignificantNode(flatNode); |
| if (CSSUtil.getStructuredDocumentRegionType(next) == type) { |
| fCreationContext.setReparseStart(flatNode.getEnd()); |
| fCreationContext.setReparseEnd(next.getEnd()); |
| } |
| } |
| |
| /** |
| * |
| */ |
| private void cleanupDeletionContext() { |
| // setupDeletionContext(null); |
| } |
| |
| /** |
| * |
| */ |
| boolean cleanupFirstNode(IStructuredDocumentRegion firstNode, CSSStructuredDocumentRegionContainer target) { |
| if (firstNode == null || target == null) { |
| return false; |
| } |
| |
| if (firstNode == target.getFirstStructuredDocumentRegion()) { |
| IStructuredDocumentRegion nextNode = fStructuredDocumentWalker.getNextNodeInCurrent(firstNode); |
| IStructuredDocumentRegion lastNode = target.getLastStructuredDocumentRegion(); |
| if (lastNode == null || fStructuredDocumentWalker.isOldNode(lastNode) || nextNode == null || lastNode.getStartOffset() < nextNode.getStartOffset()) { |
| target.setRangeStructuredDocumentRegion(null, null); |
| } |
| else { |
| target.setFirstStructuredDocumentRegion(nextNode); |
| } |
| ICSSNode parent = target.getParentNode(); |
| if (parent instanceof CSSStructuredDocumentRegionContainer) { |
| cleanupFirstNode(firstNode, (CSSStructuredDocumentRegionContainer) parent); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * |
| */ |
| boolean cleanupLastNode(IStructuredDocumentRegion lastNode, CSSStructuredDocumentRegionContainer target) { |
| if (lastNode == null || target == null) { |
| return false; |
| } |
| |
| if (lastNode == target.getLastStructuredDocumentRegion()) { |
| IStructuredDocumentRegion prevNode = fStructuredDocumentWalker.getPrevNodeInCurrent(lastNode); |
| IStructuredDocumentRegion firstNode = target.getFirstStructuredDocumentRegion(); |
| if (firstNode == null || fStructuredDocumentWalker.isOldNode(firstNode) || prevNode == null || prevNode.getStartOffset() < firstNode.getStartOffset()) { |
| target.setRangeStructuredDocumentRegion(null, null); |
| } |
| else { |
| target.setLastStructuredDocumentRegion(prevNode); |
| } |
| ICSSNode parent = target.getParentNode(); |
| if (parent instanceof CSSStructuredDocumentRegionContainer) { |
| cleanupLastNode(lastNode, (CSSStructuredDocumentRegionContainer) parent); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * |
| */ |
| int cleanupUpdateContext() { |
| int remains = fUpdateContext.getNodeCount(); |
| fUpdateContext.cleanupContext(); |
| return remains; |
| } |
| |
| /** |
| * create floating CSS rule (owner document will be set) this is for |
| * CSSStyleSheet.createCSSRule(String) |
| * |
| * @return org.w3c.dom.css.CSSRule |
| */ |
| CSSRule createCSSRule(IStructuredDocumentRegionList flatNodes) { |
| if (flatNodes == null) { |
| return null; |
| } |
| |
| fParseFloating = true; |
| |
| // setup creation context |
| fCreationContext.clear(); |
| fCreationContext.setTargetNode(null); |
| fCreationContext.setNextNode(null); |
| CSSRuleImpl parentRule = null; |
| |
| for (Enumeration e = flatNodes.elements(); e.hasMoreElements();) { |
| IStructuredDocumentRegion flatNode = (IStructuredDocumentRegion) e.nextElement(); |
| if (flatNode == null) { |
| continue; |
| } |
| CSSNodeImpl modified = insertStructuredDocumentRegion(flatNode); |
| if (parentRule == null && modified instanceof CSSRuleImpl) { |
| parentRule = (CSSRuleImpl) modified; |
| } |
| } |
| |
| fParseFloating = false; |
| |
| if (parentRule != null) { |
| CSSModelUtil.cleanupContainer(parentRule); |
| } |
| |
| return parentRule; |
| } |
| |
| /** |
| * |
| */ |
| private IStructuredDocumentRegion findBraceClose(int depth, IStructuredDocumentRegion start, boolean bBreakSibling) { |
| IStructuredDocumentRegion result = null; |
| int braceLevel = 0; |
| IStructuredDocumentRegion prevNode = null; |
| IStructuredDocumentRegion region = start.getNext(); |
| for (; region != null; region = region.getNext()) { |
| int offset = region.getStart(); |
| if (offset < 0) { |
| Assert.isTrue(false); |
| break; |
| } |
| CSSNodeImpl node = getNodeAt(offset); |
| int depthHit = (node != null) ? CSSModelUtil.getDepth(node) : -1; |
| if (depth <= depthHit) { |
| // sibling is found before "}" |
| if (bBreakSibling) { |
| CSSNodeImpl parent = (CSSNodeImpl) node.getParentNode(); |
| while (depth <= CSSModelUtil.getDepth(parent)) { |
| node = parent; |
| parent = (CSSNodeImpl) parent.getParentNode(); |
| } |
| if (parent != null && node != null) { |
| parent.removeChild(node); |
| } |
| } |
| else { |
| result = prevNode; |
| break; |
| } |
| } |
| String type = CSSUtil.getStructuredDocumentRegionType(region); |
| if (type == CSSRegionContexts.CSS_LBRACE) { |
| braceLevel++; |
| } |
| if (type == CSSRegionContexts.CSS_RBRACE) { |
| braceLevel--; |
| if (braceLevel < 0) { |
| result = region; |
| break; |
| } |
| } |
| prevNode = region; |
| } |
| |
| if (result == null && region == null) { |
| // reach the end of document |
| result = prevNode; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * |
| */ |
| private IStructuredDocumentRegionList getStructuredDocumentRegionList(int start, int end) { |
| IStructuredDocumentRegionList nodeList = null; |
| if (0 <= start && start <= end) { |
| ICSSModel model = fDocument.getModel(); |
| if (model instanceof CSSModelImpl) { |
| IStructuredDocument structuredDocument = ((CSSModelImpl) model).getStructuredDocument(); |
| if (structuredDocument != null) { |
| IStructuredDocumentRegion startNode = structuredDocument.getRegionAtCharacterOffset(start); |
| IStructuredDocumentRegion endNode = structuredDocument.getRegionAtCharacterOffset(end - 1); |
| if (startNode != null && endNode != null) { |
| nodeList = new CoreNodeList(startNode, endNode); |
| } |
| } |
| } |
| } |
| |
| return nodeList; |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl getNodeAt(int offset) { |
| CSSNodeImpl rootNode = fCreationContext.getRootNode(); |
| ICSSNode target = rootNode.getNodeAt(offset); |
| if (target instanceof CSSNodeImpl) { |
| return (CSSNodeImpl) target; |
| } |
| else { |
| return null; |
| } |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl insertBraceClose(IStructuredDocumentRegion region) { |
| ICSSNode target = CSSModelUtil.findBraceContainer(fCreationContext.getTargetNode()); |
| |
| if (!(target instanceof CSSStructuredDocumentRegionContainer)) { |
| return null; |
| } |
| |
| CSSStructuredDocumentRegionContainer parent = (CSSStructuredDocumentRegionContainer) target; |
| if (CSSModelUtil.isInterruption(parent, region)) { |
| ICSSNode node = parent.getParentNode(); |
| if (node instanceof CSSNodeImpl) { |
| fCreationContext.setReparseStart(parent.getStartOffset()); |
| fCreationContext.setReparseEnd(parent.getEndOffset()); |
| ((CSSNodeImpl) node).removeChild(parent); |
| } |
| return null; |
| } |
| |
| if (parent instanceof ICSSPageRule || parent instanceof ICSSMediaRule || parent instanceof CSSFontFaceRule || parent instanceof ICSSStyleRule) { |
| CSSModelUtil.expandStructuredDocumentRegionContainer(parent, region); |
| if (!CSSModelUtil.isBraceClosed(target)) { |
| fCreationContext.setTargetNode(parent.getParentNode()); |
| fCreationContext.setNextNode(parent.getNextSibling()); |
| return parent; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl insertBraceOpen(IStructuredDocumentRegion region) { |
| IStructuredDocumentRegion keyRegion = CSSUtil.findPreviousSignificantNode(region); |
| if (keyRegion == null) { |
| return null; |
| } |
| if (!fParseFloating) { |
| CSSNodeImpl node = getNodeAt(keyRegion.getStartOffset()); |
| if (node != null && !(node instanceof ICSSRuleContainer)) { |
| return null; |
| } |
| } |
| String type = CSSUtil.getStructuredDocumentRegionType(keyRegion); |
| |
| CSSNodeImpl inserted = null; |
| if (type == CSSRegionContexts.CSS_PAGE) { |
| inserted = insertPageRule(keyRegion, region); |
| } |
| else if (type == CSSRegionContexts.CSS_MEDIA) { |
| inserted = insertMediaRule(keyRegion, region); |
| } |
| else if (type == CSSRegionContexts.CSS_FONT_FACE) { |
| inserted = insertFontFaceRule(keyRegion, region); |
| } |
| else if (CSSUtil.isSelectorText(keyRegion)) { |
| inserted = insertStyleRule(keyRegion, region); |
| } |
| |
| if (inserted instanceof CSSStructuredDocumentRegionContainer) { |
| // CSSModelUtil.expandStructuredDocumentRegionContainer((CSSStructuredDocumentRegionContainer)inserted, |
| // flatNode); |
| IStructuredDocumentRegion braceClose = findBraceClose(CSSModelUtil.getDepth(inserted), region, (type == CSSRegionContexts.CSS_MEDIA)); |
| if (braceClose != null) { |
| fCreationContext.setReparseStart(region.getEnd()); |
| fCreationContext.setReparseEnd(braceClose.getEnd()); |
| } |
| } |
| |
| return inserted; |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl insertCharsetRule(IStructuredDocumentRegion beginDocRegion, IStructuredDocumentRegion endDocRegion) { |
| CSSNodeImpl parent = fCreationContext.getTargetNode(); |
| if (!fParseFloating && !(parent instanceof ICSSRuleContainer)) { |
| return null; |
| } |
| |
| ITextRegionList regions = new TextRegionListImpl(beginDocRegion.getRegions()); |
| regions.remove(0); // must be "@charset" |
| ITextRegion encodingRegion = null; |
| while (!regions.isEmpty()) { |
| ITextRegion textRegion = regions.remove(0); |
| if (textRegion == null) { |
| continue; |
| } |
| String type = textRegion.getType(); |
| if (type == CSSRegionContexts.CSS_S || type == CSSRegionContexts.CSS_COMMENT) { |
| continue; |
| } |
| if (type == CSSRegionContexts.CSS_STRING) { |
| encodingRegion = textRegion; |
| break; |
| } |
| else { |
| break; |
| } |
| } |
| if (encodingRegion == null) { |
| return null; |
| } |
| |
| CSSCharsetRuleImpl rule = fFeeder.getCSSCharsetRule(); |
| if (rule == null) { |
| return null; |
| } |
| |
| if (!fUpdateContext.isActive()) { |
| rule.setAttribute(ICSSCharsetRule.ENCODING, beginDocRegion.getText(encodingRegion)); |
| } |
| |
| // setup flat container |
| rule.setRangeStructuredDocumentRegion(beginDocRegion, endDocRegion); |
| CSSAttrImpl attr = rule.getAttributeNode(ICSSCharsetRule.ENCODING); |
| if (attr != null) { |
| attr.setRangeRegion(beginDocRegion, encodingRegion, encodingRegion); |
| } |
| |
| // insert to tree |
| if (!fUpdateContext.isActive() && parent != null) { |
| propagateRangePreInsert(parent, rule); |
| CSSNodeImpl next = fCreationContext.getNextNode(); |
| if (next != null) { |
| parent.insertBefore(rule, next); |
| } |
| else { |
| parent.appendChild(rule); |
| } |
| } |
| |
| return rule; |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl insertStructuredDocumentRegion(IStructuredDocumentRegion region) { |
| if (fCreationContext == null || region == null) { |
| return null; |
| } |
| |
| String type = ((BasicStructuredDocumentRegion) region).getType(); |
| CSSNodeImpl modified = null; |
| |
| ICSSNode target = fCreationContext.getTargetNode(); |
| |
| if ((fParseFloating && target == null) || target instanceof ICSSRuleContainer) { |
| if (type == CSSRegionContexts.CSS_DELIMITER) { |
| modified = insertSemiColonForRule(region); |
| } |
| else if (type == CSSRegionContexts.CSS_LBRACE) { |
| modified = insertBraceOpen(region); |
| } |
| else if (type == CSSRegionContexts.CSS_RBRACE) { |
| modified = insertBraceClose(region); |
| } |
| else if (type == CSSRegionContexts.CSS_MEDIA || type == CSSRegionContexts.CSS_PAGE || type == CSSRegionContexts.CSS_FONT_FACE || CSSUtil.isSelectorText(region)) { |
| checkNextNode(region, CSSRegionContexts.CSS_LBRACE); |
| } |
| else if (type == CSSRegionContexts.CSS_IMPORT || type == CSSRegionContexts.CSS_CHARSET) { |
| checkNextNode(region, CSSRegionContexts.CSS_DELIMITER); |
| } |
| } |
| else if ((target instanceof CSSRuleDeclContainer || target instanceof CSSStyleDeclaration) && type == CSSRegionContexts.CSS_DECLARATION_PROPERTY) { |
| modified = insertStyleDeclarationItem(region); |
| } |
| else if (target instanceof ICSSStyleDeclItem && type == CSSRegionContexts.CSS_DECLARATION_DELIMITER) { |
| modified = insertSemiColonForStyleDeclarationItem(region); |
| } |
| else if (type == CSSRegionContexts.CSS_RBRACE) { |
| modified = insertBraceClose(region); |
| } |
| |
| // post process |
| if (modified != null) { |
| if (modified instanceof CSSStructuredDocumentRegionContainer) { |
| ((CSSStructuredDocumentRegionContainer) modified).propagateRangeStructuredDocumentRegion(); |
| } |
| } |
| |
| return modified; |
| } |
| |
| /** |
| * |
| */ |
| private void insertStructuredDocumentRegions(IStructuredDocumentRegionList regionList) { |
| for (Enumeration e = regionList.elements(); e.hasMoreElements();) { |
| IStructuredDocumentRegion region = (IStructuredDocumentRegion) e.nextElement(); |
| if (region == null) { |
| continue; |
| } |
| insertStructuredDocumentRegion(region); |
| if (fCreationContext.isToReparse()) { |
| int origStart = region.getEnd(); |
| int origEnd = regionList.item(regionList.getLength() - 1).getEnd(); |
| int newStart = fCreationContext.getReparseStart(); |
| int newEnd = fCreationContext.getReparseEnd(); |
| if (newStart < origStart || origEnd < newEnd) { |
| IStructuredDocumentRegionList newNodeList = getStructuredDocumentRegionList(newStart, newEnd); |
| setupCreationContext(newNodeList.item(0)); |
| insertStructuredDocumentRegions(newNodeList); |
| return; |
| } |
| else { |
| fCreationContext.resetReparseRange(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl insertFontFaceRule(IStructuredDocumentRegion region, IStructuredDocumentRegion braceNode) { |
| CSSNodeImpl parent = fCreationContext.getTargetNode(); |
| if (!fParseFloating && !(parent instanceof ICSSRuleContainer)) { |
| return null; |
| } |
| |
| CSSFontFaceRuleImpl rule = fFeeder.getCSSFontFaceRule(); |
| if (rule == null) { |
| return null; |
| } |
| |
| // setup flat container |
| rule.setRangeStructuredDocumentRegion(region, braceNode); |
| |
| // insert to tree |
| if (!fUpdateContext.isActive() && parent != null) { |
| propagateRangePreInsert(parent, rule); |
| CSSNodeImpl next = fCreationContext.getNextNode(); |
| if (next != null) { |
| parent.insertBefore(rule, next); |
| } |
| else { |
| parent.appendChild(rule); |
| } |
| } |
| |
| fCreationContext.setTargetNode(rule); |
| // TargetNext is set to null automatically |
| |
| return rule; |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl insertImportRule(IStructuredDocumentRegion beginDocRegion, IStructuredDocumentRegion endDocRegion) { |
| CSSNodeImpl parent = fCreationContext.getTargetNode(); |
| if (!fParseFloating && !(parent instanceof ICSSRuleContainer)) { |
| return null; |
| } |
| |
| ITextRegionList regions = new TextRegionListImpl(beginDocRegion.getRegions()); |
| regions.remove(0); // must be "@import" |
| ITextRegion hrefRegion = null; |
| while (!regions.isEmpty()) { |
| ITextRegion textRegion = regions.remove(0); |
| if (textRegion == null) { |
| continue; |
| } |
| String type = textRegion.getType(); |
| if (type == CSSRegionContexts.CSS_S || type == CSSRegionContexts.CSS_COMMENT) { |
| continue; |
| } |
| if (type == CSSRegionContexts.CSS_URI || type == CSSRegionContexts.CSS_STRING) { |
| hrefRegion = textRegion; |
| break; |
| } |
| else { |
| break; |
| } |
| } |
| if (hrefRegion == null) { |
| return null; |
| } |
| |
| CSSImportRuleImpl rule = fFeeder.getCSSImportRule(); |
| if (rule == null) { |
| return null; |
| } |
| |
| CSSUtil.stripSurroundingSpace(regions); |
| MediaListImpl mediaList = (MediaListImpl) rule.getMedia(); |
| setMediaList(mediaList, beginDocRegion, regions); |
| |
| if (!fUpdateContext.isActive()) { |
| rule.setAttribute(ICSSImportRule.HREF, beginDocRegion.getText(hrefRegion)); |
| } |
| |
| // setup flat container |
| rule.setRangeStructuredDocumentRegion(beginDocRegion, endDocRegion); |
| CSSAttrImpl attr = rule.getAttributeNode(ICSSImportRule.HREF); |
| if (attr != null) { |
| attr.setRangeRegion(beginDocRegion, hrefRegion, hrefRegion); |
| } |
| |
| // insert to tree |
| if (!fUpdateContext.isActive() && parent != null) { |
| propagateRangePreInsert(parent, rule); |
| CSSNodeImpl next = fCreationContext.getNextNode(); |
| if (next != null) { |
| parent.insertBefore(rule, next); |
| } |
| else { |
| parent.appendChild(rule); |
| } |
| } |
| |
| return rule; |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl insertMediaRule(IStructuredDocumentRegion flatNode, IStructuredDocumentRegion braceNode) { |
| CSSNodeImpl parent = fCreationContext.getTargetNode(); |
| if (!fParseFloating && !(parent instanceof ICSSRuleContainer)) { |
| return null; |
| } |
| |
| CSSMediaRuleImpl rule = fFeeder.getCSSMediaRule(); |
| if (rule == null) { |
| return null; |
| } |
| |
| ITextRegionList regions = new TextRegionListImpl(flatNode.getRegions()); |
| regions.remove(0); // must be "@media" |
| |
| CSSUtil.stripSurroundingSpace(regions); |
| MediaListImpl mediaList = (MediaListImpl) rule.getMedia(); |
| setMediaList(mediaList, flatNode, regions); |
| |
| // setup flat container |
| rule.setRangeStructuredDocumentRegion(flatNode, braceNode); |
| |
| // insert to tree |
| if (!fUpdateContext.isActive() && parent != null) { |
| propagateRangePreInsert(parent, rule); |
| CSSNodeImpl next = fCreationContext.getNextNode(); |
| if (next != null) { |
| parent.insertBefore(rule, next); |
| } |
| else { |
| parent.appendChild(rule); |
| } |
| } |
| |
| fCreationContext.setTargetNode(rule); |
| // TargetNext is set to null automatically |
| |
| return rule; |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl insertPageRule(IStructuredDocumentRegion flatNode, IStructuredDocumentRegion braceNode) { |
| CSSNodeImpl parent = fCreationContext.getTargetNode(); |
| if (!fParseFloating && !(parent instanceof ICSSRuleContainer)) { |
| return null; |
| } |
| |
| // get selector regions |
| ITextRegionList selectorRegions = new TextRegionListImpl(flatNode.getRegions()); |
| selectorRegions.remove(0); // must be "@page" |
| CSSUtil.stripSurroundingSpace(selectorRegions); |
| |
| CSSPageRuleImpl rule = fFeeder.getCSSPageRule(); |
| if (rule == null) { |
| return null; |
| } |
| |
| if (!fUpdateContext.isActive()) { |
| String selectorStr = CSSUtil.getRegionText(flatNode, selectorRegions); |
| if (0 < selectorStr.length()) { |
| rule.setSelectorText(selectorStr); |
| } |
| } |
| |
| // setup flat container |
| rule.setRangeStructuredDocumentRegion(flatNode, braceNode); |
| CSSAttrImpl attr = rule.getAttributeNode(ICSSPageRule.SELECTOR); |
| if (attr != null && selectorRegions != null && !selectorRegions.isEmpty()) { |
| attr.setRangeRegion(flatNode, selectorRegions.get(0), selectorRegions.get(selectorRegions.size() - 1)); |
| } |
| |
| // insert to tree |
| if (!fUpdateContext.isActive() && parent != null) { |
| propagateRangePreInsert(parent, rule); |
| CSSNodeImpl next = fCreationContext.getNextNode(); |
| if (next != null) { |
| parent.insertBefore(rule, next); |
| } |
| else { |
| parent.appendChild(rule); |
| } |
| } |
| |
| fCreationContext.setTargetNode(rule); |
| // TargetNext is set to null automatically |
| |
| return rule; |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl insertSemiColonForRule(IStructuredDocumentRegion region) { |
| IStructuredDocumentRegion keyRegion = CSSUtil.findPreviousSignificantNode(region); |
| String type = CSSUtil.getStructuredDocumentRegionType(keyRegion); |
| |
| CSSNodeImpl inserted = null; |
| if (type == CSSRegionContexts.CSS_IMPORT) { |
| inserted = insertImportRule(keyRegion, region); |
| } |
| else if (type == CSSRegionContexts.CSS_CHARSET) { |
| inserted = insertCharsetRule(keyRegion, region); |
| } |
| |
| return inserted; |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl insertSemiColonForStyleDeclarationItem(IStructuredDocumentRegion region) { |
| // only target/net node is changed. nothing to do. |
| |
| CSSNodeImpl targetNode = fCreationContext.getTargetNode(); |
| if (targetNode instanceof ICSSStyleDeclItem) { |
| int offset = targetNode.getStartOffset(); |
| // widen document region range |
| // ((CSSStyleDeclItemImpl)targetNode).setLastStructuredDocumentRegion(region); |
| CSSModelUtil.expandStructuredDocumentRegionContainer((CSSStyleDeclItemImpl) targetNode, region); |
| // psStructuredDocumentRegion indicates CSSStyleDeclItem |
| ICSSNode parentNode = targetNode.getParentNode(); |
| fCreationContext.setTargetNode(parentNode); |
| ICSSNode next = null; |
| if (parentNode.hasChildNodes()) { |
| for (ICSSNode child = targetNode.getFirstChild(); child != null; child = child.getNextSibling()) { |
| if (child instanceof CSSStructuredDocumentRegionContainer && offset < ((CSSStructuredDocumentRegionContainer) child).getStartOffset()) { |
| next = child; |
| break; |
| } |
| } |
| } |
| fCreationContext.setNextNode(next); |
| |
| return targetNode; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl insertStyleDeclarationItem(IStructuredDocumentRegion docRegion) { |
| CSSStyleDeclarationImpl parent = null; |
| CSSNodeImpl node = fCreationContext.getTargetNode(); |
| if (node instanceof CSSRuleDeclContainer) { |
| CSSRuleDeclContainer declContainer = (CSSRuleDeclContainer) node; |
| parent = (CSSStyleDeclarationImpl) declContainer.getStyle(); |
| } |
| else if (node instanceof CSSStyleDeclarationImpl) { |
| parent = (CSSStyleDeclarationImpl) node; |
| } |
| |
| CSSDeclarationItemParser itemParser = new CSSDeclarationItemParser(parent.getOwnerDocument()); |
| itemParser.setStructuredDocumentTemporary(false); |
| itemParser.setUpdateContext(fUpdateContext); |
| CSSStyleDeclItemImpl declItem = itemParser.setupDeclarationItem(docRegion); |
| if (declItem == null) { |
| return null; |
| } |
| |
| // setup flat container |
| declItem.setRangeStructuredDocumentRegion(docRegion, docRegion); |
| |
| // insert to tree |
| if (!fUpdateContext.isActive()) { |
| propagateRangePreInsert(parent, declItem); |
| CSSNodeImpl next = fCreationContext.getNextNode(); |
| if (next != null) { |
| parent.insertBefore(declItem, next); |
| } |
| else { |
| parent.appendChild(declItem); |
| } |
| } |
| |
| fCreationContext.setTargetNode(declItem); |
| // TargetNext is set to null automatically |
| |
| return declItem; |
| } |
| |
| /** |
| * |
| */ |
| private CSSNodeImpl insertStyleRule(IStructuredDocumentRegion flatNode, IStructuredDocumentRegion braceNode) { |
| CSSNodeImpl parent = fCreationContext.getTargetNode(); |
| if (!fParseFloating && !(parent instanceof ICSSRuleContainer)) { |
| return null; |
| } |
| |
| // get selector regions |
| ITextRegionList selectorRegions = new TextRegionListImpl(flatNode.getRegions()); |
| CSSUtil.stripSurroundingSpace(selectorRegions); |
| |
| CSSStyleRuleImpl rule = fFeeder.getCSSStyleRule(); |
| if (rule == null) { |
| return null; |
| } |
| |
| if (!fUpdateContext.isActive()) { |
| String selectorStr = CSSUtil.getRegionText(flatNode, selectorRegions); |
| if (selectorStr != null && 0 < selectorStr.length()) { |
| rule.setSelectorText(selectorStr); |
| } |
| } |
| |
| // setup flat container |
| rule.setRangeStructuredDocumentRegion(flatNode, braceNode); |
| CSSAttrImpl attr = rule.getAttributeNode(ICSSPageRule.SELECTOR); |
| if (attr != null && selectorRegions != null && !selectorRegions.isEmpty()) { |
| attr.setRangeRegion(flatNode, selectorRegions.get(0), selectorRegions.get(selectorRegions.size() - 1)); |
| } |
| |
| // insert to tree |
| if (!fUpdateContext.isActive() && parent != null) { |
| propagateRangePreInsert(parent, rule); |
| CSSNodeImpl next = fCreationContext.getNextNode(); |
| if (next != null) { |
| parent.insertBefore(rule, next); |
| } |
| else { |
| parent.appendChild(rule); |
| } |
| } |
| |
| fCreationContext.setTargetNode(rule); |
| // TargetNext is set to null automatically |
| |
| return rule; |
| } |
| |
| /** |
| * |
| */ |
| private void pretendRemoveNode() { |
| CSSStructuredDocumentRegionContainer node = (CSSStructuredDocumentRegionContainer) fUpdateContext.getDeletionTarget(); |
| if (node == null) { |
| return; |
| } |
| |
| IStructuredDocumentRegion firstNode = node.getFirstStructuredDocumentRegion(); |
| if (firstNode != null) { |
| fDeletionContext.expandRemovedRangeBegin(firstNode); |
| } |
| IStructuredDocumentRegion lastNode = node.getLastStructuredDocumentRegion(); |
| if (lastNode != null) { |
| fDeletionContext.expandRemovedRangeEnd(lastNode); |
| } |
| |
| shrinkContainer((CSSStructuredDocumentRegionContainer) fUpdateContext.getDeletionTargetParent(), node); |
| } |
| |
| /** |
| * @param parent |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| * @param child |
| * org.eclipse.wst.css.core.model.CSSNodeImpl |
| */ |
| private void propagateRangePreInsert(CSSNodeImpl parent, CSSNodeImpl child) { |
| if (!(child instanceof CSSStructuredDocumentRegionContainer) || !(parent instanceof CSSStructuredDocumentRegionContainer)) { |
| return; |
| } |
| |
| CSSStructuredDocumentRegionContainer parentContainer = (CSSStructuredDocumentRegionContainer) parent; |
| CSSStructuredDocumentRegionContainer childContainer = (CSSStructuredDocumentRegionContainer) child; |
| IStructuredDocumentRegion firstNode = childContainer.getFirstStructuredDocumentRegion(); |
| IStructuredDocumentRegion lastNode = childContainer.getLastStructuredDocumentRegion(); |
| if (firstNode == null || lastNode == null) { |
| return; |
| } |
| |
| boolean bModified = parentContainer.includeRangeStructuredDocumentRegion(firstNode, lastNode); |
| if (bModified) { |
| parentContainer.propagateRangeStructuredDocumentRegion(); |
| } |
| } |
| |
| /** |
| * |
| */ |
| private void removeStructuredDocumentRegion(IStructuredDocumentRegion flatNode) { |
| String type = CSSUtil.getStructuredDocumentRegionType(flatNode); |
| if (type == CSSRegionContexts.CSS_DELIMITER || type == CSSRegionContexts.CSS_DECLARATION_DELIMITER) { |
| do { |
| flatNode = fStructuredDocumentWalker.getPrevNode(flatNode); |
| type = (flatNode != null) ? CSSUtil.getStructuredDocumentRegionType(flatNode) : null; |
| } |
| while (type != null && (type == CSSRegionContexts.CSS_S || type == CSSRegionContexts.CSS_COMMENT)); |
| } |
| if (flatNode == null) { |
| return; |
| } |
| |
| // if (fDeletionContext.isInRemovedRange(flatNode)) { // already |
| // removed |
| // return; |
| // } |
| |
| CSSStructuredDocumentRegionContainer node = fDeletionContext.findDeletionTarget(fDocument, flatNode); |
| if (node == null || node == fDocument) { |
| return; // not attached with any treeNode |
| } |
| |
| if (node instanceof CSSStyleDeclarationImpl) { |
| ICSSNode rule = node.getParentNode(); |
| if (rule instanceof CSSStyleRuleImpl) { |
| node = (CSSStructuredDocumentRegionContainer) rule; |
| } |
| else { |
| return; |
| } |
| } |
| |
| // ICSSNode p = node.getParentNode(); |
| // if (p == null || ! (p instanceof |
| // CSSStructuredDocumentRegionContainer)) { |
| // return; |
| // } |
| // CSSStructuredDocumentRegionContainer parent = |
| // (CSSStructuredDocumentRegionContainer)p; |
| |
| if (fDeletionContext.addNodeToBeRemoved(node)) { |
| IStructuredDocumentRegion firstNode = node.getFirstStructuredDocumentRegion(); |
| if (firstNode != null) { |
| fDeletionContext.expandRemovedRangeBegin(firstNode); |
| } |
| IStructuredDocumentRegion lastNode = node.getLastStructuredDocumentRegion(); |
| if (lastNode != null) { |
| fDeletionContext.expandRemovedRangeEnd(lastNode); |
| } |
| } |
| |
| // shrinkContainer(node); |
| // parent.removeChild(node); |
| } |
| |
| /** |
| * |
| */ |
| private void removeStructuredDocumentRegions(IStructuredDocumentRegionList flatNodes) { |
| for (Enumeration e = flatNodes.elements(); e.hasMoreElements();) { |
| IStructuredDocumentRegion flatNode = (IStructuredDocumentRegion) e.nextElement(); |
| if (flatNode == null) { |
| continue; |
| } |
| removeStructuredDocumentRegion(flatNode); |
| } |
| |
| Iterator i = fDeletionContext.getNodesToBeRemoved(); |
| while (i.hasNext()) { |
| CSSNodeImpl node = (CSSNodeImpl) i.next(); |
| if (!(node instanceof CSSStructuredDocumentRegionContainer)) { |
| continue; |
| } |
| CSSNodeImpl parent = (CSSNodeImpl) node.getParentNode(); |
| if (!(parent instanceof CSSStructuredDocumentRegionContainer)) { |
| continue; |
| } |
| shrinkContainer((CSSStructuredDocumentRegionContainer) parent, (CSSStructuredDocumentRegionContainer) node); |
| parent.removeChild(node); |
| } |
| } |
| |
| /** |
| * |
| */ |
| void replaceStructuredDocumentRegions(IStructuredDocumentRegionList newStructuredDocumentRegions, IStructuredDocumentRegionList oldStructuredDocumentRegions) { |
| if (fDocument == null || fCreationContext == null) { |
| return; |
| } |
| |
| if (oldStructuredDocumentRegions != null && 0 < oldStructuredDocumentRegions.getLength()) { |
| setupDeletionContext(newStructuredDocumentRegions, oldStructuredDocumentRegions); |
| short updateMode = fUpdateContext.getUpdateMode(); |
| if (updateMode == CSSModelUpdateContext.UPDATE_IDLE) { |
| removeStructuredDocumentRegions(oldStructuredDocumentRegions); |
| } |
| else { |
| pretendRemoveNode(); |
| } |
| newStructuredDocumentRegions = getStructuredDocumentRegionList(fDeletionContext.getRemovedRangeBegin(), fDeletionContext.getRemovedRangeEnd()); |
| cleanupDeletionContext(); |
| } |
| |
| if (newStructuredDocumentRegions != null && 0 < newStructuredDocumentRegions.getLength()) { |
| setupCreationContext(newStructuredDocumentRegions.item(0)); |
| insertStructuredDocumentRegions(newStructuredDocumentRegions); |
| } |
| |
| // make document hold whole structuredDocument |
| /* |
| * boolean bUpdate = false; IStructuredDocumentRegion flatNode; |
| * flatNode = fDocument.getFirstStructuredDocumentRegion(); bUpdate = |
| * bUpdate || (flatNode == null || |
| * fStructuredDocumentWalker.isOldNode(flatNode) || |
| * flatNode.getPrevious() != null); flatNode = |
| * fDocument.getLastStructuredDocumentRegion(); bUpdate = bUpdate || |
| * (flatNode == null || fStructuredDocumentWalker.isOldNode(flatNode) || |
| * flatNode.getNext() != null); |
| */ |
| IStructuredDocument structuredDocument = fStructuredDocumentWalker.getStructuredDocument(); |
| fDocument.setRangeStructuredDocumentRegion(structuredDocument.getFirstStructuredDocumentRegion(), structuredDocument.getLastStructuredDocumentRegion()); |
| /* } */ |
| |
| // remove in official release |
| // CSSModelUtil.diagnoseTree(fDocument, |
| // fStructuredDocumentWalker.getStructuredDocument()); |
| } |
| |
| /** |
| * |
| */ |
| void replaceRegions(IStructuredDocumentRegion flatNode, ITextRegionList newRegions, ITextRegionList oldRegions) { |
| if (flatNode == null) { |
| return; |
| } |
| if (newRegions == null || oldRegions == null) { |
| return; |
| } |
| if (fDocument == null) { |
| return; |
| } |
| |
| changeStructuredDocumentRegion(flatNode); |
| } |
| |
| /** |
| */ |
| private void resetCreationTarget(IStructuredDocumentRegion newStructuredDocumentRegion) { |
| if (newStructuredDocumentRegion == null || newStructuredDocumentRegion.getStartOffset() <= 0) { |
| // top of document |
| fCreationContext.setTargetNode(fDocument); |
| fCreationContext.setNextNode(fDocument.getFirstChild()); |
| return; |
| } |
| |
| int cursorPos = newStructuredDocumentRegion.getStartOffset(); |
| CSSNodeImpl cursorNode = getNodeAt(cursorPos); |
| if (cursorNode == null) { // end of document |
| cursorNode = fDocument; |
| } |
| |
| // find edge of tree node |
| CSSNodeImpl node = null; |
| // boolean bOverSemiColon = false; |
| boolean bOverOpenBrace = false; |
| IStructuredDocumentRegion flatNode; |
| for (flatNode = newStructuredDocumentRegion; flatNode != null; flatNode = flatNode.getPrevious()) { |
| node = getNodeAt(flatNode.getStartOffset()); |
| if (node == null) { |
| node = fDocument; |
| } |
| if (node != cursorNode || node.getStartOffset() == flatNode.getStartOffset()) { |
| break; |
| } |
| if (flatNode != newStructuredDocumentRegion) { |
| String type = CSSUtil.getStructuredDocumentRegionType(flatNode); |
| // if (type == CSSRegionContexts.CSS_DELIMITER || |
| // type == CSSRegionContexts.CSS_DECLARATION_DELIMITER) { |
| // bOverSemiColon = true; |
| // } else |
| if (type == CSSRegionContexts.CSS_LBRACE) { |
| bOverOpenBrace = true; |
| } |
| } |
| } |
| |
| CSSNodeImpl targetNode = null; |
| |
| // if (flatNode == null) { |
| // v<--| |
| // AAAAAA |
| // BBBBBBBBBB cursorNode:A , node:B -> target is A |
| // targetNode = (node == null) ? fDocument : node; |
| // } else |
| if (cursorNode == node) { |
| // v<--| |
| // AAAAAA |
| // BBBBBBBBBB cursorNode:A , node:B -> target is A |
| if (bOverOpenBrace && cursorNode instanceof CSSRuleDeclContainer) { |
| targetNode = (CSSNodeImpl) ((CSSRuleDeclContainer) cursorNode).getStyle(); |
| } |
| else { |
| targetNode = cursorNode; |
| } |
| } |
| else { |
| // v<--| |
| // AAA |
| // BBBBBBBBBB cursorNode:B , node:A -> depend on A's node type |
| short nodeType = node.getNodeType(); |
| if (nodeType == ICSSNode.STYLEDECLITEM_NODE || nodeType == ICSSNode.CHARSETRULE_NODE || nodeType == ICSSNode.IMPORTRULE_NODE) { |
| // targetNode = (CSSNodeImpl)((bOverSemiColon) ? |
| // node.getParentNode() : node); // NP |
| String regionType = CSSUtil.getStructuredDocumentRegionType(flatNode); |
| if (regionType == CSSRegionContexts.CSS_DELIMITER || regionType == CSSRegionContexts.CSS_DECLARATION_DELIMITER) { |
| targetNode = (CSSNodeImpl) node.getParentNode(); |
| } |
| else { |
| targetNode = node; |
| } |
| } |
| else if (CSSUtil.getStructuredDocumentRegionType(flatNode) == CSSRegionContexts.CSS_RBRACE) { |
| targetNode = (CSSNodeImpl) node.getParentNode(); |
| } |
| else { |
| targetNode = node; |
| } |
| } |
| |
| fCreationContext.setTargetNode(targetNode); |
| ICSSNode next = null; |
| if (targetNode.hasChildNodes()) { |
| for (ICSSNode child = targetNode.getFirstChild(); child != null; child = child.getNextSibling()) { |
| if (child instanceof CSSStructuredDocumentRegionContainer && cursorPos < ((CSSStructuredDocumentRegionContainer) child).getStartOffset()) { |
| next = child; |
| break; |
| } |
| } |
| } |
| fCreationContext.setNextNode(next); |
| |
| } |
| |
| /** |
| */ |
| private void resetReparseRange() { |
| fCreationContext.clear(); |
| } |
| |
| void setStructuredDocumentEvent(StructuredDocumentEvent event) { |
| if (fStructuredDocumentWalker == null) { |
| fStructuredDocumentWalker = new StructuredDocumentWalker(); |
| } |
| fStructuredDocumentWalker.initialize(event); |
| } |
| |
| /** |
| * regions: surrounding spaces should be removed. Q. Why did you set |
| * mediaTable ? A. MediaList may have two or more medium that have same |
| * value, then searcing in MediaList is not perfect. Q. |
| * fUpdateContext.isActive() is not care. Are you OK? A. OK. |
| */ |
| private void setMediaList(MediaListImpl mediaList, IStructuredDocumentRegion region, ITextRegionList textRegions) { |
| if (mediaList == null || textRegions == null) { |
| return; |
| } |
| Collection mediaTable = new HashSet(); |
| CSSNodeListImpl attrs = mediaList.getMedia(); |
| for (int i = 0; i != attrs.getLength(); i++) { |
| mediaTable.add(attrs.item(i)); |
| } |
| |
| ITextRegion start = null; |
| ITextRegion end = null; |
| |
| Iterator i = textRegions.iterator(); |
| ITextRegion textRegion = (ITextRegion) ((i.hasNext()) ? i.next() : null); |
| while (textRegion != null) { |
| if (textRegion.getType() == CSSRegionContexts.CSS_MEDIUM) { |
| String mediumStr = region.getText(textRegion); |
| if (0 < mediumStr.length()) { |
| CSSAttrImpl attr = null; |
| // is the medium already set ? |
| Iterator iTable = mediaTable.iterator(); |
| while (iTable.hasNext()) { |
| CSSAttrImpl cai = (CSSAttrImpl) iTable.next(); |
| if (mediumStr.equalsIgnoreCase(cai.getValue())) { |
| attr = cai; |
| mediaTable.remove(cai); |
| break; |
| } |
| } |
| if (attr == null) { |
| // is not set. create new attribute |
| String key = "mediumP" + mediaList.mediumCounter++; //$NON-NLS-1$ |
| mediaList.setAttribute(key, mediumStr); |
| attr = mediaList.getAttributeNode(key); |
| } |
| attr.setRangeRegion(region, textRegion, textRegion); |
| if (start == null) { |
| start = textRegion; |
| } |
| end = textRegion; |
| } |
| } |
| textRegion = (ITextRegion) ((i.hasNext()) ? i.next() : null); |
| } |
| |
| if (start != null && end != null) { |
| mediaList.setRangeRegion(region, start, end); |
| } |
| } |
| |
| /** |
| * |
| */ |
| private void setupCreationContext(IStructuredDocumentRegion region) { |
| resetReparseRange(); |
| resetCreationTarget(region); |
| } |
| |
| /** |
| * |
| */ |
| private void setupDeletionContext(IStructuredDocumentRegionList newStructuredDocumentRegions, IStructuredDocumentRegionList oldStructuredDocumentRegions) { |
| fDeletionContext.setupContext(newStructuredDocumentRegions, oldStructuredDocumentRegions); |
| } |
| |
| /** |
| * |
| */ |
| void setupUpdateContext(short updateMode, ICSSNode parentNode, ICSSNode targetNode) { |
| fUpdateContext.setupContext(updateMode, parentNode, targetNode); |
| } |
| |
| /** |
| * |
| */ |
| void shrinkContainer(CSSStructuredDocumentRegionContainer parent, CSSStructuredDocumentRegionContainer child) { |
| if (child == null) { |
| return; |
| } |
| |
| boolean bModified = false; |
| bModified = bModified || cleanupLastNode(child.getLastStructuredDocumentRegion(), parent); |
| bModified = bModified || cleanupFirstNode(child.getFirstStructuredDocumentRegion(), parent); |
| |
| if (bModified) { |
| if (parent != null) { |
| for (ICSSNode node = parent.getFirstChild(); node != null; node = node.getNextSibling()) { |
| if (child != node && node instanceof CSSStructuredDocumentRegionContainer) { |
| ((CSSStructuredDocumentRegionContainer) node).propagateRangeStructuredDocumentRegion(); |
| } |
| } |
| } |
| } |
| |
| child.setRangeStructuredDocumentRegion(null, null); |
| } |
| } |