This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-text.git


The following commit(s) were added to refs/heads/master by this push:
     new 54098055 Add StringLookupFactory.xmlStringLookup(Map, Path...) and 
deprecated xmlStringLookup() and xmlStringLookup(Map)
54098055 is described below

commit 540980552685b3ee95f9f376a9aa7a3970c5bb69
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Fri Apr 5 12:50:40 2024 -0400

    Add StringLookupFactory.xmlStringLookup(Map, Path...) and deprecated
    xmlStringLookup() and xmlStringLookup(Map)
---
 src/changes/changes.xml                            |   1 +
 .../commons/text/lookup/StringLookupFactory.java   | 251 +++++++++++----------
 .../commons/text/lookup/XmlStringLookup.java       |  13 +-
 .../commons/text/lookup/FileStringLookupTest.java  |  11 +-
 .../text/lookup/PropertiesStringLookupTest.java    |   3 +-
 .../commons/text/lookup/XmlStringLookupTest.java   |  20 +-
 6 files changed, 164 insertions(+), 135 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ce772d7f..7c4228bb 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -49,6 +49,7 @@ The <action> type attribute can be add,update,fix,remove.
     <!-- ADD -->
     <action                  type="add" dev="ggregory" due-to="Gary 
Gregory">Add StringLookupFactory.fileStringLookup(Path...) and deprecated 
fileStringLookup().</action>
     <action                  type="add" dev="ggregory" due-to="Gary 
Gregory">Add StringLookupFactory.propertiesStringLookup(Path...) and deprecated 
propertiesStringLookup().</action>
+    <action                  type="add" dev="ggregory" due-to="Gary 
Gregory">Add StringLookupFactory.xmlStringLookup(Map, Path...) and deprecated 
xmlStringLookup() and xmlStringLookup(Map).</action>
     <!-- FIX -->
     <action issue="TEXT-232" type="fix" dev="ggregory" due-to="Arnout Engelen, 
Gary Gregory">WordUtils.containsAllWords​() may throw 
PatternSyntaxException.</action>
     <action issue="TEXT-175" type="fix" dev="ggregory" due-to="David Lavati, 
seanfabs, Gary Gregory, Bruno P. Kinoshita">Fix regression for determining 
whitespace in WordUtils #519.</action>
diff --git 
a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java 
b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java
index 51096ea1..52f9f3d2 100644
--- a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java
+++ b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java
@@ -47,19 +47,15 @@ import org.apache.commons.text.StringSubstitutor;
  * <li>{@link #interpolatorStringLookup(Map, StringLookup, boolean)}.</li>
  * </ul>
  * <p>
- * Unless explicitly requested otherwise, a set of default lookups are 
included for convenience with these
- * variable interpolation methods. These defaults are listed in the table 
below. However, the exact lookups
- * included can be configured through the use of the {@value 
#DEFAULT_STRING_LOOKUPS_PROPERTY} system property.
- * If present, this system property will be parsed as a comma-separated list 
of lookup names, with the names
- * being those defined by the {@link DefaultStringLookup} enum. For example, 
setting this system property to
- * {@code "BASE64_ENCODER,ENVIRONMENT"} will only include the
- * {@link DefaultStringLookup#BASE64_ENCODER BASE64_ENCODER} and {@link 
DefaultStringLookup#ENVIRONMENT ENVIRONMENT}
- * lookups. Setting the property to the empty string will cause no defaults to 
be configured.
- * Note that not all lookups defined here and in {@link DefaultStringLookup} 
are included by default.
- * Specifically, lookups that can execute code (e.g., {@link 
DefaultStringLookup#SCRIPT SCRIPT}) and those
- * that can result in contact with remote servers (e.g., {@link 
DefaultStringLookup#URL URL} and
- * {@link DefaultStringLookup#DNS DNS}) are not included by default. The 
current set of default lookups can
- * be accessed directly with {@link #addDefaultStringLookups(Map)}.
+ * Unless explicitly requested otherwise, a set of default lookups are 
included for convenience with these variable interpolation methods. These 
defaults are
+ * listed in the table below. However, the exact lookups included can be 
configured through the use of the {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} 
system
+ * property. If present, this system property will be parsed as a 
comma-separated list of lookup names, with the names being those defined by the
+ * {@link DefaultStringLookup} enum. For example, setting this system property 
to {@code "BASE64_ENCODER,ENVIRONMENT"} will only include the
+ * {@link DefaultStringLookup#BASE64_ENCODER BASE64_ENCODER} and {@link 
DefaultStringLookup#ENVIRONMENT ENVIRONMENT} lookups. Setting the property to 
the empty
+ * string will cause no defaults to be configured. Note that not all lookups 
defined here and in {@link DefaultStringLookup} are included by default.
+ * Specifically, lookups that can execute code (e.g., {@link 
DefaultStringLookup#SCRIPT SCRIPT}) and those that can result in contact with 
remote servers (e.g.,
+ * {@link DefaultStringLookup#URL URL} and {@link DefaultStringLookup#DNS 
DNS}) are not included by default. The current set of default lookups can be 
accessed
+ * directly with {@link #addDefaultStringLookups(Map)}.
  * </p>
  * <table>
  * <caption>Default String Lookups</caption>
@@ -221,8 +217,7 @@ import org.apache.commons.text.StringSubstitutor;
 public final class StringLookupFactory {
 
     /**
-     * Internal class used to construct the default {@link StringLookup} map 
used by
-     * {@link StringLookupFactory#addDefaultStringLookups(Map)}.
+     * Internal class used to construct the default {@link StringLookup} map 
used by {@link StringLookupFactory#addDefaultStringLookups(Map)}.
      */
     static final class DefaultStringLookupsHolder {
 
@@ -230,10 +225,11 @@ public final class StringLookupFactory {
         static final DefaultStringLookupsHolder INSTANCE = new 
DefaultStringLookupsHolder(System.getProperties());
 
         /**
-         * Add the key and string lookup from {@code lookup} to {@code map}, 
also adding any additional
-         * key aliases if needed. Keys are normalized using the {@link 
#toKey(String)} method.
+         * Add the key and string lookup from {@code lookup} to {@code map}, 
also adding any additional key aliases if needed. Keys are normalized using the
+         * {@link #toKey(String)} method.
+         *
          * @param lookup lookup to add
-         * @param map map to add to
+         * @param map    map to add to
          */
         private static void addLookup(final DefaultStringLookup lookup, final 
Map<String, StringLookup> map) {
             map.put(toKey(lookup.getKey()), lookup.getStringLookup());
@@ -245,6 +241,7 @@ public final class StringLookupFactory {
 
         /**
          * Create the lookup map used when the user has requested no 
customization.
+         *
          * @return default lookup map
          */
         private static Map<String, StringLookup> createDefaultStringLookups() {
@@ -271,9 +268,9 @@ public final class StringLookupFactory {
         }
 
         /**
-         * Constructs a lookup map by parsing the given string. The string is 
expected to contain
-         * comma or space-separated names of values from the {@link 
DefaultStringLookup} enum. If
-         * the given string is null or empty, an empty map is returned.
+         * Constructs a lookup map by parsing the given string. The string is 
expected to contain comma or space-separated names of values from the
+         * {@link DefaultStringLookup} enum. If the given string is null or 
empty, an empty map is returned.
+         *
          * @param str string to parse; may be null or empty
          * @return lookup map parsed from the given string
          */
@@ -296,18 +293,19 @@ public final class StringLookupFactory {
 
         /**
          * Constructs a new instance initialized with the given properties.
+         *
          * @param props initialization properties
          */
         DefaultStringLookupsHolder(final Properties props) {
-            final Map<String, StringLookup> lookups =
-                    
props.containsKey(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY)
-                        ? 
parseStringLookups(props.getProperty(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY))
-                        : createDefaultStringLookups();
+            final Map<String, StringLookup> lookups = 
props.containsKey(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY)
+                    ? 
parseStringLookups(props.getProperty(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY))
+                    : createDefaultStringLookups();
             defaultStringLookups = Collections.unmodifiableMap(lookups);
         }
 
         /**
          * Gets the default string lookups map.
+         *
          * @return default string lookups map
          */
         Map<String, StringLookup> getDefaultStringLookups() {
@@ -315,6 +313,14 @@ public final class StringLookupFactory {
         }
     }
 
+    /**
+     * Name of the system property used to determine the string lookups added 
by the {@link #addDefaultStringLookups(Map)} method. Use of this property is 
only
+     * required in cases where the set of default lookups must be modified. 
(See the class documentation for details.)
+     *
+     * @since 1.10.0
+     */
+    public static final String DEFAULT_STRING_LOOKUPS_PROPERTY = 
"org.apache.commons.text.lookup.StringLookupFactory.defaultStringLookups";
+
     /**
      * Defines the singleton for this class.
      */
@@ -341,7 +347,7 @@ public final class StringLookupFactory {
      * </p>
      */
     static final FunctionStringLookup<String> INSTANCE_BASE64_DECODER = 
FunctionStringLookup
-        .on(key -> new String(Base64.getDecoder().decode(key), 
StandardCharsets.ISO_8859_1));
+            .on(key -> new String(Base64.getDecoder().decode(key), 
StandardCharsets.ISO_8859_1));
 
     /**
      * Encodes Base64 Strings.
@@ -365,7 +371,7 @@ public final class StringLookupFactory {
      * Defines the singleton for this class.
      */
     static final FunctionStringLookup<String> INSTANCE_BASE64_ENCODER = 
FunctionStringLookup
-        .on(key -> 
Base64.getEncoder().encodeToString(key.getBytes(StandardCharsets.ISO_8859_1)));
+            .on(key -> 
Base64.getEncoder().encodeToString(key.getBytes(StandardCharsets.ISO_8859_1)));
 
     /**
      * Looks up keys from environment variables.
@@ -384,8 +390,7 @@ public final class StringLookupFactory {
      * StringSubstitutor.createInterpolator().replace("... ${env:USER} ..."));
      * </pre>
      * <p>
-     * The above examples convert (on Linux) {@code "USER"} to the current 
user name. On Windows 10, you would use
-     * {@code "USERNAME"} to the same effect.
+     * The above examples convert (on Linux) {@code "USER"} to the current 
user name. On Windows 10, you would use {@code "USERNAME"} to the same effect.
      * </p>
      */
     static final FunctionStringLookup<String> INSTANCE_ENVIRONMENT_VARIABLES = 
FunctionStringLookup.on(System::getenv);
@@ -533,16 +538,6 @@ public final class StringLookupFactory {
      */
     public static final String KEY_XML_ENCODER = "xmlEncoder";
 
-    /**
-     * Name of the system property used to determine the string lookups added 
by the
-     * {@link #addDefaultStringLookups(Map)} method. Use of this property is 
only required
-     * in cases where the set of default lookups must be modified. (See the 
class documentation
-     * for details.)
-     *
-     * @since 1.10.0
-     */
-    public static final String DEFAULT_STRING_LOOKUPS_PROPERTY = 
"org.apache.commons.text.lookup.StringLookupFactory.defaultStringLookups";
-
     /**
      * Clears any static resources.
      *
@@ -554,6 +549,7 @@ public final class StringLookupFactory {
 
     /**
      * Gets a string suitable for use as a key in the string lookup map.
+     *
      * @param key string to convert to a string lookup map key
      * @return string lookup map key
      */
@@ -581,10 +577,9 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Adds the default string lookups for this class to {@code 
stringLookupMap}. The default string
-     * lookups are a set of built-in lookups added for convenience during 
string interpolation. The
-     * defaults may be configured using the {@value 
#DEFAULT_STRING_LOOKUPS_PROPERTY} system property.
-     * See the class documentation for details and a list of lookups.
+     * Adds the default string lookups for this class to {@code 
stringLookupMap}. The default string lookups are a set of built-in lookups 
added for convenience
+     * during string interpolation. The defaults may be configured using the 
{@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system property. See the class
+     * documentation for details and a list of lookups.
      *
      * @param stringLookupMap the map of string lookups to edit.
      * @since 1.5
@@ -679,11 +674,10 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Returns a new function-based lookup where the request for a lookup is 
answered by applying the function with a
-     * lookup key.
+     * Returns a new function-based lookup where the request for a lookup is 
answered by applying the function with a lookup key.
      *
-     * @param <R> the function return type.
-     * @param <U> the function's second parameter type.
+     * @param <R>        the function return type.
+     * @param <U>        the function's second parameter type.
      * @param biFunction the function.
      * @return a new MapStringLookup.
      * @since 1.9
@@ -695,15 +689,14 @@ public final class StringLookupFactory {
     /**
      * Returns the ConstantStringLookup singleton instance to look up the 
value of a fully-qualified static final value.
      * <p>
-     * Sometimes it is necessary in a configuration file to refer to a 
constant defined in a class. This can be done
-     * with this lookup implementation. Variable names must be in the format 
{@code apackage.AClass.AFIELD}. The
-     * {@code lookup(String)} method will split the passed in string at the 
last dot, separating the fully qualified
-     * class name and the name of the constant (i.e. <b>static final</b>) 
member field. Then the class is loaded and the
-     * field's value is obtained using reflection.
+     * Sometimes it is necessary in a configuration file to refer to a 
constant defined in a class. This can be done with this lookup implementation. 
Variable
+     * names must be in the format {@code apackage.AClass.AFIELD}. The {@code 
lookup(String)} method will split the passed in string at the last dot, 
separating
+     * the fully qualified class name and the name of the constant (i.e. 
<b>static final</b>) member field. Then the class is loaded and the field's 
value is
+     * obtained using reflection.
      * </p>
      * <p>
-     * Once retrieved values are cached for fast access. This class is 
thread-safe. It can be used as a standard (i.e.
-     * global) lookup object and serve multiple clients concurrently.
+     * Once retrieved values are cached for fast access. This class is 
thread-safe. It can be used as a standard (i.e. global) lookup object and serve 
multiple
+     * clients concurrently.
      * </p>
      * <p>
      * Using a {@link StringLookup} from the {@link StringLookupFactory}:
@@ -731,8 +724,8 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Returns the DateStringLookup singleton instance to format the current 
date with the format given in the key in a
-     * format compatible with {@link java.text.SimpleDateFormat}.
+     * Returns the DateStringLookup singleton instance to format the current 
date with the format given in the key in a format compatible with
+     * {@link java.text.SimpleDateFormat}.
      * <p>
      * Using a {@link StringLookup} from the {@link StringLookupFactory}:
      * </p>
@@ -773,9 +766,8 @@ public final class StringLookupFactory {
      * 
StringLookupFactory.INSTANCE.dnsStringLookup().lookup("address|apache.org");
      * </pre>
      * <p>
-     * When used through a {@link StringSubstitutor}, this lookup must either 
be added programmatically
-     * (as below) or enabled as a default lookup using the {@value 
#DEFAULT_STRING_LOOKUPS_PROPERTY} system property
-     * (see class documentation).
+     * When used through a {@link StringSubstitutor}, this lookup must either 
be added programmatically (as below) or enabled as a default lookup using the
+     * {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system property (see class 
documentation).
      * </p>
      *
      * <pre>
@@ -798,8 +790,7 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Returns the EnvironmentVariableStringLookup singleton instance where 
the lookup key is an environment variable
-     * name.
+     * Returns the EnvironmentVariableStringLookup singleton instance where 
the lookup key is an environment variable name.
      * <p>
      * Using a {@link StringLookup} from the {@link StringLookupFactory}:
      * </p>
@@ -815,8 +806,7 @@ public final class StringLookupFactory {
      * StringSubstitutor.createInterpolator().replace("... ${env:USER} ..."));
      * </pre>
      * <p>
-     * The above examples convert (on Linux) {@code "USER"} to the current 
user name. On Windows 10, you would use
-     * {@code "USERNAME"} to the same effect.
+     * The above examples convert (on Linux) {@code "USER"} to the current 
user name. On Windows 10, you would use {@code "USERNAME"} to the same effect.
      * </p>
      *
      * @return The EnvironmentVariableStringLookup singleton instance.
@@ -889,10 +879,9 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Returns a new function-based lookup where the request for a lookup is 
answered by applying the function with a
-     * lookup key.
+     * Returns a new function-based lookup where the request for a lookup is 
answered by applying the function with a lookup key.
      *
-     * @param <R> the function return type.
+     * @param <R>      the function return type.
      * @param function the function.
      * @return a new MapStringLookup.
      * @since 1.9
@@ -902,9 +891,8 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Returns a {@link InterpolatorStringLookup} containing the configured
-     * {@link #addDefaultStringLookups(Map) default lookups}. See the class 
documentation for
-     * details on how these defaults are configured.
+     * Returns a {@link InterpolatorStringLookup} containing the configured 
{@link #addDefaultStringLookups(Map) default lookups}. See the class 
documentation
+     * for details on how these defaults are configured.
      * <p>
      * Using a {@link StringLookup} from the {@link StringLookupFactory}:
      * </p>
@@ -930,27 +918,25 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Returns a new InterpolatorStringLookup. If {@code addDefaultLookups} is 
{@code true}, the configured
-     * {@link #addDefaultStringLookups(Map) default lookups} are included in 
addition to the ones
-     * provided in {@code stringLookupMap}. (See the class documentation for 
details on how default lookups
-     * are configured.)
+     * Returns a new InterpolatorStringLookup. If {@code addDefaultLookups} is 
{@code true}, the configured {@link #addDefaultStringLookups(Map) default
+     * lookups} are included in addition to the ones provided in {@code 
stringLookupMap}. (See the class documentation for details on how default 
lookups are
+     * configured.)
      *
-     * @param stringLookupMap the map of string lookups.
-     * @param defaultStringLookup the default string lookup; this lookup is 
used when a variable cannot be
-     *      resolved using the lookups in {@code stringLookupMap} or the 
configured default lookups (if enabled)
-     * @param addDefaultLookups whether to use default lookups as described 
above.
+     * @param stringLookupMap     the map of string lookups.
+     * @param defaultStringLookup the default string lookup; this lookup is 
used when a variable cannot be resolved using the lookups in {@code 
stringLookupMap}
+     *                            or the configured default lookups (if 
enabled)
+     * @param addDefaultLookups   whether to use default lookups as described 
above.
      * @return a new InterpolatorStringLookup.
      * @since 1.4
      */
-    public StringLookup interpolatorStringLookup(final Map<String, 
StringLookup> stringLookupMap,
-        final StringLookup defaultStringLookup, final boolean 
addDefaultLookups) {
+    public StringLookup interpolatorStringLookup(final Map<String, 
StringLookup> stringLookupMap, final StringLookup defaultStringLookup,
+            final boolean addDefaultLookups) {
         return new InterpolatorStringLookup(stringLookupMap, 
defaultStringLookup, addDefaultLookups);
     }
 
     /**
-     * Returns a new InterpolatorStringLookup using the given key-value pairs 
and the configured
-     * {@link #addDefaultStringLookups(Map) default lookups} to resolve 
variables. (See the class
-     * documentation for details on how default lookups are configured.)
+     * Returns a new InterpolatorStringLookup using the given key-value pairs 
and the configured {@link #addDefaultStringLookups(Map) default lookups} to
+     * resolve variables. (See the class documentation for details on how 
default lookups are configured.)
      *
      * @param <V> the value type the default string lookup's map.
      * @param map the default map for string lookups.
@@ -961,9 +947,8 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Returns a new InterpolatorStringLookup using the given lookup and the 
configured
-     * {@link #addDefaultStringLookups(Map) default lookups} to resolve 
variables. (See the class
-     * documentation for details on how default lookups are configured.)
+     * Returns a new InterpolatorStringLookup using the given lookup and the 
configured {@link #addDefaultStringLookups(Map) default lookups} to resolve
+     * variables. (See the class documentation for details on how default 
lookups are configured.)
      *
      * @param defaultStringLookup the default string lookup.
      * @return a new InterpolatorStringLookup.
@@ -973,8 +958,7 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Returns the JavaPlatformStringLookup singleton instance. Looks up keys 
related to Java: Java version, JRE
-     * version, VM version, and so on.
+     * Returns the JavaPlatformStringLookup singleton instance. Looks up keys 
related to Java: Java version, JRE version, VM version, and so on.
      * <p>
      * The lookup keys with examples are:
      * </p>
@@ -1002,8 +986,7 @@ public final class StringLookupFactory {
      * StringSubstitutor.createInterpolator().replace("... ${java:version} 
..."));
      * </pre>
      * <p>
-     * The above examples convert {@code "version"} to the current VM version, 
for example,
-     * {@code "Java version 1.8.0_181"}.
+     * The above examples convert {@code "version"} to the current VM version, 
for example, {@code "Java version 1.8.0_181"}.
      * </p>
      *
      * @return The JavaPlatformStringLookup singleton instance.
@@ -1035,8 +1018,7 @@ public final class StringLookupFactory {
      * StringSubstitutor.createInterpolator().replace("... 
${localhost:canonical-name} ..."));
      * </pre>
      * <p>
-     * The above examples convert {@code "canonical-name"} to the current host 
name, for example,
-     * {@code "EXAMPLE.apache.org"}.
+     * The above examples convert {@code "canonical-name"} to the current host 
name, for example, {@code "EXAMPLE.apache.org"}.
      * </p>
      *
      * @return The DateStringLookup singleton instance.
@@ -1092,8 +1074,8 @@ public final class StringLookupFactory {
      * StringSubstitutor.createInterpolator().replace("... 
${properties:com/domain/document.properties::MyKey} ..."));
      * </pre>
      * <p>
-     * The above examples convert {@code 
"com/domain/document.properties::MyKey"} to the key value in the properties
-     * file at the path "com/domain/document.properties".
+     * The above examples convert {@code 
"com/domain/document.properties::MyKey"} to the key value in the properties 
file at the path
+     * "com/domain/document.properties".
      * </p>
      *
      * @return The PropertiesStringLookup singleton instance.
@@ -1135,8 +1117,8 @@ public final class StringLookupFactory {
      * stringSubstitutor.replace("... 
${properties:com/domain/document.properties::MyKey} ..."));
      * </pre>
      * <p>
-     * The above examples convert {@code 
"com/domain/document.properties::MyKey"} to the key value in the properties
-     * file at the path "com/domain/document.properties".
+     * The above examples convert {@code 
"com/domain/document.properties::MyKey"} to the key value in the properties 
file at the path
+     * "com/domain/document.properties".
      * </p>
      * <p>
      * Methods {@link StringSubstitutor#replace(String)} will throw a {@link 
IllegalArgumentException} when a file doesn't resolves in a fence.
@@ -1173,8 +1155,7 @@ public final class StringLookupFactory {
      * StringSubstitutor.createInterpolator().replace("... 
${resourceBundle:com.domain.messages:MyKey} ..."));
      * </pre>
      * <p>
-     * The above examples convert {@code "com.domain.messages:MyKey"} to the 
key value in the resource bundle at
-     * {@code "com.domain.messages"}.
+     * The above examples convert {@code "com.domain.messages:MyKey"} to the 
key value in the resource bundle at {@code "com.domain.messages"}.
      * </p>
      *
      * @return The ResourceBundleStringLookup singleton instance.
@@ -1199,8 +1180,7 @@ public final class StringLookupFactory {
      * 
StringLookupFactory.INSTANCE.resourceBundleStringLookup("com.domain.messages").lookup("MyKey");
      * </pre>
      * <p>
-     * The above example converts {@code "MyKey"} to the key value in the 
resource bundle at
-     * {@code "com.domain.messages"}.
+     * The above example converts {@code "MyKey"} to the key value in the 
resource bundle at {@code "com.domain.messages"}.
      * </p>
      *
      * @param bundleName Only lookup in this bundle.
@@ -1212,9 +1192,8 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Returns the ScriptStringLookup singleton instance. NOTE: This lookup is 
not included
-     * as a {@link #addDefaultStringLookups(Map) default lookup} unless 
explicitly enabled. See
-     * the class level documentation for details.
+     * Returns the ScriptStringLookup singleton instance. NOTE: This lookup is 
not included as a {@link #addDefaultStringLookups(Map) default lookup} unless
+     * explicitly enabled. See the class level documentation for details.
      * <p>
      * Looks up the value for the key in the format "ScriptEngineName:Script".
      * </p>
@@ -1229,9 +1208,8 @@ public final class StringLookupFactory {
      * StringLookupFactory.INSTANCE.scriptStringLookup().lookup("javascript:3 
+ 4");
      * </pre>
      * <p>
-     * When used through a {@link StringSubstitutor}, this lookup must either 
be added programmatically
-     * (as below) or enabled as a default lookup using the {@value 
#DEFAULT_STRING_LOOKUPS_PROPERTY} system property
-     * (see class documentation).
+     * When used through a {@link StringSubstitutor}, this lookup must either 
be added programmatically (as below) or enabled as a default lookup using the
+     * {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system property (see class 
documentation).
      * </p>
      *
      * <pre>
@@ -1347,9 +1325,8 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Returns the UrlStringLookup singleton instance. This lookup is not 
included
-     * as a {@link #addDefaultStringLookups(Map) default lookup} unless 
explicitly enabled. See
-     * the class level documentation for details.
+     * Returns the UrlStringLookup singleton instance. This lookup is not 
included as a {@link #addDefaultStringLookups(Map) default lookup} unless 
explicitly
+     * enabled. See the class level documentation for details.
      * <p>
      * Looks up the value for the key in the format "CharsetName:URL".
      * </p>
@@ -1357,8 +1334,7 @@ public final class StringLookupFactory {
      * For example, using the HTTP scheme: "UTF-8:http://www.google.com";
      * </p>
      * <p>
-     * For example, using the file scheme:
-     * 
"UTF-8:file:///C:/somehome/commons/commons-text/src/test/resources/document.properties"
+     * For example, using the file scheme: 
"UTF-8:file:///C:/somehome/commons/commons-text/src/test/resources/document.properties"
      * </p>
      * <p>
      * Using a {@link StringLookup} from the {@link StringLookupFactory}:
@@ -1368,9 +1344,8 @@ public final class StringLookupFactory {
      * 
StringLookupFactory.INSTANCE.urlStringLookup().lookup("UTF-8:https://www.apache.org";);
      * </pre>
      * <p>
-     * When used through a {@link StringSubstitutor}, this lookup must either 
be added programmatically
-     * (as below) or enabled as a default lookup using the {@value 
#DEFAULT_STRING_LOOKUPS_PROPERTY} system property
-     * (see class documentation).
+     * When used through a {@link StringSubstitutor}, this lookup must either 
be added programmatically (as below) or enabled as a default lookup using the
+     * {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system property (see class 
documentation).
      * </p>
      *
      * <pre>
@@ -1481,19 +1456,20 @@ public final class StringLookupFactory {
      * StringSubstitutor.createInterpolator().replace("... 
${xml:com/domain/document.xml:/path/to/node} ..."));
      * </pre>
      * <p>
-     * The above examples convert {@code 
"com/domain/document.xml:/path/to/node"} to the value of the XPath in the XML
-     * document.
+     * The above examples convert {@code 
"com/domain/document.xml:/path/to/node"} to the value of the XPath in the XML 
document.
      * </p>
      *
      * @return The XmlStringLookup singleton instance.
      * @since 1.5
+     * @deprecated Use {@link #xmlStringLookup(Map, Path...)}.
      */
+    @Deprecated
     public StringLookup xmlStringLookup() {
         return XmlStringLookup.INSTANCE;
     }
 
     /**
-     * Returns the XmlStringLookup singleton instance.
+     * Returns a XmlStringLookup instance.
      * <p>
      * Looks up the value for the key in the format "DocumentPath:XPath".
      * </p>
@@ -1515,16 +1491,55 @@ public final class StringLookupFactory {
      * StringSubstitutor.createInterpolator().replace("... 
${xml:com/domain/document.xml:/path/to/node} ..."));
      * </pre>
      * <p>
-     * The above examples convert {@code 
"com/domain/document.xml:/path/to/node"} to the value of the XPath in the XML
-     * document.
+     * The above examples convert {@code 
"com/domain/document.xml:/path/to/node"} to the value of the XPath in the XML 
document.
      * </p>
      *
      * @param xPathFactoryFeatures XPathFactory features to set.
      * @return The XmlStringLookup singleton instance.
      * @see XPathFactory#setFeature(String, boolean)
      * @since 1.11.0
+     * @deprecated Use {@link #xmlStringLookup(Map, Path...)}.
      */
+    @Deprecated
     public StringLookup xmlStringLookup(final Map<String, Boolean> 
xPathFactoryFeatures) {
         return new XmlStringLookup(xPathFactoryFeatures);
     }
+
+    /**
+     * Returns a fenced XmlStringLookup instance.
+     * <p>
+     * Looks up the value for the key in the format "DocumentPath:XPath".
+     * </p>
+     * <p>
+     * For example: "com/domain/document.xml:/path/to/node".
+     * </p>
+     * <p>
+     * Using a {@link StringLookup} from the {@link StringLookupFactory} 
fenced by the current directory ({@code Paths.get("")}):
+     * </p>
+     *
+     * <pre>
+     * StringLookupFactory.INSTANCE.xmlStringLookup(map, 
Pathe.get("")).lookup("com/domain/document.xml:/path/to/node");
+     * </pre>
+     * <p>
+     * Using a {@link StringSubstitutor}:
+     * </p>
+     *
+     * <pre>
+     * StringSubstitutor stringSubstitutor = 
StringSubstitutor.createInterpolator();
+     * final InterpolatorStringLookup stringLookup = 
(InterpolatorStringLookup) stringSubstitutor.getStringLookup();
+     * 
stringLookup.getStringLookupMap().replace(StringLookupFactory.KEY_PROPERTIES, 
StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")));
+     * stringSubstitutor.replace("... 
${xml:com/domain/document.xml:/path/to/node} ..."));
+     * </pre>
+     * <p>
+     * The above examples convert {@code 
"com/domain/document.xml:/path/to/node"} to the value of the XPath in the XML 
document.
+     * </p>
+     *
+     * @param xPathFactoryFeatures XPathFactory features to set.
+     * @param fences               The fences guarding Path resolution.
+     * @return The XmlStringLookup singleton instance.
+     * @since 1.12.0
+     */
+    public StringLookup xmlStringLookup(final Map<String, Boolean> 
xPathFactoryFeatures, final Path... fences) {
+        return new XmlStringLookup(xPathFactoryFeatures, fences);
+    }
 }
diff --git a/src/main/java/org/apache/commons/text/lookup/XmlStringLookup.java 
b/src/main/java/org/apache/commons/text/lookup/XmlStringLookup.java
index f3bdd0aa..27aada14 100644
--- a/src/main/java/org/apache/commons/text/lookup/XmlStringLookup.java
+++ b/src/main/java/org/apache/commons/text/lookup/XmlStringLookup.java
@@ -19,7 +19,7 @@ package org.apache.commons.text.lookup;
 
 import java.io.InputStream;
 import java.nio.file.Files;
-import java.nio.file.Paths;
+import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -42,12 +42,12 @@ import org.xml.sax.InputSource;
  *
  * @since 1.5
  */
-final class XmlStringLookup extends AbstractStringLookup {
+final class XmlStringLookup extends AbstractPathFencedLookup {
 
     /**
      * Defines default XPath factory features.
      */
-    private static final Map<String, Boolean> DEFAULT_FEATURES;
+    static final Map<String, Boolean> DEFAULT_FEATURES;
 
     static {
         DEFAULT_FEATURES = new HashMap<>(1);
@@ -57,7 +57,7 @@ final class XmlStringLookup extends AbstractStringLookup {
     /**
      * Defines the singleton for this class.
      */
-    static final XmlStringLookup INSTANCE = new 
XmlStringLookup(DEFAULT_FEATURES);
+    static final XmlStringLookup INSTANCE = new 
XmlStringLookup(DEFAULT_FEATURES, (Path[]) null);
 
     /**
      * Defines XPath factory features.
@@ -70,7 +70,8 @@ final class XmlStringLookup extends AbstractStringLookup {
      * @param xPathFactoryFeatures XPathFactory features to set.
      * @see XPathFactory#setFeature(String, boolean)
      */
-    XmlStringLookup(final Map<String, Boolean> xPathFactoryFeatures) {
+    XmlStringLookup(final Map<String, Boolean> xPathFactoryFeatures, final 
Path... fences) {
+        super(fences);
         this.xPathFactoryFeatures = 
Objects.requireNonNull(xPathFactoryFeatures, "xPathFfactoryFeatures");
     }
 
@@ -96,7 +97,7 @@ final class XmlStringLookup extends AbstractStringLookup {
         }
         final String documentPath = keys[0];
         final String xpath = StringUtils.substringAfter(key, SPLIT_CH);
-        try (InputStream inputStream = 
Files.newInputStream(Paths.get(documentPath))) {
+        try (InputStream inputStream = 
Files.newInputStream(getPath(documentPath))) {
             final XPathFactory factory = XPathFactory.newInstance();
             for (final Entry<String, Boolean> p : 
xPathFactoryFeatures.entrySet()) {
                 factory.setFeature(p.getKey(), p.getValue());
diff --git 
a/src/test/java/org/apache/commons/text/lookup/FileStringLookupTest.java 
b/src/test/java/org/apache/commons/text/lookup/FileStringLookupTest.java
index c8467d3e..c9dd6742 100644
--- a/src/test/java/org/apache/commons/text/lookup/FileStringLookupTest.java
+++ b/src/test/java/org/apache/commons/text/lookup/FileStringLookupTest.java
@@ -33,6 +33,8 @@ import org.junit.jupiter.api.Test;
  */
 public class FileStringLookupTest {
 
+    private static final Path CURRENT_PATH = Paths.get(StringUtils.EMPTY);
+
     @Test
     public void testDefaultInstanceBadCharsetName() {
         assertThrows(IllegalArgumentException.class,
@@ -78,8 +80,7 @@ public class FileStringLookupTest {
     public void testFenceBadDirPlusGoodOne() throws Exception {
         final byte[] expectedBytes = 
Files.readAllBytes(Paths.get("src/test/resources/org/apache/commons/text/document.properties"));
         final String expectedString = new String(expectedBytes, 
StandardCharsets.UTF_8);
-        final Path currentPath = Paths.get(StringUtils.EMPTY);
-        final FileStringLookup fileStringLookup = new 
FileStringLookup(Paths.get("dir does not exist at all"), currentPath);
+        final FileStringLookup fileStringLookup = new 
FileStringLookup(Paths.get("dir does not exist at all"), CURRENT_PATH);
         Assertions.assertEquals(expectedString, 
fileStringLookup.lookup("UTF-8:src/test/resources/org/apache/commons/text/document.properties"));
         assertThrows(IllegalArgumentException.class, () -> 
fileStringLookup.lookup("UTF-8:/src/test/resources/org/apache/commons/text/document.properties"));
     }
@@ -88,8 +89,7 @@ public class FileStringLookupTest {
     public void testFenceCurrentDirOne() throws Exception {
         final byte[] expectedBytes = 
Files.readAllBytes(Paths.get("src/test/resources/org/apache/commons/text/document.properties"));
         final String expectedString = new String(expectedBytes, 
StandardCharsets.UTF_8);
-        final Path currentPath = Paths.get(StringUtils.EMPTY);
-        final FileStringLookup fileStringLookup = new 
FileStringLookup(currentPath);
+        final FileStringLookup fileStringLookup = new 
FileStringLookup(CURRENT_PATH);
         Assertions.assertEquals(expectedString, 
fileStringLookup.lookup("UTF-8:src/test/resources/org/apache/commons/text/document.properties"));
         assertThrows(IllegalArgumentException.class, () -> 
fileStringLookup.lookup("UTF-8:/src/test/resources/org/apache/commons/text/document.properties"));
     }
@@ -98,8 +98,7 @@ public class FileStringLookupTest {
     public void testFenceCurrentDirPlusOne() throws Exception {
         final byte[] expectedBytes = 
Files.readAllBytes(Paths.get("src/test/resources/org/apache/commons/text/document.properties"));
         final String expectedString = new String(expectedBytes, 
StandardCharsets.UTF_8);
-        final Path currentPath = Paths.get(StringUtils.EMPTY);
-        final FileStringLookup fileStringLookup = new 
FileStringLookup(Paths.get("target"), currentPath);
+        final FileStringLookup fileStringLookup = new 
FileStringLookup(Paths.get("target"), CURRENT_PATH);
         Assertions.assertEquals(expectedString, 
fileStringLookup.lookup("UTF-8:src/test/resources/org/apache/commons/text/document.properties"));
         assertThrows(IllegalArgumentException.class, () -> 
fileStringLookup.lookup("UTF-8:/src/test/resources/org/apache/commons/text/document.properties"));
     }
diff --git 
a/src/test/java/org/apache/commons/text/lookup/PropertiesStringLookupTest.java 
b/src/test/java/org/apache/commons/text/lookup/PropertiesStringLookupTest.java
index 26aacf3f..a296c6d5 100644
--- 
a/src/test/java/org/apache/commons/text/lookup/PropertiesStringLookupTest.java
+++ 
b/src/test/java/org/apache/commons/text/lookup/PropertiesStringLookupTest.java
@@ -25,6 +25,7 @@ import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.text.StringSubstitutor;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -35,7 +36,7 @@ import org.junit.jupiter.api.Test;
 public class PropertiesStringLookupTest {
 
     private static final Path[] NULL_PATH_ARRAY = null;
-    private static final Path CURRENT_PATH = Paths.get(""); // NOT "."!
+    private static final Path CURRENT_PATH = Paths.get(StringUtils.EMPTY); // 
NOT "."!
     private static final String DOC_RELATIVE = 
"src/test/resources/org/apache/commons/text/document.properties";
     private static final String DOC_ROOT = "/foo.txt";
     private static final String KEY = "mykey";
diff --git 
a/src/test/java/org/apache/commons/text/lookup/XmlStringLookupTest.java 
b/src/test/java/org/apache/commons/text/lookup/XmlStringLookupTest.java
index 12211597..04784970 100644
--- a/src/test/java/org/apache/commons/text/lookup/XmlStringLookupTest.java
+++ b/src/test/java/org/apache/commons/text/lookup/XmlStringLookupTest.java
@@ -24,10 +24,13 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.HashMap;
 
 import javax.xml.XMLConstants;
 
+import org.apache.commons.lang3.StringUtils;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -35,12 +38,15 @@ import org.junit.jupiter.api.Test;
  */
 public class XmlStringLookupTest {
 
-    private static final String DOC_PATH = 
"src/test/resources/org/apache/commons/text/document.xml";
+    private static final Path CURRENT_PATH = Paths.get(StringUtils.EMPTY); // 
NOT "."
+    private static final Path ABSENT_PATH = Paths.get("does not exist at all");
+    private static final String DOC_RELATIVE = 
"src/test/resources/org/apache/commons/text/document.xml";
+    private static final String DOC_ROOT = "/document.xml";
 
     static void assertLookup(final StringLookup xmlStringLookup) {
         assertNotNull(xmlStringLookup);
         assertTrue(xmlStringLookup instanceof XmlStringLookup);
-        assertEquals("Hello World!", xmlStringLookup.lookup(DOC_PATH + 
":/root/path/to/node"));
+        assertEquals("Hello World!", xmlStringLookup.lookup(DOC_RELATIVE + 
":/root/path/to/node"));
         assertNull(xmlStringLookup.lookup(null));
     }
 
@@ -51,13 +57,19 @@ public class XmlStringLookupTest {
 
     @Test
     public void testMissingXPath() {
-        assertThrows(IllegalArgumentException.class, () -> 
XmlStringLookup.INSTANCE.lookup(DOC_PATH + ":" + "!JUNK!"));
+        assertThrows(IllegalArgumentException.class, () -> 
XmlStringLookup.INSTANCE.lookup(DOC_RELATIVE + ":" + "!JUNK!"));
     }
 
     @Test
     public void testNoFeatures() {
         final String xpath = "/root/path/to/node";
-        assertEquals("Hello World!", new XmlStringLookup(new 
HashMap<>()).lookup(DOC_PATH + ":" + xpath));
+        assertEquals("Hello World!", new XmlStringLookup(new 
HashMap<>()).lookup(DOC_RELATIVE + ":" + xpath));
+        assertEquals("Hello World!", new XmlStringLookup(new HashMap<>(), 
CURRENT_PATH).lookup(DOC_RELATIVE + ":" + xpath));
+        assertEquals("Hello World!", new XmlStringLookup(new HashMap<>(), 
CURRENT_PATH, ABSENT_PATH).lookup(DOC_RELATIVE + ":" + xpath));
+        assertEquals("Hello World!", new XmlStringLookup(new HashMap<>(), 
ABSENT_PATH, CURRENT_PATH).lookup(DOC_RELATIVE + ":" + xpath));
+        assertThrows(IllegalArgumentException.class, () -> new 
XmlStringLookup(new HashMap<>(), ABSENT_PATH).lookup(DOC_ROOT + ":" + xpath));
+        assertThrows(IllegalArgumentException.class, () -> new 
XmlStringLookup(new HashMap<>(), CURRENT_PATH).lookup(DOC_ROOT + ":" + xpath));
+        assertThrows(IllegalArgumentException.class, () -> new 
XmlStringLookup(new HashMap<>(), ABSENT_PATH, CURRENT_PATH).lookup(DOC_ROOT + 
":" + xpath));
     }
 
     @Test


Reply via email to