2525import java .time .Instant ;
2626import java .util .ArrayList ;
2727import java .util .List ;
28+ import java .util .Map ;
2829import java .util .function .Consumer ;
2930import java .util .function .Function ;
3031
@@ -51,6 +52,7 @@ public static class Builder {
5152 private Consumer <DocumentWriteOperation []> skippedDocumentsConsumer ;
5253 private String [] jsonExclusions ;
5354 private String [] xmlExclusions ;
55+ private Map <String , String > xmlNamespaces ;
5456
5557 /**
5658 * @param keyName the name of the MarkLogic metadata key that will hold the hash value; defaults to "incrementalWriteHash".
@@ -117,13 +119,22 @@ public Builder xmlExclusions(String... xpathExpressions) {
117119 return this ;
118120 }
119121
122+ /**
123+ * @param namespaces a map of namespace prefixes to URIs for use in XPath exclusion expressions.
124+ * For example, Map.of("ns", "http://example.com/ns") allows XPath like "//ns:timestamp".
125+ */
126+ public Builder xmlNamespaces (Map <String , String > namespaces ) {
127+ this .xmlNamespaces = namespaces ;
128+ return this ;
129+ }
130+
120131 public IncrementalWriteFilter build () {
121132 validateJsonExclusions ();
122133 validateXmlExclusions ();
123134 if (useEvalQuery ) {
124- return new IncrementalWriteEvalFilter (hashKeyName , timestampKeyName , canonicalizeJson , skippedDocumentsConsumer , jsonExclusions , xmlExclusions );
135+ return new IncrementalWriteEvalFilter (hashKeyName , timestampKeyName , canonicalizeJson , skippedDocumentsConsumer , jsonExclusions , xmlExclusions , xmlNamespaces );
125136 }
126- return new IncrementalWriteOpticFilter (hashKeyName , timestampKeyName , canonicalizeJson , skippedDocumentsConsumer , jsonExclusions , xmlExclusions );
137+ return new IncrementalWriteOpticFilter (hashKeyName , timestampKeyName , canonicalizeJson , skippedDocumentsConsumer , jsonExclusions , xmlExclusions , xmlNamespaces );
127138 }
128139
129140 private void validateJsonExclusions () {
@@ -151,6 +162,9 @@ private void validateXmlExclusions() {
151162 return ;
152163 }
153164 XPath xpath = XmlFactories .getXPathFactory ().newXPath ();
165+ if (xmlNamespaces != null && !xmlNamespaces .isEmpty ()) {
166+ xpath .setNamespaceContext (new SimpleNamespaceContext (xmlNamespaces ));
167+ }
154168 for (String xpathExpression : xmlExclusions ) {
155169 if (xpathExpression == null || xpathExpression .trim ().isEmpty ()) {
156170 throw new IllegalArgumentException (
@@ -173,18 +187,20 @@ private void validateXmlExclusions() {
173187 private final Consumer <DocumentWriteOperation []> skippedDocumentsConsumer ;
174188 private final String [] jsonExclusions ;
175189 private final String [] xmlExclusions ;
190+ private final Map <String , String > xmlNamespaces ;
176191
177192 // Hardcoding this for now, with a good general purpose hashing function.
178193 // See https://xxhash.com for benchmarks.
179194 private final LongHashFunction hashFunction = LongHashFunction .xx3 ();
180195
181- public IncrementalWriteFilter (String hashKeyName , String timestampKeyName , boolean canonicalizeJson , Consumer <DocumentWriteOperation []> skippedDocumentsConsumer , String [] jsonExclusions , String [] xmlExclusions ) {
196+ public IncrementalWriteFilter (String hashKeyName , String timestampKeyName , boolean canonicalizeJson , Consumer <DocumentWriteOperation []> skippedDocumentsConsumer , String [] jsonExclusions , String [] xmlExclusions , Map < String , String > xmlNamespaces ) {
182197 this .hashKeyName = hashKeyName ;
183198 this .timestampKeyName = timestampKeyName ;
184199 this .canonicalizeJson = canonicalizeJson ;
185200 this .skippedDocumentsConsumer = skippedDocumentsConsumer ;
186201 this .jsonExclusions = jsonExclusions ;
187202 this .xmlExclusions = xmlExclusions ;
203+ this .xmlNamespaces = xmlNamespaces ;
188204 }
189205
190206 protected final DocumentWriteSet filterDocuments (Context context , Function <String , String > hashRetriever ) {
@@ -260,7 +276,7 @@ private String serializeContent(DocumentWriteOperation doc) {
260276 }
261277 } else if (xmlExclusions != null && xmlExclusions .length > 0 ) {
262278 try {
263- content = ContentExclusionUtil .applyXmlExclusions (doc .getUri (), content , xmlExclusions );
279+ content = ContentExclusionUtil .applyXmlExclusions (doc .getUri (), content , xmlNamespaces , xmlExclusions );
264280 } catch (Exception e ) {
265281 logger .warn ("Unable to apply XML exclusions for URI {}, using original content for hashing; cause: {}" ,
266282 doc .getUri (), e .getMessage ());
0 commit comments