Subversion Repositories mkgmap

Rev

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.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|]+:[^|]*)"),
                        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 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) {
                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() {
                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;
        }
}