
com.adobe.aemds.guide.utils.XMLUtils Maven / Gradle / Ivy
/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* Copyright 2014 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
package com.adobe.aemds.guide.utils;
import com.adobe.aemds.guide.service.GuideException;
import com.adobe.forms.common.service.FormDataXMLProviderRegistry;
import com.adobe.forms.foundation.util.ContentConverterUtils;
import com.adobe.granite.resourceresolverhelper.ResourceResolverHelper;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.NonExistingResource;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.*;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import javax.xml.XMLConstants;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
/**
* @pad.exclude Exclude from Published API.
*/
public class XMLUtils {
private static Logger logger = LoggerFactory.getLogger(XMLUtils.class);
/**
* @pad.exclude Exclude from Published API.
*/
public static boolean checkIfStringHasIndexOperator(String bindRef) {
boolean bContainsIndex = false;
// Since XML cannot have a node name with "[" in it
// Also, as of now we support only constant inside [] operator, doesn't make to support conditional's inside []
if(StringUtils.contains(bindRef, "[")){
bContainsIndex = true;
}
return bContainsIndex;
}
public static String extractXsdRootElement(JSONObject guideJson) {
String xsdRootEl = "";
try {
if(guideJson.has(GuideConstants.XSD_ROOT_EL_LOCAL_NAME)){
xsdRootEl = guideJson.getString(GuideConstants.XSD_ROOT_EL_LOCAL_NAME);
} else {
logger.debug("Unable to find xsdRootElement in guide JSON");
}
} catch (JSONException ex) {
logger.debug("Unable to read xsdRootElement from guide JSON", ex);
}
return xsdRootEl;
}
/**
* Returns the normalized bindref for XSD binded field. It would return the bindRef relative to root node of data document.
* For example if bindRef is /a/b/c/d then it would return b/c/d as /a would always bind to document root.
* @param jsonObject
* @throws JSONException
* @pad.exclude Exclude from Published API.
*/
public static String getXSDRootBindRef(JSONObject jsonObject, String xsdRoot) throws JSONException {
String effectiveBindRef = "";
if (isXsd(jsonObject)) {
effectiveBindRef = getRelativeXpath(jsonObject.optString(GuideConstants.BIND_REF, ""), xsdRoot);
//TODO:Check case when root XSD element is directly dragged
}
return effectiveBindRef;
}
public static String getRelativeXpath(String bindRef, String root) {
String xpath = "";
if (StringUtils.isNotBlank(bindRef))
{
xpath = bindRef;
if ((isValidXsdRoot(root)
&& xpath.matches("/" + root + "(?:/.+)?"))
|| StringUtils.equals(root, GuideConstants.UNKNOWN_XSD_ROOT_ELEMENT))
{
xpath = xpath.replaceFirst("^/[^/]+", ""); // drop 1st part from xpath
}
}
return StringUtils.removeStart(xpath, "/");
}
/**
*
* @param doc
* @pad.exclude Exclude from Published API.
*/
public static String getXMLfromXsdDom(Element doc) {
return getXMLfromXsdDom(doc, false);
}
/**
*
* @param doc
* @param omitXmlDeclaration
* @pad.exclude Exclude from Published API.
*/
public static String getXMLfromXsdDom(Element doc, Boolean omitXmlDeclaration) {
// CQ-4263698 : Replacing LSSerializer with Transformer for serializing xml
// due to bug in default LSSerializer implementation since jdk 9
// that misses "xmlns" attribute while serializing a DOM Element to xml
String xmlStr = "";
try {
if (doc != null) {
TransformerFactory transfac = TransformerFactory.newInstance();
transfac.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer trans = transfac.newTransformer();
trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration ? "yes" : "no");
trans.setOutputProperty(OutputKeys.METHOD, "xml");
trans.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
StringWriter sw = new StringWriter();
StreamResult result = new StreamResult(sw);
DOMSource source = new DOMSource(doc);
trans.transform(source, result);
xmlStr = sw.toString();
}
} catch (Exception e) {
throw new GuideException(e);
}
return xmlStr;
}
/**
* Utility to add child nodes of parent directly under root node. Parent and root nodes belong to different documents.
* @param parent node whose child nodes are to added under root node
* @param root node under which child nodes have to be directly inserted
* @pad.exclude Exclude from Published API.
*/
public static void addChildNodesOfParentUnderRoot(Node parent, Node root) {
// Walk through the child nodes of parent and add them as child nodes of root
Document document = root.getOwnerDocument();
NodeList children = parent.getChildNodes();
int length = children.getLength();
for (int i = 0; i < length; i++) {
Node child = children.item(i);
if(child.getNodeType() == Node.ELEMENT_NODE) {
root.appendChild(document.importNode(child, true));
}
}
}
/**
* Returns the DOR part of data XML
* Dor Part is:
* a) ExcludeFromDorIfHidden Tag
* b) Unbound part of guide data
* c) bound part of guide data
* d) afSubmissionInfo of guide data
* @param dorDoc document representing dor
* @param doc document representing the data xml
* @param excludeFromDorIfHidden
* @param bSetXfaNameSpace this is true if xdpRef is not present
* @param guideJson
* @return string representing the data xml
* @see org.w3c.dom.Document
*
* @pad.exclude Exclude from Published API.
*/
public static String getDorDataXmlPart(Document dorDoc, Document doc, String excludeFromDorIfHidden, boolean bSetXfaNameSpace, JSONObject guideJson) {
try {
Node root = null;
Element nakedBoundXml = getBoundDataXmlElement(doc);
Element nakedUnBoundXml = getUnboundDataXmlElement(doc);
if (nakedBoundXml != null) {
root = dorDoc.importNode(nakedBoundXml, false);
} else if (nakedUnBoundXml != null) {
root = dorDoc.importNode(nakedUnBoundXml, false);
} else {
root = dorDoc.createElement("data");
}
if (isWrappedXml(doc)) {
if (nakedUnBoundXml != null) {
// remove data tag and make it flat
addChildNodesOfParentUnderRoot(nakedUnBoundXml, root);
}
if (nakedBoundXml != null) {
// remove data tag and keep it flat
addChildNodesOfParentUnderRoot(nakedBoundXml, root);
}
} else {
root = dorDoc.importNode(doc.getDocumentElement(), true);
}
if (bSetXfaNameSpace) {
setXfaNameSpace((Element) root);
setXmlSchemaInstanceNamespace((Element) root);
}
Element nakedExcludeFromDoRXml = getSubmissionInfoDataXmlElement(doc, GuideConstants.EXCLUDE_FROM_DOR);
if(nakedExcludeFromDoRXml != null){
root.appendChild(dorDoc.importNode(nakedExcludeFromDoRXml, true));
}
Element nakedStateOverrideXml = getSubmissionInfoDataXmlElement(doc, GuideConstants.STATE_OVERRIDE);
// only set nakedstateoverride for non xfa based form, bSetXfaNameSpace is true if no xdp ref is present
if(nakedStateOverrideXml != null && bSetXfaNameSpace){
nakedStateOverrideXml.setAttribute(GuideConstants.XFA_DATANODE, GuideConstants.DATAGROUP);
root.appendChild(dorDoc.importNode(nakedStateOverrideXml, true));
}
handleXfaDataNode((Element)root, guideJson, StringUtils.isNotEmpty(guideJson.optString(GuideConstants.XSD_ROOT_EL_LOCAL_NAME)));
convertRTEValXfaCmplntHTML(root);
return getXMLfromXsdDom((Element)root);
} catch (Exception e) {
throw new GuideException("Error in getting dor data xml "+e.getMessage(),e);
}
}
private static void handleXfaDataNode(Element root, JSONObject guideJson, boolean schemaHasRoot) {
try {
Iterator iterator = guideJson.keys();
while (iterator.hasNext()) {
String key = iterator.next();
Object value = guideJson.get(key);
if (value instanceof JSONObject) {
handleXfaDataNode(root, (JSONObject) value, schemaHasRoot);
continue;
}
if (StringUtils.equals(key, GuideConstants.GUIDE_NODE_CLASS)) {
String guideNodeClass = (String) value;
boolean isContainer = guideNodeClass.equals(GuideConstants.GUIDE_PANEL)
|| guideNodeClass.equals(GuideConstants.ROOTPANEL_NODECLASS)
|| guideNodeClass.equals(GuideConstants.GUIDE_TABLE)
|| guideNodeClass.equals(GuideConstants.GUIDE_TABLE_ROW);
if (isContainer) {
String xPathExpression = null;
if (guideJson.has(GuideConstants.BIND_REF)) {
// bound panel - get xpath expression for it
// get node in xml corresponding to bindRef
xPathExpression = getXPathExprForBindRef(guideJson.getString(GuideConstants.BIND_REF), schemaHasRoot);
} else {
// unbound panel - use name to get xpath expression
// get node in xml corresponding to name - assume flat hierarchy
xPathExpression = guideJson.optString("name");
}
addDataNodeAttribute(xPathExpression, root, GuideConstants.DATAGROUP);
}
}
if (StringUtils.equals(key, GuideConstants.BIND_REF) && StringUtils.contains((String) value, "/text()")) {
// content binding
String xPathExpression = getXPathExprForBindRef(StringUtils.substringBefore((String) value, "/text()"), schemaHasRoot);
addDataNodeAttribute(xPathExpression, root, GuideConstants.DATAVALUE);
}
}
} catch (JSONException e) {
logger.error("Exception while parsing JSON ", e);
} catch (XPathExpressionException e) {
logger.error("Exception while adding data node attribute in XML " + e);
}
}
private static String getXPathExprForBindRef(String bindRef, Boolean schemaHasRoot) {
String xPathExpression = null;
if (StringUtils.isNotEmpty(bindRef)) {
if (!schemaHasRoot) {
bindRef = "/data" + bindRef;
}
int index = bindRef.indexOf('/', 1);
xPathExpression = index != -1 ? StringUtils.substring(bindRef, index + 1) : null;
}
return xPathExpression;
}
private static void addDataNodeAttribute(String xPathExpression, Element element, String attributeValue)
throws XPathExpressionException {
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList targetNodes = null;
if (StringUtils.isNotEmpty(xPathExpression)) {
targetNodes = (NodeList) xPath.evaluate(xPathExpression, element, XPathConstants.NODESET);
}
if (targetNodes != null) {
int length = targetNodes.getLength();
for (int i = 0; i < length; i++) {
((Element)targetNodes.item(i)).setAttribute(GuideConstants.XFA_DATANODE, attributeValue);
}
}
}
/**
* Returns the html compliant dor xml.
* @param node Node node representing the dor xml.
* @return void
* @see org.w3c.dom.Node
*
* @pad.exclude Exclude from Published API.
*/
public static void convertRTEValXfaCmplntHTML(Node node) {
try {
XPath xPath = XPathFactory.newInstance().newXPath();
//get all the body nodes from node.
NodeList rteNodes = (NodeList) xPath.evaluate("//body", node, XPathConstants.NODESET);
for (int i = 0 ; i < rteNodes.getLength() ; i++) {
Node rteNode = rteNodes.item(i);
//convert nodes to string and pass to the converter util and again convert to nodelist to append to parent node.
if (GuideConstants.XML_HTML_NAMESPACE.equals(rteNode.getAttributes().getNamedItem(GuideConstants.XML_NAMESPACE_ATTR).getNodeValue())) {
// body tag has to go to the convertToXFAHTML api, hence sending the parent node of rte
// since getStringFromNode API walks through all the child nodes
String rteString = getStringFromNode(rteNode.getParentNode(), true); // omit whitespace nodes
if(StringUtils.isNotBlank(rteString)) {
// if there are new lines remove them manually before calling converter utility
String resolvedRT = rteString.replaceAll("\r\n", "").replaceAll("\n", "");
NodeList modifiedRteNodeList = getNodeListFromXmlString(ContentConverterUtils.convertToXFAHTML(resolvedRT));
Node rteParentNode = rteNode.getParentNode();
rteParentNode.removeChild(rteNode);
appendNodeListToNode(rteParentNode, modifiedRteNodeList);
}
}
}
} catch (Exception e) {
throw new GuideException("Error in converting rte to html compliant xml."+e.getMessage(),e);
}
return ;
}
/**
* Returns the Submission info part of data XML as element object
* The returned element must be cloned to do further modification
* @param doc document representing the data xml
* @param submitInfoPart child tag of submission info if any, null would return the submission info tag of data xml
* @return string representing the submission info element object
* @see org.w3c.dom.Document
* @pad.exclude Exclude from Published API.
*/
public static Element getSubmissionInfoDataXmlElement(Document doc, String submitInfoPart) {
try {
//if the dataxml is wrapped, get the bound part
if(isWrappedXml(doc)) {
XPath xPath = XPathFactory.newInstance().newXPath();
String xPathString = GuideConstants.AF_SUBMISSION_INFO;
if(submitInfoPart != null && submitInfoPart.length() > 0) {
xPathString = xPathString + "/" + submitInfoPart;
} else {
xPathString = xPathString + "/*";
}
Node afSubmissionInfo = (Node) xPath.evaluate(xPathString, doc.getDocumentElement(), XPathConstants.NODE);
return (Element)afSubmissionInfo;
} else {
// todo: what if the doc is a naked xml ? do we need to add the excludeFromDoR tag in that case
//if data xml is not already wrapped
//doc.getDocumentElement();
return null;
}
} catch (Exception e) {
throw new GuideException("Error in getting bound xml part"+e.getMessage(),e);
}
}
/**
* Returns the submission info part of data XML
* @param doc document representing the data xml
* @param submitInfoPart child tag of submission info if any, null would return the submission info tag of data xml
* @return string representing the submission info of data xml
* @see org.w3c.dom.Document
*
* @pad.exclude Exclude from Published API.
*/
public static String getSubmissionInfoDataXmlPart(Document doc, String submitInfoPart) {
return getXMLfromXsdDom(getSubmissionInfoDataXmlElement(doc, submitInfoPart));
}
/**
* Set XFA namespace to the element object passed
* @param data element object
*
* @pad.exclude Exclude from Published API.
*/
public static void setXfaNameSpace(Element data){
if(data != null){
//Set namespace for xsd case
data.setAttribute(GuideConstants.XML_NS_XFA_KEY, GuideConstants.XML_NS_XFA_VALUE);
}
}
/**
* Set XML Schema Instance namespace to the element object passed
* @param data element object
*
* @pad.exclude Exclude from Published API.
*/
public static void setXmlSchemaInstanceNamespace(Element data) {
if (data != null) {
//Set namespace for nillable attribute
data.setAttribute(GuideConstants.XML_NS_SCHEMA_INSTANCE_KEY, GuideConstants.XML_NS_SCHEMA_INSTANCE_VALUE);
}
}
/**
* Returns the bound part(portion bound to schema XSD/XFA) of data XML
* @param doc document representing the data xml
* @return string representing the bound portion of data xml
* @see org.w3c.dom.Document
*/
public static String getBoundDataXmlPart(Document doc) {
return getXMLfromXsdDom(getBoundDataXmlElement(doc));
}
/**
* Returns the bound part(portion bound to schema XSD/XFA) of data XML as element object
* @param doc document representing the data xml
* @return Element object representing the bound portion of data xml
* @see org.w3c.dom.Document
*
* @pad.exclude Exclude from Published API.
*/
public static Element getBoundDataXmlElement(Document doc) {
try {
//if the dataxml is wrapped, get the bound part
if(isWrappedXml(doc)) {
XPath xPath = XPathFactory.newInstance().newXPath();
Node afBoundData = (Node) xPath.evaluate(GuideConstants.WRAPPED_SUBMIT_BOUND_ROOT+"/*", doc.getDocumentElement(), XPathConstants.NODE);
return (Element)afBoundData;
} else {
//if data xml is not already wrapped
return doc.getDocumentElement();
}
} catch (Exception e) {
throw new GuideException("Error in getting bound xml part"+e.getMessage(),e);
}
}
/**
*
* Returns the unbound part of data XML
* @param doc document representing the data xml
* @return string representing the unbound portion of data xml
* @see org.w3c.dom.Document
*
*/
public static String getUnboundDataXmlPart(Document doc) {
return getXMLfromXsdDom(getUnboundDataXmlElement(doc));
}
/**
*
* Returns the unbound part of data XML as element object
* @param doc document representing the data xml
* @return Element object representing the unbound portion of data xml
* @see org.w3c.dom.Document
*
* @pad.exclude Exclude from Published API.
*/
public static Element getUnboundDataXmlElement(Document doc) {
try {
//if the dataxml is wrapped, get the un bound part
if(isWrappedXml(doc)) {
XPath xPath = XPathFactory.newInstance().newXPath();
Node afUnboundData = (Node) xPath.evaluate(GuideConstants.WRAPPED_SUBMIT_UNBOUND_ROOT+"/*", doc.getDocumentElement(), XPathConstants.NODE);
return (Element)afUnboundData;
} else {
//if data xml is not already wrapped
return doc.getDocumentElement();
}
} catch (Exception e) {
throw new GuideException("Error in getting unbound xml part"+e.getMessage(),e);
}
}
/**
*
* Returns the signers part of data XML as element object
* @param doc document representing the data xml
* @return Element object representing the signers portion of data xml
* @see org.w3c.dom.Document
*
* @pad.exclude Exclude from Published API.
*/
public static Element getSignersDataXmlElement(Document doc) {
return getSubmissionInfoDataXmlElement(doc, GuideConstants.WRAPPED_SIGNERS_ROOT);
}
/**
* Utility API to get a child node by name
* @param node node under which the given childName is to be found
* @param childName name of the child to be found
* @return node representing the given child
* @pad.exclude
*/
public static Node getChildNodeByName(Node node, String childName){
Node retNode = null;
NodeList children = node.getChildNodes();
int length = children.getLength();
for (int i = 0; i < length; i++) {
Node child = children.item(i);
// check if afSubmissionInfo
if(StringUtils.equals(child.getNodeName(), childName)) {
retNode = child;
}
}
return retNode;
}
private static void populateMap(Node data, Map map) {
NodeList children = data.getChildNodes();
int length = children.getLength();
if (length > 1 && !isRichTextDataNode(children)) {
for (int i = 1; i < length; i++) {
populateMap(children.item(i), map);
}
} else {
String value = data.getTextContent();
if (StringUtils.isNotEmpty(value)) {
map.put(data.getNodeName(), value);
}
}
}
/**
* Returns Map of all the fields and their values
* @param document representing the data xml
* @return Map of all the fields and their values
*/
public static Map getDataMap(Document document) {
Map dataMap = new HashMap();
Element unboundData = XMLUtils.getUnboundDataXmlElement(document);
if (unboundData != null) {
populateMap(unboundData, dataMap);
}
Element boundData = XMLUtils.getBoundDataXmlElement(document);
if (boundData != null) {
populateMap(boundData, dataMap);
}
return dataMap;
}
/**
* Returns JSON representation of unbound part of Data XML
* @param doc document representing the data xml
* @return jsonobject of unbound part of data xml
* @see org.w3c.dom.Document
*/
public static JSONObject getMapOfUnboundData(Document doc) {
JSONObject xmlJSONObj = null;
XPath xPath = XPathFactory.newInstance().newXPath();
StringWriter stringWriter = new StringWriter();
CustomJSONWriter jsonWriter = new CustomJSONWriter(stringWriter);
try {
Document unboundData = strToDoc(getUnboundDataXmlPart(doc));
jsonWriter.object();
jsonWriter.key(GuideConstants.UNWRAPPED_SUBMIT_ROOT).object();
convertUnboundedNodeToJson(GuideConstants.UNWRAPPED_SUBMIT_ROOT, xPath, unboundData, jsonWriter);
jsonWriter.endObject();
jsonWriter.endObject();
String jsonStr = stringWriter.toString();
// the hidden \r\n in xmls served from win file systems creeps into multivalued fields
xmlJSONObj = new JSONObject(jsonStr);
} catch(Exception e) {
throw new GuideException("Error in getting map of unbound data"+e.getMessage(),e);
}
return xmlJSONObj;
}
private static void convertUnboundedNodeToJson(String rootNodeName, XPath xPath, Document unboundedXMLData, CustomJSONWriter jsonWriter) throws XPathExpressionException {
NodeList nodes = (NodeList) xPath.evaluate(rootNodeName + "/*", unboundedXMLData, XPathConstants.NODESET);
if (nodes != null && nodes.getLength() > 0) {
for (int i = 0; i < nodes.getLength(); i++) {
Element node = (Element) nodes.item(i);
String nodeName = node.getNodeName();
String nodeValue = null;
String childNodeName = rootNodeName + "/" + nodeName;
NodeList childrenNodes = (NodeList) xPath.evaluate(childNodeName + "/*", unboundedXMLData, XPathConstants.NODESET);
/* Values could be rich Text XML, extract the Rich text Value as String from the document */
if( childrenNodes.getLength() == 0 || isRichTextDataNode(childrenNodes) ) {
//single or rich text node case;
nodeValue = getXMLfromXsdDom((Element) xPath.evaluate(childNodeName + "/*", unboundedXMLData, XPathConstants.NODE),true);
if(nodeValue.length() == 0) {
nodeValue = node.getTextContent();
}
jsonWriter.key(nodeName).value(nodeValue);
} else {
//Composite node case.
jsonWriter.key(nodeName).object();
convertUnboundedNodeToJson(childNodeName, xPath, unboundedXMLData, jsonWriter);
jsonWriter.endObject();
}
}
}
}
private static boolean isRichTextDataNode(NodeList childrenNodes) {
boolean isRichText = false;
if( childrenNodes != null && childrenNodes.item(0) != null
&& GuideConstants.RICH_TEXT_FIRST_TAG_NAME.equalsIgnoreCase(childrenNodes.item(0).getNodeName()) ) {
isRichText = true;
}
return isRichText;
}
/*
* Returns the data xml of the bound part(portion bound to schema) of data xml.
* Note: The returned data xml will not have the bound tag() in it
* @param doc document representing the data xml
* @see org.w3c.dom.Document
*/
public static String getPrefillXmlWithoutBoundPart(Document doc) {
try {
if(isWrappedXml(doc)) {
XPath xPath;
xPath = XPathFactory.newInstance().newXPath();
//NOCHECKMARX - No user input preventing XPath Injection. DataRef goes through prefill service to create doc.
Node afBoundData = (Node) xPath.evaluate(GuideConstants.WRAPPED_SUBMIT_BOUND_ROOT, doc.getDocumentElement(), XPathConstants.NODE);
if(afBoundData!=null) {
doc.getDocumentElement().removeChild(afBoundData);
}
return getXMLfromXsdDom((Element)doc.getDocumentElement());
} else {
return getXMLfromXsdDom((Element)doc.getDocumentElement());
}
} catch (Exception e) {
throw new GuideException("Error in getting bound xml part"+e.getMessage(),e);
}
}
/**
* Returns the XML String of the Node whose tag name is provided from a Document object
* @param doc Document Object where to search for the tag Name
* @param tagName tag name of the XML Element whose String representation has to be returned
* @pad.exclude Exclude from Published API.
*/
public static String getXMLFromDom(Document doc, String tagName) {
try {
XPath xPath = XPathFactory.newInstance().newXPath();
Node xmlNode = (Node) xPath.evaluate(tagName + "/*", doc.getDocumentElement(), XPathConstants.NODE);
return getXMLfromXsdDom((Element) xmlNode);
} catch(Exception e) {
throw new GuideException("Error in Getting XML of the element <" + tagName + "> From DOM", e);
}
}
/**
* Get the Input Stream of the URI provided in dataRef.
* @param dataRef
* @param formDataXMLProviderRegistry
* @param resourceResolverHelper
* @throws Exception
* @pad.exclude Exclude from Published API.
* @deprecated
*/
@Deprecated
public static InputStream getDataRefInputStream(String dataRef,
FormDataXMLProviderRegistry formDataXMLProviderRegistry,
ResourceResolverHelper resourceResolverHelper) {
String xml = null;
try {
if (dataRef.startsWith(GuideConstants.PROTOCOL_CRX)) {
Resource fileResource = resourceResolverHelper.getResourceResolver().resolve(dataRef.substring(6));
javax.jcr.Node jcrNode = fileResource.adaptTo(javax.jcr.Node.class);
javax.jcr.Node jcrContent = jcrNode.getNode("jcr:content");
InputStream is = jcrContent.getProperty("jcr:data").getBinary().getStream();
return is;
}
/*
* should use this code only if it is an unsupported protocol.
*/
if (!(dataRef.startsWith(GuideConstants.PROTOCOL_HTTPS) || dataRef.startsWith(GuideConstants.PROTOCOL_HTTP) || dataRef.startsWith(GuideConstants.PROTOCOL_FILE))) {
xml = formDataXMLProviderRegistry.getDataXMLFromService(dataRef);
logger.info("[AEMForm] XML Recieved from Prefill Service = " + xml);
if (xml != null && xml.length() > 0) {
return new ByteArrayInputStream(xml.getBytes("UTF-8"));
}
return null;
} else {
/*
* to enable compatibility with the already working protocols
* should be invoked if document could be formed as yet.
*/
URL url = new URL(dataRef);
InputStream in = url.openStream();
return in;
}
} catch (PathNotFoundException e) {
logger.error("[AEMForm] unable to locate Data XML in the repository " + dataRef + ": " + e.getMessage(), e);
} catch (RepositoryException e) {
logger.error("[AEMForm] Exception while reading Data XML in the repository " + dataRef + ": " + e.getMessage(), e);
} catch (UnsupportedEncodingException e) {
logger.error("[AEMForm] Unable to read xml using UTF-8 encoding for the " + dataRef + "," + xml + ": " + e.getMessage(), e);
} catch (MalformedURLException e) {
logger.error("[AEMForm] Malformed URL passed for getting the xml " + dataRef + ": " + e.getMessage(), e);
} catch (IOException e) {
logger.error("[AEMForm] Unable to read from the dataRef URL " + dataRef + ": " + e.getMessage(), e);
}
return null;
}
/**
* @param dataRef, document builder and reference to the service
* @param builder
* @param formDataXMLProviderRegistry
* @param resourceResolverHelper
* @return document obtained from the service
* @deprecated
* @throws Exception
* @pad.exclude Exclude from Published API.
*/
@Deprecated
public static Document exportDocumentFromDataRef(String dataRef,DocumentBuilder builder,
FormDataXMLProviderRegistry formDataXMLProviderRegistry,ResourceResolverHelper resourceResolverHelper)throws Exception{
Document doc = null;
if(dataRef.startsWith(GuideConstants.PROTOCOL_CRX)) {
Resource fileResource = resourceResolverHelper.getResourceResolver().resolve(dataRef.substring(6));
if (fileResource instanceof NonExistingResource) {
return null;
}
javax.jcr.Node jcrNode = fileResource.adaptTo(javax.jcr.Node.class);
javax.jcr.Node jcrContent = jcrNode.getNode("jcr:content");
InputStream is = jcrContent.getProperty("jcr:data").getBinary().getStream();
byte[] fileBytes = IOUtils.toByteArray(is);
is.close();
doc = builder.parse(new ByteArrayInputStream(fileBytes));
}
/*
* should use this code only if it is an unsupported protocol.
*/
else if(!(dataRef.startsWith(GuideConstants.PROTOCOL_HTTPS)||dataRef.startsWith(GuideConstants.PROTOCOL_HTTP)|| dataRef.startsWith(GuideConstants.PROTOCOL_FILE))){
InputSource inputSource = new InputSource(new StringReader(formDataXMLProviderRegistry.getDataXMLFromService(dataRef)));
doc= builder.parse(inputSource);
}
/*
* to enable compatibility with the already working protocols
* should be invoked if document could be formed as yet.
* e.g. http, https and file protocol
*/
if(doc==null){
URL url = new URL(dataRef);
InputStream in = url.openStream();
doc = builder.parse(in);
in.close();
}
return doc;
}
/*
* This API adds a node to the XML document.
* If a refNode is there then it adds the new node before the refNode.
* If there is no refNode, then it adds the new node at the end.
*/
private static void addNodeToDocument(Node currentNode, Node toAdd, Node refNode) {
if (refNode != null) {
currentNode.insertBefore(toAdd, refNode);
} else {
currentNode.appendChild(toAdd);
}
}
/**
*
* @param dataDoc
* @param xPath
* @param node
* @param path
* @throws XPathExpressionException
* @pad.exclude Exclude from Published API.
*/
public static Node createNode(Document dataDoc, XPath xPath, Node node, String path) throws XPathExpressionException {
return XMLUtils.createNode(dataDoc, xPath, node, path, null);
}
/**
*
* @param dataDoc
* @param xPath
* @param node
* @param path
* @param refNode
* @throws XPathExpressionException
* @pad.exclude Exclude from Published API.
*/
public static Node createNode(Document dataDoc, XPath xPath, Node node, String path, Node refNode) throws XPathExpressionException {
Document document = dataDoc;
String[] pathTokens = path.split("/");
Node currentNode = node;
int pathTokensSize = pathTokens.length;
for(int i = 0; i < pathTokensSize; i++){
String pathToken = pathTokens[i];
boolean isLastToken = (i == pathTokensSize -1);
//NOCHECKMARX - No user input preventing XPath Injection.
Node newNode = (Node) xPath.evaluate(pathToken, currentNode, XPathConstants.NODE);
if(newNode == null){
if (pathToken.charAt(0) == '@') {
//if current node to be created is attribute
Attr newAttribute = document.createAttribute(pathToken.substring(1));
((Element)currentNode).setAttributeNode(newAttribute);
newNode = newAttribute;
} else if ("text()".equals(pathToken)) {
//if current node to be created is text
//TODO: check what would happen in createTextNode is passed null
Node textNode = document.createTextNode(null);
XMLUtils.addNodeToDocument(currentNode, textNode, refNode);
newNode = textNode;
} else {
//if current node to created is element
// check if pathToken has index
if(XMLUtils.checkIfStringHasIndexOperator(pathToken)){
// Please Note: In xpath, the index should start with 1 and not 0
// If index starts with 0, then XML wouldn't generate as desired
// Also, substring before should work since name of a node in xml cannot have "[" as per grammar
// Also other generic operator like "(" would not be present while writing data xml before "[" operator
pathToken = StringUtils.substringBefore(pathToken, "[");
}
Node elNode = document.createElement(pathToken);
XMLUtils.addNodeToDocument(currentNode, elNode, refNode);
newNode = elNode;
}
} else if (isLastToken && !(pathToken.charAt(0) == '@' || "text()".equals(pathToken))) {
//if it is last token which is not an attribute or text, always force create it.
Node elNode = document.createElement(pathToken);
XMLUtils.addNodeToDocument(currentNode, elNode, refNode);
newNode = elNode;
}
currentNode = newNode;
}
return currentNode;
}
/**
*
* @param xPath
* @param currentNode
* @param path
* @pad.exclude Exclude from Published API.
*/
public static void removeFromDocument(XPath xPath, Node currentNode, String path) {
try {
Node toDelete = (Node) xPath.evaluate(path, currentNode, XPathConstants.NODE);
if(toDelete != null) {
Node parentNode = toDelete.getParentNode();
if(parentNode != null) {
parentNode.removeChild(toDelete);
}
}
} catch (XPathExpressionException ex){
logger.error("Invalid Xpath Syntax: " + path, ex);
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new GuideException(e);
}
}
/**
* Create XML tag for File attachment component
*
* @param document
* @param fileComponentNode
* @param jsonObject
* @pad.exclude Exclude from Published API.
*/
public static void createAttachmentTag(Document document, Node fileComponentNode, JSONObject jsonObject) {
try {
Node fileAttachments = document.createElement(GuideConstants.GUIDE_FILE_ATTACHMENT);
fileComponentNode.appendChild(fileAttachments);
fileAttachments.setTextContent(jsonObject.getString(GuideConstants.FILES));
JSONObject items = jsonObject.getJSONObject(GuideConstants.ITEMS_NODENAME);
if (items.has(GuideConstants.GUIDE_FILE_COMMENT)) {
JSONObject commentsJson = items.getJSONObject(GuideConstants.GUIDE_FILE_COMMENT);
if (commentsJson.has(GuideConstants._VALUE)) {
Node comments = document.createElement(GuideConstants.GUIDE_FILE_COMMENT);
comments.setTextContent(commentsJson.getString(GuideConstants._VALUE));
fileComponentNode.appendChild(comments);
}
}
} catch (Exception e) {
logger.error("Error in creating file attachment tag", e);
}
}
/**
*
* @param dataDoc
* @param xPath
* @param currentNode
* @param path
* @param value
* @return Node created at the specified path
* @pad.exclude Exclude from Published API.
*/
public static Node addToDocument(Document dataDoc, XPath xPath, Node currentNode, String path, Object value) {
try {
//NOCHECKMARX - No user input preventing XPath Injection.
Node toUpdate = (Node) xPath.evaluate(path, currentNode, XPathConstants.NODE);
if(toUpdate == null) {
toUpdate = createNode(dataDoc, xPath, currentNode, path);
}
//update the value
_updateValue(dataDoc, toUpdate, value);
return toUpdate;
} catch (XPathExpressionException ex) {
logger.warn("Invalid Xpath Syntax: " + path, ex);
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new GuideException(e);
}
return null;
}
private static void _updateValue(Document dataDoc, Node toUpdate, Object value){
switch (toUpdate.getNodeType()) {
case Node.ATTRIBUTE_NODE:
if(value instanceof String) {
((Attr) toUpdate).setValue((String) value);
}
break;
case Node.ELEMENT_NODE:
if(value instanceof String) {
toUpdate.setTextContent((String) value);
}
else if(value instanceof Node) {
value = dataDoc.importNode((Node)value, true);
while (toUpdate.hasChildNodes()) {
toUpdate.removeChild(toUpdate.getFirstChild());
}
toUpdate.appendChild((Node)value);
}
else {
logger.debug("Unsupported type of value");
}
break;
case Node.TEXT_NODE:
if(value instanceof String) {
toUpdate.setNodeValue((String) value);
}
break;
default:
break;
}
}
/**
* This API adds a node in the document before the refNode
*
* @param dataDoc
* @param xPath
* @param currentNode
* @param path
* @param value
* @param refNode reference node with respect to which the new node is to be inserted
* @return Node created at the specified path
* @pad.exclude Exclude from Published API.
*/
public static Node addToDocument(Document dataDoc, XPath xPath, Node currentNode, String path, Object value, Node refNode) {
try {
//NOCHECKMARX - No user input preventing XPath Injection.
Node toUpdate = (Node) xPath.evaluate(path, currentNode, XPathConstants.NODE);
if (toUpdate == null) {
toUpdate = createNode(dataDoc, xPath, currentNode, path, refNode);
}
//update the value
_updateValue(dataDoc, toUpdate, value);
return toUpdate;
} catch (XPathExpressionException ex){
logger.warn("Invalid Xpath Syntax: " + path, ex);
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new GuideException(e);
}
return null;
}
public static boolean isXsd(JSONObject obj) {
String bindRef = obj.optString(GuideConstants.BIND_REF);
return StringUtils.isNotBlank(bindRef) && StringUtils.startsWith(bindRef, "/");
}
public static boolean isWrappedXml(Document doc) {
try {
return (GuideConstants.WRAPPED_SUBMIT_XML_ROOT.equals(doc.getDocumentElement().getTagName()));
} catch (Exception e) {
logger.debug("Error while trying to check document root tag : " + e.getMessage(), e);
}
return false;
}
public static boolean isWrappedXmlStr(String docStr) {
if (StringUtils.isNotBlank(docStr)) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
XMLUtils.disableExternalEntities(dbf);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(docStr)));
return isWrappedXml(doc);
} catch (SAXException e) {
logger.error("Error while parsing Guide Prefill Xml String : " + e.getMessage(), e);
} catch (IOException e) {
logger.error("Error while parsing Guide Prefill Xml String : " + e.getMessage(), e);
} catch (ParserConfigurationException e) {
logger.error("Error while parsing Guide Prefill Xml String : " + e.getMessage(), e);
}
}
return false;
}
public static boolean isValidXsdRoot(String root) {
return StringUtils.isNotBlank(root) && root.matches("[a-zA-Z].*");
}
public static final String xmlSkeleton = "\n" +
"\n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n" +
" \n";
public static Document getEmptySubmitDoc() {
Document doc = strToDoc(xmlSkeleton);
if(!(doc instanceof Document)) {
logger.debug("Unable to Generate Empty Xml Doc");
return null;
}
return doc;
}
public static String getChildBoundRootXpath(String fsRoot, String bindRefPrefix, String childRoot) {
String xpath = "";
if (StringUtils.isBlank(bindRefPrefix))
{
if (isValidXsdRoot(childRoot)
&& !StringUtils.equals(fsRoot, childRoot))
{
xpath = childRoot;
}
} else {
xpath = getRelativeXpath(bindRefPrefix, fsRoot);
}
return xpath;
}
/**
* Returns list of child and attribute nodes which have passed name
* @param parent node whose children and attributes are searched
* @param childNode name of child to be returned
* @return list of child nodes having childNode as name
*/
public static List getNamedChildNodes(Node parent, String childNode) {
List nodes = new ArrayList<>();
if (parent == null || StringUtils.isEmpty(childNode)) {
return nodes;
}
NodeList children = parent.getChildNodes();
int count = children.getLength();
for (int i = 0; i < count; i++) {
Node node = children.item(i);
if (StringUtils.equals(childNode, node.getNodeName())) {
nodes.add(node);
}
}
NamedNodeMap attributeMap = parent.getAttributes();
Node attributeNode = attributeMap.getNamedItem(childNode);
if (attributeNode != null) {
nodes.add(attributeNode);
}
return nodes;
}
public static Object evaluateXPath(String xPathQuery, Node contextNode, QName type) {
try {
if (StringUtils.isBlank(xPathQuery)) {
return contextNode;
}
XPathFactory xpFactory = XPathFactory.newInstance();
XPath xPath = xpFactory.newXPath();
return xPath.evaluate(xPathQuery, contextNode, type);
} catch (XPathExpressionException e) {
logger.debug("Unable to evaluate XPath" + xPathQuery, e);
}
return null;
}
public static void copyChildren(Node source, Node target) {
if (source == null || target == null) return;
Document destination = target.getOwnerDocument();
DocumentFragment container = destination.createDocumentFragment();
NodeList children = source.getChildNodes();
int length = children.getLength();
for (int i = 0; i < length; i++) {
container.appendChild(destination.importNode(children.item(i), true));
}
target.appendChild(container);
}
public static Document getChildXmlDoc(Element fsUnBoundDataRoot, Element fsBoundDataRoot, String childBoundRootXpath, boolean keepBoundPart) {
Document childDoc = getEmptySubmitDoc();
copyChildren(fsUnBoundDataRoot, getUnboundDataXmlElement(childDoc)); // copy unbound data
if (keepBoundPart) {
Element childBoundRoot = (Element) evaluateXPath(childBoundRootXpath, fsBoundDataRoot, XPathConstants.NODE);
if (childBoundRoot == null) {
childBoundRoot = fsBoundDataRoot;
}
copyChildren(childBoundRoot, getBoundDataXmlElement(childDoc)); // copy bound data
}
return childDoc;
}
private static String getStringFromNode(Node node) {
return getStringFromNode(node, false);
}
private static String getStringFromNode(Node node, boolean ignoreWhitespace) {
DOMImplementationLS domImplementation = (DOMImplementationLS)node.getOwnerDocument().getImplementation();
LSSerializer lsSerializer = domImplementation.createLSSerializer();
// set the xml declaration to false
lsSerializer.getDomConfig().setParameter("xml-declaration", false);
NodeList childNodes = node.getChildNodes();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (!ignoreWhitespace || !isWhitespaceNode(child)) {
sb.append(lsSerializer.writeToString(child));
}
}
return sb.toString();
}
private static boolean isWhitespaceNode(Node node) {
if (node.getNodeType() == Node.TEXT_NODE) {
String val = node.getNodeValue();
return val.trim().length() == 0;
} else {
return false;
}
}
/**
* convert node object to string.
* @param node Node to be converted to string.
* @param omitXmlDeclaration Boolean to check if xml declaration tag should be there in converted string or not.
* @throws Exception
* @pad.exclude Exclude from Published API.
*/
public static String nodeToStr(Node node, Boolean omitXmlDeclaration) {
String xmlStr = "";
try {
if (node != null) {
TransformerFactory transfac = TransformerFactory.newInstance();
Transformer trans = transfac.newTransformer();
trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration ? "yes" : "no");
trans.setOutputProperty(OutputKeys.METHOD, "xml");
trans.setOutputProperty(OutputKeys.INDENT, "yes");
trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", Integer.toString(2));
StringWriter sw = new StringWriter();
StreamResult result = new StreamResult(sw);
DOMSource source = new DOMSource(node);
trans.transform(source, result);
xmlStr = sw.toString();
}
} catch (Exception e) {
logger.error("Exception while converting document to xml", e);
}
return xmlStr;
}
/**
* convert document object to string.
* This api is deprecated and new api to be used is {@link #nodeToStr(Node, Boolean) nodeToStr}
* @param doc Document to be converted to string.
* @throws Exception
* @pad.exclude Exclude from Published API.
* @deprecated
*/
@Deprecated
public static String docToStr(Document doc) {
String xmlStr = "";
try {
return nodeToStr(doc, false);
} catch (Exception e) {
logger.error("Exception while converting document to xml", e);
}
return xmlStr;
}
/**
* This API converts xmlstring to node.
*
* @param xmlStr String xml String which needs to be converted to nodeList.
* @return NodeList NodeList created converting xml string.
* @pad.exclude Exclude from Published API.
*/
public static NodeList getNodeListFromXmlString (String xmlStr) {
NodeList nodeList = null;
try {
//This is done as xml wihthout root tag is unparsable by DocumentBuilder.
String tempRootBeginTag = "";
String tempRootEndTag = " ";
String rootAppendedString = tempRootBeginTag + xmlStr +tempRootEndTag;
nodeList = DocumentBuilderFactory
.newInstance()
.newDocumentBuilder()
.parse(new ByteArrayInputStream(rootAppendedString.getBytes(GuideConstants.UTF_8)))
.getDocumentElement()
.getChildNodes();
} catch (Exception e) {
throw new GuideException("Error in getting node list from xml "+e.getMessage(),e);
}
return nodeList;
};
/**
* This API adds a nodelist to the root node.
*
* @param rootNode Node root node to which the nodelist needs to be appended.
* @param nodeList NodeList created converting xml string.
* @return void
* @pad.exclude Exclude from Published API.
*/
public static void appendNodeListToNode (Node rootNode, NodeList nodeList) {
Document doc = rootNode.getOwnerDocument();
for (int j = 0 ; j < nodeList.getLength() ; j++) {
rootNode.appendChild(doc.importNode(nodeList.item(j), true));
}
return;
}
public static Document strToDoc(String xmlStr) {
Document doc = null;
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
XMLUtils.disableExternalEntities(dbFactory);
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
doc = dBuilder.parse(new InputSource(new StringReader(xmlStr)));
doc.getDocumentElement().normalize();
} catch (Exception e) {
logger.debug("Unable to Generate Xml Doc from String : " + xmlStr, e);
}
return doc;
}
public static List extractAttachmentNames(Document doc) {
List attachmentNames = new ArrayList();
NodeList attachments = (NodeList) evaluateXPath("//" + GuideConstants.GUIDE_FILE_ATTACHMENT, doc.getDocumentElement(), XPathConstants.NODESET);
if (attachments != null) {
int length = attachments.getLength();
for (int i = 0; i < length; i++) {
Element attachment = (Element) attachments.item(i);
String combinedNames = attachment.getTextContent();
if (StringUtils.isNotBlank(combinedNames)) {
String[] fileNames = combinedNames.split("\\r?\\n");
if (fileNames != null && fileNames.length>0) {
attachmentNames.addAll(Arrays.asList(fileNames));
}
}
}
}
return attachmentNames;
}
public static void disableExternalEntities(DocumentBuilderFactory factory){
String FEATURE;
try {
factory.setValidating(false);
FEATURE = "http://xml.org/sax/features/validation";
factory.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-general-entities";
factory.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
factory.setFeature(FEATURE, false);
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
factory.setFeature(FEATURE, false);
}
catch (ParserConfigurationException e) {
logger.warn("Error in disabling external entities ", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy