| /* GENERATED SOURCE. DO NOT MODIFY. */ |
| // © 2022 and later: Unicode, Inc. and others. |
| // License & terms of use: https://www.unicode.org/copyright.html |
| |
| package android.icu.message2; |
| |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Matcher; |
| |
| import android.icu.message2.MFDataModel.Annotation; |
| import android.icu.message2.MFDataModel.Attribute; |
| import android.icu.message2.MFDataModel.CatchallKey; |
| import android.icu.message2.MFDataModel.Declaration; |
| import android.icu.message2.MFDataModel.Expression; |
| import android.icu.message2.MFDataModel.FunctionAnnotation; |
| import android.icu.message2.MFDataModel.FunctionExpression; |
| import android.icu.message2.MFDataModel.InputDeclaration; |
| import android.icu.message2.MFDataModel.Literal; |
| import android.icu.message2.MFDataModel.LiteralExpression; |
| import android.icu.message2.MFDataModel.LiteralOrCatchallKey; |
| import android.icu.message2.MFDataModel.LiteralOrVariableRef; |
| import android.icu.message2.MFDataModel.LocalDeclaration; |
| import android.icu.message2.MFDataModel.Markup; |
| import android.icu.message2.MFDataModel.Option; |
| import android.icu.message2.MFDataModel.Pattern; |
| import android.icu.message2.MFDataModel.PatternMessage; |
| import android.icu.message2.MFDataModel.PatternPart; |
| import android.icu.message2.MFDataModel.SelectMessage; |
| import android.icu.message2.MFDataModel.StringPart; |
| import android.icu.message2.MFDataModel.UnsupportedAnnotation; |
| import android.icu.message2.MFDataModel.UnsupportedExpression; |
| import android.icu.message2.MFDataModel.UnsupportedStatement; |
| import android.icu.message2.MFDataModel.VariableExpression; |
| import android.icu.message2.MFDataModel.VariableRef; |
| import android.icu.message2.MFDataModel.Variant; |
| |
| /** |
| * This class serializes a MessageFormat 2 data model {@link MFDataModel.Message} to a string, |
| * with the proper MessageFormat 2 syntax. |
| * |
| * @deprecated This API is for technology preview only. |
| * @hide Only a subset of ICU is exposed in Android |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| @Deprecated |
| public class MFSerializer { |
| private boolean shouldDoubleQuotePattern = false; |
| private boolean needSpace = false; |
| private final StringBuilder result = new StringBuilder(); |
| |
| /** |
| * Method converting the {@link MFDataModel.Message} to a string in MessageFormat 2 syntax. |
| * |
| * <p>The result is not necessarily identical with the original string parsed to generate |
| * the data model. But is is functionally equivalent.</p> |
| * |
| * @param message the data model message to serialize |
| * @return the serialized message, in MessageFormat 2 syntax |
| * |
| * @deprecated This API is for technology preview only. |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| @Deprecated |
| public static String dataModelToString(MFDataModel.Message message) { |
| return new MFSerializer().messageToString(message); |
| } |
| |
| private String messageToString(MFDataModel.Message message) { |
| if (message instanceof PatternMessage) { |
| patternMessageToString((PatternMessage) message); |
| } else if (message instanceof SelectMessage) { |
| selectMessageToString((SelectMessage) message); |
| } else { |
| errorType("Message", message); |
| } |
| return result.toString(); |
| } |
| |
| private void selectMessageToString(SelectMessage message) { |
| declarationsToString(message.declarations); |
| shouldDoubleQuotePattern = true; |
| addSpaceIfNeeded(); |
| result.append(".match"); |
| for (Expression selector : message.selectors) { |
| result.append(' '); |
| expressionToString(selector); |
| } |
| for (Variant variant : message.variants) { |
| variantToString(variant); |
| } |
| } |
| |
| private void patternMessageToString(PatternMessage message) { |
| declarationsToString(message.declarations); |
| patternToString(message.pattern); |
| } |
| |
| private void patternToString(Pattern pattern) { |
| addSpaceIfNeeded(); |
| if (shouldDoubleQuotePattern) { |
| result.append("{{"); |
| } |
| for (PatternPart part : pattern.parts) { |
| if (part instanceof StringPart) { |
| stringPartToString((StringPart) part); |
| } else { |
| expressionToString((Expression) part); |
| } |
| } |
| if (shouldDoubleQuotePattern) { |
| result.append("}}"); |
| } |
| } |
| |
| private void expressionToString(Expression expression) { |
| if (expression == null) { |
| return; |
| } |
| if (expression instanceof LiteralExpression) { |
| literalExpressionToString((LiteralExpression) expression); |
| } else if (expression instanceof VariableExpression) { |
| variableExpressionToString((VariableExpression) expression); |
| } else if (expression instanceof FunctionExpression) { |
| functionExpressionToString((FunctionExpression) expression); |
| } else if (expression instanceof Markup) { |
| markupToString((Markup) expression); |
| } else if (expression instanceof UnsupportedExpression) { |
| unsupportedExpressionToString((UnsupportedExpression) expression); |
| } else { |
| errorType("Expression", expression); |
| } |
| } |
| |
| private void unsupportedExpressionToString(UnsupportedExpression ue) { |
| result.append('{'); |
| annotationToString(ue.annotation); |
| attributesToString(ue.attributes); |
| result.append('}'); |
| } |
| |
| private void markupToString(Markup markup) { |
| result.append('{'); |
| if (markup.kind == Markup.Kind.CLOSE) { |
| result.append('/'); |
| } else { |
| result.append('#'); |
| } |
| result.append(markup.name); |
| optionsToString(markup.options); |
| attributesToString(markup.attributes); |
| if (markup.kind == Markup.Kind.STANDALONE) { |
| result.append('/'); |
| } |
| result.append('}'); |
| } |
| |
| private void optionsToString(Map<String, Option> options) { |
| for (Option option : options.values()) { |
| result.append(' '); |
| result.append(option.name); |
| result.append('='); |
| literalOrVariableRefToString(option.value); |
| } |
| } |
| |
| private void functionExpressionToString(FunctionExpression fe) { |
| result.append('{'); |
| annotationToString(fe.annotation); |
| attributesToString(fe.attributes); |
| result.append('}'); |
| } |
| |
| private void attributesToString(List<Attribute> attributes) { |
| if (attributes == null) { |
| return; |
| } |
| for (Attribute attribute : attributes) { |
| result.append(" @"); |
| result.append(attribute.name); |
| // Attributes can be with without a value (for now?) |
| if (attribute.value != null) { |
| result.append('='); |
| literalOrVariableRefToString(attribute.value); |
| } |
| } |
| } |
| |
| private void annotationToString(Annotation annotation) { |
| if (annotation == null) { |
| return; |
| } |
| if (annotation instanceof FunctionAnnotation) { |
| addSpaceIfNeeded(); |
| result.append(":"); |
| result.append(((FunctionAnnotation) annotation).name); |
| optionsToString(((FunctionAnnotation) annotation).options); |
| } else if (annotation instanceof UnsupportedAnnotation) { |
| addSpaceIfNeeded(); |
| String value = ((UnsupportedAnnotation) annotation).source; |
| for (int i = 0; i < value.length(); i++) { |
| char c = value.charAt(i); |
| if (c == '\\' || c == '{' || c == '}') { |
| result.append('\\'); |
| } |
| result.append(c); |
| } |
| } else { |
| errorType("Annotation", annotation); |
| } |
| } |
| |
| private void variableExpressionToString(VariableExpression ve) { |
| if (ve == null) { |
| return; |
| } |
| result.append('{'); |
| literalOrVariableRefToString(ve.arg); |
| needSpace = true; |
| annotationToString(ve.annotation); |
| attributesToString(ve.attributes); |
| result.append('}'); |
| needSpace = false; |
| } |
| |
| private void literalOrVariableRefToString(LiteralOrVariableRef literalOrVarRef) { |
| if (literalOrVarRef instanceof Literal) { |
| literalToString((Literal) literalOrVarRef); |
| } else if (literalOrVarRef instanceof VariableRef) { |
| result.append("$" + ((VariableRef) literalOrVarRef).name); |
| } else { |
| errorType("LiteralOrVariableRef", literalOrVarRef); |
| } |
| } |
| |
| // abnf: number-literal = ["-"] (%x30 / (%x31-39 *DIGIT)) ["." 1*DIGIT] |
| // [%i"e" ["-" / "+"] 1*DIGIT] |
| // Not identical to the one in the parser. This one has a $ at the end, to |
| // match the whole string |
| // TBD if it can be refactored to reuse. |
| private static final java.util.regex.Pattern RE_NUMBER_LITERAL = |
| java.util.regex.Pattern.compile("^-?(0|[1-9][0-9]*)(\\.[0-9]+)?([eE][+\\-]?[0-9]+)?$"); |
| |
| private void literalToString(Literal literal) { |
| String value = literal.value; |
| Matcher matcher = RE_NUMBER_LITERAL.matcher(value); |
| if (matcher.find()) { // It is a number, output as is |
| result.append(value); |
| } else { |
| StringBuilder literalBuffer = new StringBuilder(); |
| boolean wasName = true; |
| for (int i = 0; i < value.length(); i++) { |
| char c = value.charAt(i); |
| if (c == '\\' || c == '|') { |
| literalBuffer.append('\\'); |
| } |
| literalBuffer.append(c); |
| if (i == 0 && !StringUtils.isNameStart(c)) { |
| wasName = false; |
| } else if (!StringUtils.isNameChar(c)) { |
| wasName = false; |
| } |
| } |
| if (wasName && literalBuffer.length() != 0) { |
| result.append(literalBuffer); |
| } else { |
| result.append('|'); |
| result.append(literalBuffer); |
| result.append('|'); |
| } |
| } |
| } |
| |
| private void literalExpressionToString(LiteralExpression le) { |
| result.append('{'); |
| literalOrVariableRefToString(le.arg); |
| needSpace = true; |
| annotationToString(le.annotation); |
| attributesToString(le.attributes); |
| result.append('}'); |
| } |
| |
| private void stringPartToString(StringPart part) { |
| if (part.value.startsWith(".")) { |
| if (!shouldDoubleQuotePattern) { |
| shouldDoubleQuotePattern = true; |
| result.append("{{"); |
| } |
| } |
| for (int i = 0; i < part.value.length(); i++) { |
| char c = part.value.charAt(i); |
| if (c == '\\' || c == '{' || c == '}') { |
| result.append('\\'); |
| } |
| result.append(c); |
| } |
| } |
| |
| private void declarationsToString(List<Declaration> declarations) { |
| if (declarations == null || declarations.isEmpty()) { |
| return; |
| } |
| shouldDoubleQuotePattern = true; |
| for (Declaration declaration : declarations) { |
| if (declaration instanceof LocalDeclaration) { |
| localDeclarationToString((LocalDeclaration) declaration); |
| } else if (declaration instanceof InputDeclaration) { |
| inputDeclarationToString((InputDeclaration) declaration); |
| } else if (declaration instanceof UnsupportedStatement) { |
| unsupportedStatementToString((UnsupportedStatement) declaration); |
| } else { |
| errorType("Declaration", declaration); |
| } |
| } |
| } |
| |
| private void unsupportedStatementToString(UnsupportedStatement declaration) { |
| addSpaceIfNeeded(); |
| result.append('.'); |
| result.append(declaration.keyword); |
| if (!declaration.body.isEmpty()) { |
| result.append(' '); |
| } |
| result.append('|'); |
| result.append(declaration.body); |
| result.append('|'); |
| needSpace = true; |
| for (Expression expression : declaration.expressions) { |
| addSpaceIfNeeded(); |
| expressionToString(expression); |
| needSpace = true; |
| } |
| } |
| |
| private void inputDeclarationToString(InputDeclaration declaration) { |
| addSpaceIfNeeded(); |
| result.append(".input "); |
| variableExpressionToString(declaration.value); |
| needSpace = true; |
| } |
| |
| private void localDeclarationToString(LocalDeclaration declaration) { |
| addSpaceIfNeeded(); |
| result.append(".local $"); |
| result.append(declaration.name); |
| result.append(" = "); |
| expressionToString(declaration.value); |
| needSpace = true; |
| } |
| |
| private void variantToString(Variant variant) { |
| for (LiteralOrCatchallKey key : variant.keys) { |
| result.append(' '); |
| if (key instanceof CatchallKey) { |
| result.append('*'); |
| } else { |
| literalToString(((Literal) key)); |
| } |
| } |
| result.append(' '); |
| patternToString(variant.value); |
| } |
| |
| private void addSpaceIfNeeded() { |
| if (needSpace) { |
| result.append(' '); |
| needSpace = false; |
| } |
| } |
| |
| private void errorType(String expectedType, Object obj) { |
| error("Unexpected '" + expectedType + "' type: ", obj); |
| } |
| |
| private void error(String text, Object obj) { |
| error(text + obj.getClass().getName()); |
| } |
| |
| private void error(String text) { |
| throw new RuntimeException(text); |
| } |
| } |