com.openhtmltopdf.css.newmatch.Matcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openhtmltopdf-core Show documentation
Show all versions of openhtmltopdf-core Show documentation
Open HTML to PDF is a CSS 2.1 renderer written in Java. This artifact contains the core rendering and layout code.
/*
* Matcher.java
* Copyright (c) 2004, 2005 Torbjoern Gannholm
* Copyright (c) 2006 Wisconsin Court System
*
* 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 java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import com.openhtmltopdf.css.extend.AttributeResolver;
import com.openhtmltopdf.css.extend.StylesheetFactory;
import com.openhtmltopdf.css.extend.TreeResolver;
import com.openhtmltopdf.css.sheet.MediaRule;
import com.openhtmltopdf.css.sheet.PageRule;
import com.openhtmltopdf.css.sheet.Ruleset;
import com.openhtmltopdf.css.sheet.Stylesheet;
import com.openhtmltopdf.util.Util;
import com.openhtmltopdf.util.XRLog;
/**
* @author Torbjoern Gannholm
*/
public class Matcher {
Mapper docMapper;
private com.openhtmltopdf.css.extend.AttributeResolver _attRes;
private com.openhtmltopdf.css.extend.TreeResolver _treeRes;
private com.openhtmltopdf.css.extend.StylesheetFactory _styleFactory;
private java.util.Map _map;
//handle dynamic
private Set _hoverElements;
private Set _activeElements;
private Set _focusElements;
private Set _visitElements;
private List _pageRules;
private List _fontFaceRules;
public Matcher(
TreeResolver tr, AttributeResolver ar, StylesheetFactory factory, List stylesheets, String medium) {
newMaps();
_treeRes = tr;
_attRes = ar;
_styleFactory = factory;
_pageRules = new ArrayList();
_fontFaceRules = new ArrayList();
docMapper = createDocumentMapper(stylesheets, medium);
}
public void removeStyle(Object e) {
_map.remove(e);
}
public CascadedStyle getCascadedStyle(Object e, boolean restyle) {
synchronized (e) {
Mapper em;
if (!restyle) {
em = getMapper(e);
} else {
em = matchElement(e);
}
return em.getCascadedStyle(e);
}
}
/**
* May return null.
* We assume that restyle has already been done by a getCascadedStyle if necessary.
*/
public CascadedStyle getPECascadedStyle(Object e, String pseudoElement) {
synchronized (e) {
Mapper em = getMapper(e);
return em.getPECascadedStyle(e, pseudoElement);
}
}
public PageInfo getPageCascadedStyle(String pageName, String pseudoPage) {
List props = new ArrayList();
Map marginBoxes = new HashMap();
for (Iterator i = _pageRules.iterator(); i.hasNext(); ) {
PageRule pageRule = (PageRule)i.next();
if (pageRule.applies(pageName, pseudoPage)) {
props.addAll(pageRule.getRuleset().getPropertyDeclarations());
marginBoxes.putAll(pageRule.getMarginBoxes());
}
}
CascadedStyle style = null;
if (props.isEmpty()) {
style = CascadedStyle.emptyCascadedStyle;
} else {
style = new CascadedStyle(props.iterator());
}
return new PageInfo(props, style, marginBoxes);
}
public List getFontFaceRules() {
return _fontFaceRules;
}
public boolean isVisitedStyled(Object e) {
return _visitElements.contains(e);
}
public boolean isHoverStyled(Object e) {
return _hoverElements.contains(e);
}
public boolean isActiveStyled(Object e) {
return _activeElements.contains(e);
}
public boolean isFocusStyled(Object e) {
return _focusElements.contains(e);
}
protected Mapper matchElement(Object e) {
synchronized (e) {
Object parent = _treeRes.getParentElement(e);
Mapper child;
if (parent != null) {
Mapper m = getMapper(parent);
child = m.mapChild(e);
} else {//has to be document or fragment node
child = docMapper.mapChild(e);
}
return child;
}
}
Mapper createDocumentMapper(List stylesheets, String medium) {
java.util.TreeMap sorter = new java.util.TreeMap();
addAllStylesheets(stylesheets, sorter, medium);
XRLog.match("Matcher created with " + sorter.size() + " selectors");
return new Mapper(sorter.values());
}
private void addAllStylesheets(List stylesheets, TreeMap sorter, String medium) {
int count = 0;
int pCount = 0;
for (Iterator i = stylesheets.iterator(); i.hasNext(); ) {
Stylesheet stylesheet = (Stylesheet)i.next();
for (Iterator j = stylesheet.getContents().iterator(); j.hasNext(); ) {
Object obj = (Object)j.next();
if (obj instanceof Ruleset) {
for (Iterator k = ((Ruleset)obj).getFSSelectors().iterator(); k.hasNext(); ) {
Selector selector = (Selector)k.next();
selector.setPos(++count);
sorter.put(selector.getOrder(), selector);
}
} else if (obj instanceof PageRule) {
((PageRule)obj).setPos(++pCount);
_pageRules.add(obj);
} else if (obj instanceof MediaRule) {
MediaRule mediaRule = (MediaRule)obj;
if (mediaRule.matches(medium)) {
for (Iterator k = mediaRule.getContents().iterator(); k.hasNext(); ) {
Ruleset ruleset = (Ruleset)k.next();
for (Iterator l = ruleset.getFSSelectors().iterator(); l.hasNext(); ) {
Selector selector = (Selector)l.next();
selector.setPos(++count);
sorter.put(selector.getOrder(), selector);
}
}
}
}
}
_fontFaceRules.addAll(stylesheet.getFontFaceRules());
}
Collections.sort(_pageRules, new Comparator() {
public int compare(Object o1, Object o2) {
PageRule p1 = (PageRule)o1;
PageRule p2 = (PageRule)o2;
if (p1.getOrder() - p2.getOrder() < 0) {
return -1;
} else if (p1.getOrder() == p2.getOrder()) {
return 0;
} else {
return 1;
}
}
});
}
private void link(Object e, Mapper m) {
_map.put(e, m);
}
private void newMaps() {
_map = Collections.synchronizedMap(new java.util.HashMap());
_hoverElements = Collections.synchronizedSet(new java.util.HashSet());
_activeElements = Collections.synchronizedSet(new java.util.HashSet());
_focusElements = Collections.synchronizedSet(new java.util.HashSet());
_visitElements = Collections.synchronizedSet(new java.util.HashSet());
}
private Mapper getMapper(Object e) {
Mapper m = (Mapper) _map.get(e);
if (m != null) {
return m;
}
m = matchElement(e);
return m;
}
private static java.util.Iterator getMatchedRulesets(final List mappedSelectors) {
return
new java.util.Iterator() {
java.util.Iterator selectors = mappedSelectors.iterator();
public boolean hasNext() {
return selectors.hasNext();
}
public Object next() {
if (hasNext()) {
return ((Selector) selectors.next()).getRuleset();
} else {
throw new java.util.NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
private static java.util.Iterator getSelectedRulesets(java.util.List selectorList) {
final java.util.List sl = selectorList;
return
new java.util.Iterator() {
java.util.Iterator selectors = sl.iterator();
public boolean hasNext() {
return selectors.hasNext();
}
public Object next() {
if (hasNext()) {
return ((Selector) selectors.next()).getRuleset();
} else {
throw new java.util.NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
private com.openhtmltopdf.css.sheet.Ruleset getElementStyle(Object e) {
synchronized (e) {
if (_attRes == null || _styleFactory == null) {
return null;
}
String style = _attRes.getElementStyling(e);
if (Util.isNullOrEmpty(style)) {
return null;
}
return _styleFactory.parseStyleDeclaration(com.openhtmltopdf.css.sheet.StylesheetInfo.AUTHOR, style);
}
}
private com.openhtmltopdf.css.sheet.Ruleset getNonCssStyle(Object e) {
synchronized (e) {
if (_attRes == null || _styleFactory == null) {
return null;
}
String style = _attRes.getNonCssStyling(e);
if (Util.isNullOrEmpty(style)) {
return null;
}
return _styleFactory.parseStyleDeclaration(com.openhtmltopdf.css.sheet.StylesheetInfo.AUTHOR, style);
}
}
/**
* Mapper represents a local CSS for a Node that is used to match the Node's
* children.
*
* @author Torbjoern Gannholm
*/
class Mapper {
java.util.List axes;
private HashMap pseudoSelectors;
private List mappedSelectors;
private HashMap children;
Mapper(java.util.Collection selectors) {
axes = new java.util.ArrayList(selectors.size());
axes.addAll(selectors);
}
private Mapper() {
}
/**
* Side effect: creates and stores a Mapper for the element
*
* @param e
* @return The selectors that matched, sorted according to specificity
* (more correct: preserves the sort order from Matcher creation)
*/
Mapper mapChild(Object e) {
//Mapper childMapper = new Mapper();
java.util.List childAxes = new ArrayList(axes.size() + 10);
java.util.HashMap pseudoSelectors = new java.util.HashMap();
java.util.List mappedSelectors = new java.util.LinkedList();
StringBuffer key = new StringBuffer();
for (int i = 0, size = axes.size(); i < size; i++) {
Selector sel = (Selector) axes.get(i);
if (sel.getAxis() == Selector.DESCENDANT_AXIS) {
//carry it forward to other descendants
childAxes.add(sel);
} else if (sel.getAxis() == Selector.IMMEDIATE_SIBLING_AXIS) {
throw new RuntimeException();
}
if (!sel.matches(e, _attRes, _treeRes)) {
continue;
}
//Assumption: if it is a pseudo-element, it does not also have dynamic pseudo-class
String pseudoElement = sel.getPseudoElement();
if (pseudoElement != null) {
java.util.List l = (java.util.List) pseudoSelectors.get(pseudoElement);
if (l == null) {
l = new java.util.LinkedList();
pseudoSelectors.put(pseudoElement, l);
}
l.add(sel);
key.append(sel.getSelectorID()).append(":");
continue;
}
if (sel.isPseudoClass(Selector.VISITED_PSEUDOCLASS)) {
_visitElements.add(e);
}
if (sel.isPseudoClass(Selector.ACTIVE_PSEUDOCLASS)) {
_activeElements.add(e);
}
if (sel.isPseudoClass(Selector.HOVER_PSEUDOCLASS)) {
_hoverElements.add(e);
}
if (sel.isPseudoClass(Selector.FOCUS_PSEUDOCLASS)) {
_focusElements.add(e);
}
if (!sel.matchesDynamic(e, _attRes, _treeRes)) {
continue;
}
key.append(sel.getSelectorID()).append(":");
Selector chain = sel.getChainedSelector();
if (chain == null) {
mappedSelectors.add(sel);
} else if (chain.getAxis() == Selector.IMMEDIATE_SIBLING_AXIS) {
throw new RuntimeException();
} else {
childAxes.add(chain);
}
}
if (children == null) children = new HashMap();
Mapper childMapper = (Mapper) children.get(key.toString());
if (childMapper == null) {
childMapper = new Mapper();
childMapper.axes = childAxes;
childMapper.pseudoSelectors = pseudoSelectors;
childMapper.mappedSelectors = mappedSelectors;
children.put(key.toString(), childMapper);
}
link(e, childMapper);
return childMapper;
}
CascadedStyle getCascadedStyle(Object e) {
CascadedStyle result;
synchronized (e) {
CascadedStyle cs = null;
com.openhtmltopdf.css.sheet.Ruleset elementStyling = getElementStyle(e);
com.openhtmltopdf.css.sheet.Ruleset nonCssStyling = getNonCssStyle(e);
List propList = new LinkedList();
//specificity 0,0,0,0
if (nonCssStyling != null) {
propList.addAll(nonCssStyling.getPropertyDeclarations());
}
//these should have been returned in order of specificity
for (Iterator i = getMatchedRulesets(mappedSelectors); i.hasNext();) {
com.openhtmltopdf.css.sheet.Ruleset rs = (com.openhtmltopdf.css.sheet.Ruleset) i.next();
propList.addAll(rs.getPropertyDeclarations());
}
//specificity 1,0,0,0
if (elementStyling != null) {
propList.addAll(elementStyling.getPropertyDeclarations());
}
if (propList.size() == 0)
cs = CascadedStyle.emptyCascadedStyle;
else {
cs = new CascadedStyle(propList.iterator());
}
result = cs;
}
return result;
}
/**
* May return null.
* We assume that restyle has already been done by a getCascadedStyle if necessary.
*/
public CascadedStyle getPECascadedStyle(Object e, String pseudoElement) {
java.util.Iterator si = pseudoSelectors.entrySet().iterator();
if (!si.hasNext()) {
return null;
}
CascadedStyle cs = null;
java.util.List pe = (java.util.List) pseudoSelectors.get(pseudoElement);
if (pe == null) return null;
java.util.List propList = new java.util.LinkedList();
for (java.util.Iterator i = getSelectedRulesets(pe); i.hasNext();) {
com.openhtmltopdf.css.sheet.Ruleset rs = (com.openhtmltopdf.css.sheet.Ruleset) i.next();
propList.addAll(rs.getPropertyDeclarations());
}
if (propList.size() == 0)
cs = CascadedStyle.emptyCascadedStyle;//already internalized
else {
cs = new CascadedStyle(propList.iterator());
}
return cs;
}
}
}