| /******************************************************************************* |
| * Copyright (c) 2004, 2005 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.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.wst.css.core.internal.parserz.CSSRegionContexts; |
| import org.eclipse.wst.css.core.internal.parserz.CSSTextToken; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSSelectorCombinator; |
| import org.eclipse.wst.css.core.internal.provisional.document.ICSSSelectorItem; |
| |
| |
| /** |
| * |
| */ |
| public class CSSSelectorParser { |
| |
| private final static int IDLE = 0; |
| private final static int ATTRIBUTE = 1; |
| private final static int SIMPLE = 2; |
| |
| private List fTokens = null; |
| private List fItems = null; |
| private List fErrors = null; |
| |
| /** |
| * |
| */ |
| CSSSelectorParser(List tokens) { |
| super(); |
| fTokens = new ArrayList(tokens); |
| } |
| |
| /** |
| * |
| */ |
| List getSelector() { |
| if (fItems == null) { |
| parseSelector(); |
| } |
| return fItems; |
| } |
| |
| /** |
| * |
| */ |
| private void parseSelector() { |
| fItems = new ArrayList(); |
| |
| List attrBuf = null; |
| CSSSimpleSelector item = null; |
| |
| int status = IDLE; |
| Iterator i = fTokens.iterator(); |
| while (i.hasNext()) { |
| CSSTextToken token = (CSSTextToken) i.next(); |
| if (token == null || token.kind == CSSRegionContexts.CSS_S || token.kind == CSSRegionContexts.CSS_COMMENT) { |
| continue; |
| } |
| switch (status) { |
| case IDLE : |
| if (isTag(token)) { |
| item = createSimple(); |
| appendTag(item, token); |
| status = SIMPLE; |
| } |
| else if (isID(token)) { |
| item = createSimple(); |
| appendID(item, token); |
| status = SIMPLE; |
| } |
| else if (isClass(token)) { |
| item = createSimple(); |
| appendClass(item, token); |
| status = SIMPLE; |
| } |
| else if (isPseudo(token)) { |
| item = createSimple(); |
| appendPseudo(item, token); |
| status = SIMPLE; |
| } |
| else if (isAttributeBegin(token)) { |
| item = createSimple(); |
| status = ATTRIBUTE; |
| } |
| else { |
| addError(token); |
| } |
| break; |
| case SIMPLE : |
| if (isID(token)) { |
| appendID(item, token); |
| } |
| else if (isClass(token)) { |
| appendClass(item, token); |
| } |
| else if (isPseudo(token)) { |
| appendPseudo(item, token); |
| } |
| else if (isAttributeBegin(token)) { |
| status = ATTRIBUTE; |
| } |
| else if (isCombinator(token)) { // combinator |
| closeItem(item); |
| closeItem(createCombinator(token)); |
| status = IDLE; |
| } |
| else { |
| addError(token); |
| } |
| break; |
| case ATTRIBUTE : |
| if (isAttributeEnd(token)) { |
| appendAttribute(item, attrBuf); |
| attrBuf = null; |
| status = SIMPLE; |
| } |
| else { |
| if (attrBuf == null) { |
| attrBuf = new ArrayList(); |
| } |
| attrBuf.add(token); |
| if (!isAttributeContent(token)) { |
| addError(token); |
| } |
| } |
| break; |
| default : |
| break; |
| } |
| } |
| |
| closeItem(item); |
| } |
| |
| /** |
| * @param token |
| * @return |
| */ |
| private boolean isAttributeContent(CSSTextToken token) { |
| String type = token.kind; |
| return (type == CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_NAME || type == CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_OPERATOR || type == CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_VALUE); |
| } |
| |
| /** |
| * @param token |
| * @return |
| */ |
| private boolean isAttributeEnd(CSSTextToken token) { |
| String type = token.kind; |
| return (type == CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_END); |
| } |
| |
| /** |
| * @param token |
| * @return |
| */ |
| private boolean isAttributeBegin(CSSTextToken token) { |
| String type = token.kind; |
| return (type == CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_START); |
| } |
| |
| /** |
| * @param token |
| * @return |
| */ |
| private boolean isCombinator(CSSTextToken token) { |
| String type = token.kind; |
| return (type == CSSRegionContexts.CSS_SELECTOR_COMBINATOR); |
| } |
| |
| /** |
| * @param token |
| * @return |
| */ |
| private boolean isPseudo(CSSTextToken token) { |
| String type = token.kind; |
| return (type == CSSRegionContexts.CSS_SELECTOR_PSEUDO); |
| } |
| |
| /** |
| * @param token |
| * @return |
| */ |
| private boolean isClass(CSSTextToken token) { |
| String type = token.kind; |
| return (type == CSSRegionContexts.CSS_SELECTOR_CLASS); |
| } |
| |
| /** |
| * @param token |
| * @return |
| */ |
| private boolean isID(CSSTextToken token) { |
| String type = token.kind; |
| return (type == CSSRegionContexts.CSS_SELECTOR_ID); |
| } |
| |
| /** |
| * @param token |
| * @return |
| */ |
| private boolean isTag(CSSTextToken token) { |
| String type = token.kind; |
| return (type == CSSRegionContexts.CSS_SELECTOR_ELEMENT_NAME || type == CSSRegionContexts.CSS_SELECTOR_UNIVERSAL || type == CSSRegionContexts.CSS_UNKNOWN); |
| } |
| |
| private CSSSimpleSelector createSimple() { |
| return new CSSSimpleSelector(); |
| } |
| |
| /** |
| * if " " or "+" or ">" appeared, close simpleselector and add new |
| * combinator |
| */ |
| private CSSSelectorCombinator createCombinator(CSSTextToken token) { |
| char type = 0; |
| |
| String str = token.image; |
| if (str.trim().length() == 0) { // space |
| type = ICSSSelectorCombinator.DESCENDANT; |
| } |
| else if (str.equals("+")) { //$NON-NLS-1$ |
| type = ICSSSelectorCombinator.ADJACENT; |
| } |
| else if (str.equals(">")) { //$NON-NLS-1$ |
| type = ICSSSelectorCombinator.CHILD; |
| } |
| |
| if (0 < type) { |
| return new CSSSelectorCombinator(type); |
| } |
| else { |
| return null; |
| } |
| } |
| |
| /** |
| * |
| */ |
| private void closeItem(ICSSSelectorItem item) { |
| if (item != null) { |
| fItems.add(item); |
| } |
| } |
| |
| private void appendTag(CSSSimpleSelector item, CSSTextToken token) { |
| item.setName(token.image); |
| if (token.kind == CSSRegionContexts.CSS_UNKNOWN) { |
| addError(token); |
| } |
| } |
| |
| /** |
| * if "#xxxx" appeared, add ID to current selector |
| */ |
| private void appendID(CSSSimpleSelector item, CSSTextToken token) { |
| String text = token.toString(); |
| String idContent = text.substring(1, text.length()); |
| item.addID(idContent); |
| } |
| |
| /** |
| * if ".xxxx" appeared, add Class to current selector |
| */ |
| private void appendClass(CSSSimpleSelector item, CSSTextToken token) { |
| String text = token.toString(); |
| String classContent = text.substring(1, text.length()); |
| item.addClass(classContent); |
| if (Character.isDigit(classContent.charAt(0))) { |
| addError(token); |
| } |
| } |
| |
| /** |
| * if ":xxxx" appeared, add Pseudo(element/class) to current selector |
| */ |
| private void appendPseudo(CSSSimpleSelector item, CSSTextToken token) { |
| String text = token.toString(); |
| String pseudoContent = text.substring(1, text.length()); |
| item.addPseudoName(pseudoContent); |
| } |
| |
| /** |
| * |
| */ |
| private void appendAttribute(CSSSimpleSelector item, List tokens) { |
| StringBuffer buf = new StringBuffer(); |
| |
| CSSTextToken token; |
| Iterator i = tokens.iterator(); |
| while (i.hasNext()) { |
| token = (CSSTextToken) i.next(); |
| buf.append(token.image); |
| } |
| |
| item.addAttribute(buf.toString()); |
| } |
| |
| /** |
| * |
| */ |
| List getSelectorTags() { |
| List tagList = new ArrayList(); |
| return tagList; |
| } |
| |
| /** |
| * |
| */ |
| private void addError(CSSTextToken token) { |
| if (fErrors == null) { |
| fErrors = new ArrayList(); |
| } |
| fErrors.add(token); |
| } |
| |
| /** |
| * |
| */ |
| Iterator getErrors() { |
| return (fErrors == null) ? Collections.EMPTY_LIST.iterator() : fErrors.iterator(); |
| } |
| } |