// Copyright 2012 Google Inc. All Rights Reserved. // // 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.google.api.ads.common.lib.utils; import com.google.common.base.Supplier; import com.google.common.collect.Maps; import com.google.inject.Inject; import com.google.inject.name.Named; import org.slf4j.Logger; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import java.io.IOException; import java.io.InputStream; import java.util.Map; import javax.annotation.Nullable; import javax.xml.parsers.DocumentBuilder; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; /** * Utility class to extract fields from XML. */ public class XmlFieldExtractor { private final Logger logger; private final Supplier<DocumentBuilder> documentBuilderSupplier; private final Supplier<XPath> xpathSupplier; @Inject public XmlFieldExtractor( @Named("soapXmlLogger") Logger soapXmlLogger, Supplier<DocumentBuilder> documentBuilderSupplier, Supplier<XPath> xpathSupplier) { this.documentBuilderSupplier = documentBuilderSupplier; this.xpathSupplier = xpathSupplier; this.logger = soapXmlLogger; } /** * Locates the target fields in the specified XML and uses a wildcard XPath to * identify the first matching node and return it in a map. * * <p>For example, for the xml: {@code <xml><bar>BAR</bar></xml></code>} and * field "bar", this class return a map with key "bar" and value "BAR". * * @param xml Stream of XML to process. * @param fields List of fields to look for. * @return Non-null map of fieldname to value. Will only contain entries for fields found * in the stream. */ public Map<String, String> extract(InputStream xml, String[] fields) { Map<String, String> parsedFields = Maps.newHashMap(); try { Document doc = documentBuilderSupplier.get().parse(xml); for (String field : fields) { try { String value = extract(doc, field); if (value != null) { parsedFields.put(field, value); } } catch (XPathExpressionException e) { // Ignore problems with the xpath. logger.warn("While processing xml, XPath invalid.", e); } } } catch (SAXException e) { logger.error("Couldn't process XML into a Document", e); } catch (IOException e) { logger.error("Problem reading input stream", e); } return parsedFields; } /** * Runs a wildcard xpath (i.e. "//field") to locate the provided field in the * document and returns the text content of the first matching node. * * @param doc Document to process. * @param field Field to look for. * @return Text content of first matching node or null for no match. * @throws XPathExpressionException If expression is invalid. */ @Nullable private String extract(Document doc, String field) throws XPathExpressionException { XPathExpression expr = xpathSupplier.get().compile("//" + field); NodeList nl = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); if (nl.getLength() > 0) { return nl.item(0).getTextContent(); } return null; } }