blob: bd0ba3df7198621d2f2e144911421144c43e5e03 [file] [log] [blame]
/*
* 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 java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Helper class for parsing MessageFormat-style format strings.
*/
public class MessageFormatParser {
/**
* Represents an argument in a template string.
*/
public static class ArgumentChunk extends TemplateChunk {
private int argNumber;
private String format;
private String subFormat;
public ArgumentChunk(int argNumber, String format, String subformat) {
this.argNumber = argNumber;
this.format = format;
this.subFormat = subformat;
}
public int getArgumentNumber() {
return argNumber;
}
public String getFormat() {
return format;
}
@Override
public String getString() {
StringBuilder buf = new StringBuilder();
buf.append('{');
buf.append(argNumber);
if (format != null || subFormat != null) {
buf.append(',');
}
if (format != null) {
buf.append(format);
}
if (subFormat != null) {
buf.append(',');
buf.append(subFormat);
}
buf.append('}');
return buf.toString();
}
public String getSubFormat() {
return subFormat;
}
@Override
public String toString() {
return "Argument: #=" + argNumber + ", format=" + format + ", subformat="
+ subFormat;
}
}
/**
* 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);
}
public void append(String str) {
buf.append(str);
}
@Override
public String getString() {
return buf.toString();
}
@Override
public boolean isLiteral() {
return true;
}
@Override
public String toString() {
return "StringLiteral: \"" + buf.toString() + "\"";
}
}
/**
* Represents a parsed chunk of a template.
*/
public static class TemplateChunk {
/**
* @return the string as this chunk would be represented in a MessageFormat
* template. Note that this is distinct from toString in that the latter
* is intend for human consumption.
*/
public String getString() {
return toString();
}
public boolean isLiteral() {
return false;
}
}
/**
* Pattern to find MessageFormat argument references, including format and
* subformat pieces, if present.
*/
private static final Pattern argPattern = Pattern.compile(
"\\{(\\d+)(,(\\w+)(,([^\\}]+))?)?\\}");
/**
* 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) {
StringBuilder buf = new StringBuilder();
for (TemplateChunk part : parts) {
String str = part.getString();
if (part.isLiteral()) {
// quote MessageFormat special characters
str = str.replace("'", "''");
str = str.replace("{", "'{'").replace("}", "'}'");
}
buf.append(str);
}
return buf.toString();
}
public static List<TemplateChunk> parse(String template) throws ParseException {
Matcher match = argPattern.matcher(template);
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;
}
if (match.find(curPos - 1) && match.start() == curPos - 1) {
// match group 1 is the argument number (zero-based)
// match group 3 is the format name or null if none
// match group 5 is the subformat string or null if none
int argNumber = Integer.valueOf(match.group(1));
curPos = match.end();
String format = match.group(3);
String subformat = match.group(5);
if (curChunk != null) {
chunks.add(curChunk);
}
chunks.add(new ArgumentChunk(argNumber, format, subformat));
curChunk = null;
} else {
throw new ParseException(
"Invalid message format - { not start of valid argument"
+ template, curPos);
}
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;
}
}