All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.tomakehurst.wiremock.common.xml.XmlNode Maven / Gradle / Ivy

There is a newer version: 3.9.2
Show newest version
/*
 * Copyright (C) 2020-2024 Thomas Akehurst
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.tomakehurst.wiremock.common.xml;

import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked;
import static javax.xml.transform.OutputKeys.INDENT;
import static javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION;

import com.github.tomakehurst.wiremock.common.ListOrSingle;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.XMLReader;

public class XmlNode {

  protected static final ThreadLocal XPATH_CACHE =
      ThreadLocal.withInitial(
          () -> {
            final XPathFactory xPathfactory = XPathFactory.newInstance();
            return xPathfactory.newXPath();
          });

  protected static final ThreadLocal TRANSFORMER_CACHE =
      ThreadLocal.withInitial(
          () -> {
            TransformerFactory transformerFactory;
            try {
              // Optimization to get likely transformerFactory directly, rather than going through
              // FactoryFinder#find
              transformerFactory =
                  (TransformerFactory)
                      Class.forName(
                              "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl")
                          .getDeclaredConstructor()
                          .newInstance();
            } catch (Exception e) {
              transformerFactory = TransformerFactory.newInstance();
            }
            transformerFactory.setAttribute("indent-number", 2);

            try {
              Transformer transformer = transformerFactory.newTransformer();
              transformer.setOutputProperty(INDENT, "yes");
              transformer.setOutputProperty(OMIT_XML_DECLARATION, "yes");
              return transformer;
            } catch (TransformerConfigurationException e) {
              return throwUnchecked(e, Transformer.class);
            }
          });

  private static final Class DOM2SAX_XMLREADER_CLASS = getDom2SaxAvailability();

  private static Class getDom2SaxAvailability() {
    try {
      return (Class)
          Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.DOM2SAX");
    } catch (ClassNotFoundException e) {
      return null;
    }
  }

  private final Node domNode;
  private final Map attributes;

  public XmlNode(Node domNode) {
    this.domNode = domNode;
    attributes =
        domNode.hasAttributes()
            ? convertAttributeMap(domNode.getAttributes())
            : Collections.emptyMap();
  }

  private static Map convertAttributeMap(NamedNodeMap namedNodeMap) {
    Map map = new HashMap<>();
    for (int i = 0; i < namedNodeMap.getLength(); i++) {
      Node node = namedNodeMap.item(i);
      map.put(node.getNodeName(), node.getNodeValue());
    }

    return Collections.unmodifiableMap(map);
  }

  public Map getAttributes() {
    return attributes;
  }

  protected static ListOrSingle toListOrSingle(NodeList nodeList) {
    ListOrSingle nodes = new ListOrSingle<>();
    for (int i = 0; i < nodeList.getLength(); i++) {
      nodes.add(new XmlNode(nodeList.item(i)));
    }

    return nodes;
  }

  public String getName() {
    return domNode.getNodeName();
  }

  public String getText() {
    return domNode.getTextContent();
  }

  @Override
  public String toString() {
    switch (domNode.getNodeType()) {
      case Node.TEXT_NODE:
      case Node.ATTRIBUTE_NODE:
        return domNode.getTextContent();
      case Node.DOCUMENT_NODE:
      case Node.ELEMENT_NODE:
        return render();
      default:
        return domNode.toString();
    }
  }

  private String render() {
    try {
      Transformer transformer = TRANSFORMER_CACHE.get();
      StreamResult result = new StreamResult(new StringWriter());
      Source source = getSourceForTransform(domNode);
      transformer.transform(source, result);
      return result.getWriter().toString();
    } catch (Exception e) {
      return throwUnchecked(e, String.class);
    }
  }

  // This nasty little hack attempts to ensure no exception is thrown when attempting to print an
  // XML node with
  // unbound namespace prefixes (which can happen when you've selected an element via XPath whose
  // namespaces are declared in a parent element).
  // For some reason Transformer is happy to do this with a SAX source, but not a DOM source.
  private static Source getSourceForTransform(Node node) {
    if (DOM2SAX_XMLREADER_CLASS != null) {
      try {
        Constructor constructor = DOM2SAX_XMLREADER_CLASS.getConstructor(Node.class);
        XMLReader dom2SAX = constructor.newInstance(node);
        SAXSource saxSource = new SAXSource();
        saxSource.setXMLReader(dom2SAX);
        return saxSource;
      } catch (NoSuchMethodException
          | InstantiationException
          | IllegalAccessException
          | InvocationTargetException e) {
        return new DOMSource(node);
      }
    }

    return new DOMSource(node);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy