Rev 3408 |
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.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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|]+:[^|]*)"),
};
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 is_local
) {
ValueItem item =
new ValueItem
();
if (tagname.
contains("|")) {
String[] parts = tagname.
split("[ \t]*\\|",
2);
assert parts.
length > 1;
item.
setTagname(parts
[0], is_local
);
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, is_local
);
}
items.
add(item
);
}
private void addFilter
(ValueItem item,
String expr
) {
Pattern pattern =
Pattern.
compile("([^:]+):[\"']?(.*?)[\"']?",
Pattern.
DOTALL);
Matcher matcher = pattern.
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 "country-ISO":
item.
addFilter(new CountryISOFilter
());
break;
default:
throw new SyntaxException
(String.
format("Unknown filter '%s'", cmd
));
}
}
public String toString
() {
StringBuilder sb =
new StringBuilder("'");
for (ValueItem v : items
) {
sb.
append(v
);
}
sb.
append("'");
return sb.
toString();
}
public Set<String> getUsedTags
() {
Set<String> set =
new HashSet<>();
for (ValueItem v : items
) {
String tagname = v.
getTagname();
if (tagname
!=
null)
set.
add(tagname
);
}
return set
;
}
}