org.talend.commons.runtime.xml.XPathPrefixHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.talend.commons.runtime.xml.XPathPrefixHandler.java

Source

// ============================================================================
//
// Copyright (C) 2006-2015 Talend Inc. - www.talend.com
//
// This source code is available under agreement available at
// %InstallDIR%\features\org.talend.rcp.branding.%PRODUCTNAME%\%PRODUCTNAME%license.txt
//
// You should have received a copy of the agreement
// along with this program; if not, write to Talend SA
// 9 rue Pages 92150 Suresnes, France
//
// ============================================================================
package org.talend.commons.runtime.xml;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;

import org.apache.commons.collections.BidiMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;
import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * DOC chuang class global comment. Detailled comment
 */
public class XPathPrefixHandler {

    private Map<String, List<NodeInfo>> nameNodesMap;

    private Map<String, NodeInfo> pathNodesMap;

    private Map<String, String> dataTypeFromXSD;

    private CustomNamespaceContext namespaceContext;

    private boolean isXSDFile;

    public XPathPrefixHandler(Element root) {
        this.isXSDFile = false;
        nameNodesMap = new HashMap<String, List<NodeInfo>>();
        pathNodesMap = new HashMap<String, NodeInfo>();
        dataTypeFromXSD = new HashMap<String, String>();
        namespaceContext = new CustomNamespaceContext();
        collectNodes(root, "", 0); //$NON-NLS-1$
        sortByLevel(nameNodesMap);

        if (isXSDFile) {
            replaceReferences();
        }
    }

    /**
     * DOC chuang Comment method "sortByLevel".
     * 
     * @param nameNodesMap2
     */
    private void sortByLevel(Map<String, List<NodeInfo>> map) {
        for (List<NodeInfo> list : map.values()) {
            Collections.sort(list, new Comparator<NodeInfo>() {

                public int compare(NodeInfo o1, NodeInfo o2) {
                    return o1.level - o2.level;
                }
            });
        }

    }

    public CustomNamespaceContext getNamespaceContext() {
        return namespaceContext;
    }

    private String getQualifiedName(String prefix, String name) {
        StringBuilder buf = new StringBuilder();
        if (name.indexOf("@") < 0) { //$NON-NLS-1$
            if (StringUtils.isEmpty(prefix)) {
                // no namespace
                buf.append(name);
            } else {
                buf.append(prefix).append(":").append(name); //$NON-NLS-1$
            }
        } else {
            // buf.append("@").append(prefix).append(":").append(name.substring(1));
            // attribute does not need to add prefix
            buf.append(name);
        }
        return buf.toString();
    }

    /**
     * DOC chuang Comment method "addXPathPrefix".
     * 
     * @param relativeXPathExpression
     * @param referenceNode
     * @return
     */
    public String addXPathPrefix(String relativeXPathExpression, Node node) {
        if (namespaceContext.getNamespaceCount() == 0) {
            return relativeXPathExpression;
        }
        if (relativeXPathExpression.startsWith("/")) { //$NON-NLS-1$
            // absolute path
            return addXPathPrefix(relativeXPathExpression);
        }

        List<NodeInfo> list = nameNodesMap.get(node.getNodeName());
        if (list == null) {
            return null;
        }
        NodeInfo info = getNodeInfo(node, list);
        String path = getQualifiedXPath(info.path);
        String expr = info.path + "/" + relativeXPathExpression; //$NON-NLS-1$
        String result = addXPathPrefix(expr);
        return extractRelativePath(result, relativeXPathExpression);
        // return result.substring(path.length() + 1);
    }

    /**
     * DOC chuang Comment method "extractRelativePath".
     * 
     * @param result
     * @param relativeXPathExpression
     * @return
     */
    private String extractRelativePath(String absolutePath, String relativePath) {
        String[] aPath = absolutePath.split("/"); //$NON-NLS-1$
        String[] rPath = relativePath.split("/"); //$NON-NLS-1$
        String[] path = new String[rPath.length];
        int i = aPath.length - rPath.length;
        for (int j = 0; j < rPath.length; j++, i++) {
            path[j] = aPath[i];
        }
        return StringUtils.join(path, "/"); //$NON-NLS-1$
    }

    /**
     * DOC chuang Comment method "getNodeInfo".
     * 
     * @param node
     * @param list
     */
    private NodeInfo getNodeInfo(Node node, List<NodeInfo> list) {
        NodeInfo info = null;

        for (int i = 0; i < list.size(); i++) {

            if (list.get(i).namespace.equals(node.getNamespaceURI())) {
                info = list.get(i);
                break;
            } else if (StringUtils.isEmpty(list.get(i).namespace) && StringUtils.isEmpty(node.getNamespaceURI())) {
                info = list.get(i);
                break;
            }
        }
        return info;
    }

    public NodeInfo getNodeInfo(String path) {
        return pathNodesMap.get(path);
    }

    public String getDataTypeFromXSD(String path) {
        return dataTypeFromXSD.get(path);
    }

    private String getQualifiedXPath(String path) {
        if (path.equals("/") || path.equals("")) { //$NON-NLS-1$ //$NON-NLS-2$
            return ""; //$NON-NLS-1$
        }
        int pos = path.lastIndexOf("/"); //$NON-NLS-1$
        String pre = getQualifiedXPath(path.substring(0, pos));
        String namespace = pathNodesMap.get(path).namespace;
        return pre + "/" + getQualifiedName(namespaceContext.getPrefix(namespace), path.substring(pos + 1)); //$NON-NLS-1$
    }

    public String addXPathPrefix(String xPathExpression) {
        if (namespaceContext.getNamespaceCount() == 0) {
            return xPathExpression;
        }

        String[] names = xPathExpression.split("/"); //$NON-NLS-1$
        PathSegment[] paths = createPathSegments(names);

        int totalFixed = resolvePath(paths);
        while (totalFixed < names.length) {
            int fixed = 0;
            for (int i = 0; i < paths.length; i++) {
                if (!paths[i].resolved) {
                    // several node with same name
                    List<NodeInfo> nodes = nameNodesMap.get(paths[i].originalPath);
                    if (nodes != null)
                        for (NodeInfo node : nodes) {
                            if (validatePath(paths, i, node)) {
                                fixed++;
                                totalFixed++;
                                paths[i].resolved = true;
                                paths[i].info = node;
                                paths[i].transformPath = getQualifiedName(
                                        namespaceContext.getPrefix(node.namespace), paths[i].originalPath);
                                break;
                            }
                        }
                }
            }
            if (fixed == 0) {
                // avoid dead loop
                break;
            }
        }

        if (totalFixed < names.length) {
            // try to fix some unknown segment
            fixUnknownPath(paths);
        }

        // convert to xpath string
        StringBuilder expr = new StringBuilder();
        for (PathSegment p : paths) {
            expr.append(p.transformPath + "/"); //$NON-NLS-1$
        }
        if (names.length > 0) {
            expr.deleteCharAt(expr.length() - 1);
        }
        return expr.toString();
    }

    /**
     * DOC chuang Comment method "fixXPath".
     * 
     * @param paths
     */
    private void fixUnknownPath(PathSegment[] paths) {
        for (int i = 0; i < paths.length; i++) {
            PathSegment seg = paths[i];
            if (seg.resolved == true) {
                continue;
            }
            List<NodeInfo> nodes = nameNodesMap.get(paths[i].originalPath);
            if (nodes != null) {
                paths[i].transformPath = namespaceContext.getPrefix(nodes.get(0).namespace) + ":" //$NON-NLS-1$
                        + paths[i].originalPath;
                paths[i].resolved = true;
            }
        }

    }

    /**
     * DOC chuang Comment method "validate".
     * 
     * @param paths
     * @param pos
     * @param node
     * @return
     */
    private boolean validatePath(PathSegment[] paths, int pos, NodeInfo node) {
        int level = 0;
        for (int i = pos + 1; i < paths.length; i++) {
            PathSegment seg = paths[i];
            if (seg.originalPath.equals("..")) { //$NON-NLS-1$
                // get to parent node
                level--;
            } else if (!seg.originalPath.equals(".")) { //$NON-NLS-1$
                // get to child node
                level++;
            }

            if (!seg.resolved || seg.info == null || level < 1) {
                continue;
            }
            // check xpath of two node
            if (seg.info.level < node.level || seg.info.path.indexOf(node.path) < 0) {
                return false;
            }
        }

        level = 0;
        for (int i = pos - 1; i >= 0; i--) {
            PathSegment seg = paths[i];
            if (seg.originalPath.equals("..")) { //$NON-NLS-1$
                // get to parent node
                level--;
            } else if (!seg.originalPath.equals(".")) { //$NON-NLS-1$
                // get to child node
                if (level < 0) {
                    level++;
                    continue;
                }
            }
            if (!seg.resolved || seg.info == null) {
                continue;
            }
            // check xpath of two node
            if (node.level < seg.info.level || node.path.indexOf(seg.info.path) < 0) {
                return false;
            }

        }

        return true;
    }

    /**
     * DOC chuang Comment method "resolve".
     * 
     * @param path
     */
    private int resolvePath(PathSegment[] path) {
        int fixed = 0;
        for (PathSegment p : path) {
            List<NodeInfo> list = nameNodesMap.get(p.originalPath);
            if (p.originalPath.indexOf(":") > -1) { //$NON-NLS-1$
                p.resolved = true;
                p.transformPath = p.originalPath;
                /* handle NPE for 0020293 */
                if (list != null) {
                    p.info = list.get(0);
                } else
                    p.info = null;
                fixed++;
            } else if (p.originalPath.indexOf(".") > -1 || p.originalPath.equals("")) { //$NON-NLS-1$ //$NON-NLS-2$
                p.resolved = true;
                p.transformPath = p.originalPath;
                p.info = null;
                fixed++;
            } else {
                List<NodeInfo> nodes = list;
                // has only one node
                if (nodes != null && nodes.size() == 1) {
                    p.resolved = true;
                    p.info = nodes.get(0);
                    p.transformPath = getQualifiedName(namespaceContext.getPrefix(p.info.namespace),
                            p.originalPath);
                    fixed++;
                }
            }
        }
        return fixed;
    }

    /**
     * DOC chuang Comment method "createPathSegments".
     * 
     * @param names
     * @return
     */
    private PathSegment[] createPathSegments(String[] names) {
        PathSegment[] path = new PathSegment[names.length];
        for (int i = 0; i < names.length; i++) {
            path[i] = new PathSegment(names[i]);
        }
        return path;
    }

    /**
     * 
     * DOC chuang XPathPrefixHandler class global comment. Detailled comment
     */
    static class PathSegment {

        String originalPath;

        String transformPath;

        boolean resolved;

        NodeInfo info;

        PathSegment(String path) {
            originalPath = path;
            transformPath = null;
            resolved = false;
            info = null;
        }

    }

    /**
     * DOC chuang Comment method "getPrefix".
     * 
     * @param name
     * @return
     */
    private String getPrefix(String name) {
        List<NodeInfo> nodes = nameNodesMap.get(name);
        NodeInfo info = nodes.get(0);
        return namespaceContext.getPrefix(info.namespace);
    }

    /**
     * DOC chuang Comment method "collectNodes".
     * 
     * @param root2
     * @param string
     */
    private void collectNodes(Node node, String path, int level) {

        int type = node.getNodeType();
        if (type == Node.ELEMENT_NODE) {
            String elementName = node.getNodeName();

            if (isXSDFile) {
                elementName = null;
                if (node.getNodeName().equals("xs:element")) { //$NON-NLS-1$
                    if (node.getAttributes().getLength() > 0) {
                        Node nameAttribute = node.getAttributes().getNamedItem("name"); //$NON-NLS-1$
                        if (nameAttribute != null) {
                            elementName = nameAttribute.getNodeValue();
                        }
                        Node refAttribute = node.getAttributes().getNamedItem("ref"); //$NON-NLS-1$
                        if (refAttribute != null) {
                            elementName = "#" + refAttribute.getNodeValue(); // add a mark to set as ref element. //$NON-NLS-1$
                        }
                    }
                }
            }

            String currentPath = null;
            if (elementName != null) {
                currentPath = new StringBuilder(path).append("/").append(elementName).toString(); //$NON-NLS-1$
                // if (pathNodesMap.get(currentPath) != null) {
                // return;
                // }
            }
            if (currentPath == null) {
                currentPath = path;
            }
            NodeInfo info = new NodeInfo(node, currentPath);
            info.level = level;
            computeNamespace(info, path);
            // System.out.println(node.getNodeName() + " " + info.path + " " + info.namespace + "\n");
            if (elementName != null) {
                if (pathNodesMap.get(currentPath) == null) {
                    pathNodesMap.put(currentPath, info);
                }
                if (isXSDFile) {
                    elementName = null;
                    if (node.getNodeName().equals("xs:element")) { //$NON-NLS-1$
                        if (node.getAttributes().getLength() > 0) {
                            Node nameAttribute = node.getAttributes().getNamedItem("type"); //$NON-NLS-1$
                            if (nameAttribute != null) {
                                dataTypeFromXSD.put(currentPath, nameAttribute.getNodeValue());
                            }
                        }
                    }
                }
                addNodeInfoToMap(elementName, info);
            }

            // visit child nodes
            NodeList childNodes = node.getChildNodes();
            int length = childNodes.getLength();
            for (int i = 0; i < length; i++) {
                Node item = childNodes.item(i);
                collectNodes(item, currentPath, level + 1);
            }
        }
    }

    /**
     * DOC nrousseau Comment method "replaceReferences".
     */
    private void replaceReferences() {
        Map<String, NodeInfo> newPathNodesMap = new HashMap<String, NodeInfo>();

        for (String path : pathNodesMap.keySet()) {
            if (path.contains("#")) { //$NON-NLS-1$
                replaceReferences(path, newPathNodesMap);
            }
        }

        pathNodesMap.putAll(newPathNodesMap);
    }

    private void replaceReferences(String path, Map<String, NodeInfo> newPathNodesMap) {
        String newPath = path.substring(0, path.indexOf("#") - 1); //$NON-NLS-1$
        String objectToReplace = "/" + path.substring(path.indexOf("#") + 1); //$NON-NLS-1$ //$NON-NLS-2$
        for (String refPath : pathNodesMap.keySet()) {
            if (refPath.equals(objectToReplace) || refPath.startsWith(objectToReplace + "/")) { //$NON-NLS-1$
                String newFullPath = newPath + refPath;

                if (!newFullPath.contains("#")) { //$NON-NLS-1$
                    if (dataTypeFromXSD.containsKey(refPath)) {
                        dataTypeFromXSD.put(newFullPath, dataTypeFromXSD.get(refPath));
                    }
                    newPathNodesMap.put(newFullPath, pathNodesMap.get(refPath));
                } else {
                    replaceReferences(newFullPath, newPathNodesMap);
                }
            }
        }
    }

    /**
     * DOC chuang Comment method "addInfoToMap".
     * 
     * @param node
     * @param info
     */
    private void addNodeInfoToMap(String name, NodeInfo info) {
        List<NodeInfo> nodeInfos = nameNodesMap.get(name);
        if (nodeInfos == null) {
            nodeInfos = new ArrayList<NodeInfo>();
            nameNodesMap.put(name, nodeInfos);
        }
        nodeInfos.add(info);
    }

    /**
     * DOC chuang Comment method "getNamespace".
     * 
     * @param node
     * @param parentPath
     * @return
     */
    private void computeNamespace(NodeInfo info, String parentPath) {
        Node node = info.node;
        List<Node> attributes = new ArrayList<Node>();
        // return node.getNamespaceURI();
        String defaultNamespace = null;
        if (isXSDFile) {
            if (node.getNodeName().equals("xs:attribute")) { //$NON-NLS-1$
                attributes.add(node);
            }
        }
        NamedNodeMap nodeMap = node.getAttributes();
        for (int i = 0; i < nodeMap.getLength(); i++) {
            Node attr = nodeMap.item(i);
            String attrName = attr.getNodeName();
            boolean isPrefix = attrName.startsWith(XMLConstants.XMLNS_ATTRIBUTE)
                    && (!isXSDFile || !attrName.equals("xmlns:xs")); //$NON-NLS-1$
            if (isPrefix) {
                if (attrName.length() == XMLConstants.XMLNS_ATTRIBUTE.length()) {
                    defaultNamespace = attr.getNodeValue();
                    namespaceContext.addNamespaceURI(XMLConstants.NULL_NS_URI, attr.getNodeValue());
                } else {
                    int index = attrName.indexOf(':');
                    String prefix = attrName.substring(index + 1);
                    namespaceContext.addNamespaceURI(prefix, attr.getNodeValue());
                }
            } else if (!isXSDFile) {
                attributes.add(attr);
            }
        }

        if (defaultNamespace != null) {
            info.defaultNamespace = defaultNamespace;
        } else {
            // same as parent
            if (parentPath.equals("")) { //$NON-NLS-1$
                // no namespace
                info.defaultNamespace = ""; //$NON-NLS-1$
            } else {
                info.defaultNamespace = pathNodesMap.get(parentPath).defaultNamespace;
            }
        }

        String part[] = node.getNodeName().split(":"); //$NON-NLS-1$
        if (part.length > 1) {
            info.namespace = namespaceContext.getNamespaceURI(part[0]);
        } else {
            info.namespace = info.defaultNamespace;
        }

        collectAttributes(info, attributes);
    }

    /**
     * DOC chuang Comment method "collectAttributes".
     * 
     * @param info
     * @param attributes
     */
    private void collectAttributes(NodeInfo info, List<Node> attributes) {
        for (Node attr : attributes) {
            String elementName = attr.getNodeName();
            if (isXSDFile) {
                if (attr.getAttributes().getLength() > 0) {
                    Node nameAttribute = attr.getAttributes().getNamedItem("name"); //$NON-NLS-1$
                    if (nameAttribute != null) {
                        elementName = nameAttribute.getNodeValue();
                    }
                }
            }
            String currentPath = new StringBuilder(info.path).append("/@").append(elementName).toString(); //$NON-NLS-1$
            if (pathNodesMap.get(currentPath) != null) {
                continue;
            }

            if (isXSDFile) {
                if (attr.getAttributes().getLength() > 0) {
                    Node nameAttribute = attr.getAttributes().getNamedItem("type"); //$NON-NLS-1$
                    if (nameAttribute != null) {
                        dataTypeFromXSD.put(currentPath, nameAttribute.getNodeValue());
                    }
                }
            }
            NodeInfo attrInfo = new NodeInfo(attr, currentPath);
            attrInfo.level = info.level + 1;
            attrInfo.defaultNamespace = info.defaultNamespace;
            int pos = elementName.indexOf(':');
            if (pos > -1) {
                attrInfo.namespace = namespaceContext.getNamespaceURI(elementName.substring(0, pos));
            } else {
                attrInfo.namespace = info.defaultNamespace;
            }
            pathNodesMap.put(currentPath, attrInfo);
            addNodeInfoToMap("@" + elementName, attrInfo); //$NON-NLS-1$
        }
    }

    /**
     * 
     * DOC chuang XPathPrefixHandler class global comment. Detailled comment
     */
    static class NodeInfo {

        Node node;

        String path;

        int level;

        String namespace;

        String defaultNamespace;

        NodeInfo(Node node, String path) {
            super();
            this.node = node;
            this.path = path;
        }

    };

    /**
     * 
     * DOC chuang XPathPrefixHandler class global comment. Detailled comment
     */
    static class CustomNamespaceContext implements NamespaceContext {

        private BidiMap prefixToNamespace = new DualHashBidiMap();

        private static final String DEFAULT_PREFIX = "default_ns_"; //$NON-NLS-1$

        private int defaultNamespaceCount = 0;

        private int namespaceCount = 0;

        public int getNamespaceCount() {
            return namespaceCount;
        }

        public void addNamespaceURI(String prefix, String namespace) {
            if (prefix == null || namespace == null) {
                return;
            }
            namespaceCount++;
            if (prefix.equals("")) { //$NON-NLS-1$
                // default namespace
                defaultNamespaceCount++;
                prefixToNamespace.put(DEFAULT_PREFIX + defaultNamespaceCount, namespace);
            } else {
                prefixToNamespace.put(prefix, namespace);
            }
        }

        /*
         * (non-Javadoc)
         * 
         * @see javax.xml.namespace.NamespaceContext#getNamespaceURI(java.lang.String)
         */
        public String getNamespaceURI(String prefix) {
            return (String) prefixToNamespace.get(prefix);
        }

        /*
         * (non-Javadoc)
         * 
         * @see javax.xml.namespace.NamespaceContext#getPrefix(java.lang.String)
         */
        public String getPrefix(String namespaceURI) {
            if (namespaceURI == null || "".equals(namespaceURI)) {
                return ""; //$NON-NLS-1$
            }
            return (String) prefixToNamespace.getKey(namespaceURI);
        }

        /*
         * (non-Javadoc)
         * 
         * @see javax.xml.namespace.NamespaceContext#getPrefixes(java.lang.String)
         */
        public Iterator getPrefixes(String namespaceURI) {
            return null;
        }
    }

}