diff --git a/Java/SuperUtilities/src/main/java/com/nuix/superutilities/export/CustomExporter.java b/Java/SuperUtilities/src/main/java/com/nuix/superutilities/export/CustomExporter.java index 732058e..d75d8be 100644 --- a/Java/SuperUtilities/src/main/java/com/nuix/superutilities/export/CustomExporter.java +++ b/Java/SuperUtilities/src/main/java/com/nuix/superutilities/export/CustomExporter.java @@ -1,3 +1,8 @@ +/****************************************** + Copyright 2024 Nuix + http://www.apache.org/licenses/LICENSE-2.0 + *******************************************/ + package com.nuix.superutilities.export; import com.nuix.superutilities.SuperUtilities; diff --git a/Java/SuperUtilities/src/main/java/com/nuix/superutilities/misc/PlaceholderResolver.java b/Java/SuperUtilities/src/main/java/com/nuix/superutilities/misc/PlaceholderResolver.java index be4e980..3e194c3 100644 --- a/Java/SuperUtilities/src/main/java/com/nuix/superutilities/misc/PlaceholderResolver.java +++ b/Java/SuperUtilities/src/main/java/com/nuix/superutilities/misc/PlaceholderResolver.java @@ -1,22 +1,18 @@ /****************************************** -Copyright 2018 Nuix -http://www.apache.org/licenses/LICENSE-2.0 -*******************************************/ + Copyright 2024 Nuix + http://www.apache.org/licenses/LICENSE-2.0 + *******************************************/ package com.nuix.superutilities.misc; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import lombok.Getter; +import nuix.Case; +import nuix.Item; import org.joda.time.DateTime; -import nuix.Item; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /*** * This class provides a way to allow user input to make use of place holder values which will be @@ -27,253 +23,290 @@ public class PlaceholderResolver { /** * -- GETTER -- - * Gets the Map containing all the current place holder data + * Gets the Map containing all the current place holder data * * @return A Map containing all the current place holder data */ @Getter - private Map placeholderData = new LinkedHashMap(); - private Map placeholderPatterns = new LinkedHashMap(); - private Set placeholderPaths = new HashSet(); - - private void recordPlaceholderPattern(String key) { - if(!placeholderPatterns.containsKey(key)) { - placeholderPatterns.put(key,Pattern.compile(Pattern.quote("{"+key+"}"),Pattern.CASE_INSENSITIVE)); - } - } - - /*** - * Automatically sets various place holders based on the provided item. Place holders set:

- * {guid} - The item's GUID.
- * {guid_prefix} Characters 0-2 of the item's GUID. Useful for creating sub-directories based on GUID.
- * {guid_infix} Characters 3-5 of the item's GUID. Useful for creating sub-directories based on GUID.
- * {name} - The item's name as obtained by Item.getLocalisedName
- * {md5} - The item's MD5 or NO_MD5 for items without an MD5 value
- * {type} - The item's type name as obtained by ItemType.getLocalisedName
- * {mime_type} - The item's mime type as obtained by ItemType.getName
- * {kind} - The item's kind name as obtained by ItemType.getKind.getName
- * {custodian} - The item's assigned custodian or NO_CUSTODIAN for items without a custodian assigned
- * {evidence_name} - The name of the evidence the item belongs to.
- * {item_date_short} - The item's item date formatted YYYYMMDD or NO_DATE for items without an item date.
- * {item_date_long} - The item's item date formatted YYYYMMdd-HHmmss or NO_DATE for items without an item date.
- * {item_date_year} - The item's item date 4 digit year or NO_DATE for items without an item date.
- * {item_date_month} - The item's item date 2 digit month or NO_DATE for items without an item date.
- * {item_date_day} - The item's item date 2 digit day of the month or NO_DATE for items without an item date.
- * {top_level_guid} - The GUID of the provided item's top level item or ABOVE_TOP_LEVEL for items which are above top level.
- * {top_level_name} - The name (via Item.getLocalisedName) of the provided item's top level item or ABOVE_TOP_LEVEL for items which are above top level.
- * {top_level_kind} - The kind (via ItemType.getKind.getName) of the provided item's top level item or ABOVE_TOP_LEVEL for items which are above top level.
- * {original_extension} - The original extension as obtained from Nuix via Item.getOriginalExtension or NO_ORIGINAL_EXTENSION for items where Nuix does not have an original extension value.
- * {corrected_extension} - The corrected extension as obtained from Nuix via Item.getCorrectedExtension or NO_CORRECTED_EXTENSION for items where Nuix does not have a corrected extension value.
- * @param item The item used to set all the item based placeholder values. - */ - public void setFromItem(Item item) { - String guid = item.getGuid(); - set("guid",guid); - set("guid_prefix",guid.substring(0, 3)); - set("guid_infix",guid.substring(3, 6)); - - set("name",item.getLocalisedName()); - set("type",item.getType().getLocalisedName()); - set("mime_type",item.getType().getName()); - set("kind",item.getType().getKind().getName()); - set("evidence_name",item.getRoot().getLocalisedName()); - - String md5 = item.getDigests().getMd5(); - if(md5 == null || md5.trim().isEmpty()) { - md5 = "NO_MD5"; - } - set("md5",md5); - - String custodian = item.getCustodian(); - if(custodian == null || custodian.trim().isEmpty()) { custodian = "NO_CUSTODIAN"; } - set("custodian",custodian); - - DateTime itemDate = item.getDate(); - if(itemDate == null) { - set("item_date_short","NO_DATE"); - set("item_date_long","NO_DATE"); - set("item_date_year","NO_DATE"); - set("item_date_month","NO_DATE"); - set("item_date_day","NO_DATE"); - } else { - set("item_date_short",itemDate.toString("YYYYMMdd")); - set("item_date_long",itemDate.toString("YYYYMMdd-HHmmss")); - set("item_date_year",itemDate.toString("YYYY")); - set("item_date_month",itemDate.toString("MM")); - set("item_date_day",itemDate.toString("dd")); - } - - Item topLevelItem = item.getTopLevelItem(); - if(topLevelItem == null) { - set("top_level_guid","ABOVE_TOP_LEVEL"); - set("top_level_name","ABOVE_TOP_LEVEL"); - set("top_level_kind","ABOVE_TOP_LEVEL"); - } else { - set("top_level_guid",topLevelItem.getGuid()); - set("top_level_name",topLevelItem.getLocalisedName()); - set("top_level_kind",topLevelItem.getType().getKind().getName()); - } - - String originalExtension = item.getOriginalExtension(); - if(originalExtension == null || originalExtension.trim().isEmpty()) { originalExtension = "NO_ORIGINAL_EXTENSION"; } - set("original_extension",originalExtension); - - String correctedExtension = item.getCorrectedExtension(); - if(correctedExtension == null || correctedExtension.trim().isEmpty()) { correctedExtension = "NO_CORRECTED_EXTENSION"; } - set("corrected_extension",correctedExtension); - } - - /*** - * Set they value for a given placeholder - * @param key The placeholder name without '{' or '}' - * @param value The value to associate - */ - public void set(String key, String value) { - key = key.toLowerCase(); - placeholderData.put(key,value); - recordPlaceholderPattern(key); - } - - /*** - * Similar to the {@link #set} method except this has logic to appropriately handle file paths. - * @param key The placeholder name without '{' or '}' - * @param value The file/directory path value to associate - */ - public void setPath(String key, String value) { - key = key.toLowerCase(); - placeholderData.put(key,value); - recordPlaceholderPattern(key); - placeholderPaths.add(key); - } - - /*** - * Get the value currently associated for a given placeholder - * @param key The placeholder name without '{' or '}' - * @return The currently associated placeholder value - */ - public String get(String key) { - return placeholderData.get(key.toLowerCase()); - } - - /*** - * Clears all currently associated place holders (keys and values) - */ - public void clear() { - placeholderData.clear(); - placeholderPaths.clear(); - placeholderPatterns.clear(); - } + private Map placeholderData = new LinkedHashMap(); + private Map placeholderPatterns = new LinkedHashMap(); + private Set placeholderPaths = new HashSet(); + + private void recordPlaceholderPattern(String key) { + if (!placeholderPatterns.containsKey(key)) { + placeholderPatterns.put(key, Pattern.compile(Pattern.quote("{" + key + "}"), Pattern.CASE_INSENSITIVE)); + } + } + + /*** + * Automatically sets various place holders based on the provided item. Place holders set:

+ * {guid} - The item's GUID.
+ * {guid_prefix} Characters 0-2 of the item's GUID. Useful for creating sub-directories based on GUID.
+ * {guid_infix} Characters 3-5 of the item's GUID. Useful for creating sub-directories based on GUID.
+ * {name} - The item's name as obtained by Item.getLocalisedName
+ * {md5} - The item's MD5 or NO_MD5 for items without an MD5 value
+ * {type} - The item's type name as obtained by ItemType.getLocalisedName
+ * {mime_type} - The item's mime type as obtained by ItemType.getName
+ * {kind} - The item's kind name as obtained by ItemType.getKind.getName
+ * {custodian} - The item's assigned custodian or NO_CUSTODIAN for items without a custodian assigned
+ * {evidence_name} - The name of the evidence the item belongs to.
+ * {item_date_short} - The item's item date formatted YYYYMMDD or NO_DATE for items without an item date.
+ * {item_date_long} - The item's item date formatted YYYYMMdd-HHmmss or NO_DATE for items without an item date.
+ * {item_date_year} - The item's item date 4 digit year or NO_DATE for items without an item date.
+ * {item_date_month} - The item's item date 2 digit month or NO_DATE for items without an item date.
+ * {item_date_day} - The item's item date 2 digit day of the month or NO_DATE for items without an item date.
+ * {top_level_guid} - The GUID of the provided item's top level item or ABOVE_TOP_LEVEL for items which are above top level.
+ * {top_level_name} - The name (via Item.getLocalisedName) of the provided item's top level item or ABOVE_TOP_LEVEL for items which are above top level.
+ * {top_level_kind} - The kind (via ItemType.getKind.getName) of the provided item's top level item or ABOVE_TOP_LEVEL for items which are above top level.
+ * {original_extension} - The original extension as obtained from Nuix via Item.getOriginalExtension or NO_ORIGINAL_EXTENSION for items where Nuix does not have an original extension value.
+ * {corrected_extension} - The corrected extension as obtained from Nuix via Item.getCorrectedExtension or NO_CORRECTED_EXTENSION for items where Nuix does not have a corrected extension value.
+ * @param item The item used to set all the item based placeholder values. + */ + public void setFromItem(Item item) { + String guid = item.getGuid(); + set("guid", guid); + set("guid_prefix", guid.substring(0, 3)); + set("guid_infix", guid.substring(3, 6)); + + set("name", item.getLocalisedName()); + set("type", item.getType().getLocalisedName()); + set("mime_type", item.getType().getName()); + set("kind", item.getType().getKind().getName()); + set("evidence_name", item.getRoot().getLocalisedName()); + + String md5 = item.getDigests().getMd5(); + if (md5 == null || md5.trim().isEmpty()) { + md5 = "NO_MD5"; + } + set("md5", md5); + + String custodian = item.getCustodian(); + if (custodian == null || custodian.trim().isEmpty()) { + custodian = "NO_CUSTODIAN"; + } + set("custodian", custodian); + + DateTime itemDate = item.getDate(); + if (itemDate == null) { + set("item_date_short", "NO_DATE"); + set("item_date_long", "NO_DATE"); + set("item_date_year", "NO_DATE"); + set("item_date_month", "NO_DATE"); + set("item_date_day", "NO_DATE"); + } else { + set("item_date_short", itemDate.toString("YYYYMMdd")); + set("item_date_long", itemDate.toString("YYYYMMdd-HHmmss")); + set("item_date_year", itemDate.toString("YYYY")); + set("item_date_month", itemDate.toString("MM")); + set("item_date_day", itemDate.toString("dd")); + } + + Item topLevelItem = item.getTopLevelItem(); + if (topLevelItem == null) { + set("top_level_guid", "ABOVE_TOP_LEVEL"); + set("top_level_name", "ABOVE_TOP_LEVEL"); + set("top_level_kind", "ABOVE_TOP_LEVEL"); + } else { + set("top_level_guid", topLevelItem.getGuid()); + set("top_level_name", topLevelItem.getLocalisedName()); + set("top_level_kind", topLevelItem.getType().getKind().getName()); + } + + String originalExtension = item.getOriginalExtension(); + if (originalExtension == null || originalExtension.trim().isEmpty()) { + originalExtension = "NO_ORIGINAL_EXTENSION"; + } + set("original_extension", originalExtension); + + String correctedExtension = item.getCorrectedExtension(); + if (correctedExtension == null || correctedExtension.trim().isEmpty()) { + correctedExtension = "NO_CORRECTED_EXTENSION"; + } + set("corrected_extension", correctedExtension); + } /*** - * Resolves place holders into a string based on the currently associated values - * @param template The input string containing place holders - * @return The input string in which place holders have been replaced with associated values - */ - public String resolveTemplate(String template) { - String result = template; - for(Map.Entry entry : placeholderPatterns.entrySet()){ - Pattern p = entry.getValue(); - String value = Matcher.quoteReplacement(get(entry.getKey())); - result = p.matcher(result).replaceAll(value); - } - return result; - } - - /*** - * Resolves place holders into a string based on the currently associated values and the multi-value place holders provided. For example - * imagine you wish to render the template 1 time for each tag associated to an item that has 3 tags assigned. You can call this method, - * providing a {@link NamedStringList} with a name of tags and those 3 tags as values. This method will then return 3 resolutions - * of a template containing the placeholder {tags}. Each returned result containing 1 of the 3 tags substituted. This method allows - * you to provide multiple multi-value place holders like this, but it is important to note the number of resulting values is multiplied. So if I provide - * a placeholder animals with 3 values, a placeholder colors with 5 values and a placeholder names with 4 values, the number - * of possible resulting values is 3*5*4=60. - * @param template The template to resolve - * @param multiValuePlaceholders Place holders to resolve multiple times with multiple values. - * @return The various resulting values generated from all the combinations. - */ - public Set resolveTemplateMultiValues(String template, List multiValuePlaceholders){ - Set resolvedValues = new HashSet<>(); - resolveTemplateMultiValuesRecursive(template,multiValuePlaceholders,0,resolvedValues); - return resolvedValues; - } - - private void resolveTemplateMultiValuesRecursive(String template, List multiValuePlaceholders, int namedListIndex, Set results) { - if(namedListIndex == multiValuePlaceholders.size() - 1) { - NamedStringList nsl = multiValuePlaceholders.get(namedListIndex); - nsl.forEach(v -> { - set(nsl.getName(),v); - results.add(resolveTemplate(template)); - }); - } else { - NamedStringList nsl = multiValuePlaceholders.get(namedListIndex); - nsl.forEach(v -> { - set(nsl.getName(),v); - resolveTemplateMultiValuesRecursive(template,multiValuePlaceholders,namedListIndex+1,results); - }); - } - } - - /*** - * Resolves placeholders into a path string based on the currently associated values. Contains logic - * to sterilize the resulting path string so that it does not contain common illegal path characters. - * @param template A file/directory path string containing placeholders - * @return The input string in which placeholders have been replaced with associated values - */ - public String resolveTemplatePath(String template) { - String result = template; - for(Map.Entry entry : placeholderPatterns.entrySet()){ - Pattern p = entry.getValue(); - String value = Matcher.quoteReplacement(get(entry.getKey())); - if(!placeholderPaths.contains(entry.getKey())){ - value = cleanPathString(value); - } - result = p.matcher(result).replaceAll(value); - } - return result; - } - - /*** - * Resolves placeholders into a string based on the currently associated values and the multi-value place holders provided. For example - * imagine you wish to render the template 1 time for each tag associated to an item that has 3 tags assigned. You can call this method, - * providing a {@link NamedStringList} with a name of tags and those 3 tags as values. This method will then return 3 resolutions - * of a template containing the placeholder {tags}. Each returned result containing 1 of the 3 tags substituted. This method allows - * you to provide multiple multi-value placeholders like this, but it is important to note the number of resulting values is multiplied. So if I provide - * a placeholder animals with 3 values, a placeholder colors with 5 values and a placeholder names with 4 values, the number - * of possible resulting values is 3*5*4=60. - * This method is similar to {{@link #resolveTemplateMultiValues(String, List)} except the template is resolved using {{@link #resolveTemplatePath(String)} instead - * of the method {{@link #resolveTemplate(String)} which is used by {{@link #resolveTemplateMultiValues(String, List)}. - * @param template The template to resolve - * @param multiValuePlaceholders placeholders to resolve multiple times with multiple values. - * @return The various resulting values generated from all the combinations. - */ - public Set resolveTemplatePathMultiValues(String template, List multiValuePlaceholders){ - Set resolvedValues = new HashSet(); - resolveTemplatePathMultiValuesRecursive(template,multiValuePlaceholders,0,resolvedValues); - return resolvedValues; - } - - private void resolveTemplatePathMultiValuesRecursive(String template, List multiValuePlaceholders, int namedListIndex, Set results) { - if(namedListIndex == multiValuePlaceholders.size() - 1) { - NamedStringList nsl = multiValuePlaceholders.get(namedListIndex); - nsl.forEach(v -> { - set(nsl.getName(),v); - results.add(resolveTemplatePath(template)); - }); - } else { - NamedStringList nsl = multiValuePlaceholders.get(namedListIndex); - nsl.forEach(v -> { - set(nsl.getName(),v); - resolveTemplatePathMultiValuesRecursive(template,multiValuePlaceholders,namedListIndex+1,results); - }); - } - } - - /*** - * Helper method to strip common illegal path characters from a string - * @param input The string to clean up - * @return The string with illegal path characters replaced with '_' - */ - public static String cleanPathString(String input){ - return input.replaceAll("[\\Q<>:\\/\"|?*[]\\E]","_"); - } + * Convenience method for setting various standard values:

+ * {date_short} - The datetime of invocation, formatted YYYYMMDD
+ * {date_long} - The datetime of invocation, formatted YYYYMMdd-HHmmss
+ * {date_year} - The datetime of invocation, as 4 digit year
+ * {date_month} - The datetime of invocation, as 2 digit month
+ * {date_day} - The datetime of invocation, as 2 digit day of the month
+ * {nuix_version} - The Nuix version as defined in NUIX_VERSION
+ */ + public void setStandardValues() { + DateTime now = DateTime.now(); + set("date_short", now.toString("YYYYMMdd")); + set("date_long", now.toString("YYYYMMdd-HHmmss")); + set("date_year", now.toString("YYYY")); + set("date_month", now.toString("MM")); + set("date_day", now.toString("dd")); + } + + /*** + * Convenience method for setting various values of a Nuix case:

+ * {case_name} - The name assigned to the case, as obtained by calling Case.getName
+ * {case_guid} - The GUID assigned to the case, as obtained by calling Case.getGuid
+ * {investigator} - The investigator associated to the case, as obtained by calling Case.getInvestigator
+ * @param nuixCase The case to assign values from + */ + public void setFromCase(Case nuixCase) { + set("case_name", nuixCase.getName()); + set("case_guid", nuixCase.getGuid()); + set("investigator", nuixCase.getInvestigator()); + } + + /*** + * Set they value for a given placeholder + * @param key The placeholder name without '{' or '}' + * @param value The value to associate + */ + public void set(String key, String value) { + key = key.toLowerCase(); + placeholderData.put(key, value); + recordPlaceholderPattern(key); + } + + /*** + * Similar to the {@link #set} method except this has logic to appropriately handle file paths. + * @param key The placeholder name without '{' or '}' + * @param value The file/directory path value to associate + */ + public void setPath(String key, String value) { + key = key.toLowerCase(); + placeholderData.put(key, value); + recordPlaceholderPattern(key); + placeholderPaths.add(key); + } + + /*** + * Get the value currently associated for a given placeholder + * @param key The placeholder name without '{' or '}' + * @return The currently associated placeholder value + */ + public String get(String key) { + return placeholderData.get(key.toLowerCase()); + } + + /*** + * Clears all currently associated place holders (keys and values) + */ + public void clear() { + placeholderData.clear(); + placeholderPaths.clear(); + placeholderPatterns.clear(); + } + + /*** + * Resolves place holders into a string based on the currently associated values + * @param template The input string containing place holders + * @return The input string in which place holders have been replaced with associated values + */ + public String resolveTemplate(String template) { + String result = template; + for (Map.Entry entry : placeholderPatterns.entrySet()) { + Pattern p = entry.getValue(); + String value = Matcher.quoteReplacement(get(entry.getKey())); + result = p.matcher(result).replaceAll(value); + } + return result; + } + + /*** + * Resolves place holders into a string based on the currently associated values and the multi-value place holders provided. For example + * imagine you wish to render the template 1 time for each tag associated to an item that has 3 tags assigned. You can call this method, + * providing a {@link NamedStringList} with a name of tags and those 3 tags as values. This method will then return 3 resolutions + * of a template containing the placeholder {tags}. Each returned result containing 1 of the 3 tags substituted. This method allows + * you to provide multiple multi-value place holders like this, but it is important to note the number of resulting values is multiplied. So if I provide + * a placeholder animals with 3 values, a placeholder colors with 5 values and a placeholder names with 4 values, the number + * of possible resulting values is 3*5*4=60. + * @param template The template to resolve + * @param multiValuePlaceholders Place holders to resolve multiple times with multiple values. + * @return The various resulting values generated from all the combinations. + */ + public Set resolveTemplateMultiValues(String template, List multiValuePlaceholders) { + Set resolvedValues = new HashSet<>(); + resolveTemplateMultiValuesRecursive(template, multiValuePlaceholders, 0, resolvedValues); + return resolvedValues; + } + + private void resolveTemplateMultiValuesRecursive(String template, List multiValuePlaceholders, int namedListIndex, Set results) { + if (namedListIndex == multiValuePlaceholders.size() - 1) { + NamedStringList nsl = multiValuePlaceholders.get(namedListIndex); + nsl.forEach(v -> { + set(nsl.getName(), v); + results.add(resolveTemplate(template)); + }); + } else { + NamedStringList nsl = multiValuePlaceholders.get(namedListIndex); + nsl.forEach(v -> { + set(nsl.getName(), v); + resolveTemplateMultiValuesRecursive(template, multiValuePlaceholders, namedListIndex + 1, results); + }); + } + } + + /*** + * Resolves placeholders into a path string based on the currently associated values. Contains logic + * to sterilize the resulting path string so that it does not contain common illegal path characters. + * @param template A file/directory path string containing placeholders + * @return The input string in which placeholders have been replaced with associated values + */ + public String resolveTemplatePath(String template) { + String result = template; + for (Map.Entry entry : placeholderPatterns.entrySet()) { + Pattern p = entry.getValue(); + String value = Matcher.quoteReplacement(get(entry.getKey())); + if (!placeholderPaths.contains(entry.getKey())) { + value = cleanPathString(value); + } + result = p.matcher(result).replaceAll(value); + } + return result; + } + + /*** + * Resolves placeholders into a string based on the currently associated values and the multi-value place holders provided. For example + * imagine you wish to render the template 1 time for each tag associated to an item that has 3 tags assigned. You can call this method, + * providing a {@link NamedStringList} with a name of tags and those 3 tags as values. This method will then return 3 resolutions + * of a template containing the placeholder {tags}. Each returned result containing 1 of the 3 tags substituted. This method allows + * you to provide multiple multi-value placeholders like this, but it is important to note the number of resulting values is multiplied. So if I provide + * a placeholder animals with 3 values, a placeholder colors with 5 values and a placeholder names with 4 values, the number + * of possible resulting values is 3*5*4=60. + * This method is similar to {{@link #resolveTemplateMultiValues(String, List)} except the template is resolved using {{@link #resolveTemplatePath(String)} instead + * of the method {{@link #resolveTemplate(String)} which is used by {{@link #resolveTemplateMultiValues(String, List)}. + * @param template The template to resolve + * @param multiValuePlaceholders placeholders to resolve multiple times with multiple values. + * @return The various resulting values generated from all the combinations. + */ + public Set resolveTemplatePathMultiValues(String template, List multiValuePlaceholders) { + Set resolvedValues = new HashSet(); + resolveTemplatePathMultiValuesRecursive(template, multiValuePlaceholders, 0, resolvedValues); + return resolvedValues; + } + + private void resolveTemplatePathMultiValuesRecursive(String template, List multiValuePlaceholders, int namedListIndex, Set results) { + if (namedListIndex == multiValuePlaceholders.size() - 1) { + NamedStringList nsl = multiValuePlaceholders.get(namedListIndex); + nsl.forEach(v -> { + set(nsl.getName(), v); + results.add(resolveTemplatePath(template)); + }); + } else { + NamedStringList nsl = multiValuePlaceholders.get(namedListIndex); + nsl.forEach(v -> { + set(nsl.getName(), v); + resolveTemplatePathMultiValuesRecursive(template, multiValuePlaceholders, namedListIndex + 1, results); + }); + } + } + + /*** + * Helper method to strip common illegal path characters from a string + * @param input The string to clean up + * @return The string with illegal path characters replaced with '_' + */ + public static String cleanPathString(String input) { + return input.replaceAll("[\\Q<>:\\/\"|?*[]\\E]", "_"); + } } \ No newline at end of file