blob: 44471209511fa0abca16d2b46fbfc07a81a40cc1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 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
*******************************************************************************/
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.Attr;
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;
StyleSheet sheet = ((IStyleSheetAdapter) validAdapters.get(index)).getSheet();
if (sheet == null) {// for LINK element whose link is broken
ICSSModel model = ((AbstractStyleSheetAdapter) validAdapters.get(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 :
if (newValue instanceof Node)
node = (Node) newValue;
else if (changedFeature instanceof Attr)
node = ((Attr) changedFeature);
break;
case INodeNotifier.REMOVE :
if (oldValue instanceof Node)
node = (Node) oldValue;
else if (changedFeature instanceof Attr)
node = ((Attr) changedFeature);
break;
case INodeNotifier.CHANGE :
node = (Node) notifier;
break;
default :
break;
}
if (node == null)
return;
switch(node.getNodeType()) {
case Node.ELEMENT_NODE: {
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();
}
}
}
}
case Node.ATTRIBUTE_NODE : {
if (HTML40Namespace.ElementName.BASE.equals(((Attr) node).getOwnerElement().getLocalName())) {
refreshAdapters();
}
}
}
}
/**
* 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;
}
}