All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.openhtmltopdf.css.newmatch.Selector Maven / Gradle / Ivy

Go to download

Open HTML to PDF is a CSS 2.1 renderer written in Java. This artifact contains the core rendering and layout code.

There is a newer version: 1.0.10
Show newest version
/*
 * Selector.java
 * Copyright (c) 2004, 2005 Torbjoern Gannholm
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */
package com.openhtmltopdf.css.newmatch;

import com.openhtmltopdf.css.extend.AttributeResolver;
import com.openhtmltopdf.css.extend.TreeResolver;
import com.openhtmltopdf.css.sheet.Ruleset;
import com.openhtmltopdf.util.LogMessageId;
import com.openhtmltopdf.util.XRLog;

import java.util.List;
import java.util.Set;
import java.util.logging.Level;


/**
 * A Selector is really a chain of CSS selectors that all need to be valid for
 * the selector to match.
 *
 * @author Torbjoern Gannholm
 */
public class Selector {
    private Ruleset _parent;
    private Selector chainedSelector = null;
    private Selector siblingSelector = null;

    private int _axis;
    private String _name;
    private String _namespaceURI;
    private int _pc = 0;
    private String _pe;

    //specificity - correct values are gotten from the last Selector in the chain
    private int _specificityB;
    private int _specificityC;
    private int _specificityD;

    private int _pos;//to distinguish between selectors of same specificity

    private List conditions;

    public final static int DESCENDANT_AXIS = 0;
    public final static int CHILD_AXIS = 1;
    public final static int IMMEDIATE_SIBLING_AXIS = 2;

    public final static int VISITED_PSEUDOCLASS = 2;
    public final static int HOVER_PSEUDOCLASS = 4;
    public final static int ACTIVE_PSEUDOCLASS = 8;
    public final static int FOCUS_PSEUDOCLASS = 16;

    /**
     * Give each a unique ID to be able to create a key to internalize Matcher.Mappers
     */
    private int selectorID;
    private Selector _ancestorSelector;
    private static int selectorCount = 0;

    public Selector() {
        selectorID = selectorCount++;
    }

    /**
     * Check if the given Element matches this selector. Note: the parser should
     * give all class
     */
    public boolean matches(Object e, AttributeResolver attRes, TreeResolver treeRes) {
        if (siblingSelector != null) {
            Object sib = siblingSelector.getAppropriateSibling(e, treeRes);
            if (sib == null) {
                return false;
            }
            if (!siblingSelector.matches(sib, attRes, treeRes)) {
                return false;
            }
        }
        if (_name == null || treeRes.matchesElement(e, _namespaceURI, _name)) {
            if (conditions != null) {
                // all conditions need to be true
                for (Condition c : conditions) {
                    if (!c.matches(e, attRes, treeRes)) {
                        return false;
                    }
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Check if the given Element matches this selector's dynamic properties.
     * Note: the parser should give all class
     */
    public boolean matchesDynamic(Object e, AttributeResolver attRes, TreeResolver treeRes) {
        if (siblingSelector != null) {
            Object sib = siblingSelector.getAppropriateSibling(e, treeRes);
            if (sib == null) {
                return false;
            }
            if (!siblingSelector.matchesDynamic(sib, attRes, treeRes)) {
                return false;
            }
        }
        if (isPseudoClass(VISITED_PSEUDOCLASS)) {
            if (attRes == null || !attRes.isVisited(e)) {
                return false;
            }
        }
        if (isPseudoClass(ACTIVE_PSEUDOCLASS)) {
            if (attRes == null || !attRes.isActive(e)) {
                return false;
            }
        }
        if (isPseudoClass(HOVER_PSEUDOCLASS)) {
            if (attRes == null || !attRes.isHover(e)) {
                return false;
            }
        }
        if (isPseudoClass(FOCUS_PSEUDOCLASS)) {
            if (attRes == null || !attRes.isFocus(e)) {
                return false;
            }
        }
        return true;
    }

    /**
     * for unsupported or invalid CSS
     */
    public void addUnsupportedCondition() {
        addCondition(Condition.createUnsupportedCondition());
    }

    /**
     * the CSS condition that element has pseudo-class :link
     */
    public void addLinkCondition() {
        _specificityC++;
        addCondition(Condition.createLinkCondition());
    }

    /**
     * the CSS condition that element has pseudo-class :first-child
     */
    public void addFirstChildCondition() {
        _specificityC++;
        addCondition(Condition.createFirstChildCondition());
    }
    
    /**
     * the CSS condition that element has pseudo-class :last-child
     */
    public void addLastChildCondition() {
        _specificityC++;
        addCondition(Condition.createLastChildCondition());
    }

    /**
     * the CSS condition that element has pseudo-class :nth-child(an+b)
     */
    public void addNthChildCondition(String number) {
        _specificityC++;
        addCondition(Condition.createNthChildCondition(number));
    }

    /**
     * the CSS condition that element has pseudo-class :even
     */
    public void addEvenChildCondition() {
        _specificityC++;
        addCondition(Condition.createEvenChildCondition());
    }
    
    /**
     * the CSS condition that element has pseudo-class :odd
     */
    public void addOddChildCondition() {
        _specificityC++;
        addCondition(Condition.createOddChildCondition());
    }

    /**
     * the CSS condition :lang(Xx)
     */
    public void addLangCondition(String lang) {
        _specificityC++;
        addCondition(Condition.createLangCondition(lang));
    }

    /**
     * the CSS condition #ID
     */
    public void addIDCondition(String id) {
        _specificityB++;
        addCondition(Condition.createIDCondition(id));
    }

    /**
     * the CSS condition .class
     */
    public void addClassCondition(String className) {
        _specificityC++;
        addCondition(Condition.createClassCondition(className));
    }

    /**
     * the CSS condition [attribute]
     */
    public void addAttributeExistsCondition(String namespaceURI, String name) {
        _specificityC++;
        addCondition(Condition.createAttributeExistsCondition(namespaceURI, name));
    }

    /**
     * the CSS condition [attribute=value]
     */
    public void addAttributeEqualsCondition(String namespaceURI, String name, String value) {
        _specificityC++;
        addCondition(Condition.createAttributeEqualsCondition(namespaceURI, name, value));
    }
    
    /**
     * the CSS condition [attribute^=value]
     */
    public void addAttributePrefixCondition(String namespaceURI, String name, String value) {
        _specificityC++;
        addCondition(Condition.createAttributePrefixCondition(namespaceURI, name, value));
    }
    
    /**
     * the CSS condition [attribute$=value]
     */
    public void addAttributeSuffixCondition(String namespaceURI, String name, String value) {
        _specificityC++;
        addCondition(Condition.createAttributeSuffixCondition(namespaceURI, name, value));
    }
    
    /**
     * the CSS condition [attribute*=value]
     */
    public void addAttributeSubstringCondition(String namespaceURI, String name, String value) {
        _specificityC++;
        addCondition(Condition.createAttributeSubstringCondition(namespaceURI, name, value));
    }

    /**
     * the CSS condition [attribute~=value]
     */
    public void addAttributeMatchesListCondition(String namespaceURI, String name, String value) {
        _specificityC++;
        addCondition(Condition.createAttributeMatchesListCondition(namespaceURI, name, value));
    }

    /**
     * the CSS condition [attribute|=value]
     */
    public void addAttributeMatchesFirstPartCondition(String namespaceURI, String name, String value) {
        _specificityC++;
        addCondition(Condition.createAttributeMatchesFirstPartCondition(namespaceURI, name, value));
    }

    /**
     * set which pseudoclasses must apply for this selector
     *
     * @param pc the values from AttributeResolver should be used. Once set
     *           they cannot be unset. Note that the pseudo-classes should be set one
     *           at a time, otherwise specificity of declaration becomes wrong.
     */
    public void setPseudoClass(int pc) {
        if (!isPseudoClass(pc)) {
            _specificityC++;
        }
        _pc |= pc;
    }

    /**
     * check if selector queries for dynamic properties
     *
     * @param pseudoElement The new pseudoElement value
     */
    /*
     * public boolean isDynamic() {
     * return (_pc != 0);
     * }
     */
    public void setPseudoElement(String pseudoElement) {
        if (_pe != null) {
            addUnsupportedCondition();
            XRLog.log(Level.WARNING, LogMessageId.LogMessageId0Param.MATCH_TRYING_TO_SET_MORE_THAN_ONE_PSEUDO_ELEMENT);
        } else {
            _specificityD++;
            _pe = pseudoElement;
        }
    }

    /**
     * query if a pseudoclass must apply for this selector
     *
     * @param pc the values from AttributeResolver should be used.
     * @return The pseudoClass value
     */
    public boolean isPseudoClass(int pc) {
        return ((_pc & pc) != 0);
    }

    /**
     * Gets the pseudoElement attribute of the Selector object
     *
     * @return The pseudoElement value
     */
    public String getPseudoElement() {
    	return _pe;
    }

    /**
     * get the next selector in the chain, for matching against elements along
     * the appropriate axis
     *
     * @return The chainedSelector value
     */
    public Selector getChainedSelector() {
        return chainedSelector;
    }

    /**
     * get the Ruleset that this Selector is part of
     *
     * @return The ruleset value
     */
    public Ruleset getRuleset() {
        return _parent;
    }

    /**
     * get the axis that this selector should be evaluated on
     *
     * @return The axis value
     */
    public int getAxis() {
        return _axis;
    }

    /**
     * The correct specificity value for this selector and its sibling-axis
     * selectors
     */
    public int getSpecificityB() {
        return _specificityB;
    }

    /**
     * The correct specificity value for this selector and its sibling-axis
     * selectors
     */
    public int getSpecificityD() {
        return _specificityD;
    }

    /**
     * The correct specificity value for this selector and its sibling-axis
     * selectors
     */
    public int getSpecificityC() {
        return _specificityC;
    }

    /**
     * returns "a number in a large base" with specificity and specification
     * order of selector
     *
     * @return The order value
     */
    String getOrder() {
        if (chainedSelector != null) {
            return chainedSelector.getOrder();
        }//only "deepest" value is correct
        String b = "000" + getSpecificityB();
        String c = "000" + getSpecificityC();
        String d = "000" + getSpecificityD();
        String p = "00000" + _pos;
        return "0" + b.substring(b.length() - 3) + c.substring(c.length() - 3) + d.substring(d.length() - 3) + p.substring(p.length() - 5);
    }

    /**
     * Gets the appropriateSibling attribute of the Selector object
     *
     * @param e       PARAM
     * @param treeRes
     * @return The appropriateSibling value
     */
    Object getAppropriateSibling(Object e, TreeResolver treeRes) {
        Object sibling = null;
        switch (_axis) {
            case IMMEDIATE_SIBLING_AXIS:
                sibling = treeRes.getPreviousSiblingElement(e);
                break;
            default:
                XRLog.log(Level.WARNING, LogMessageId.LogMessageId0Param.EXCEPTION_SELECTOR_BAD_SIBLING_AXIS);
        }
        return sibling;
    }

    /**
     * Adds a feature to the Condition attribute of the Selector object
     *
     * @param c The feature to be added to the Condition attribute
     */
    private void addCondition(Condition c) {
        if (conditions == null) {
            conditions = new java.util.ArrayList<>();
        }
        if (_pe != null) {
            conditions.add(Condition.createUnsupportedCondition());
            XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.MATCH_TRYING_TO_APPEND_CONDITIONS_TO_PSEUDO_ELEMENT, _pe);
        }
        conditions.add(c);
    }

    /**
     * Prints the selector chain to a StringBuilder, stopping
     * when it hits a selector in the stopAt set.
     * 
     * For example, given the selector 'body svg rect' and the stop
     * set contains 'svg' then this will print 'rect' to the builder.
     * 
     * This method is used to recreate CSS selectors to pass to SVG or
     * other plugins.
     * 
     * FIXME: Does not handle sibling selector.
     */
    public void toCSS(StringBuilder sb, Set stopAt) {
        if (stopAt.contains(this)) {
            return;
        }

        Selector ancestor = this;

        while (ancestor != null) {
            Selector current = ancestor.getAncestorSelector();

            if (current == null || stopAt.contains(current)) {
                break;
            }

            ancestor = current;
        }

        Selector chained = ancestor;

        if (chained.getAxis() == Selector.CHILD_AXIS) {
            sb.append('>');
            sb.append(' ');
        }

        if (chained._name != null) {
            sb.append(chained._name);
        }

        if (chained.conditions != null) {
            for (Condition condition : chained.conditions) {
                condition.toCSS(sb);
            }
        }

        sb.append(' ');

        Selector next = chained.getChainedSelector();

        while (next != null) {
            if (next.getAxis() == Selector.CHILD_AXIS) {
                sb.append('>');
                sb.append(' ');
            } else if (next.getAxis() == Selector.DESCENDANT_AXIS) {
                // Do nothing, already have a space.
            }

            if (next._name != null) {
                sb.append(next._name);
            }

            if (next.conditions != null) {
                for (Condition condition : next.conditions) {
                    condition.toCSS(sb);
                }
            }
            sb.append(' ');

            next = next.getChainedSelector();
        }
    }

    /**
     * Gets the elementStylingOrder attribute of the Selector class
     *
     * @return The elementStylingOrder value
     */
    static String getElementStylingOrder() {
        return "1" + "000" + "000" + "000" + "00000";
    }

    public int getSelectorID() {
        return selectorID;
    }
    
    public void setName(String name) {
        _name = name;
        _specificityD++;
    }
    
    public void setPos(int pos) {
        _pos = pos;
        if (siblingSelector != null) {
            siblingSelector.setPos(pos);
        }
        if (chainedSelector != null) {
            chainedSelector.setPos(pos);
        }
    }
    
    public void setParent(Ruleset ruleset) {
        _parent = ruleset;
    }
    
    public void setAxis(int axis) {
        _axis = axis;
    }
    
    public void setSpecificityB(int b) {
        _specificityB = b;
    }
    
    public void setSpecificityC(int c) {
        _specificityC = c;
    }
    
    public void setSpecificityD(int d) {
        _specificityD = d;
    }
    
    public void setChainedSelector(Selector selector) {
        chainedSelector = selector;
    }
    
    public void setSiblingSelector(Selector selector) {
        siblingSelector = selector;
    }
    
    public void setNamespaceURI(String namespaceURI) {
        _namespaceURI = namespaceURI;
    }

    public void setAncestorSelector(Selector ancestor) {
        _ancestorSelector = ancestor;
    }

    public Selector getAncestorSelector() {
        return _ancestorSelector;
    }

    /**
     * For debugging, prints the entire selector chain.
     * FIXME: Does not handle sibling selectors.
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        Selector current = this;
        Selector ancestor = this;

        while (current != null) {
            current = current.getAncestorSelector();
            if (current != null) {
                ancestor = current;
            }
        }

        current = ancestor;

        while (current != null) {
            if (current.getAxis() == Selector.CHILD_AXIS) {
                sb.append(" > ");
            } else {
                sb.append(' ');
            }

            if (current._name != null) {
                sb.append(current._name);
            }

            if (current.conditions != null) {
                for (Condition cond : current.conditions) {
                    cond.toCSS(sb);
                }
            }

            current = current.getChainedSelector();
        }

        return sb.toString();
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy