88import com .fasterxml .jackson .databind .JsonNode ;
99import com .fasterxml .jackson .databind .ObjectMapper ;
1010import com .fasterxml .jackson .databind .node .ObjectNode ;
11+ import com .marklogic .client .impl .XmlFactories ;
1112import org .slf4j .Logger ;
1213import org .slf4j .LoggerFactory ;
14+ import org .w3c .dom .Document ;
15+ import org .w3c .dom .Node ;
16+ import org .w3c .dom .NodeList ;
17+
18+ import javax .xml .namespace .QName ;
19+ import javax .xml .parsers .DocumentBuilder ;
20+ import javax .xml .transform .OutputKeys ;
21+ import javax .xml .transform .Transformer ;
22+ import javax .xml .transform .TransformerException ;
23+ import javax .xml .transform .dom .DOMSource ;
24+ import javax .xml .transform .stream .StreamResult ;
25+ import javax .xml .xpath .XPath ;
26+ import javax .xml .xpath .XPathConstants ;
27+ import javax .xml .xpath .XPathExpression ;
28+ import javax .xml .xpath .XPathExpressionException ;
29+ import java .io .ByteArrayInputStream ;
30+ import java .io .StringWriter ;
31+ import java .nio .charset .StandardCharsets ;
1332
1433/**
1534 * Utility class for applying content exclusions to documents before hash calculation.
1635 * Supports removing specific paths from JSON and XML documents using JSON Pointer and XPath expressions.
1736 *
1837 * @since 8.1.0
1938 */
20- public class ContentExclusionUtil {
39+ class ContentExclusionUtil {
2140
2241 private static final Logger logger = LoggerFactory .getLogger (ContentExclusionUtil .class );
2342 private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper ();
@@ -31,7 +50,7 @@ public class ContentExclusionUtil {
3150 * @return the modified JSON content with specified paths removed
3251 * @throws JsonProcessingException if the JSON content cannot be parsed or serialized
3352 */
34- public static String applyJsonExclusions (String uri , String jsonContent , String [] jsonPointers ) throws JsonProcessingException {
53+ static String applyJsonExclusions (String uri , String jsonContent , String [] jsonPointers ) throws JsonProcessingException {
3554 if (jsonPointers == null || jsonPointers .length == 0 ) {
3655 return jsonContent ;
3756 }
@@ -72,6 +91,59 @@ private static void removeNodeAtPointer(String uri, JsonNode rootNode, String js
7291 }
7392 }
7493
75- // Future method for XML exclusions
76- // public static String applyXmlExclusions(String xmlContent, String[] xpaths) { ... }
94+ /**
95+ * Applies XPath exclusions to XML content by removing the specified elements.
96+ *
97+ * @param uri the document URI (used for logging purposes)
98+ * @param xmlContent the XML content as a string
99+ * @param xpathExpressions array of XPath expressions identifying elements to exclude
100+ * @return the modified XML content with specified elements removed
101+ * @throws Exception if the XML content cannot be parsed or serialized
102+ */
103+ static String applyXmlExclusions (String uri , String xmlContent , String ... xpathExpressions ) throws Exception {
104+ if (xpathExpressions == null || xpathExpressions .length == 0 ) {
105+ return xmlContent ;
106+ }
107+
108+ DocumentBuilder builder = XmlFactories .getDocumentBuilderFactory ().newDocumentBuilder ();
109+ Document document = builder .parse (new ByteArrayInputStream (xmlContent .getBytes (StandardCharsets .UTF_8 )));
110+ applyXmlExclusions (uri , document , xpathExpressions );
111+ return serializeDocument (document );
112+ }
113+
114+ private static void applyXmlExclusions (String uri , Document document , String [] xpathExpressions ) {
115+ final XPath xpath = XmlFactories .getXPathFactory ().newXPath ();
116+ for (String xpathExpression : xpathExpressions ) {
117+ try {
118+ XPathExpression expr = xpath .compile (xpathExpression );
119+ QName returnType = XPathConstants .NODESET ;
120+ NodeList nodes = (NodeList ) expr .evaluate (document , returnType );
121+
122+ if (nodes .getLength () == 0 ) {
123+ logger .debug ("XPath '{}' does not match any nodes in document {}, skipping" , xpathExpression , uri );
124+ continue ;
125+ }
126+
127+ // Remove nodes in reverse order to avoid index issues
128+ for (int i = nodes .getLength () - 1 ; i >= 0 ; i --) {
129+ Node node = nodes .item (i );
130+ Node parent = node .getParentNode ();
131+ if (parent != null ) {
132+ parent .removeChild (node );
133+ }
134+ }
135+ } catch (XPathExpressionException e ) {
136+ logger .warn ("Invalid XPath expression '{}' for document {}: {}" , xpathExpression , uri , e .getMessage ());
137+ }
138+ }
139+ }
140+
141+ private static String serializeDocument (Document document ) throws TransformerException {
142+ Transformer transformer = XmlFactories .newTransformer ();
143+ transformer .setOutputProperty (OutputKeys .OMIT_XML_DECLARATION , "yes" );
144+ transformer .setOutputProperty (OutputKeys .INDENT , "no" );
145+ StringWriter writer = new StringWriter ();
146+ transformer .transform (new DOMSource (document ), new StreamResult (writer ));
147+ return writer .toString ();
148+ }
77149}
0 commit comments