/* * DSS - Digital Signature Services * * Copyright (C) 2013 European Commission, Directorate-General Internal Market and Services (DG MARKT), B-1049 Bruxelles/Brussel * * Developed by: 2013 ARHS Developments S.A. (rue Nicolas Bové 2B, L-1253 Luxembourg) http://www.arhs-developments.com * * This file is part of the "DSS - Digital Signature Services" project. * * "DSS - Digital Signature Services" 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. * * DSS 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 * "DSS - Digital Signature Services". If not, see <http://www.gnu.org/licenses/>. */ package eu.europa.ec.markt.dss.validation102853.xml; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.commons.lang.StringEscapeUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import eu.europa.ec.markt.dss.DSSXMLUtils; import eu.europa.ec.markt.dss.exception.DSSException; import eu.europa.ec.markt.dss.validation102853.rules.MessageTag; public class XmlNode { private String name; private String value; private String nameSpace = ""; private HashMap<String, String> attributes = new HashMap<String, String>(); private List<XmlNode> children = new ArrayList<XmlNode>(); private XmlNode parentNode; public XmlNode(final String name) { this(name, null); } public XmlNode(final String name, final String value) { int _pos = name.indexOf(' '); if (_pos != -1) { throw new DSSException("The node name is not correct: " + name); } this.name = name; this.value = value; } public XmlNode(final String name, final MessageTag messageTag, final Map<String, String> attributes) { int _pos = name.indexOf(' '); if (_pos != -1) { throw new DSSException("The node name is not correct: " + name); } this.name = name; if (messageTag != null && !messageTag.equals(MessageTag.EMPTY)) { this.value = messageTag.getMessage(); this.attributes.put(MessageTag.NAME_ID, messageTag.name()); } if (attributes != null) { this.attributes.putAll(attributes); } } public void addChild(final XmlNode child) { /* if (!children.contains(child)) */ children.add(child); } public void addChildrenOf(final XmlNode parent) { for (final XmlNode child : parent.children) { children.add(child); } } public void addChildren(final List<XmlDom> xmlDomList) { for (final XmlDom xmlDom : xmlDomList) { addChild(xmlDom); } } public void addChild(final XmlDom child) { final Element element = child.rootElement; recursiveCopy(this, element); } public void addChildrenOf(final XmlDom parent) { final Element element = parent.rootElement; final NodeList nodes = element.getChildNodes(); for (int ii = 0; ii < nodes.getLength(); ii++) { final Node node = nodes.item(ii); if (node.getNodeType() == Node.ELEMENT_NODE) { recursiveCopy(this, node); } } } /** * @param xmlNode the {@code XmlNode} to which the element is added * @param element the {@code Node} to be copied */ private static void recursiveCopy(final XmlNode xmlNode, final Node element) { final String name = element.getNodeName(); final XmlNode _xmlNode = new XmlNode(name); final NamedNodeMap attributes = element.getAttributes(); for (int jj = 0; jj < attributes.getLength(); jj++) { final Node attrNode = attributes.item(jj); final String attrName = attrNode.getNodeName(); if (!"xmlns".equals(attrName)) { _xmlNode.setAttribute(attrName, attrNode.getNodeValue()); } } final NodeList nodes = element.getChildNodes(); boolean hasElementNodes = false; for (int ii = 0; ii < nodes.getLength(); ii++) { final Node node = nodes.item(ii); if (node.getNodeType() == Node.ELEMENT_NODE) { hasElementNodes = true; recursiveCopy(_xmlNode, node); } } if (!hasElementNodes) { final String value = element.getTextContent(); _xmlNode.setValue(value); } _xmlNode.setParent(xmlNode); } /** * This method adds a new empty child {@code XmlNode} with the given element name. * * @param childName the name of the element to add * @return added {@code XmlNode} */ public XmlNode addChild(final String childName) { final XmlNode child = new XmlNode(childName); children.add(child); child.parentNode = this; return child; } /** * This method adds a new child {@code XmlNode} with the given element name and value. * * @param childName the name of the element to add * @param value the text content of the child {@code XmlNode} * @return added {@code XmlNode} */ public XmlNode addChild(final String childName, final String value) { final XmlNode child = new XmlNode(childName, value); children.add(child); return child; } /** * This method adds a new child {@code XmlNode} with the given element name. The value of the new element is set from the call to {@code messageTag.getMessage()}. A new * attribute 'NAME_ID' is added with the value set to {@code messageTag.name()}. * * @param childName the name of the element to add * @param messageTag {@code MessageTag} * @return added {@code XmlNode} */ public XmlNode addChild(final String childName, final MessageTag messageTag) { final XmlNode child = new XmlNode(childName, messageTag, null); children.add(child); return child; } /** * This method adds a new child {@code XmlNode} with the given element name. The value of the new element is set from the call to {@code messageTag.getMessage()}. A new * attribute 'NAME_ID' is added with the value set to {@code messageTag.name()}. New attributes are created from the {@code attributes} {@code Map}. * * @param childName the name of the element to add * @param messageTag {@code MessageTag} * @param attributes {@code Map} containing pairs: attribute name, attribute value * @return added {@code XmlNode} */ public XmlNode addChild(final String childName, final MessageTag messageTag, final Map<String, String> attributes) { final XmlNode child = new XmlNode(childName, messageTag, attributes); children.add(child); return child; } public XmlNode addFirstChild(final String childName, final String value) { final XmlNode child = new XmlNode(childName, value); children.add(0, child); return child; } /** * This method allows to remove a first child with the given element name. * * @param elementName name of the element to remove * @return {@code boolean} {@code true} if the child was removed, {@code false} otherwise */ public boolean removeChild(final String elementName) { for (final XmlNode child : children) { if (child.name.equals(elementName)) { children.remove(child); return true; } } return false; } public XmlNode getParent() { return parentNode; } public void setParent(final XmlNode parentNode) { this.parentNode = parentNode; if (parentNode != null) { parentNode.addChild(this); } } public String getName() { return name; } /** * This method return the string value of the node. * * @return {@code String} content of the node */ public String getValue() { return value; } public void setValue(final String value) { this.value = value; } public String getNameSpace() { return nameSpace; } public void setNameSpace(final String nameSpace) { this.nameSpace = nameSpace; } /** * @return the {@code Map} of associated attributes. */ public Map<String, String> getAttributes() { return attributes; } /** * This method allows to set the attribute and its value. * * @param name the attribute name * @param value the attribute value * @return "this" which allows to call the method again. */ public XmlNode setAttribute(final String name, final String value) { attributes.put(name, value); return this; } /** * The returned list is never null. * * @return a modifiable list of children {@code XmlNode}. */ public List<XmlNode> getChildren() { return children; } private String getAttributeString() { final StringBuilder attributeString = new StringBuilder(); final Set<Map.Entry<String, String>> entries = attributes.entrySet(); for (final Entry<String, String> entry : entries) { String entryValue = entry.getValue(); entryValue = StringEscapeUtils.escapeXml(entryValue); attributeString.append(" ").append(entry.getKey()).append("='").append(entryValue).append("'"); } return attributeString.toString(); } /** * This method returns {@link org.w3c.dom.Document} based on the current {@link XmlNode}. * * @return */ public Document toDocument() { final InputStream inputStream = getInputStream(); final Document document = DSSXMLUtils.buildDOM(inputStream); return document; } /** * This method returns {@code XmlDom} representation of the current {@code XmlNode}. * * @return the {@code XmlDom} representation of the current {@code XmlNode}. */ public XmlDom toXmlDom() { final Document document = toDocument(); final XmlDom xmlDom = new XmlDom(document); return xmlDom; } private void writeNodes(final XmlNode node, final StringBuilder xml, final StringBuilder indent, String nameSpace) { for (final XmlNode node_ : node.children) { xml.append(indent).append('<').append(node_.name); if (!node_.attributes.isEmpty()) { xml.append(node_.getAttributeString()); } if (!node_.nameSpace.isEmpty()) { if (!nameSpace.equals(node_.nameSpace)) { xml.append(' ').append(String.format("xmlns=\"%s\"", node_.nameSpace)); nameSpace = node_.nameSpace; } } xml.append('>'); if (node_.children.size() > 0) { xml.append('\n'); indent.append('\t'); writeNodes(node_, xml, indent, nameSpace); indent.setLength(indent.length() - 1); xml.append(indent).append("</").append(node_.name).append('>').append('\n'); } else { if (node_.value == null) { xml.append("</").append(node_.name).append('>').append('\n'); } else { xml.append(node_.value).append("</").append(node_.name).append('>').append('\n'); } } } } /** * @return the {@code InputStream} representing the content of the node. */ public InputStream getInputStream() { try { final StringBuilder indent = new StringBuilder(); final StringBuilder xml = new StringBuilder(); final XmlNode masterNode = new XmlNode("__Master__"); final XmlNode savedParentNode = getParent(); if (savedParentNode != null) { setNameSpace(savedParentNode.getNameSpace()); } setParent(masterNode); writeNodes(masterNode, xml, indent, ""); parentNode = savedParentNode; final byte[] bytes = xml.toString().getBytes("UTF-8"); final InputStream in = new ByteArrayInputStream(bytes); return in; } catch (UnsupportedEncodingException e) { throw new DSSException("Error during the conversion of the XmlNode to the InputStream :", e); } } @Override public String toString() { try { final StringBuilder indent = new StringBuilder(); final StringBuilder xml = new StringBuilder(); final XmlNode masterNode = new XmlNode("__Master__", null); final XmlNode savedParentNode = getParent(); setParent(masterNode); writeNodes(masterNode, xml, indent, ""); parentNode = savedParentNode; return xml.toString(); } catch (Exception e) { return super.toString(); } } }