| /* |
| * Copyright 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.i18n.rebind; |
| |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| |
| import java.text.ParseException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Helper class for parsing MessageFormat-style format strings. |
| * |
| * @deprecated use {@link com.google.gwt.i18n.server.MessageFormatUtils} instead |
| */ |
| @Deprecated |
| public class MessageFormatParser { |
| |
| /** |
| * Represents an argument in a template string. |
| */ |
| public static class ArgumentChunk extends TemplateChunk { |
| |
| private final int argNumber; |
| private final String format; |
| private final Map<String, String> formatArgs; |
| private final String subFormat; |
| private final Map<String, String> listArgs; |
| |
| public ArgumentChunk(int argNumber, Map<String, String> listArgs, |
| String format, Map<String, String> formatArgs, String subformat) { |
| this.argNumber = argNumber; |
| this.format = format; |
| this.subFormat = subformat; |
| this.listArgs = listArgs; |
| this.formatArgs = formatArgs; |
| } |
| |
| @Override |
| public void accept(TemplateChunkVisitor visitor) |
| throws UnableToCompleteException { |
| visitor.visit(this); |
| } |
| |
| /** |
| * Get the argument number this chunk refers to. |
| * |
| * @return the argument number or -1 to refer to the right-most plural |
| * argument |
| */ |
| public int getArgumentNumber() { |
| return argNumber; |
| } |
| |
| public String getFormat() { |
| return format; |
| } |
| |
| public Map<String, String> getFormatArgs() { |
| return formatArgs; |
| } |
| |
| public Map<String, String> getListArgs() { |
| return listArgs; |
| } |
| |
| public String getSubFormat() { |
| return subFormat; |
| } |
| |
| public boolean isList() { |
| return listArgs != null; |
| } |
| |
| @Override |
| public String toString() { |
| return "Argument: #=" + argNumber + ", format=" + format + ", subformat=" |
| + subFormat; |
| } |
| |
| @Override |
| protected String getStringValue(boolean quote) { |
| StringBuilder buf = new StringBuilder(); |
| buf.append('{'); |
| if (argNumber < 0) { |
| buf.append('#'); |
| } else { |
| buf.append(argNumber); |
| } |
| Map<String, String> map = listArgs; |
| if (map != null) { |
| buf.append(",list"); |
| appendArgs(buf, map, quote); |
| } |
| if (format != null || subFormat != null) { |
| buf.append(','); |
| } |
| if (format != null) { |
| buf.append(quoteMessageFormatChars(format, quote)); |
| appendArgs(buf, formatArgs, quote); |
| } |
| if (subFormat != null) { |
| buf.append(','); |
| buf.append(subFormat); |
| } |
| buf.append('}'); |
| return buf.toString(); |
| } |
| |
| /** |
| * @param buf |
| * @param map |
| * @param quote |
| */ |
| private void appendArgs( |
| StringBuilder buf, Map<String, String> map, boolean quote) { |
| char prefix = ':'; |
| for (Map.Entry<String, String> entry : map.entrySet()) { |
| String key = entry.getKey(); |
| if (quote) { |
| key = quoteMessageFormatChars(key); |
| } |
| buf.append(prefix).append(key); |
| String value = entry.getValue(); |
| if (value != null) { |
| if (quote) { |
| value = quoteMessageFormatChars(value); |
| } |
| buf.append('=').append(value); |
| } |
| prefix = ','; |
| } |
| } |
| } |
| |
| /** |
| * Default implementation of TemplateChunkVisitor -- other implementations |
| * should extend this if possible to avoid breakage when new TemplateChunk |
| * subtypes are added. |
| */ |
| public static class DefaultTemplateChunkVisitor |
| implements TemplateChunkVisitor { |
| |
| public void visit(ArgumentChunk argChunk) throws UnableToCompleteException { |
| } |
| |
| public void visit(StaticArgChunk staticArgChunk) |
| throws UnableToCompleteException { |
| } |
| |
| public void visit(StringChunk stringChunk) |
| throws UnableToCompleteException { |
| } |
| } |
| |
| /** |
| * Represents a static argument, which is used to remove markup from |
| * translator view without having to supply it at each callsite. |
| */ |
| public static class StaticArgChunk extends TemplateChunk { |
| |
| private final String argName; |
| private final String replacement; |
| |
| public StaticArgChunk(String argName, String replacement) { |
| this.argName = argName; |
| this.replacement = replacement; |
| } |
| |
| @Override |
| public void accept(TemplateChunkVisitor visitor) |
| throws UnableToCompleteException { |
| visitor.visit(this); |
| } |
| |
| public String getArgName() { |
| return argName; |
| } |
| |
| public String getReplacement() { |
| return replacement; |
| } |
| |
| @Override |
| protected String getStringValue(boolean quoted) { |
| StringBuilder buf = new StringBuilder(); |
| buf.append('{').append(argName); |
| if (replacement != null) { |
| buf.append(',').append(quoteMessageFormatChars(replacement, quoted)); |
| } |
| buf.append('}'); |
| return buf.toString(); |
| } |
| } |
| |
| /** |
| * Represents a literal string portion of a template string. |
| */ |
| public static class StringChunk extends TemplateChunk { |
| |
| private StringBuilder buf = new StringBuilder(); |
| |
| public StringChunk() { |
| } |
| |
| public StringChunk(String str) { |
| buf.append(str); |
| } |
| |
| @Override |
| public void accept(TemplateChunkVisitor visitor) |
| throws UnableToCompleteException { |
| visitor.visit(this); |
| } |
| |
| public void append(String str) { |
| buf.append(str); |
| } |
| |
| @Override |
| public boolean isLiteral() { |
| return true; |
| } |
| |
| @Override |
| public String toString() { |
| return "StringLiteral: \"" + buf.toString() + "\""; |
| } |
| |
| @Override |
| protected String getStringValue(boolean quote) { |
| String str = buf.toString(); |
| return quoteMessageFormatChars(str, quote); |
| } |
| } |
| |
| /** |
| * Represents a parsed chunk of a template. |
| */ |
| public abstract static class TemplateChunk { |
| |
| /** |
| * Quote a string in the MessageFormat-style. |
| * |
| * @param str string to quote, must not be null |
| * @return quoted string |
| */ |
| protected static String quoteMessageFormatChars(String str) { |
| str = str.replace("'", "''"); |
| str = str.replace("{", "'{'"); |
| str = str.replace("}", "'}'"); |
| return str; |
| } |
| |
| /** |
| * Possibly quote a string in the MessageFormat-style. |
| * |
| * @param str |
| * @return quoted string |
| */ |
| protected static String quoteMessageFormatChars(String str, boolean quote) { |
| return quote ? quoteMessageFormatChars(str) : str; |
| } |
| |
| public abstract void accept(TemplateChunkVisitor visitor) |
| throws UnableToCompleteException; |
| |
| /** |
| * Returns the string as this chunk would be represented in a MessageFormat |
| * template, with any required quoting such that reparsing this value would |
| * produce an equivalent (note, not identical) parse. |
| * |
| * Note that the default implementation may not be sufficient for all |
| * subclasses. |
| */ |
| public String getAsMessageFormatString() { |
| return getStringValue(true); |
| } |
| |
| /** |
| * Returns the string as this chunk would be represented in a MessageFormat |
| * template, with any quoting removed. Note that this is distinct from |
| * toString in that the latter is intend for human consumption. |
| */ |
| public String getString() { |
| return getStringValue(false); |
| } |
| |
| public boolean isLiteral() { |
| return false; |
| } |
| |
| /** |
| * Returns the optionally quoted string value of this chunk as represented |
| * in a MessgeFormat string. |
| * |
| * @param quote true if the result should be quoted |
| * @return optionally quoted MessageFormat string |
| */ |
| protected abstract String getStringValue(boolean quote); |
| } |
| |
| /** |
| * Visitor for template chunks. |
| */ |
| public interface TemplateChunkVisitor { |
| void visit(ArgumentChunk argChunk) throws UnableToCompleteException; |
| |
| void visit(StaticArgChunk staticArgChunk) throws UnableToCompleteException; |
| |
| void visit(StringChunk stringChunk) throws UnableToCompleteException; |
| } |
| |
| /** |
| * Generate a MessageFormat-style string representing the supplied components, |
| * properly quoting any special characters in string literal portions. |
| * |
| * Note that additional quoting may be required depending on how it will be |
| * used, such as backslash-escaping double quotes if it will be used in a |
| * generated string constant. |
| * |
| * @param parts list of TemplateChunks to assemble |
| * @return assembled/quoted string |
| */ |
| public static String assemble(Iterable<TemplateChunk> parts) { |
| final StringBuilder buf = new StringBuilder(); |
| for (TemplateChunk part : parts) { |
| buf.append(part.getAsMessageFormatString()); |
| } |
| return buf.toString(); |
| } |
| |
| public static List<TemplateChunk> parse(String template) |
| throws ParseException { |
| int curPos = 0; |
| boolean inQuote = false; |
| int templateLen = template.length(); |
| ArrayList<TemplateChunk> chunks = new ArrayList<TemplateChunk>(); |
| TemplateChunk curChunk = null; |
| while (curPos < templateLen) { |
| char ch = template.charAt(curPos++); |
| switch (ch) { |
| case '\'': |
| if (curPos < templateLen && template.charAt(curPos) == '\'') { |
| curChunk = appendString(chunks, curChunk, "'"); |
| ++curPos; |
| break; |
| } |
| inQuote = !inQuote; |
| break; |
| |
| case '{': |
| if (inQuote) { |
| curChunk = appendString(chunks, curChunk, "{"); |
| break; |
| } |
| StringBuilder argBuf = new StringBuilder(); |
| boolean argQuote = false; |
| while (curPos < templateLen) { |
| ch = template.charAt(curPos++); |
| if (ch == '\'') { |
| if (curPos < templateLen && template.charAt(curPos) == '\'') { |
| argBuf.append(ch); |
| ++curPos; |
| } else { |
| argQuote = !argQuote; |
| } |
| } else { |
| if (!argQuote && ch == '}') { |
| break; |
| } |
| argBuf.append(ch); |
| } |
| } |
| if (ch != '}') { |
| throw new ParseException( |
| "Invalid message format - { not start of valid argument" |
| + template, curPos); |
| } |
| if (curChunk != null) { |
| chunks.add(curChunk); |
| } |
| String arg = argBuf.toString(); |
| int firstComma = arg.indexOf(','); |
| String format = null; |
| if (firstComma > 0) { |
| format = arg.substring(firstComma + 1); |
| arg = arg.substring(0, firstComma); |
| } |
| if (!"#".equals(arg) && !Character.isDigit(arg.charAt(0))) { |
| // static argument |
| chunks.add(new StaticArgChunk(arg, format)); |
| } else { |
| int argNumber = -1; |
| if (!"#".equals(arg)) { |
| argNumber = Integer.valueOf(arg); |
| } |
| Map<String, String> formatArgs = new HashMap<String, String>(); |
| Map<String, String> listArgs = null; |
| String subFormat = null; |
| if (format != null) { |
| int comma = format.indexOf(','); |
| if (comma >= 0) { |
| subFormat = format.substring(comma + 1); |
| format = format.substring(0, comma); |
| } |
| format = parseFormatArgs(format, formatArgs); |
| if ("list".equals(format)) { |
| listArgs = formatArgs; |
| formatArgs = new HashMap<String, String>(); |
| format = subFormat; |
| subFormat = null; |
| if (format != null) { |
| comma = format.indexOf(','); |
| if (comma >= 0) { |
| subFormat = format.substring(comma + 1); |
| format = format.substring(0, comma); |
| } |
| format = parseFormatArgs(format, formatArgs); |
| } |
| } |
| } |
| chunks.add(new ArgumentChunk( |
| argNumber, listArgs, format, formatArgs, subFormat)); |
| } |
| curChunk = null; |
| break; |
| |
| case '\n': |
| curChunk = appendString(chunks, curChunk, "\\n"); |
| break; |
| |
| case '\r': |
| curChunk = appendString(chunks, curChunk, "\\r"); |
| break; |
| |
| case '\\': |
| curChunk = appendString(chunks, curChunk, "\\\\"); |
| break; |
| |
| case '"': |
| curChunk = appendString(chunks, curChunk, "\\\""); |
| break; |
| |
| default: |
| curChunk = appendString(chunks, curChunk, String.valueOf(ch)); |
| break; |
| } |
| } |
| if (inQuote) { |
| throw new ParseException( |
| "Unterminated single quote: " + template, template.length()); |
| } |
| if (curChunk != null) { |
| chunks.add(curChunk); |
| } |
| return chunks; |
| } |
| |
| private static TemplateChunk appendString( |
| ArrayList<TemplateChunk> chunks, TemplateChunk curChunk, String string) { |
| if (curChunk != null && !curChunk.isLiteral()) { |
| chunks.add(curChunk); |
| curChunk = null; |
| } |
| if (curChunk == null) { |
| curChunk = new StringChunk(string); |
| } else { |
| ((StringChunk) curChunk).append(string); |
| } |
| return curChunk; |
| } |
| |
| /** |
| * Parse any arguments appended to a format. The syntax is: |
| * format[:tag[=value][:tag[=value]]... for example: "date:tz=EST:showoffset" |
| * |
| * @param format format value to parse |
| * @param formatArgs map to add tag/value pairs to |
| * @return format portion of supplied string |
| */ |
| private static String parseFormatArgs( |
| String format, Map<String, String> formatArgs) { |
| int colon = format.indexOf(':'); |
| if (colon >= 0) { |
| for (String tagValue : format.substring(colon + 1).split(":")) { |
| int equals = tagValue.indexOf('='); |
| String value = ""; |
| if (equals >= 0) { |
| value = tagValue.substring(equals + 1).trim(); |
| tagValue = tagValue.substring(0, equals); |
| } |
| formatArgs.put(tagValue.trim(), value); |
| } |
| format = format.substring(0, colon); |
| } |
| return format; |
| } |
| } |