Return-Path: X-Original-To: apmail-freemarker-notifications-archive@minotaur.apache.org Delivered-To: apmail-freemarker-notifications-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 2382E185EC for ; Thu, 24 Sep 2015 20:24:10 +0000 (UTC) Received: (qmail 84577 invoked by uid 500); 24 Sep 2015 20:24:10 -0000 Delivered-To: apmail-freemarker-notifications-archive@freemarker.apache.org Received: (qmail 84561 invoked by uid 500); 24 Sep 2015 20:24:10 -0000 Mailing-List: contact notifications-help@freemarker.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@freemarker.incubator.apache.org Delivered-To: mailing list notifications@freemarker.incubator.apache.org Received: (qmail 84551 invoked by uid 99); 24 Sep 2015 20:24:09 -0000 Received: from Unknown (HELO spamd4-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 24 Sep 2015 20:24:09 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd4-us-west.apache.org (ASF Mail Server at spamd4-us-west.apache.org) with ESMTP id 66BE6C0E34 for ; Thu, 24 Sep 2015 20:24:09 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd4-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: 1.794 X-Spam-Level: * X-Spam-Status: No, score=1.794 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, RP_MATCHES_RCVD=-0.006] autolearn=disabled Received: from mx1-us-west.apache.org ([10.40.0.8]) by localhost (spamd4-us-west.apache.org [10.40.0.11]) (amavisd-new, port 10024) with ESMTP id mB83grliQTni for ; Thu, 24 Sep 2015 20:23:54 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-us-west.apache.org (ASF Mail Server at mx1-us-west.apache.org) with SMTP id AF31623247 for ; Thu, 24 Sep 2015 20:23:48 +0000 (UTC) Received: (qmail 82144 invoked by uid 99); 24 Sep 2015 20:23:48 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 24 Sep 2015 20:23:48 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 62BEFE096D; Thu, 24 Sep 2015 20:23:48 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: ddekany@apache.org To: notifications@freemarker.incubator.apache.org Date: Thu, 24 Sep 2015 20:23:55 -0000 Message-Id: In-Reply-To: <092352dbed7d4a918b30e33dc95dfc70@git.apache.org> References: <092352dbed7d4a918b30e33dc95dfc70@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [08/12] incubator-freemarker git commit: Formatting to markup with ${numOrDate} and `something + numOrDate`. Incomplete: (a) String built-ins should fail instead of formatting to plain text; (a) More testing needed, like for TemplateDateFormatter-s Formatting to markup with ${numOrDate} and `something + numOrDate`. Incomplete: (a) String built-ins should fail instead of formatting to plain text; (a) More testing needed, like for TemplateDateFormatter-s Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/b6bd3c2c Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/b6bd3c2c Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/b6bd3c2c Branch: refs/heads/master Commit: b6bd3c2cc0abb871f0a59c1e7115f76e45cfeb53 Parents: dde63a1 Author: ddekany Authored: Sun Sep 20 17:51:22 2015 +0200 Committer: ddekany Committed: Wed Sep 23 08:29:25 2015 +0200 ---------------------------------------------------------------------- .../freemarker/core/AddConcatExpression.java | 56 ++++---- src/main/java/freemarker/core/Assignment.java | 9 +- .../core/BuiltInForLegacyEscaping.java | 11 +- .../core/BuiltInsForMultipleTypes.java | 17 ++- .../core/BuiltInsForOutputFormatRelated.java | 21 ++- .../core/BuiltInsForStringsEncoding.java | 25 ++++ .../java/freemarker/core/DollarVariable.java | 33 +++-- src/main/java/freemarker/core/Environment.java | 52 +++---- src/main/java/freemarker/core/EvalUtil.java | 107 ++++++++++++--- src/main/java/freemarker/core/Expression.java | 4 - .../core/JavaTemplateNumberFormat.java | 2 +- src/main/java/freemarker/core/MessageUtil.java | 27 +++- .../java/freemarker/core/NumberLiteral.java | 2 +- .../freemarker/core/TemplateDateFormat.java | 4 +- .../freemarker/core/TemplateFormatUtil.java | 13 +- .../freemarker/core/TemplateNumberFormat.java | 12 +- src/main/javacc/FTL.jj | 15 ++- .../core/BaseNTemplateNumberFormatFactory.java | 2 +- .../core/HexTemplateNumberFormatFactory.java | 2 +- ...aleSensitiveTemplateNumberFormatFactory.java | 2 +- .../java/freemarker/core/NumberFormatTest.java | 31 ++++- .../PrintfGTemplateNumberFormatFactory.java | 135 +++++++++++++++++++ 22 files changed, 440 insertions(+), 142 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/AddConcatExpression.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/AddConcatExpression.java b/src/main/java/freemarker/core/AddConcatExpression.java index 3c12da9..c90cb19 100644 --- a/src/main/java/freemarker/core/AddConcatExpression.java +++ b/src/main/java/freemarker/core/AddConcatExpression.java @@ -45,15 +45,17 @@ final class AddConcatExpression extends Expression { private final Expression left; private final Expression right; + private final MarkupOutputFormat markupOutputFormat; - AddConcatExpression(Expression left, Expression right) { + AddConcatExpression(Expression left, Expression right, MarkupOutputFormat markupOutputFormat) { this.left = left; this.right = right; + this.markupOutputFormat = markupOutputFormat; } @Override TemplateModel _eval(Environment env) throws TemplateException { - return _eval(env, this, left, left.eval(env), right, right.eval(env)); + return _eval(env, this, left, left.eval(env), right, right.eval(env), markupOutputFormat); } /** @@ -65,7 +67,8 @@ final class AddConcatExpression extends Expression { static TemplateModel _eval(Environment env, TemplateObject parent, Expression leftExp, TemplateModel leftModel, - Expression rightExp, TemplateModel rightModel) + Expression rightExp, TemplateModel rightModel, + MarkupOutputFormat markupOutputFormat) throws TemplateModelException, TemplateException, NonStringException { if (leftModel instanceof TemplateNumberModel && rightModel instanceof TemplateNumberModel) { Number first = EvalUtil.modelToNumber((TemplateNumberModel) leftModel, leftExp); @@ -75,27 +78,33 @@ final class AddConcatExpression extends Expression { return new ConcatenatedSequence((TemplateSequenceModel) leftModel, (TemplateSequenceModel) rightModel); } else { try { - String leftStr = EvalUtil.coerceModelToString(leftModel, leftExp, (String) null, true, env); - String rightStr = EvalUtil.coerceModelToString(rightModel, rightExp, (String) null, true, env); - - if (leftStr == null) { // Signals that the model is markup output - if (leftModel instanceof TemplateMarkupOutputModel) { - TemplateMarkupOutputModel leftMO = (TemplateMarkupOutputModel) leftModel; - if (rightStr == null) { // Signals that the model is markup output - return concatMarkupOutputs(parent, leftMO, (TemplateMarkupOutputModel) rightModel); - } - return concatMarkupOutputs(parent, leftMO, leftMO.getOutputFormat().fromPlainTextByEscaping(rightStr)); - } else { - leftStr = "null"; // For B.C. only; should be an error - // Falls through + Object leftOMOrStr = EvalUtil.coerceModelToMarkupOutputOrString( + leftModel, leftExp, (String) null, markupOutputFormat, env); + Object rightOMOrStr = EvalUtil.coerceModelToMarkupOutputOrString( + rightModel, rightExp, (String) null, markupOutputFormat, env); + // TODO prove that neither can be null + + if (leftOMOrStr instanceof String) { + if (rightOMOrStr instanceof String) { + return new SimpleScalar(((String) leftOMOrStr).concat((String) rightOMOrStr)); + } else { // rightOMOrStr instanceof TemplateMarkupOutputModel + TemplateMarkupOutputModel rightMO = (TemplateMarkupOutputModel) rightOMOrStr; + return concatMarkupOutputs(parent, + rightMO.getOutputFormat().fromPlainTextByEscaping((String) leftOMOrStr), + rightMO); + } + } else { // leftOMOrStr instanceof TemplateMarkupOutputModel + TemplateMarkupOutputModel leftMO = (TemplateMarkupOutputModel) leftOMOrStr; + if (rightOMOrStr instanceof String) { // markup output + return concatMarkupOutputs(parent, + leftMO, + leftMO.getOutputFormat().fromPlainTextByEscaping((String) rightOMOrStr)); + } else { // rightOMOrStr instanceof TemplateMarkupOutputModel + return concatMarkupOutputs(parent, + leftMO, + (TemplateMarkupOutputModel) rightOMOrStr); } } - if (rightStr == null) { // Signals that the model is markup output - TemplateMarkupOutputModel rightMO = (TemplateMarkupOutputModel) rightModel; - return concatMarkupOutputs(parent, rightMO.getOutputFormat().fromPlainTextByEscaping(leftStr), rightMO); - } - - return new SimpleScalar(leftStr.concat(rightStr)); } catch (NonStringOrTemplateOutputException e) { if (leftModel instanceof TemplateHashModel && rightModel instanceof TemplateHashModel) { if (leftModel instanceof TemplateHashModelEx && rightModel instanceof TemplateHashModelEx) { @@ -161,7 +170,8 @@ final class AddConcatExpression extends Expression { String replacedIdentifier, Expression replacement, ReplacemenetState replacementState) { return new AddConcatExpression( left.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState), - right.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState)); + right.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState), + markupOutputFormat); } @Override http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/Assignment.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/Assignment.java b/src/main/java/freemarker/core/Assignment.java index 0521250..8f5206b 100644 --- a/src/main/java/freemarker/core/Assignment.java +++ b/src/main/java/freemarker/core/Assignment.java @@ -41,6 +41,7 @@ final class Assignment extends TemplateElement { private final String variableName; private final int operatorType; private final Expression valueExp; + private final MarkupOutputFormat markupOutputFormat; private Expression namespaceExp; static final int NAMESPACE = 1; @@ -57,7 +58,8 @@ final class Assignment extends TemplateElement { Assignment(String variableName, int operator, Expression valueExp, - int scope) { + int scope, + MarkupOutputFormat markupOutputFormat) { this.scope = scope; this.variableName = variableName; @@ -93,6 +95,8 @@ final class Assignment extends TemplateElement { } this.valueExp = valueExp; + + this.markupOutputFormat = markupOutputFormat; } void setNamespaceExp(Expression namespaceExp) { @@ -165,7 +169,8 @@ final class Assignment extends TemplateElement { throw InvalidReferenceException.getInstance(valueExp, env); } } - value = AddConcatExpression._eval(env, namespaceExp, null, lhoValue, valueExp, value); + value = AddConcatExpression._eval(env, + namespaceExp, null, lhoValue, valueExp, value, markupOutputFormat); } else { // Numerical operation Number lhoNumber; if (lhoValue instanceof TemplateNumberModel) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/BuiltInForLegacyEscaping.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/BuiltInForLegacyEscaping.java b/src/main/java/freemarker/core/BuiltInForLegacyEscaping.java index bd1c19e..2ccd491 100644 --- a/src/main/java/freemarker/core/BuiltInForLegacyEscaping.java +++ b/src/main/java/freemarker/core/BuiltInForLegacyEscaping.java @@ -31,17 +31,20 @@ abstract class BuiltInForLegacyEscaping extends BuiltInBannedWhenAutoEscaping { TemplateModel _eval(Environment env) throws TemplateException { TemplateModel tm = target.eval(env); - String targetString = EvalUtil.coerceModelToString(tm, target, null, true, env); - if (targetString == null) { - TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) tm; + Object moOrStr = EvalUtil.coerceModelToMarkupOutputOrString(tm, target, null, getMarkupOutputFormat(), env); + if (moOrStr instanceof String) { + return calculateResult((String) moOrStr, env); + } else { + TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) moOrStr; if (mo.getOutputFormat().isLegacyBuiltInBypassed(key)) { return mo; } throw new NonStringException(target, tm, env); } - return calculateResult(targetString, env); } abstract TemplateModel calculateResult(String s, Environment env) throws TemplateException; + abstract MarkupOutputFormat getMarkupOutputFormat(); + } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java index 6a6a86e..5a0ee73 100644 --- a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java +++ b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java @@ -560,7 +560,7 @@ class BuiltInsForMultipleTypes { private TemplateModel formatWith(String key) throws TemplateModelException { try { - return new SimpleScalar(env.formatDate(dateModel, key, target, stringBI.this, true)); + return new SimpleScalar(env.formatDateToString(dateModel, key, target, stringBI.this, true)); } catch (TemplateException e) { // `e` should always be a TemplateModelException here, but to be sure: throw _CoreAPI.ensureIsTemplateModelException("Failed to format value", e); @@ -580,7 +580,12 @@ class BuiltInsForMultipleTypes { } cachedValue = defaultFormat.format(dateModel); } catch (TemplateValueFormatException e) { - throw MessageUtil.newCantFormatDateException(target, e); + try { + throw MessageUtil.newCantFormatDateException(defaultFormat, target, e, true); + } catch (TemplateException e2) { + // `e` should always be a TemplateModelException here, but to be sure: + throw _CoreAPI.ensureIsTemplateModelException("Failed to format date/time/datetime", e2); + } } } return cachedValue; @@ -633,9 +638,9 @@ class BuiltInsForMultipleTypes { String result; try { if (format instanceof BackwardCompatibleTemplateNumberFormat) { - result = env.formatNumber(number, (BackwardCompatibleTemplateNumberFormat) format, target); + result = env.formatNumberToString(number, (BackwardCompatibleTemplateNumberFormat) format, target); } else { - result = env.formatNumber(numberModel, format, target, true); + result = env.formatNumberToString(numberModel, format, target, true); } } catch (TemplateException e) { // `e` should always be a TemplateModelException here, but to be sure: @@ -649,10 +654,10 @@ class BuiltInsForMultipleTypes { if (cachedValue == null) { try { if (defaultFormat instanceof BackwardCompatibleTemplateNumberFormat) { - cachedValue = env.formatNumber( + cachedValue = env.formatNumberToString( number, (BackwardCompatibleTemplateNumberFormat) defaultFormat, target); } else { - cachedValue = env.formatNumber(numberModel, defaultFormat, target, true); + cachedValue = env.formatNumberToString(numberModel, defaultFormat, target, true); } } catch (TemplateException e) { // `e` should always be a TemplateModelException here, but to be sure: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java b/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java index a9b5d8d..556d12b 100644 --- a/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java +++ b/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java @@ -48,24 +48,24 @@ class BuiltInsForOutputFormatRelated { @Override protected TemplateModel calculateResult(Environment env) throws TemplateException { TemplateModel lhoTM = target.eval(env); - String lhoStr = EvalUtil.coerceModelToString(lhoTM, target, null, true, env); MarkupOutputFormat contextOF = outputFormat; - if (lhoStr == null) { // should indicate that lhoTM is a TemplateMarkupOutputModel - TemplateMarkupOutputModel lhoMO; - try { - lhoMO = (TemplateMarkupOutputModel) lhoTM; - } catch (ClassCastException e) { + Object lhoMOOrStr = EvalUtil.coerceModelToMarkupOutputOrString(lhoTM, target, null, contextOF, env); + if (lhoMOOrStr instanceof String) { // TemplateMarkupOutputModel + return calculateResult((String) lhoMOOrStr, contextOF, env); + } else { + if (lhoMOOrStr == null) { throw EvalUtil.newModelHasStoredNullException(null, lhoTM, target); } + TemplateMarkupOutputModel lhoMO = (TemplateMarkupOutputModel) lhoMOOrStr; MarkupOutputFormat lhoOF = lhoMO.getOutputFormat(); // ATTENTION: Keep this logic in sync. with ${...}'s logic! if (lhoOF == contextOF || contextOF.isOutputFormatMixingAllowed()) { // bypass - return lhoTM; + return lhoMO; } else { // ATTENTION: Keep this logic in sync. with ${...}'s logic! - lhoStr = lhoOF.getSourcePlainText(lhoMO); - if (lhoStr == null) { + String lhoPlainTtext = lhoOF.getSourcePlainText(lhoMO); + if (lhoPlainTtext == null) { throw new _TemplateModelException(target, "The left side operand of ?", key, " is in ", new _DelayedToString(lhoOF), " format, which differs from the current output format, ", @@ -73,10 +73,9 @@ class BuiltInsForOutputFormatRelated { } // Here we know that lho is escaped plain text. So we re-escape it to the current format and // bypass it, just as if the two output formats were the same earlier. - return contextOF.fromPlainTextByEscaping(lhoStr); + return contextOF.fromPlainTextByEscaping(lhoPlainTtext); } } - return calculateResult(lhoStr, contextOF, env); } protected abstract TemplateModel calculateResult(String lho, MarkupOutputFormat outputFormat, Environment env) http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java b/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java index c47f9ec..f182430 100644 --- a/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java +++ b/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java @@ -39,6 +39,11 @@ class BuiltInsForStringsEncoding { TemplateModel calculateResult(String s, Environment env) { return new SimpleScalar(StringUtil.HTMLEnc(s)); } + + @Override + MarkupOutputFormat getMarkupOutputFormat() { + return HTMLOutputFormat.INSTANCE; + } } private final BIBeforeICI2d3d20 prevICIObj = new BIBeforeICI2d3d20(); @@ -55,6 +60,11 @@ class BuiltInsForStringsEncoding { public Object getPreviousICIChainMember() { return prevICIObj; } + + @Override + MarkupOutputFormat getMarkupOutputFormat() { + return HTMLOutputFormat.INSTANCE; + } } static class j_stringBI extends BuiltInForString { @@ -83,6 +93,11 @@ class BuiltInsForStringsEncoding { TemplateModel calculateResult(String s, Environment env) { return new SimpleScalar(StringUtil.RTFEnc(s)); } + + @Override + MarkupOutputFormat getMarkupOutputFormat() { + return RTFOutputFormat.INSTANCE; + } } static class urlBI extends BuiltInForString { @@ -134,6 +149,11 @@ class BuiltInsForStringsEncoding { TemplateModel calculateResult(String s, Environment env) { return new SimpleScalar(StringUtil.XHTMLEnc(s)); } + + @Override + MarkupOutputFormat getMarkupOutputFormat() { + return XMLOutputFormat.INSTANCE; // TODO XHTMLOutputFormat + } } static class xmlBI extends BuiltInForLegacyEscaping { @@ -141,6 +161,11 @@ class BuiltInsForStringsEncoding { TemplateModel calculateResult(String s, Environment env) { return new SimpleScalar(StringUtil.XMLEnc(s)); } + + @Override + MarkupOutputFormat getMarkupOutputFormat() { + return XMLOutputFormat.INSTANCE; + } } // Can't be instantiated http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/DollarVariable.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/DollarVariable.java b/src/main/java/freemarker/core/DollarVariable.java index a277a68..d08f37c 100644 --- a/src/main/java/freemarker/core/DollarVariable.java +++ b/src/main/java/freemarker/core/DollarVariable.java @@ -38,15 +38,18 @@ final class DollarVariable extends Interpolation { /** For OutputFormat-based auto-escaping */ private final OutputFormat outputFormat; - private final MarkupOutputFormat autoEscapeOutputFormat; + private final MarkupOutputFormat markupOutputFormat; + private final boolean autoEscape; DollarVariable( Expression expression, Expression escapedExpression, - OutputFormat outputFormat, MarkupOutputFormat autoEscapeOutputFormat) { + OutputFormat outputFormat, boolean autoEscape) { this.expression = expression; this.escapedExpression = escapedExpression; this.outputFormat = outputFormat; - this.autoEscapeOutputFormat = autoEscapeOutputFormat; + this.markupOutputFormat + = (MarkupOutputFormat) (outputFormat instanceof MarkupOutputFormat ? outputFormat : null); + this.autoEscape = autoEscape; } /** @@ -54,26 +57,28 @@ final class DollarVariable extends Interpolation { */ @Override void accept(Environment env) throws TemplateException, IOException { - TemplateModel tm = escapedExpression.eval(env); - Writer out = env.getOut(); - String s = EvalUtil.coerceModelToString(tm, escapedExpression, null, true, env); - if (s != null) { - if (autoEscapeOutputFormat != null) { - autoEscapeOutputFormat.output(s, out); + final TemplateModel tm = escapedExpression.eval(env); + final Writer out = env.getOut(); + final Object moOrStr = EvalUtil.coerceModelToMarkupOutputOrString( + tm, escapedExpression, null, markupOutputFormat, out, env); + if (moOrStr instanceof String) { + final String s = (String) moOrStr; + if (autoEscape) { + markupOutputFormat.output(s, out); } else { out.write(s); } - } else { - TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) tm; - MarkupOutputFormat moOF = mo.getOutputFormat(); + } else if (moOrStr != null) { // moOrStr wasn't output yet + final TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) moOrStr; + final MarkupOutputFormat moOF = mo.getOutputFormat(); // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic! if (moOF != outputFormat && !outputFormat.isOutputFormatMixingAllowed()) { - String srcPlainText; + final String srcPlainText; // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic! srcPlainText = moOF.getSourcePlainText(mo); if (srcPlainText == null) { throw new _TemplateModelException(escapedExpression, - "Tha value to print is in ", new _DelayedToString(moOF), + "The value to print is in ", new _DelayedToString(moOF), " format, which differs from the current output format, ", new _DelayedToString(outputFormat), ". Format conversion wasn't possible."); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/Environment.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java index 1e6aa73..5078eb7 100644 --- a/src/main/java/freemarker/core/Environment.java +++ b/src/main/java/freemarker/core/Environment.java @@ -1033,20 +1033,9 @@ public final class Environment extends Configurable { * @param exp * The blamed expression if an error occurs; it's only needed for better error messages */ - String formatNumber(TemplateNumberModel number, Expression exp, boolean useTempModelExc) throws TemplateException { - return formatNumber(number, getTemplateNumberFormat(exp, useTempModelExc), exp, useTempModelExc); - } - - /** - * Format number with the number format specified as the parameter, with the current locale. - * - * @param exp - * The blamed expression if an error occurs; it's only needed for better error messages - */ - String formatNumber( - TemplateNumberModel number, String formatString, Expression exp, - boolean useTempModelExc) throws TemplateException { - return formatNumber(number, getTemplateNumberFormat(formatString, exp, useTempModelExc), exp, useTempModelExc); + String formatNumberToString(TemplateNumberModel number, Expression exp, boolean useTempModelExc) + throws TemplateException { + return formatNumberToString(number, getTemplateNumberFormat(exp, useTempModelExc), exp, useTempModelExc); } /** @@ -1055,19 +1044,14 @@ public final class Environment extends Configurable { * @param exp * The blamed expression if an error occurs; it's only needed for better error messages */ - String formatNumber( + String formatNumberToString( TemplateNumberModel number, TemplateNumberFormat format, Expression exp, boolean useTempModelExc) throws TemplateException { try { return format.format(number); } catch (TemplateValueFormatException e) { - _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder( - "Failed to format number with format ", new _DelayedJQuote(format.getDescription()), ": ", - e.getMessage()) - .blame(exp); - throw useTempModelExc - ? new _TemplateModelException(e, this, desc) : new _MiscTemplateException(e, this, desc); + throw MessageUtil.newCantFormatNumberException(format, exp, e, useTempModelExc); } } @@ -1077,7 +1061,7 @@ public final class Environment extends Configurable { * @param exp * The blamed expression if an error occurs; it's only needed for better error messages */ - String formatNumber(Number number, BackwardCompatibleTemplateNumberFormat format, Expression exp) + String formatNumberToString(Number number, BackwardCompatibleTemplateNumberFormat format, Expression exp) throws TemplateModelException, _MiscTemplateException { try { return format.format(number); @@ -1335,18 +1319,14 @@ public final class Environment extends Configurable { * @param tdmSourceExpr * The blamed expression if an error occurs; only used for error messages. */ - String formatDate(TemplateDateModel tdm, Expression tdmSourceExpr, + String formatDateToString(TemplateDateModel tdm, Expression tdmSourceExpr, boolean useTempModelExc) throws TemplateException { - Date date = EvalUtil.modelToDate(tdm, tdmSourceExpr); - - TemplateDateFormat format = getTemplateDateFormat( - tdm.getDateType(), date.getClass(), tdmSourceExpr, - useTempModelExc); + TemplateDateFormat format = getTemplateDateFormat(tdm, tdmSourceExpr, useTempModelExc); try { return format.format(tdm); } catch (TemplateValueFormatException e) { - throw MessageUtil.newCantFormatDateException(tdmSourceExpr, e); + throw MessageUtil.newCantFormatDateException(format, tdmSourceExpr, e, useTempModelExc); } } @@ -1356,7 +1336,7 @@ public final class Environment extends Configurable { * @param blamedFormatterExp * The blamed expression if an error occurs; only used for error messages. */ - String formatDate(TemplateDateModel tdm, String formatString, + String formatDateToString(TemplateDateModel tdm, String formatString, Expression blamedDateSourceExp, Expression blamedFormatterExp, boolean useTempModelExc) throws TemplateException { Date date = EvalUtil.modelToDate(tdm, blamedDateSourceExp); @@ -1369,7 +1349,7 @@ public final class Environment extends Configurable { try { return format.format(tdm); } catch (TemplateValueFormatException e) { - throw MessageUtil.newCantFormatDateException(blamedDateSourceExp, e); + throw MessageUtil.newCantFormatDateException(format, blamedDateSourceExp, e, useTempModelExc); } } @@ -1537,6 +1517,16 @@ public final class Environment extends Configurable { return getTemplateDateFormatWithoutCache(formatString, dateType, locale, timeZone, zonelessInput); } + TemplateDateFormat getTemplateDateFormat(TemplateDateModel tdm, Expression tdmSourceExpr, boolean useTempModelExc) + throws TemplateModelException, TemplateException { + Date date = EvalUtil.modelToDate(tdm, tdmSourceExpr); + + TemplateDateFormat format = getTemplateDateFormat( + tdm.getDateType(), date.getClass(), tdmSourceExpr, + useTempModelExc); + return format; + } + /** * Same as {@link #getTemplateDateFormat(int, Class)}, but translates the exceptions to {@link TemplateException}-s. */ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/EvalUtil.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/EvalUtil.java b/src/main/java/freemarker/core/EvalUtil.java index ff41523..517a29e 100644 --- a/src/main/java/freemarker/core/EvalUtil.java +++ b/src/main/java/freemarker/core/EvalUtil.java @@ -19,6 +19,8 @@ package freemarker.core; +import java.io.IOException; +import java.io.Writer; import java.util.Date; import freemarker.ext.beans.BeanModel; @@ -44,7 +46,7 @@ class EvalUtil { static final int CMP_OP_LESS_THAN_EQUALS = 5; static final int CMP_OP_GREATER_THAN_EQUALS = 6; // If you add a new operator here, update the "compare" and "cmpOpToString" methods! - + // Prevents instantination. private EvalUtil() { } @@ -341,24 +343,93 @@ class EvalUtil { } } - static String coerceModelToString(TemplateModel tm, Expression exp, String seqHint, Environment env) throws TemplateException { - return coerceModelToString(tm, exp, seqHint, false, env); - } - - /** - * @param allowTOM - * Instead of throwing exception, return {@code null} for a {@link TemplateMarkupOutputModel}. - */ static String coerceModelToString(TemplateModel tm, Expression exp, String seqHint, - boolean allowTOM, Environment env) throws TemplateException { if (tm instanceof TemplateNumberModel) { - return env.formatNumber((TemplateNumberModel) tm, exp, false); + return env.formatNumberToString((TemplateNumberModel) tm, exp, false); + } else if (tm instanceof TemplateDateModel) { + return env.formatDateToString((TemplateDateModel) tm, exp, false); + } else { + return coerceModelToStringCommon(tm, exp, seqHint, false, env); + } + } + + static Object coerceModelToMarkupOutputOrString(TemplateModel tm, Expression exp, String seqHint, + MarkupOutputFormat markupOutputFormat, Environment env) throws TemplateException { + try { + return coerceModelToMarkupOutputOrString(tm, exp, seqHint, markupOutputFormat, null, env); + } catch (IOException e) { + throw new BugException("Unexpected exception", e); + } + } + + static Object coerceModelToMarkupOutputOrString(TemplateModel tm, Expression exp, String seqHint, + MarkupOutputFormat markupOutputFormat, Writer out, Environment env) throws TemplateException, IOException { + if (tm instanceof TemplateNumberModel) { + TemplateNumberModel tnm = (TemplateNumberModel) tm; + TemplateNumberFormat format = env.getTemplateNumberFormat(exp, false); + try { + if (markupOutputFormat != null) { + // Try to return markup output: + if (out == null) { + TemplateMarkupOutputModel r = format.format(tnm, markupOutputFormat); + if (r != null) { + return r; + } + // Falls through + } else { + if (format.format(tnm, markupOutputFormat, out)) { + return null; + } + // Falls through + } + } + + // Return a String: + return format.format(tnm); + } catch (TemplateValueFormatException e) { + throw MessageUtil.newCantFormatNumberException(format, exp, e, false); + } } else if (tm instanceof TemplateDateModel) { - return env.formatDate((TemplateDateModel) tm, exp, false); - } else if (allowTOM && tm instanceof TemplateMarkupOutputModel) { - return null; - } else if (tm instanceof TemplateScalarModel) { + TemplateDateModel tdm = (TemplateDateModel) tm; + TemplateDateFormat format = env.getTemplateDateFormat(tdm, exp, false); + try { + if (markupOutputFormat != null) { + // Try to return markup output: + if (out == null) { + TemplateMarkupOutputModel r = format.format(tdm, markupOutputFormat); + if (r != null) { + return r; + } + // Falls through + } else { + if (format.format(tdm, markupOutputFormat, out)) { + return null; + } + // Falls through + } + } + + // Return a String: + return format.format(tdm); + } catch (TemplateValueFormatException e) { + throw MessageUtil.newCantFormatDateException(format, exp, e, false); + } + } else if (tm instanceof TemplateMarkupOutputModel) { + return tm; + } else { + return coerceModelToStringCommon(tm, exp, seqHint, true, env); + } + } + + /** + * @param supportsTOM + * Whether the caller {@code coerceModelTo...} method could handle a {@link TemplateMarkupOutputModel}. + */ + private static String coerceModelToStringCommon(TemplateModel tm, Expression exp, String seqHint, boolean supportsTOM, + Environment env) throws TemplateModelException, InvalidReferenceException, TemplateException, + NonStringOrTemplateOutputException, NonStringException { + if (tm instanceof TemplateScalarModel) { return modelToString((TemplateScalarModel) tm, exp, env); } else if (tm == null) { if (env.isClassicCompatible()) { @@ -399,13 +470,13 @@ class EvalUtil { return _BeansAPI.getAsClassicCompatibleString((BeanModel) tm); } if (seqHint != null && (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)) { - if (allowTOM) { + if (supportsTOM) { throw new NonStringOrTemplateOutputException(exp, tm, seqHint, env); } else { throw new NonStringException(exp, tm, seqHint, env); } } else { - if (allowTOM) { + if (supportsTOM) { throw new NonStringOrTemplateOutputException(exp, tm, env); } else { throw new NonStringException(exp, tm, env); @@ -413,7 +484,7 @@ class EvalUtil { } } } - + /** * Returns an {@link ArithmeticEngine} even if {@code env} is {@code null}, because we are in parsing phase. */ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/Expression.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/Expression.java b/src/main/java/freemarker/core/Expression.java index fef08cd..c1d5355 100644 --- a/src/main/java/freemarker/core/Expression.java +++ b/src/main/java/freemarker/core/Expression.java @@ -93,10 +93,6 @@ abstract public class Expression extends TemplateObject { return EvalUtil.coerceModelToString(eval(env), this, seqTip, env); } - static String coerceModelToString(TemplateModel tm, Expression exp, Environment env) throws TemplateException { - return EvalUtil.coerceModelToString(tm, exp, null, env); - } - Number evalToNumber(Environment env) throws TemplateException { TemplateModel model = eval(env); return modelToNumber(model, env); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/JavaTemplateNumberFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/JavaTemplateNumberFormat.java b/src/main/java/freemarker/core/JavaTemplateNumberFormat.java index 4144af8..ccbfb9d 100644 --- a/src/main/java/freemarker/core/JavaTemplateNumberFormat.java +++ b/src/main/java/freemarker/core/JavaTemplateNumberFormat.java @@ -40,7 +40,7 @@ final class JavaTemplateNumberFormat extends BackwardCompatibleTemplateNumberFor } @Override - public MO format(TemplateNumberModel dateModel, + public MO format(TemplateNumberModel numberModel, MarkupOutputFormat outputFormat) throws UnformattableValueException, TemplateModelException { return null; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/MessageUtil.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/MessageUtil.java b/src/main/java/freemarker/core/MessageUtil.java index 2875aa3..3aebacc 100644 --- a/src/main/java/freemarker/core/MessageUtil.java +++ b/src/main/java/freemarker/core/MessageUtil.java @@ -296,13 +296,28 @@ class MessageUtil { .tips(MessageUtil.UNKNOWN_DATE_TO_STRING_TIPS)); } - static TemplateModelException newCantFormatDateException( - Expression dateSourceExpr, TemplateValueFormatException cause) { - return new _TemplateModelException(cause, null, new _ErrorDescriptionBuilder( - cause.getMessage()) - .blame(dateSourceExpr)); + static TemplateException newCantFormatDateException(TemplateDateFormat format, Expression dataSrcExp, + TemplateValueFormatException e, boolean useTempModelExc) { + _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder( + "Failed to format date/time/datetime with format ", new _DelayedJQuote(format.getDescription()), ": ", + e.getMessage()) + .blame(dataSrcExp); + return useTempModelExc + ? new _TemplateModelException(e, (Environment) null, desc) + : new _MiscTemplateException(e, (Environment) null, desc); } - + + static TemplateException newCantFormatNumberException(TemplateNumberFormat format, Expression dataSrcExp, + TemplateValueFormatException e, boolean useTempModelExc) { + _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder( + "Failed to format number with format ", new _DelayedJQuote(format.getDescription()), ": ", + e.getMessage()) + .blame(dataSrcExp); + return useTempModelExc + ? new _TemplateModelException(e, (Environment) null, desc) + : new _MiscTemplateException(e, (Environment) null, desc); + } + /** * @return "a" or "an" or "a(n)" (or "" for empty string) for an FTL type name */ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/NumberLiteral.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/NumberLiteral.java b/src/main/java/freemarker/core/NumberLiteral.java index f008aa9..91ded3c 100644 --- a/src/main/java/freemarker/core/NumberLiteral.java +++ b/src/main/java/freemarker/core/NumberLiteral.java @@ -43,7 +43,7 @@ final class NumberLiteral extends Expression implements TemplateNumberModel { @Override public String evalAndCoerceToString(Environment env) throws TemplateException { - return env.formatNumber(this, this, false); + return env.formatNumberToString(this, this, false); } public Number getAsNumber() { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/TemplateDateFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/TemplateDateFormat.java b/src/main/java/freemarker/core/TemplateDateFormat.java index 980d543..cb58aac 100644 --- a/src/main/java/freemarker/core/TemplateDateFormat.java +++ b/src/main/java/freemarker/core/TemplateDateFormat.java @@ -43,11 +43,11 @@ public abstract class TemplateDateFormat extends TemplateValueFormat { /** * @param dateModel - * The date/time/dateTime to format. Most implementations will just work with the return value of + * The date/time/dateTime to format; not {@code null}. Most implementations will just work with the return value of * {@link TemplateDateModel#getAsDate()}, but some may format differently depending on the properties of * a custom {@link TemplateDateModel} implementation. * - * @return The date/time/dateTime as text, with no escaping (like no HTML escaping). Can't be {@code null}. + * @return The date/time/dateTime as text, with no escaping (like no HTML escaping); can't be {@code null}. * * @throws TemplateValueFormatException * When a problem occurs during the formatting of the value. Notable subclass: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/TemplateFormatUtil.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/TemplateFormatUtil.java b/src/main/java/freemarker/core/TemplateFormatUtil.java index 66e10da..034d5cb 100644 --- a/src/main/java/freemarker/core/TemplateFormatUtil.java +++ b/src/main/java/freemarker/core/TemplateFormatUtil.java @@ -20,11 +20,14 @@ package freemarker.core; import java.util.Date; +import freemarker.template.ObjectWrapper; import freemarker.template.TemplateDateModel; import freemarker.template.TemplateModelException; import freemarker.template.TemplateNumberModel; /** + * Utility classes for implementing {@link TemplateValueFormat}-s. + * * @since 2.3.24 */ public final class TemplateFormatUtil { @@ -42,8 +45,10 @@ public final class TemplateFormatUtil { } /** - * Utility method to extract the {@link Number} from an {@link TemplateNumberModel}, and throw - * {@link UnformattableValueException} with a standard error message if that's {@code null}. + * Utility method to extract the {@link Number} from an {@link TemplateNumberModel}, and throws + * {@link TemplateModelException} with a standard error message if that's {@code null}. {@link TemplateNumberModel} + * that store {@code null} are in principle not allowed, and so are considered to be bugs in the + * {@link ObjectWrapper} or {@link TemplateNumberModel} implementation. */ public static Number getNonNullNumber(TemplateNumberModel numberModel) throws TemplateModelException, UnformattableValueException { @@ -56,7 +61,9 @@ public final class TemplateFormatUtil { /** * Utility method to extract the {@link Date} from an {@link TemplateDateModel}, and throw - * {@link UnformattableValueException} with a standard error message if that's {@code null}. + * {@link TemplateModelException} with a standard error message if that's {@code null}. {@link TemplateDateModel} + * that store {@code null} are in principle not allowed, and so are considered to be bugs in the + * {@link ObjectWrapper} or {@link TemplateNumberModel} implementation. */ public static Date getNonNullDate(TemplateDateModel dateModel) throws TemplateModelException { Date date = dateModel.getAsDate(); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/TemplateNumberFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/TemplateNumberFormat.java b/src/main/java/freemarker/core/TemplateNumberFormat.java index 68d2c4d..3c29738 100644 --- a/src/main/java/freemarker/core/TemplateNumberFormat.java +++ b/src/main/java/freemarker/core/TemplateNumberFormat.java @@ -42,11 +42,11 @@ public abstract class TemplateNumberFormat extends TemplateValueFormat { /** * @param numberModel - * The date/time/dateTime to format. Most implementations will just work with the return value of + * The number to format; not {@code null}. Most implementations will just work with the return value of * {@link TemplateDateModel#getAsDate()}, but some may format differently depending on the properties of * a custom {@link TemplateDateModel} implementation. - * - * @return The date/time/dateTime as text, with no escaping (like no HTML escaping). Can't be {@code null}. + * + * @return The number as text, with no escaping (like no HTML escaping); can't be {@code null}. * * @throws TemplateValueFormatException * If any problem occurs while parsing/getting the format. Notable subclass: @@ -64,7 +64,7 @@ public abstract class TemplateNumberFormat extends TemplateValueFormat { * {@link #format(TemplateNumberModel)} escaped, it should return {@code null}. */ public abstract MO format( - TemplateNumberModel dateModel, MarkupOutputFormat outputFormat) + TemplateNumberModel numberModel, MarkupOutputFormat outputFormat) throws TemplateValueFormatException, TemplateModelException; /** @@ -79,9 +79,9 @@ public abstract class TemplateNumberFormat extends TemplateValueFormat { * {@link #format(TemplateNumberModel, MarkupOutputFormat)} and writes its result to the {@link Writer}. */ public boolean format( - TemplateNumberModel dateModel, MarkupOutputFormat outputFormat, Writer out) + TemplateNumberModel numberModel, MarkupOutputFormat outputFormat, Writer out) throws TemplateValueFormatException, TemplateModelException, IOException { - MO mo = format(dateModel, outputFormat); + MO mo = format(numberModel, outputFormat); if (mo == null) { return false; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/javacc/FTL.jj ---------------------------------------------------------------------- diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj index c36bad7..33c8ecb 100644 --- a/src/main/javacc/FTL.jj +++ b/src/main/javacc/FTL.jj @@ -326,6 +326,10 @@ public class FMParser { autoEscaping = false; } } + + MarkupOutputFormat getMarkupOutputFormat() { + return outputFormat instanceof MarkupOutputFormat ? (MarkupOutputFormat) outputFormat : null; + } /** * Don't use it, unless you are developing FreeMarker itself. @@ -1661,7 +1665,7 @@ Expression AdditiveExpression() : if (plus) { // plus is treated separately, since it is also // used for concatenation. - result = new AddConcatExpression(lhs, rhs); + result = new AddConcatExpression(lhs, rhs, getMarkupOutputFormat()); } else { numberLiteralOnly(lhs); numberLiteralOnly(rhs); @@ -2322,8 +2326,7 @@ DollarVariable StringOutput() : DollarVariable result = new DollarVariable( exp, escapedExpression(exp), outputFormat, - autoEscaping && outputFormat instanceof MarkupOutputFormat - ? (MarkupOutputFormat) outputFormat : null); + autoEscaping); result.setLocation(template, begin, end); return result; } @@ -2930,7 +2933,7 @@ TemplateElement Assign() : ) ) { - ass = new Assignment(varName, equalsOp.kind, exp, scope); + ass = new Assignment(varName, equalsOp.kind, exp, scope, getMarkupOutputFormat()); if (exp != null) { ass.setLocation(template, nameExp, exp); } else { @@ -2970,7 +2973,7 @@ TemplateElement Assign() : ) ) { - ass = new Assignment(varName, equalsOp.kind, exp, scope); + ass = new Assignment(varName, equalsOp.kind, exp, scope, getMarkupOutputFormat()); if (exp != null) { ass.setLocation(template, nameExp, exp); } else { @@ -3043,7 +3046,7 @@ TemplateElement Assign() : { BlockAssignment ba = new BlockAssignment( block, varName, scope, nsExp, - outputFormat instanceof MarkupOutputFormat ? (MarkupOutputFormat) outputFormat : null); + getMarkupOutputFormat()); ba.setLocation(template, start, end); return ba; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java index d527aa0..18a06ea 100644 --- a/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java +++ b/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java @@ -96,7 +96,7 @@ public class BaseNTemplateNumberFormatFactory extends TemplateNumberFormatFactor } @Override - public MO format(TemplateNumberModel dateModel, + public MO format(TemplateNumberModel numberModel, MarkupOutputFormat outputFormat) throws UnformattableValueException, TemplateModelException { return null; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java index f18d97b..8687b58 100644 --- a/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java +++ b/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java @@ -57,7 +57,7 @@ public class HexTemplateNumberFormatFactory extends TemplateNumberFormatFactory } @Override - public MO format(TemplateNumberModel dateModel, + public MO format(TemplateNumberModel numberModel, MarkupOutputFormat outputFormat) throws UnformattableValueException, TemplateModelException { return null; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java index 3fb2fae..fb989b9 100644 --- a/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java +++ b/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java @@ -58,7 +58,7 @@ public class LocaleSensitiveTemplateNumberFormatFactory extends TemplateNumberFo } @Override - public MO format(TemplateNumberModel dateModel, + public MO format(TemplateNumberModel numberModel, MarkupOutputFormat outputFormat) throws UnformattableValueException, TemplateModelException { return null; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/test/java/freemarker/core/NumberFormatTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/core/NumberFormatTest.java b/src/test/java/freemarker/core/NumberFormatTest.java index b30b72a..8db57dc 100644 --- a/src/test/java/freemarker/core/NumberFormatTest.java +++ b/src/test/java/freemarker/core/NumberFormatTest.java @@ -22,6 +22,8 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.Collections; import java.util.Locale; import java.util.Map; @@ -56,7 +58,8 @@ public class NumberFormatTest extends TemplateTest { cfg.setCustomNumberFormats(ImmutableMap.of( "hex", HexTemplateNumberFormatFactory.INSTANCE, "loc", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE, - "base", BaseNTemplateNumberFormatFactory.INSTANCE)); + "base", BaseNTemplateNumberFormatFactory.INSTANCE, + "printfG", PrintfGTemplateNumberFormatFactory.INSTANCE)); } @Test @@ -283,6 +286,32 @@ public class NumberFormatTest extends TemplateTest { "1.0_en 1.0_en_GB 1.0_en_GB 1,0_fr_FR 1,0"); } + @Test + public void testMarkupFormat() throws IOException, TemplateException { + getConfiguration().setNumberFormat("@printfG_3"); + + String commonFTL = "${1234567} ${'cat:' + 1234567} ${0.0000123}"; + assertOutput(commonFTL, + "1.23E+06 cat:1.23E+06 1.23E-05"); + assertOutput("<#ftl outputFormat='HTML'>" + commonFTL, + "1.23*106 cat:1.23*106 1.23*10-5"); + assertOutput("<#ftl outputFormat='HTML'>${\"" + commonFTL + "\"}", + "1.23E+06 cat:1.23E+06 1.23E-05"); + } + + @Test + public void testPrintG() throws IOException, TemplateException { + for (Number n : new Number[] { + 1234567, 1234567L, 1234567d, 1234567f, BigInteger.valueOf(1234567), BigDecimal.valueOf(1234567) }) { + addToDataModel("n", n); + + assertOutput("${n?string.@printfG}", "1.23457E+06"); + assertOutput("${n?string.@printfG_3}", "1.23E+06"); + assertOutput("${n?string.@printfG_7}", "1234567"); + assertOutput("${0.0000123?string.@printfG}", "1.23000E-05"); + } + } + private static class MutableTemplateNumberModel implements TemplateNumberModel { private Number number; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java new file mode 100644 index 0000000..a44dac3 --- /dev/null +++ b/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 freemarker.core; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Locale; + +import freemarker.template.TemplateModelException; +import freemarker.template.TemplateNumberModel; +import freemarker.template.utility.StringUtil; + +/** + * Formats like {@code %G} in {@code printf}, with the specified number of significant digits. Also has special + * formatter for HTML output format, where it uses the HTML "sup" element for exponents. + */ +public class PrintfGTemplateNumberFormatFactory extends TemplateNumberFormatFactory { + + public static final PrintfGTemplateNumberFormatFactory INSTANCE = new PrintfGTemplateNumberFormatFactory(); + + private PrintfGTemplateNumberFormatFactory() { + // Defined to decrease visibility + } + + @Override + public TemplateNumberFormat get(String params, Locale locale, Environment env) + throws InvalidFormatParametersException { + Integer significantDigits; + if (!params.isEmpty()) { + try { + significantDigits = Integer.valueOf(params); + } catch (NumberFormatException e) { + throw new InvalidFormatParametersException( + "The format parameter must be an integer, but was (shown quoted) " + + StringUtil.jQuote(params) + "."); + } + } else { + // Use the default of %G + significantDigits = null; + } + return new PrintfGTemplateNumberFormat(significantDigits, locale); + } + + private static class PrintfGTemplateNumberFormat extends TemplateNumberFormat { + + private final Locale locale; + private final String printfFormat; + + private PrintfGTemplateNumberFormat(Integer significantDigits, Locale locale) { + this.printfFormat = "%" + (significantDigits != null ? "." + significantDigits : "") + "G"; + this.locale = locale; + } + + @Override + public String format(TemplateNumberModel numberModel) + throws UnformattableValueException, TemplateModelException { + final Number n = TemplateFormatUtil.getNonNullNumber(numberModel); + + // printf %G only accepts Double, BigDecimal and Float + final Number gCompatibleN; + if (n instanceof Double || n instanceof BigDecimal || n instanceof Float) { + gCompatibleN = n; + } else { + if (n instanceof BigInteger) { + gCompatibleN = new BigDecimal((BigInteger) n); + } else if (n instanceof Long) { + gCompatibleN = BigDecimal.valueOf(((Long) n).longValue()); + } else { + gCompatibleN = Double.valueOf(n.doubleValue()); + } + } + + return String.format(locale, printfFormat, gCompatibleN); + } + + @Override + public MO format(TemplateNumberModel numberModel, + MarkupOutputFormat outputFormat) throws UnformattableValueException, TemplateModelException { + // TODO XHTMLOutputFormat + if (!(outputFormat instanceof HTMLOutputFormat)) { + return null; + } + + String s = StringUtil.XHTMLEnc(format(numberModel)); + int eIdx = s.indexOf('E'); + if (eIdx == -1) { + return outputFormat.fromMarkup(s); + } + + String expStr = s.substring(eIdx + 1); + int expNumStart = 0; + while (expNumStart < expStr.length() && isExpSignificantDigitPrefix(expStr.charAt(expNumStart))) { + expNumStart++; + } + + return outputFormat.fromMarkup( + s.substring(0, eIdx) + + "*10" + + (expStr.charAt(0) == '-' ? "-" : "") + expStr.substring(expNumStart) + + ""); + } + + private boolean isExpSignificantDigitPrefix(char c) { + return c == '+' || c == '-' || c == '0'; + } + + @Override + public boolean isLocaleBound() { + return true; + } + + @Override + public String getDescription() { + return "printf " + printfFormat; + } + + } + +}