| /******************************************************************************* |
| * 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.html.core.internal.htmlcss; |
| |
| |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Enumeration; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Vector; |
| |
| import org.eclipse.wst.css.core.internal.provisional.adapters.IStyleSheetAdapter; |
| import org.eclipse.wst.css.core.internal.provisional.adapters.IStyleSheetListAdapter; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSImportRule; |
| 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.util.CSSClassTraverser; |
| import org.eclipse.wst.css.core.internal.util.ImportRuleCollector; |
| import org.eclipse.wst.html.core.internal.contentmodel.JSP11Namespace; |
| import org.eclipse.wst.html.core.internal.provisional.HTML40Namespace; |
| import org.eclipse.wst.sse.core.internal.provisional.INodeAdapter; |
| import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.css.CSSStyleDeclaration; |
| import org.w3c.dom.stylesheets.StyleSheet; |
| import org.w3c.dom.stylesheets.StyleSheetList; |
| |
| |
| |
| /** |
| */ |
| public class HTMLDocumentAdapter implements IStyleSheetListAdapter, StyleSheetList { |
| |
| private Document document = null; |
| private Vector styleAdapters = null; |
| private Vector oldStyleAdapters = null; |
| |
| /** |
| */ |
| HTMLDocumentAdapter() { |
| super(); |
| } |
| |
| /** |
| */ |
| private void addStyleSheet(Element node) { |
| IDOMElement element = (IDOMElement) node; |
| String tagName = element.getTagName(); |
| if (tagName == null) |
| return; |
| boolean isContainer = false; |
| if (tagName.equalsIgnoreCase(HTML40Namespace.ElementName.HTML) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.HEAD) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.NOSCRIPT) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.BASE) || tagName.equalsIgnoreCase(JSP11Namespace.ElementName.ROOT) || (!element.isGlobalTag() && element.isContainer())) { |
| isContainer = true; |
| } |
| else if (element.isCommentTag()) { |
| Node parent = element.getParentNode(); |
| if (parent == element.getOwnerDocument()) { |
| // This condition is too severe, actually do not work for JSF template. |
| // But above (! globalTag() && isContainer()) cover JSF template + tpl template |
| isContainer = true; |
| } |
| else if (parent.getNodeType() == Node.ELEMENT_NODE) { |
| tagName = ((Element) parent).getTagName(); |
| if (tagName != null && tagName.equalsIgnoreCase(HTML40Namespace.ElementName.HEAD)) { |
| isContainer = true; |
| } |
| } |
| } |
| |
| else { |
| String localName = element.getLocalName(); |
| if (localName != null && localName.equalsIgnoreCase(HTML40Namespace.ElementName.HTML)) { |
| // taglib html tag |
| isContainer = true; |
| } |
| else { |
| INodeNotifier notifier = element; |
| INodeAdapter adapter = notifier.getAdapterFor(IStyleSheetAdapter.class); |
| if (adapter != null && adapter instanceof IStyleSheetAdapter) { |
| this.styleAdapters.addElement(adapter); |
| } |
| } |
| } |
| if (isContainer) { |
| INodeNotifier notifier = element; |
| if (notifier.getExistingAdapter(IStyleSheetListAdapter.class) == null) { |
| notifier.addAdapter(this); |
| } |
| for (Node child = element.getFirstChild(); child != null; child = child.getNextSibling()) { |
| if (child.getNodeType() != Node.ELEMENT_NODE) |
| continue; |
| addStyleSheet((Element) child); |
| } |
| } |
| } |
| |
| /** |
| */ |
| void childReplaced() { |
| if (this.styleAdapters == null) |
| return; |
| |
| // backup old adapters to be released on updating in getStyleSheets() |
| this.oldStyleAdapters = this.styleAdapters; |
| // invalidate the list |
| this.styleAdapters = null; |
| |
| notifyStyleSheetsChanged(this.document); |
| } |
| |
| /** |
| */ |
| public Enumeration getClasses() { |
| StyleSheetList sheetList = getStyleSheets(); |
| int nSheets = sheetList.getLength(); |
| |
| final ArrayList classes = new ArrayList(); |
| |
| CSSClassTraverser traverser = new CSSClassTraverser(); |
| traverser.setTraverseImported(true); |
| |
| for (int i = 0; i < nSheets; i++) { |
| org.w3c.dom.stylesheets.StyleSheet sheet = sheetList.item(i); |
| if (sheet instanceof ICSSNode) { |
| traverser.apply((ICSSNode) sheet); |
| } |
| } |
| classes.addAll(traverser.getClassNames()); |
| |
| return new Enumeration() { |
| int i = 0; |
| |
| public boolean hasMoreElements() { |
| return i < classes.size(); |
| } |
| |
| public Object nextElement() { |
| return classes.get(i++); |
| } |
| }; |
| } |
| |
| /** |
| */ |
| private List getValidAdapters() { |
| Vector validAdapters = new Vector(); |
| if (this.styleAdapters != null) { |
| Iterator i = this.styleAdapters.iterator(); |
| while (i.hasNext()) { |
| Object obj = i.next(); |
| if (obj instanceof AbstractStyleSheetAdapter && ((AbstractStyleSheetAdapter) obj).isValidAttribute()) { |
| validAdapters.add(obj); |
| } |
| } |
| } |
| return validAdapters; |
| } |
| |
| /** |
| */ |
| public int getLength() { |
| return getValidAdapters().size(); |
| } |
| |
| /** |
| */ |
| public CSSStyleDeclaration getOverrideStyle(Element element, String pseudoName) { |
| StyleSheetList ssl = getStyleSheets(); |
| int numStyles = ssl.getLength(); |
| |
| CSSQueryTraverser query = new CSSQueryTraverser(); |
| query.setTraverseImported(true); |
| query.setTraverseImportFirst(true); |
| query.setElement(element, pseudoName); |
| |
| for (int i = 0; i < numStyles; i++) { |
| // loop for styles (<style> and <link>) |
| org.w3c.dom.stylesheets.StyleSheet ss = ssl.item(i); |
| |
| try { |
| query.apply((ICSSNode) ss); |
| } |
| catch (ClassCastException ex) { |
| // I can handle only CSS style |
| } |
| } |
| |
| return query.getDeclaration(); |
| } |
| |
| /** |
| */ |
| public StyleSheetList getStyleSheets() { |
| if (this.styleAdapters == null) { |
| if (this.document == null) |
| return null; |
| |
| this.styleAdapters = new Vector(); |
| for (Node child = this.document.getFirstChild(); child != null; child = child.getNextSibling()) { |
| if (child.getNodeType() != Node.ELEMENT_NODE) |
| continue; |
| addStyleSheet((Element) child); |
| } |
| |
| removeOldStyleSheets(); |
| } |
| return this; |
| } |
| |
| /** |
| * Allowing the INodeAdapter to compare itself against the type |
| * allows it to return true in more than one case. |
| */ |
| public boolean isAdapterForType(Object type) { |
| return (type == IStyleSheetListAdapter.class); |
| } |
| |
| /** |
| */ |
| public StyleSheet item(int index) { |
| if (this.styleAdapters == null) |
| return null; |
| |
| List validAdapters = getValidAdapters(); |
| |
| if (index < 0 || index >= validAdapters.size()) |
| return null; |
| org.w3c.dom.stylesheets.StyleSheet sheet = ((IStyleSheetAdapter) validAdapters.get(index)).getSheet(); |
| if (sheet == null) {// for LINK element whose link is broken |
| ICSSModel model = ((AbstractStyleSheetAdapter) this.styleAdapters.elementAt(index)).createModel(); |
| sheet = ((model != null) ? (StyleSheet) model.getDocument() : null); |
| } |
| return sheet; |
| } |
| |
| /** |
| */ |
| public void notifyChanged(INodeNotifier notifier, int eventType, Object changedFeature, Object oldValue, Object newValue, int pos) { |
| Node node = null; |
| switch (eventType) { |
| case INodeNotifier.ADD : |
| node = (Node) newValue; |
| break; |
| case INodeNotifier.REMOVE : |
| node = (Node) oldValue; |
| break; |
| case INodeNotifier.CHANGE : |
| node = (Node) notifier; |
| break; |
| default : |
| break; |
| } |
| if (node == null || node.getNodeType() != Node.ELEMENT_NODE) |
| return; |
| IDOMElement element = (IDOMElement) node; |
| String tagName = element.getTagName(); |
| if (tagName == null) |
| return; |
| |
| if (eventType == INodeNotifier.CHANGE) { |
| if (tagName.equalsIgnoreCase(HTML40Namespace.ElementName.BASE)) { |
| refreshAdapters(); |
| } |
| } |
| else { |
| if (tagName.equalsIgnoreCase(HTML40Namespace.ElementName.HTML) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.HEAD) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.STYLE) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.LINK) || tagName.equalsIgnoreCase(HTML40Namespace.ElementName.NOSCRIPT) || tagName.equalsIgnoreCase(JSP11Namespace.ElementName.ROOT) || element.isCommentTag() || (!element.isGlobalTag() && element.isContainer())) { |
| childReplaced(); |
| } |
| else if (tagName.equalsIgnoreCase(HTML40Namespace.ElementName.BASE)) { |
| refreshAdapters(); |
| } |
| else { |
| String localName = element.getLocalName(); |
| if (localName != null && localName.equalsIgnoreCase(HTML40Namespace.ElementName.HTML)) { |
| // taglib html tag |
| childReplaced(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * reload LINK / @import if BASE changed |
| */ |
| private void refreshAdapters() { |
| Iterator iAdapter = this.styleAdapters.iterator(); |
| while (iAdapter.hasNext()) { |
| Object adapter = iAdapter.next(); |
| if (adapter instanceof LinkElementAdapter) { |
| ((LinkElementAdapter) adapter).refreshSheet(); |
| } |
| else if (adapter instanceof StyleElementAdapter) { |
| ICSSModel model = ((StyleElementAdapter) adapter).getModel(); |
| ImportRuleCollector trav = new ImportRuleCollector(); |
| trav.apply(model); |
| Iterator iRule = trav.getRules().iterator(); |
| while (iRule.hasNext()) { |
| ICSSImportRule rule = (ICSSImportRule) iRule.next(); |
| rule.refreshStyleSheet(); |
| } |
| } |
| } |
| } |
| |
| /** |
| */ |
| private void notifyStyleSheetsChanged(Document target) { |
| INodeNotifier notifier = (INodeNotifier) target; |
| if (notifier == null) |
| return; |
| Collection adapters = notifier.getAdapters(); |
| if (adapters == null) |
| return; |
| Iterator it = adapters.iterator(); |
| if (it == null) |
| return; |
| while (it.hasNext()) { |
| INodeAdapter adapter = (INodeAdapter) it.next(); |
| if (adapter instanceof StyleListener) { |
| StyleListener listener = (StyleListener) adapter; |
| listener.styleChanged(); |
| } |
| } |
| } |
| |
| /** |
| */ |
| private void releaseOldStyleSheets() { |
| if (this.oldStyleAdapters == null) |
| return; |
| Iterator it = this.oldStyleAdapters.iterator(); |
| while (it.hasNext()) { |
| IStyleSheetAdapter adapter = (IStyleSheetAdapter) it.next(); |
| if (adapter == null) |
| continue; |
| // if the same adapter is in the current list, |
| // do not release |
| if (this.styleAdapters != null && this.styleAdapters.contains(adapter)) |
| continue; |
| adapter.released(); |
| } |
| this.oldStyleAdapters = null; |
| } |
| |
| /** |
| */ |
| public void releaseStyleSheets() { |
| releaseOldStyleSheets(); |
| |
| if (this.styleAdapters == null) |
| return; |
| Iterator it = this.styleAdapters.iterator(); |
| while (it.hasNext()) { |
| IStyleSheetAdapter adapter = (IStyleSheetAdapter) it.next(); |
| if (adapter != null) |
| adapter.released(); |
| } |
| this.styleAdapters = null; |
| } |
| |
| /** |
| */ |
| private void removeOldStyleSheets() { |
| if (this.oldStyleAdapters == null) |
| return; |
| Iterator it = this.oldStyleAdapters.iterator(); |
| while (it.hasNext()) { |
| IStyleSheetAdapter adapter = (IStyleSheetAdapter) it.next(); |
| if (adapter == null) |
| continue; |
| // if the same adapter is in the current list, |
| // do not release |
| if (this.styleAdapters != null && this.styleAdapters.contains(adapter)) |
| continue; |
| adapter.removed(); |
| } |
| this.oldStyleAdapters = null; |
| } |
| |
| /** |
| */ |
| void setDocument(Document document) { |
| this.document = document; |
| } |
| } |