
jodd.util.StringTemplateParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of extractor Show documentation
Show all versions of extractor Show documentation
Web Data Extractor - Extract data from common web format. like HTML,XML,JSON.
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package jodd.util;
import java.util.Map;
/**
* Parser for string macro templates. On parsing, macro values
* in provided string are resolved and replaced with real values.
* Once set, one string template parser can be reused for parsing,
* even using different macro resolvers.
*/
public class StringTemplateParser {
/**
* Static ctor.
*/
public static StringTemplateParser create() {
return new StringTemplateParser();
}
public static final String DEFAULT_MACRO_PREFIX = "$";
public static final String DEFAULT_MACRO_START = "${";
public static final String DEFAULT_MACRO_END = "}";
// ---------------------------------------------------------------- properties
protected boolean replaceMissingKey = true;
protected String missingKeyReplacement;
protected boolean resolveEscapes = true;
protected String macroPrefix = DEFAULT_MACRO_PREFIX;
protected String macroStart = DEFAULT_MACRO_START;
protected String macroEnd = DEFAULT_MACRO_END;
protected char escapeChar = '\\';
protected boolean parseValues;
public boolean isReplaceMissingKey() {
return replaceMissingKey;
}
/**
* Specifies if missing keys should be resolved at all,
* true
by default.
* If false
missing keys will be left as it were, i.e.
* they will not be replaced.
*/
public void setReplaceMissingKey(boolean replaceMissingKey) {
this.replaceMissingKey = replaceMissingKey;
}
public String getMissingKeyReplacement() {
return missingKeyReplacement;
}
/**
* Specifies replacement for missing keys. If null
* exception will be thrown.
*/
public StringTemplateParser setMissingKeyReplacement(String missingKeyReplacement) {
this.missingKeyReplacement = missingKeyReplacement;
return this;
}
public boolean isResolveEscapes() {
return resolveEscapes;
}
/**
* Specifies if escaped values should be resolved. In special usecases,
* when the same string has to be processed more then once,
* this may be set to false
so escaped values
* remains.
*/
public StringTemplateParser setResolveEscapes(boolean resolveEscapes) {
this.resolveEscapes = resolveEscapes;
return this;
}
public String getMacroStart() {
return macroStart;
}
/**
* Defines macro start string.
*/
public StringTemplateParser setMacroStart(String macroStart) {
this.macroStart = macroStart;
return this;
}
public String getMacroPrefix() {
return macroPrefix;
}
public StringTemplateParser setMacroPrefix(String macroPrefix) {
this.macroPrefix = macroPrefix;
return this;
}
public String getMacroEnd() {
return macroEnd;
}
/**
* Defines macro end string.
*/
public StringTemplateParser setMacroEnd(String macroEnd) {
this.macroEnd = macroEnd;
return this;
}
/**
* Sets the strict format by setting the macro prefix to null
.
*/
public StringTemplateParser setStrictFormat() {
macroPrefix = null;
return this;
}
public char getEscapeChar() {
return escapeChar;
}
/**
* Defines escape character.
*/
public StringTemplateParser setEscapeChar(char escapeChar) {
this.escapeChar = escapeChar;
return this;
}
public boolean isParseValues() {
return parseValues;
}
/**
* Defines if macro values has to be parsed, too.
* By default, macro values are returned as they are.
*/
public StringTemplateParser setParseValues(boolean parseValues) {
this.parseValues = parseValues;
return this;
}
// ---------------------------------------------------------------- parse
/**
* Parses string template and replaces macros with resolved values.
*/
public String parse(String template, MacroResolver macroResolver) {
StringBuilder result = new StringBuilder(template.length());
int i = 0;
int len = template.length();
// strict flag means that start and end tag are not necessary
boolean strict;
if (macroPrefix == null) {
// when prefix is not specified, make it equals to macro start
// so we can use the same code
macroPrefix = macroStart;
strict = true;
}
else {
strict = false;
}
int prefixLen = macroPrefix.length();
int startLen = macroStart.length();
int endLen = macroEnd.length();
while (i < len) {
int ndx = template.indexOf(macroPrefix, i);
if (ndx == -1) {
result.append(i == 0 ? template : template.substring(i));
break;
}
// check escaped
int j = ndx - 1;
boolean escape = false;
int count = 0;
while ((j >= 0) && (template.charAt(j) == escapeChar)) {
escape = !escape;
if (escape) {
count++;
}
j--;
}
if (resolveEscapes) {
result.append(template.substring(i, ndx - count));
} else {
result.append(template.substring(i, ndx));
}
if (escape == true) {
result.append(macroPrefix);
i = ndx + prefixLen;
continue;
}
// macro started, detect strict format
boolean strictFormat = strict;
if (strictFormat == false) {
if (StringUtil.isSubstringAt(template, macroStart, ndx)) {
strictFormat = true;
}
}
int ndx1;
int ndx2;
if (!strictFormat) {
// not strict format: $foo
ndx += prefixLen;
ndx1 = ndx;
ndx2 = ndx;
while ((ndx2 < len) && CharUtil.isPropertyNameChar(template.charAt(ndx2))) {
ndx2++;
}
if (ndx2 == len) {
ndx2--;
}
while ((ndx2 > ndx) && !CharUtil.isAlphaOrDigit(template.charAt(ndx2))) {
ndx2--;
}
ndx2++;
if (ndx2 == ndx1 + 1) {
// no value, hence no macro
result.append(macroPrefix);
i = ndx1;
continue;
}
}
else {
// strict format: ${foo}
// find macros end
ndx += startLen;
ndx2 = template.indexOf(macroEnd, ndx);
if (ndx2 == -1) {
throw new IllegalArgumentException("Invalid template, unclosed macro at: " + (ndx - startLen));
}
// detect inner macros, there is no escaping
ndx1 = ndx;
while (ndx1 < ndx2) {
int n = StringUtil.indexOf(template, macroStart, ndx1, ndx2);
if (n == -1) {
break;
}
ndx1 = n + startLen;
}
}
final String name = template.substring(ndx1, ndx2);
// find value and append
Object value;
if (missingKeyReplacement != null || replaceMissingKey == false) {
try {
value = macroResolver.resolve(name);
} catch (Exception ignore) {
value = null;
}
if (value == null) {
if (replaceMissingKey == true) {
value = missingKeyReplacement;
} else {
value = template.substring(ndx1 - startLen, ndx2 + 1);
}
}
} else {
value = macroResolver.resolve(name);
if (value == null) {
value = StringPool.EMPTY;
}
}
if (ndx == ndx1) {
String stringValue = value.toString();
if (parseValues == true) {
if (stringValue.contains(macroStart)) {
stringValue = parse(stringValue, macroResolver);
}
}
result.append(stringValue);
i = ndx2;
if (strictFormat) {
i += endLen;
}
} else {
// inner macro
template = template.substring(0, ndx1 - startLen) + value.toString() + template.substring(ndx2 + endLen);
len = template.length();
i = ndx - startLen;
}
}
return result.toString();
}
// ---------------------------------------------------------------- resolver
/**
* Macro value resolver.
*/
public interface MacroResolver {
/**
* Resolves macro value for macro name founded in
* string template. null
values will
* be replaced with empty strings.
*/
String resolve(String macroName);
}
/**
* Creates commonly used {@link MacroResolver} that resolved
* macros in the provided map.
*/
public static MacroResolver createMapMacroResolver(final Map map) {
return new MacroResolver() {
public String resolve(String macroName) {
Object value = map.get(macroName);
if (value == null) {
return null;
}
return value.toString();
}
};
}
}