Rev 3432 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
* Copyright (C) 2008 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: 02-Dec-2008
*/
package uk.me.parabola.mkgmap.osmstyle.actions;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.scan.SyntaxException;
/**
* Build a value that can have tag values substituted in it.
*
* @author Steve Ratcliffe
* @author Toby Speight
*/
public class ValueBuilder
{
private static final Pattern[] FILTER_ARG_PATTERNS =
{
Pattern.
compile("[ \t]*([^: \\t|]+:\"[^\"]+\")[ \t]*"),
Pattern.
compile("[ \t]*([^: \\t|]+:'[^']+')[ \t]*"),
// This must be last
Pattern.
compile("[ \t]*([^: \\t|]+:[^|]*)"),
Pattern.
compile("[ \t]*([^: \\t|]+)"),
};
private static final Pattern NAME_ARG_SPLIT =
Pattern.
compile("([^:]+)(?::[\"']?(.*?)[\"']?)?",
Pattern.
DOTALL);
private final List<ValueItem
> items =
new ArrayList<>();
private final boolean completeCheck
;
public ValueBuilder
(String pattern
) {
this (pattern,
true);
}
public ValueBuilder
(String pattern,
boolean completeCheck
) {
this.
completeCheck =completeCheck
;
compile
(pattern
);
}
/**
* Build this string if all the tags that are required are available.
*
* If a tag does not exist then the whole string is rejected. This allows
* you to make conditional replacements.
*
* @param el Used as a source of tags.
* @param lel Used as a source of local tags.
* @return The built string if all required tags are available. If any
* are missing then it returns null.
*/
public String build
(Element el,
Element lel
) {
if (completeCheck
) {
// Check early for no match and return early
for (ValueItem item : items
) {
if (item.
getValue(el, lel
) ==
null)
return null;
}
}
// If we get here we can build the final string. A common case
// is that there is just one, so return it directly.
if (items.
size() ==
1)
return items.
get(0).
getValue(el, lel
);
// OK we have to construct the result.
StringBuilder sb =
new StringBuilder();
for (ValueItem item : items
)
sb.
append(item.
getValue(el, lel
));
return sb.
toString();
}
/**
* A tag value can contain variables that are the values of other tags.
* This is especially useful for 'name', as you might want to set it to
* some combination of other tags.
*
* If there are no replacement values, the same string as was passed
* in. If all the replacement values exist, then the string with the
* values all replaced. If any replacement tagname does not exist
* then returns null.
* @param in An input string that may contain tag replacement introduced
* by ${tagname}.
*/
private void compile
(String in
) {
if (!in.
contains("$")) {
items.
add(new ValueItem
(in
));
return;
}
char state =
'\0';
StringBuilder text =
new StringBuilder();
StringBuilder tagname =
null;
for (char c : in.
toCharArray()) {
switch (state
) {
case '\0':
if (c ==
'$') {
state =
'$';
} else {
text.
append(c
);
}
break;
case '$':
switch (c
) {
case '{':
case '(':
if (text.
length() > 0) {
items.
add(new ValueItem
(text.
toString()));
text.
setLength(0);
}
tagname =
new StringBuilder();
state =
(c ==
'{') ? '}' :
')';
break;
default:
state =
'\0';
text.
append('$');
text.
append(c
);
}
break;
case '}':
case ')':
if (c == state
) {
//noinspection ConstantConditions
assert tagname
!=
null;
addTagValue
(tagname.
toString(), c ==
')');
state =
'\0';
tagname =
null;
} else {
tagname.
append(c
);
}
break;
default:
assert false;
}
}
if (text.
length() > 0)
items.
add(new ValueItem
(text.
toString()));
}
private void addTagValue
(String tagname,
boolean isLocal
) {
ValueItem item =
new ValueItem
();
if (tagname.
contains("|")) {
String[] parts = tagname.
split("[ \t]*\\|",
2);
assert parts.
length > 1;
item.
setTagname(parts
[0], isLocal
);
String s = parts
[1];
int start =
0;
int end = s.
length();
while (start
< end
) {
Matcher matcher =
null;
for (Pattern p : FILTER_ARG_PATTERNS
) {
matcher = p.
matcher(s
);
matcher.
region(start, end
);
if (matcher.
lookingAt())
break;
}
if (matcher
!=
null && matcher.
lookingAt()) {
start = matcher.
end() +
1;
addFilter
(item, matcher.
group(1));
} else {
assert false;
start = end
;
}
}
} else {
item.
setTagname(tagname, isLocal
);
}
items.
add(item
);
}
private static void addFilter
(ValueItem item,
String expr
) {
Matcher matcher = NAME_ARG_SPLIT.
matcher(expr
);
matcher.
matches();
String cmd = matcher.
group(1);
String arg = matcher.
group(2);
switch (cmd
) {
case "def":
item.
addFilter(new DefaultFilter
(arg
));
break;
case "conv":
item.
addFilter(new ConvertFilter
(arg
));
break;
case "subst":
item.
addFilter(new SubstitutionFilter
(arg
));
break;
case "prefix":
item.
addFilter(new PrependFilter
(arg
));
break;
case "highway-symbol":
item.
addFilter(new HighwaySymbolFilter
(arg
));
break;
case "height":
item.
addFilter(new HeightFilter
(arg
));
break;
case "not-equal":
item.
addFilter(new NotEqualFilter
(arg
));
break;
case "substring":
item.
addFilter(new SubstringFilter
(arg
));
break;
case "part":
item.
addFilter(new PartFilter
(arg
));
break;
case "ascii":
item.
addFilter(new TransliterateFilter
("ascii"));
break;
case "latin1":
item.
addFilter(new TransliterateFilter
("latin1"));
break;
case "country-ISO":
item.
addFilter(new CountryISOFilter
());
break;
case "not-contained":
item.
addFilter(new NotContainedFilter
(arg
));
break;
default:
throw new SyntaxException
(String.
format("Unknown filter '%s'", cmd
));
}
}
public String toString
() {
return items.
stream().
map(ValueItem::toString
).
collect(Collectors.
joining(" | "));
}
public Set<String> getUsedTags
() {
return items.
stream().
map(ValueItem::getTagname
).
filter(Objects::nonNull
).
collect(Collectors.
toSet());
}
}