org.dita.dost.reader.DitaValReader.java Source code

Java tutorial

Introduction

Here is the source code for org.dita.dost.reader.DitaValReader.java

Source

/*
 * This file is part of the DITA Open Toolkit project.
 *
 * Copyright 2004, 2005 IBM Corporation
 *
 * See the accompanying LICENSE file for applicable license.
    
 */
package org.dita.dost.reader;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import org.dita.dost.exception.DITAOTXMLErrorHandler;
import org.dita.dost.log.DITAOTLogger;
import org.dita.dost.log.MessageUtils;
import org.dita.dost.module.GenMapAndTopicListModule.TempFileNameScheme;
import org.dita.dost.util.Configuration;
import org.dita.dost.util.FilterUtils.Action;
import org.dita.dost.util.FilterUtils.FilterKey;
import org.dita.dost.util.FilterUtils.Flag;
import org.dita.dost.util.FilterUtils.Flag.FlagImage;
import org.dita.dost.util.Job;
import org.dita.dost.util.URLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static javax.xml.XMLConstants.XML_NS_PREFIX;
import static javax.xml.XMLConstants.XML_NS_URI;
import static org.dita.dost.util.Constants.*;
import static org.dita.dost.util.FilterUtils.DEFAULT;
import static org.dita.dost.util.URLUtils.getRelativePath;
import static org.dita.dost.util.XMLUtils.*;

/**
 * DitaValReader reads and parses the information from ditaval file which
 * contains the information of filtering and flagging.
 *
 * @author Zhang, Yuan Peng
 */
public final class DitaValReader implements AbstractReader {

    private static final QName REV = QName.valueOf(ATTRIBUTE_NAME_REV);

    private Set<QName> filterAttributes;
    private Set<QName> flagAttributes;
    protected DITAOTLogger logger;
    protected Job job;
    private final Map<FilterKey, Action> filterMap;
    private String foregroundConflictColor;
    private String backgroundConflictColor;

    private TempFileNameScheme tempFileNameScheme;
    private final DocumentBuilder builder;
    /** List of absolute flagging image paths. */
    private final List<URI> imageList;

    private URI ditaVal = null;

    private Map<QName, Map<String, Set<Element>>> bindingMap;
    /** List of relative flagging image paths. */
    private final List<URI> relFlagImageList;

    /**
     * Default constructor of DitaValReader class.
     */
    public DitaValReader() {
        super();
        filterMap = new HashMap<>();
        imageList = new ArrayList<>(256);
        relFlagImageList = new ArrayList<>(256);

        try {
            final DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
            builderFactory.setNamespaceAware(true);
            builder = builderFactory.newDocumentBuilder();
        } catch (final ParserConfigurationException e) {
            throw new RuntimeException(e);
        }
        filterAttributes = Stream
                .of(Configuration.configuration.getOrDefault("filter-attributes", "").trim().split("\\s*,\\s*"))
                .map(QName::valueOf).collect(Collectors.toSet());
        flagAttributes = Stream
                .of(Configuration.configuration.getOrDefault("flag-attributes", "").trim().split("\\s*,\\s*"))
                .map(QName::valueOf).collect(Collectors.toSet());
    }

    @VisibleForTesting
    DitaValReader(Set<QName> filterAttributes, Set<QName> flagAttributes) {
        this();
        this.filterAttributes = Sets.union(this.filterAttributes, filterAttributes);
        this.flagAttributes = Sets.union(this.flagAttributes, flagAttributes);
    }

    @Override
    public void setLogger(final DITAOTLogger logger) {
        this.logger = logger;
    }

    @Override
    public void setJob(final Job job) {
        this.job = job;
        // XXX: This is a hack to disable temp filename generation, because in gen-list the base input dir is unknown.
        if (job.getInputDir() != null) {
            tempFileNameScheme = job.getTempFileNameScheme();
        }
    }

    /**
     * Set the map of subject scheme definitions. The contents of the map is in pseudo-code
     * {@code Map<AttName, Map<ElemName, Set<Element>>>}. For default element mapping, the value is {@code *}.
     */
    public void setSubjectScheme(final Map<QName, Map<String, Set<Element>>> bindingMap) {
        this.bindingMap = bindingMap;
    }

    /** Use {@link #read(URI)} instead. */
    @Deprecated
    @Override
    public void read(final File input) {
        assert input.isAbsolute();
        read(input.toURI());
    }

    public void read(final URI input) {
        assert input.isAbsolute();
        ditaVal = input;

        final Document doc;
        builder.setErrorHandler(new DITAOTXMLErrorHandler(ditaVal.toString(), logger));
        try {
            doc = builder.parse(input.toString());
        } catch (SAXException | IOException e) {
            logger.error("Failed to read DITAVAL file: " + e.getMessage(), e);
            return;
        }

        readDocument(doc);

        if (bindingMap != null && !bindingMap.isEmpty()) {
            final Map<FilterKey, Action> buf = new HashMap<>(filterMap);
            for (final Map.Entry<FilterKey, Action> e : buf.entrySet()) {
                refineAction(e.getValue(), e.getKey());
            }
        }
    }

    private void readDocument(final Document doc) {
        final Element root = doc.getDocumentElement();
        final List<Element> props = toList(root.getElementsByTagName(ELEMENT_NAME_PROP));
        for (final Element prop : props) {
            readProp(prop);
        }
        final List<Element> revprops = toList(root.getElementsByTagName(ELEMENT_NAME_REVPROP));
        for (final Element revprop : revprops) {
            readProp(revprop);
        }
        final List<Element> styleConflicts = toList(root.getElementsByTagName("style-conflict"));
        for (final Element styleConflict : styleConflicts) {
            readStyleConflict(styleConflict);
        }
    }

    public void readProp(final Element elem) {
        final String attAction = elem.getAttribute(ELEMENT_NAME_ACTION);
        Action action;
        switch (attAction) {
        case "include":
            action = Action.INCLUDE;
            break;
        case "exclude":
            action = Action.EXCLUDE;
            break;
        case "passthrough":
            action = Action.PASSTHROUGH;
            break;
        case "flag":
            action = readFlag(elem);
            break;
        default:
            throw new IllegalArgumentException(MessageUtils.getMessage("DOTJ077F", attAction).toString());
        }
        if (action != null) {
            final QName attName;
            if (elem.getTagName().equals(ELEMENT_NAME_REVPROP)) {
                attName = REV;
            } else {
                final String attValue = getValue(elem, ATTRIBUTE_NAME_ATT);
                if (attValue != null) {
                    if (attValue.contains(":")) {
                        final String[] parts = attValue.split(":");
                        final String uri;
                        if (parts[0].equals(XML_NS_PREFIX)) {
                            uri = XML_NS_URI;
                        } else {
                            uri = elem.lookupNamespaceURI(parts[0]);
                        }
                        attName = new QName(uri, parts[1], parts[0]);
                    } else {
                        attName = QName.valueOf(attValue);
                    }
                } else {
                    attName = null;
                }
                if (attName != null && attName.equals(REV) && !filterAttributes.isEmpty()
                        && !filterAttributes.contains(REV)) {
                    logger.warn(MessageUtils.getMessage("DOTJ074W").toString());
                    return;
                }
            }
            final String attValue = getValue(elem, ATTRIBUTE_NAME_VAL);
            final FilterKey key = attName != null ? new FilterKey(attName, attValue) : DEFAULT;
            insertAction(action, key);
        }
    }

    private Flag readFlag(Element elem) {
        final String style = getValue(elem, ATTRIBUTE_NAME_STYLE);
        return new Flag(elem.getLocalName(), getValue(elem, ATTRIBUTE_NAME_COLOR),
                getValue(elem, ATTRIBUTE_NAME_BACKCOLOR), style != null ? style.trim().split("\\s+") : null,
                getValue(elem, ATTRIBUTE_NAME_CHANGEBAR), readFlagImage(elem, "startflag"),
                readFlagImage(elem, "endflag"));
    }

    private FlagImage readFlagImage(final Element elem, final String name) {
        final NodeList children = elem.getElementsByTagName(name);
        if (children.getLength() != 0) {
            final Element img = (Element) children.item(0);
            URI absolute = null;
            if (!img.getAttribute(ATTRIBUTE_NAME_IMAGEREF).isEmpty()) {
                absolute = URLUtils.toURI(img.getAttribute(ATTRIBUTE_NAME_IMAGEREF));
                URI relative;
                if (absolute.isAbsolute()) {
                    relative = getRelativePath(ditaVal, absolute);
                } else if (!img.getAttributeNS(DITA_OT_NAMESPACE, ATTRIBUTE_NAME_IMAGEREF_URI).isEmpty()) {
                    absolute = URI.create(img.getAttributeNS(DITA_OT_NAMESPACE, ATTRIBUTE_NAME_IMAGEREF_URI));
                    relative = absolute;
                } else {
                    relative = absolute;
                    absolute = ditaVal.resolve(absolute);
                }
                imageList.add(absolute);
                relFlagImageList.add(relative);

                if (tempFileNameScheme != null && job.getFileInfo(absolute) == null) {
                    final URI dstTemp = tempFileNameScheme.generateTempFileName(absolute);
                    final Job.FileInfo.Builder fi = new Job.FileInfo.Builder().src(absolute).uri(dstTemp)
                            .format("flag");
                    job.add(fi.build());
                }
            }

            String altText = null;
            final NodeList alts = img.getElementsByTagName("alt-text");
            if (alts.getLength() != 0) {
                altText = getText(alts.item(0));
            }

            if (absolute != null || altText != null) {
                return new FlagImage(absolute, altText);
            }
        }
        return null;
    }

    private void readStyleConflict(final Element elem) {
        foregroundConflictColor = getValue(elem, "foreground-conflict-color");
        backgroundConflictColor = getValue(elem, "background-conflict-color");
    }

    /**
     * Refine action key with information from subject schemes.
     */
    private void refineAction(final Action action, final FilterKey key) {
        if (key.value != null && bindingMap != null && !bindingMap.isEmpty()) {
            final Map<String, Set<Element>> schemeMap = bindingMap.get(key.attribute);
            if (schemeMap != null && !schemeMap.isEmpty()) {
                for (final Set<Element> submap : schemeMap.values()) {
                    for (final Element e : submap) {
                        final Element subRoot = searchForKey(e, key.value);
                        if (subRoot != null) {
                            insertAction(subRoot, key.attribute, action);
                        }
                    }
                }
            }
        }
    }

    /**
     * Insert subject scheme based action into filetermap if key not present in the map
     *
     * @param subTree subject scheme definition element
     * @param attName attribute name
     * @param action action to insert
     */
    private void insertAction(final Element subTree, final QName attName, final Action action) {
        if (subTree == null || action == null) {
            return;
        }

        final LinkedList<Element> queue = new LinkedList<>();

        // Skip the sub-tree root because it has been added already.
        NodeList children = subTree.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
                queue.offer((Element) children.item(i));
            }
        }

        while (!queue.isEmpty()) {
            final Element node = queue.poll();
            children = node.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
                    queue.offer((Element) children.item(i));
                }
            }
            if (SUBJECTSCHEME_SUBJECTDEF.matches(node)) {
                final String key = node.getAttribute(ATTRIBUTE_NAME_KEYS);
                if (key != null && !key.trim().isEmpty()) {
                    final FilterKey k = new FilterKey(attName, key);
                    if (!filterMap.containsKey(k)) {
                        filterMap.put(k, action);
                    }
                }
            }
        }
    }

    /**
     * Search subject scheme elements for a given key
     * @param root subject scheme element tree to search through
     * @param keyValue key to locate
     * @return element that matches the key, otherwise {@code null}
     */
    private Element searchForKey(final Element root, final String keyValue) {
        if (root == null || keyValue == null) {
            return null;
        }
        final LinkedList<Element> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            final Element node = queue.removeFirst();
            final NodeList children = node.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
                    queue.add((Element) children.item(i));
                }
            }
            if (SUBJECTSCHEME_SUBJECTDEF.matches(node)) {
                final String key = node.getAttribute(ATTRIBUTE_NAME_KEYS);
                if (keyValue.equals(key)) {
                    return node;
                }
            }
        }
        return null;
    }

    /**
     * Insert action into filetermap if key not present in the map
     */
    private void insertAction(final Action action, final FilterKey key) {
        if (filterMap.get(key) == null) {
            filterMap.put(key, action);
        } else {
            logger.warn(MessageUtils.getMessage("DOTJ007W", key.toString()).toString());
        }
    }

    /**
     * Return the image list.
     * @return image list
     */
    public List<URI> getImageList() {
        return imageList;
    }

    /**
     * Return the filter map.
     * @return filter map
     */
    public Map<FilterKey, Action> getFilterMap() {
        return Collections.unmodifiableMap(filterMap);
    }

    /**
     * reset filter map.
     */
    public void filterReset() {
        filterMap.clear();
    }

    /**
     * get image list relative to the .ditaval file.
     * @return image list
     */
    public List<URI> getRelFlagImageList() {
        return relFlagImageList;
    }

    public String getForegroundConflictColor() {
        return foregroundConflictColor;
    }

    public String getBackgroundConflictColor() {
        return backgroundConflictColor;
    }
}