com.itextpdf.tool.xml.css.StyleAttrCSSResolver.java Source code

Java tutorial

Introduction

Here is the source code for com.itextpdf.tool.xml.css.StyleAttrCSSResolver.java

Source

/*
 *
 * This file is part of the iText (R) project.
Copyright (c) 1998-2019 iText Group NV
 * Authors: Balder Van Camp, Emiel Ackermann, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS
 *
 * 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 Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every PDF that is created
 * or manipulated using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: sales@itextpdf.com
 */
package com.itextpdf.tool.xml.css;

import com.itextpdf.tool.xml.Tag;
import com.itextpdf.tool.xml.exceptions.CssResolverException;
import com.itextpdf.tool.xml.html.HTML;
import com.itextpdf.tool.xml.net.FileRetrieve;
import com.itextpdf.tool.xml.net.FileRetrieveImpl;
import com.itextpdf.tool.xml.pipeline.css.CSSResolver;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.Map.Entry;

/**
 * Resolves CSS properties.
 *
 * @author redlab_b
 *
 */
public class StyleAttrCSSResolver implements CSSResolver {

    /**
     *
     */
    public static final String STYLE = HTML.Attribute.STYLE;
    private final CssUtils utils;
    private CssInheritanceRules inherit;
    private final CssFiles cssFiles;
    private FileRetrieve retrieve;

    /**
     * Construct a new {@link StyleAttrCSSResolver} with default settings.
     */
    public StyleAttrCSSResolver() {
        this(new CssFilesImpl(), CssUtils.getInstance());
    }

    /**
     * Construct a new StyleAttrCSSResolver with the given {@link CssFiles} and the {@link DefaultCssInheritanceRules}.
     *
     * @param cssFiles a {@link CssFiles} implementation.
     */
    public StyleAttrCSSResolver(final CssFiles cssFiles) {
        this(cssFiles, CssUtils.getInstance());

    }

    /**
     * Construct a new StyleAttrCSSResolver with the given {@link CssFiles} and {@link CssUtils} and the
     * {@link DefaultCssInheritanceRules}.
     *
     * @param cssFiles a {@link CssFiles} implementation.
     * @param utils the CssUtils to use.
     */
    public StyleAttrCSSResolver(final CssFiles cssFiles, final CssUtils utils) {
        this(new DefaultCssInheritanceRules(), cssFiles, utils);
    }

    /**
     * Construct a new StyleAttrCSSResolver with the given {@link CssFiles} and {@link CssUtils}.
     *
     * @param rules the {@link CssInheritanceRules} to use.
     * @param cssFiles a {@link CssFiles} implementation.
     * @param utils the CssUtils to use.
     */
    public StyleAttrCSSResolver(final CssInheritanceRules rules, final CssFiles cssFiles, final CssUtils utils) {
        this(rules, cssFiles, utils, new FileRetrieveImpl());
    }

    /**
     * Construct a new StyleAttrCSSResolver with the given {@link CssFiles} and {@link CssUtils}.
     *
     * @param rules the {@link CssInheritanceRules} to use.
     * @param cssFiles a {@link CssFiles} implementation.
     * @param utils the CssUtils to use.
     * @param fileRetrieve the {@link FileRetrieve} implementation
     */
    public StyleAttrCSSResolver(final CssInheritanceRules rules, final CssFiles cssFiles, final CssUtils utils,
            final FileRetrieve fileRetrieve) {
        this.utils = utils;
        this.cssFiles = cssFiles;
        this.inherit = rules;
        this.retrieve = fileRetrieve;
    }

    /**
     * @param cssFiles the {@link CssFile} implementation
     * @param r the {@link FileRetrieve} implementation
     */
    public StyleAttrCSSResolver(final CssFiles cssFiles, final FileRetrieve r) {
        this(new DefaultCssInheritanceRules(), cssFiles, CssUtils.getInstance(), r);
    }

    /**
     * Also taking into account the CSS properties of any parent tag in the given tag.
     *
     * @see com.itextpdf.tool.xml.pipeline.css.CSSResolver#resolveStyles(com.itextpdf.tool.xml.Tag)
     */
    public void resolveStyles(final Tag t) {
        // get css for this tag from resolver
        Map<String, String> tagCss = new LinkedHashMap<String, String>();
        Map<String, String> listCss = null;
        if (null != cssFiles && cssFiles.hasFiles()) {
            tagCss = cssFiles.getCSS(t);
            if (t.getName().equalsIgnoreCase(HTML.Tag.P) || t.getName().equalsIgnoreCase(HTML.Tag.TD)) {
                listCss = cssFiles.getCSS(new Tag(HTML.Tag.UL));
            }
            //         Map<String, String> css = cssFiles.getCSS(t);
            //         if (null != css) {
            //            for (Entry<String, String> entry : css.entrySet()) {
            //               splitRules(tagCss,utils.stripDoubleSpacesAndTrim(entry.getKey()), utils.stripDoubleSpacesAndTrim(entry.getValue()));
            //            }
            //         }
        }
        // get css from style attr
        if (null != t.getAttributes() && !t.getAttributes().isEmpty()) {
            if (t.getAttributes().get(HTML.Attribute.CELLPADDING) != null) {
                tagCss.putAll(utils.parseBoxValues(t.getAttributes().get(HTML.Attribute.CELLPADDING),
                        "cellpadding-", ""));
            }
            if (t.getAttributes().get(HTML.Attribute.CELLSPACING) != null) {
                tagCss.putAll(utils.parseBoxValues(t.getAttributes().get(HTML.Attribute.CELLSPACING),
                        "cellspacing-", ""));
            }
            String styleAtt = t.getAttributes().get(HTML.Attribute.STYLE);
            if (null != styleAtt && styleAtt.length() > 0) {
                Map<String, String> tagAttrCss = new LinkedHashMap<String, String>();
                String[] styles = styleAtt.split(";");
                for (String s : styles) {
                    String[] part = s.split(":", 2);
                    if (part.length == 2) {
                        String key = utils.stripDoubleSpacesTrimAndToLowerCase(part[0]);
                        String value = utils.stripDoubleSpacesAndTrim(part[1]);
                        splitRules(tagAttrCss, key, value);
                    }
                }

                for (Entry<String, String> e : tagAttrCss.entrySet()) {
                    tagCss.put(e.getKey(), e.getValue());
                }
            }
        }
        // inherit css from parent tags, as defined in provided CssInheritanceRules or if property = inherit
        Map<String, String> css = t.getCSS();
        if (t.getName() != null) {
            if (t.getName().equalsIgnoreCase(HTML.Tag.I) || t.getName().equalsIgnoreCase(HTML.Tag.CITE)
                    || t.getName().equalsIgnoreCase(HTML.Tag.EM) || t.getName().equalsIgnoreCase(HTML.Tag.VAR)
                    || t.getName().equalsIgnoreCase(HTML.Tag.DFN)
                    || t.getName().equalsIgnoreCase(HTML.Tag.ADDRESS)) {
                tagCss.put(CSS.Property.FONT_STYLE, CSS.Value.ITALIC);
            } else if (t.getName().equalsIgnoreCase(HTML.Tag.B) || t.getName().equalsIgnoreCase(HTML.Tag.STRONG)) {
                tagCss.put(CSS.Property.FONT_WEIGHT, CSS.Value.BOLD);
            } else if (t.getName().equalsIgnoreCase(HTML.Tag.U) || t.getName().equalsIgnoreCase(HTML.Tag.INS)) {
                tagCss.put(CSS.Property.TEXT_DECORATION, CSS.Value.UNDERLINE);
            } else if (t.getName().equalsIgnoreCase(HTML.Tag.S) || t.getName().equalsIgnoreCase(HTML.Tag.STRIKE)
                    || t.getName().equalsIgnoreCase(HTML.Tag.DEL)) {
                tagCss.put(CSS.Property.TEXT_DECORATION, CSS.Value.LINE_THROUGH);
            } else if (t.getName().equalsIgnoreCase(HTML.Tag.BIG)) {
                tagCss.put(CSS.Property.FONT_SIZE, CSS.Value.LARGER);
            } else if (t.getName().equalsIgnoreCase(HTML.Tag.SMALL)) {
                tagCss.put(CSS.Property.FONT_SIZE, CSS.Value.SMALLER);
            }
        }

        if (listCss != null && listCss.containsKey(CSS.Property.LIST_STYLE_TYPE)) {
            css.put(CSS.Property.LIST_STYLE_TYPE, listCss.get(CSS.Property.LIST_STYLE_TYPE));
        }

        if (mustInherit(t.getName()) && null != t.getParent() && null != t.getParent().getCSS()) {
            if (null != this.inherit) {
                for (Entry<String, String> entry : t.getParent().getCSS().entrySet()) {
                    String key = entry.getKey();
                    if ((tagCss.containsKey(key) && CSS.Value.INHERIT.equals(tagCss.get(key)))
                            || canInherite(t, key)) {
                        if (key.contains(CSS.Property.CELLPADDING)
                                && (HTML.Tag.TD.equals(t.getName()) || HTML.Tag.TH.equals(t.getName()))) {
                            String paddingKey = key.replace(CSS.Property.CELLPADDING, CSS.Property.PADDING);
                            //if (!tagCss.containsKey(paddingKey)) {
                            tagCss.put(paddingKey, entry.getValue());
                            //continue;
                            //}
                        } else {
                            //splitRules(css, key, entry.getValue());
                            css.put(key, entry.getValue());
                        }
                    }
                }
            } else {
                css.putAll(t.getParent().getCSS());
            }
        }

        if (t.getName() != null) {
            if (t.getName().equals(HTML.Tag.FONT)) {
                String font_family = t.getAttributes().get(HTML.Attribute.FACE);
                if (font_family != null)
                    css.put(CSS.Property.FONT_FAMILY, font_family);
                String color = t.getAttributes().get(HTML.Attribute.COLOR);
                if (color != null)
                    css.put(CSS.Property.COLOR, color);
                String size = t.getAttributes().get(HTML.Attribute.SIZE);
                if (size != null) {
                    if (size.equals("1"))
                        css.put(CSS.Property.FONT_SIZE, CSS.Value.XX_SMALL);
                    else if (size.equals("2"))
                        css.put(CSS.Property.FONT_SIZE, CSS.Value.X_SMALL);
                    else if (size.equals("3"))
                        css.put(CSS.Property.FONT_SIZE, CSS.Value.SMALL);
                    else if (size.equals("4"))
                        css.put(CSS.Property.FONT_SIZE, CSS.Value.MEDIUM);
                    else if (size.equals("5"))
                        css.put(CSS.Property.FONT_SIZE, CSS.Value.LARGE);
                    else if (size.equals("6"))
                        css.put(CSS.Property.FONT_SIZE, CSS.Value.X_LARGE);
                    else if (size.equals("7"))
                        css.put(CSS.Property.FONT_SIZE, CSS.Value.XX_LARGE);

                }
            } else if (t.getName().equals(HTML.Tag.A)) {
                css.put(CSS.Property.TEXT_DECORATION, CSS.Value.UNDERLINE);
                css.put(CSS.Property.COLOR, "blue");
            }
        }

        // overwrite properties (if value != inherit)
        for (Entry<String, String> e : tagCss.entrySet()) {
            if (!CSS.Value.INHERIT.equalsIgnoreCase(e.getValue())) {
                if (e.getKey().equals(CSS.Property.TEXT_DECORATION)) {
                    String oldValue = css.get(e.getKey());
                    css.put(e.getKey(), mergeTextDecorationRules(oldValue, e.getValue()));
                } else {
                    css.put(e.getKey(), e.getValue());
                }
            }
        }

    }

    private String mergeTextDecorationRules(String oldRule, String newRule) {
        if (CSS.Value.NONE.equals(newRule))
            return newRule;
        TreeSet<String> attrSet = new TreeSet<String>();
        if (oldRule != null)
            Collections.addAll(attrSet, oldRule.split("\\s+"));
        if (newRule != null)
            Collections.addAll(attrSet, newRule.split("\\s+"));
        StringBuilder resultantStr = new StringBuilder();
        for (String attr : attrSet) {
            if (attr.equals(CSS.Value.NONE) || attr.equals(CSS.Value.INHERIT))
                continue;
            if (resultantStr.length() > 0)
                resultantStr.append(' ');
            resultantStr.append(attr);
        }
        return resultantStr.length() == 0 ? null : resultantStr.toString();
    }

    /**
     * @param css the css map to populate
     * @param key the property
     * @param value the value
     */
    private void splitRules(final Map<String, String> css, final String key, final String value) {
        if (CSS.Property.BORDER.equalsIgnoreCase(key)) {
            css.putAll(utils.parseBorder(value));
        } else if (CSS.Property.BORDER_TOP.equalsIgnoreCase(key)) {
            css.putAll(utils.parseBorder(value, CSS.Property.BORDER_TOP));
        } else if (CSS.Property.BORDER_BOTTOM.equalsIgnoreCase(key)) {
            css.putAll(utils.parseBorder(value, CSS.Property.BORDER_BOTTOM));
        } else if (CSS.Property.BORDER_LEFT.equalsIgnoreCase(key)) {
            css.putAll(utils.parseBorder(value, CSS.Property.BORDER_LEFT));
        } else if (CSS.Property.BORDER_RIGHT.equalsIgnoreCase(key)) {
            css.putAll(utils.parseBorder(value, CSS.Property.BORDER_RIGHT));
        } else if (CSS.Property.MARGIN.equalsIgnoreCase(key)) {
            css.putAll(utils.parseBoxValues(value, "margin-", ""));
        } else if (CSS.Property.BORDER_WIDTH.equalsIgnoreCase(key)) {
            css.putAll(utils.parseBoxValues(value, "border-", "-width"));
        } else if (CSS.Property.BORDER_STYLE.equalsIgnoreCase(key)) {
            css.putAll(utils.parseBoxValues(value, "border-", "-style"));
        } else if (CSS.Property.BORDER_COLOR.equalsIgnoreCase(key)) {
            css.putAll(utils.parseBoxValues(value, "border-", "-color"));
        } else if (CSS.Property.PADDING.equalsIgnoreCase(key)) {
            css.putAll(utils.parseBoxValues(value, "padding-", ""));
        } else if (CSS.Property.FONT.equalsIgnoreCase(key)) {
            css.putAll(utils.processFont(value));
        } else if (CSS.Property.LIST_STYLE.equalsIgnoreCase(key)) {
            css.putAll(utils.processListStyle(value));
        } else if (key.toLowerCase().contains(CSS.Property.BACKGROUND)) {
            Map<String, String> backgroundStyles = utils.processBackground(value);
            for (String backgroundKey : backgroundStyles.keySet()) {
                if (!css.containsKey(backgroundKey)) {
                    css.put(backgroundKey, backgroundStyles.get(backgroundKey));
                }
            }
        } else {
            css.put(key, value);
        }
    }

    /**
     * By setting an implementation of {@link CssInheritanceRules} a developer can set rules on what css selectors are
     * inherited from parent tags.
     *
     * @param cssInheritanceRules the inherit to set
     */
    public void setCssInheritance(final CssInheritanceRules cssInheritanceRules) {
        this.inherit = cssInheritanceRules;
    }

    /**
     * Defaults to true if no {@link CssInheritanceRules} implementation set.
     *
     * @param t
     * @param property
     * @return true if may be inherited false otherwise
     */
    private boolean canInherite(final Tag t, final String property) {
        if (null != this.inherit) {
            return this.inherit.inheritCssSelector(t, property);
        }
        return true;
    }

    /**
     * Defaults to true if no {@link CssInheritanceRules} implementation set.
     *
     * @param tag
     * @return true if must be inherited false otherwise
     */
    private boolean mustInherit(final String tag) {
        if (null != this.inherit) {
            return this.inherit.inheritCssTag(tag);
        }
        return true;
    }

    /*
     * (non-Javadoc)
     * @see com.itextpdf.tool.xml.pipeline.css.CSSResolver#addCss(java.lang.String, java.lang.String)
     */
    public void addCss(final String content, final String charSet, final boolean isPersistent)
            throws CssResolverException {
        CssFileProcessor proc = new CssFileProcessor();
        try {
            retrieve.processFromStream(new ByteArrayInputStream(content.getBytes(charSet)), proc);
            CssFile css = proc.getCss();
            css.isPersistent(isPersistent);
            this.cssFiles.add(css);
        } catch (UnsupportedEncodingException e) {
            throw new CssResolverException(e);
        } catch (IOException e) {
            throw new CssResolverException(e);
        }
    }

    /**
     * Add a file to the CssFiles Collection.
     *
     * @param href the path, if it starts with http we try to retrieve the file
     *            from the net, if not we try a normal file operation.
     */
    public void addCssFile(final String href, final boolean isPersistent) throws CssResolverException {
        CssFileProcessor cssFileProcessor = new CssFileProcessor();
        try {
            retrieve.processFromHref(href, cssFileProcessor);
        } catch (IOException e) {
            throw new CssResolverException(e);
        }
        CssFile css = cssFileProcessor.getCss();
        css.isPersistent(isPersistent);
        this.cssFiles.add(css);
    }

    /**
     * Add a file to the CssFiles Collection.
     * @param file the CssFile to add.
     */
    public void addCss(final CssFile file) {
        this.cssFiles.add(file);
    }

    /* (non-Javadoc)
     * @see com.itextpdf.tool.xml.pipeline.css.CSSResolver#addCss(java.lang.String)
     */
    public void addCss(final String content, final boolean isPersistent) throws CssResolverException {
        CssFileProcessor proc = new CssFileProcessor();
        FileRetrieve retrieve = new FileRetrieveImpl();
        try {
            retrieve.processFromStream(new ByteArrayInputStream(content.getBytes()), proc);
            CssFile css = proc.getCss();
            css.isPersistent(isPersistent);
            this.cssFiles.add(css);
        } catch (UnsupportedEncodingException e) {
            throw new CssResolverException(e);
        } catch (IOException e) {
            throw new CssResolverException(e);
        }

    }

    /**
     * @param inherit the inherit to set
     */
    public void setCssInheritanceRules(final CssInheritanceRules inherit) {
        this.inherit = inherit;
    }

    /**
     * The {@link FileRetrieve} implementation to use in {@link StyleAttrCSSResolver#addCss(String, boolean)}.
     * @param retrieve the retrieve to set
     */
    public void setFileRetrieve(final FileRetrieve retrieve) {
        this.retrieve = retrieve;
    }

    /* (non-Javadoc)
     * @see com.itextpdf.tool.xml.pipeline.css.CSSResolver#clear()
     */
    public CSSResolver clear() throws CssResolverException {
        cssFiles.clear();
        return this;
    }

}