You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2015/09/29 02:06:00 UTC

[4/5] incubator-freemarker git commit: Simplified number/date-like formatting more: The TemplateValueFormat-s don't get the desired TemplateOutputFormat as parameter, instead, an TemplateValueFormat should just return the single type of output it was des

Simplified number/date-like formatting more: The TemplateValueFormat-s don't get the desired TemplateOutputFormat as parameter, instead, an TemplateValueFormat should just return the single type of output it was designed for when format(...) is called, or plain text if formatToPlainText(...) is called. This resulting behavior is less sophisticated, but easy to understand for the end users. Also this way no output format has to be around when formatting, which simplifies the core and the implementation of user methods that would otherwise need that information from the lexical context somehow. Also string built-ins are now call format(...) and then fail if the result is markup, rather than formatting the LHO to plain text. Some internal class name changes.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/c798862a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/c798862a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/c798862a

Branch: refs/heads/2.3
Commit: c798862ac437ded4e1ccf6566374e40febd3bc73
Parents: b680e9c
Author: ddekany <dd...@apache.org>
Authored: Sat Sep 26 19:17:57 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Tue Sep 29 02:02:03 2015 +0200

----------------------------------------------------------------------
 .../freemarker/core/AddConcatExpression.java    |  20 ++--
 src/main/java/freemarker/core/Assignment.java   |   9 +-
 .../core/BuiltInForLegacyEscaping.java          |   4 +-
 .../BuiltInForMarkupOutputFormatRelated.java    |  46 -------
 .../core/BuiltInForOutputFormatRelated.java     |  46 -------
 .../java/freemarker/core/BuiltInForString.java  |   2 +-
 .../core/BuiltInsForMultipleTypes.java          |  14 +--
 .../core/BuiltInsForOutputFormatRelated.java    |   7 +-
 .../freemarker/core/BuiltInsForSequences.java   |   2 +-
 .../core/BuiltInsForStringsBasic.java           |   6 +-
 .../core/BuiltInsForStringsEncoding.java        |  26 +---
 .../freemarker/core/BuiltInsForStringsMisc.java |   2 +-
 .../java/freemarker/core/DollarVariable.java    |   4 +-
 .../java/freemarker/core/DynamicKeyName.java    |   4 +-
 src/main/java/freemarker/core/Environment.java  |  18 +--
 src/main/java/freemarker/core/EvalUtil.java     | 102 ++++++++++++----
 src/main/java/freemarker/core/Expression.java   |  30 ++++-
 src/main/java/freemarker/core/HashLiteral.java  |   4 +-
 .../core/ISOLikeTemplateDateFormat.java         |   2 +-
 src/main/java/freemarker/core/Include.java      |   6 +-
 src/main/java/freemarker/core/Interpret.java    |   6 +-
 .../freemarker/core/JavaTemplateDateFormat.java |   2 +-
 .../core/JavaTemplateNumberFormat.java          |   2 +-
 src/main/java/freemarker/core/LibraryLoad.java  |   2 +-
 src/main/java/freemarker/core/ListLiteral.java  |   4 +-
 .../core/MarkupOutputFormatBoundBuiltIn.java    |  46 +++++++
 src/main/java/freemarker/core/NewBI.java        |   2 +-
 .../java/freemarker/core/NumberLiteral.java     |   4 +-
 .../core/OutputFormatBoundBuiltIn.java          |  46 +++++++
 .../java/freemarker/core/PropertySetting.java   |   2 +-
 .../java/freemarker/core/StopInstruction.java   |   2 +-
 .../java/freemarker/core/StringLiteral.java     |   4 +-
 .../freemarker/core/TemplateDateFormat.java     |  23 ++--
 .../freemarker/core/TemplateNumberFormat.java   |  21 ++--
 src/main/javacc/FTL.jj                          |  14 +--
 src/manual/book.xml                             |  32 ++---
 .../core/AppMetaTemplateDateFormatFactory.java  |   2 +-
 .../core/BaseNTemplateNumberFormatFactory.java  |   4 +-
 .../freemarker/core/CorectionToTextualTest.java | 119 +++++++++++++++++++
 .../java/freemarker/core/DateFormatTest.java    |  34 +++---
 ...EpochMillisDivTemplateDateFormatFactory.java |   2 +-
 .../EpochMillisTemplateDateFormatFactory.java   |   2 +-
 .../core/HTMLISOTemplateDateFormatFactory.java  | 104 ++++++++++++++++
 .../core/HexTemplateNumberFormatFactory.java    |   2 +-
 ...AndTZSensitiveTemplateDateFormatFactory.java |   2 +-
 ...aleSensitiveTemplateNumberFormatFactory.java |   2 +-
 .../java/freemarker/core/NumberFormatTest.java  |  30 +++--
 .../PrintfGTemplateNumberFormatFactory.java     |  11 +-
 48 files changed, 560 insertions(+), 320 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 f92b933..7c1eb75 100644
--- a/src/main/java/freemarker/core/AddConcatExpression.java
+++ b/src/main/java/freemarker/core/AddConcatExpression.java
@@ -45,17 +45,15 @@ final class AddConcatExpression extends Expression {
 
     private final Expression left;
     private final Expression right;
-    private final MarkupOutputFormat markupOutputFormat;
 
-    AddConcatExpression(Expression left, Expression right, MarkupOutputFormat markupOutputFormat) {
+    AddConcatExpression(Expression left, Expression right) {
         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), markupOutputFormat);
+        return _eval(env, this, left, left.eval(env), right, right.eval(env));
     }
 
     /**
@@ -67,8 +65,7 @@ final class AddConcatExpression extends Expression {
     static TemplateModel _eval(Environment env,
             TemplateObject parent,
             Expression leftExp, TemplateModel leftModel,
-            Expression rightExp, TemplateModel rightModel,
-            MarkupOutputFormat markupOutputFormat)
+            Expression rightExp, TemplateModel rightModel)
             throws TemplateModelException, TemplateException, NonStringException {
         if (leftModel instanceof TemplateNumberModel && rightModel instanceof TemplateNumberModel) {
             Number first = EvalUtil.modelToNumber((TemplateNumberModel) leftModel, leftExp);
@@ -78,10 +75,10 @@ final class AddConcatExpression extends Expression {
             return new ConcatenatedSequence((TemplateSequenceModel) leftModel, (TemplateSequenceModel) rightModel);
         } else {
             try {
-                Object leftOMOrStr = EvalUtil.coerceModelToMarkupOutputOrString(
-                        leftModel, leftExp, (String) null, markupOutputFormat, env);
-                Object rightOMOrStr = EvalUtil.coerceModelToMarkupOutputOrString(
-                        rightModel, rightExp, (String) null, markupOutputFormat, env);
+                Object leftOMOrStr = EvalUtil.coerceModelToStringOrMarkup(
+                        leftModel, leftExp, (String) null, env);
+                Object rightOMOrStr = EvalUtil.coerceModelToStringOrMarkup(
+                        rightModel, rightExp, (String) null, env);
 
                 if (leftOMOrStr instanceof String) {
                     if (rightOMOrStr instanceof String) {
@@ -169,8 +166,7 @@ final class AddConcatExpression extends Expression {
             String replacedIdentifier, Expression replacement, ReplacemenetState replacementState) {
     	return new AddConcatExpression(
     	left.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),
-    	right.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),
-    	markupOutputFormat);
+    	right.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState));
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 8f5206b..0521250 100644
--- a/src/main/java/freemarker/core/Assignment.java
+++ b/src/main/java/freemarker/core/Assignment.java
@@ -41,7 +41,6 @@ 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;
@@ -58,8 +57,7 @@ final class Assignment extends TemplateElement {
     Assignment(String variableName,
             int operator,
             Expression valueExp,
-            int scope,
-            MarkupOutputFormat markupOutputFormat) {
+            int scope) {
         this.scope = scope;
         
         this.variableName = variableName;
@@ -95,8 +93,6 @@ final class Assignment extends TemplateElement {
         }
         
         this.valueExp = valueExp;
-        
-        this.markupOutputFormat = markupOutputFormat;
     }
     
     void setNamespaceExp(Expression namespaceExp) {
@@ -169,8 +165,7 @@ final class Assignment extends TemplateElement {
                         throw InvalidReferenceException.getInstance(valueExp, env);
                     }
                 }
-                value = AddConcatExpression._eval(env,
-                        namespaceExp, null, lhoValue, valueExp, value, markupOutputFormat);
+                value = AddConcatExpression._eval(env, namespaceExp, null, lhoValue, valueExp, value);
             } else {  // Numerical operation
                 Number lhoNumber;
                 if (lhoValue instanceof TemplateNumberModel) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 2ccd491..81fbe30 100644
--- a/src/main/java/freemarker/core/BuiltInForLegacyEscaping.java
+++ b/src/main/java/freemarker/core/BuiltInForLegacyEscaping.java
@@ -31,7 +31,7 @@ abstract class BuiltInForLegacyEscaping extends BuiltInBannedWhenAutoEscaping {
     TemplateModel _eval(Environment env)
     throws TemplateException {
         TemplateModel tm = target.eval(env);
-        Object moOrStr = EvalUtil.coerceModelToMarkupOutputOrString(tm, target, null, getMarkupOutputFormat(), env);
+        Object moOrStr = EvalUtil.coerceModelToStringOrMarkup(tm, target, null, env);
         if (moOrStr instanceof String) {
             return calculateResult((String) moOrStr, env);
         } else {
@@ -45,6 +45,4 @@ abstract class BuiltInForLegacyEscaping extends BuiltInBannedWhenAutoEscaping {
     
     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/c798862a/src/main/java/freemarker/core/BuiltInForMarkupOutputFormatRelated.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInForMarkupOutputFormatRelated.java b/src/main/java/freemarker/core/BuiltInForMarkupOutputFormatRelated.java
deleted file mode 100644
index 17eef81..0000000
--- a/src/main/java/freemarker/core/BuiltInForMarkupOutputFormatRelated.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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 freemarker.template.TemplateException;
-import freemarker.template.TemplateModel;
-import freemarker.template.utility.NullArgumentException;
-
-abstract class BuiltInForMarkupOutputFormatRelated extends SpecialBuiltIn {
-    
-    protected MarkupOutputFormat outputFormat;
-    
-    void bindToMarkupOutputFormat(MarkupOutputFormat outputFormat) {
-        NullArgumentException.check(outputFormat);
-        this.outputFormat = outputFormat;
-    }
-    
-    @Override
-    TemplateModel _eval(Environment env) throws TemplateException {
-        if (outputFormat == null) {
-            // The parser should prevent this situation
-            throw new NullPointerException("outputFormat was null");
-        }
-        return calculateResult(env);
-    }
-
-    protected abstract TemplateModel calculateResult(Environment env)
-            throws TemplateException;
-    
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/BuiltInForOutputFormatRelated.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInForOutputFormatRelated.java b/src/main/java/freemarker/core/BuiltInForOutputFormatRelated.java
deleted file mode 100644
index d8c3df5..0000000
--- a/src/main/java/freemarker/core/BuiltInForOutputFormatRelated.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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 freemarker.template.TemplateException;
-import freemarker.template.TemplateModel;
-import freemarker.template.utility.NullArgumentException;
-
-abstract class BuiltInForOutputFormatRelated extends SpecialBuiltIn {
-    
-    protected OutputFormat outputFormat;
-    
-    void bindToOutputFormat(OutputFormat outputFormat) {
-        NullArgumentException.check(outputFormat);
-        this.outputFormat = outputFormat;
-    }
-    
-    @Override
-    TemplateModel _eval(Environment env) throws TemplateException {
-        if (outputFormat == null) {
-            // The parser should prevent this situation
-            throw new NullPointerException("outputFormat was null");
-        }
-        return calculateResult(env);
-    }
-
-    protected abstract TemplateModel calculateResult(Environment env)
-            throws TemplateException;
-    
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/BuiltInForString.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInForString.java b/src/main/java/freemarker/core/BuiltInForString.java
index b9a3bd1..d38dfd7 100644
--- a/src/main/java/freemarker/core/BuiltInForString.java
+++ b/src/main/java/freemarker/core/BuiltInForString.java
@@ -31,7 +31,7 @@ abstract class BuiltInForString extends BuiltIn {
     abstract TemplateModel calculateResult(String s, Environment env) throws TemplateException;
     
     static String getTargetString(Expression target, Environment env) throws TemplateException {
-        return target.evalAndCoerceToString(env);
+        return target.evalAndCoerceToStringOrUnsupportedMarkup(env);
     }
     
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 deb88b2..4b5d548 100644
--- a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
@@ -241,7 +241,7 @@ class BuiltInsForMultipleTypes {
             }
             // Otherwise, interpret as a string and attempt 
             // to parse it into a date.
-            String s = target.evalAndCoerceToString(env);
+            String s = target.evalAndCoerceToPlainText(env);
             return new DateParser(s, env);
         }
 
@@ -560,7 +560,7 @@ class BuiltInsForMultipleTypes {
             private TemplateModel formatWith(String key)
             throws TemplateModelException {
                 try {
-                    return new SimpleScalar(env.formatDateToString(dateModel, key, target, stringBI.this, true));
+                    return new SimpleScalar(env.formatDateToPlainText(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); 
@@ -578,7 +578,7 @@ class BuiltInsForMultipleTypes {
                                 throw new BugException();
                             }
                         }
-                        cachedValue = EvalUtil.formatResultNotNull(defaultFormat.formatToString(dateModel));
+                        cachedValue = EvalUtil.assertFormatResultNotNull(defaultFormat.formatToPlainText(dateModel));
                     } catch (TemplateValueFormatException e) {
                         try {
                             throw MessageUtil.newCantFormatDateException(defaultFormat, target, e, true);
@@ -638,9 +638,9 @@ class BuiltInsForMultipleTypes {
                 String result;
                 try {
                     if (format instanceof BackwardCompatibleTemplateNumberFormat) {
-                        result = env.formatNumberToString(number, (BackwardCompatibleTemplateNumberFormat) format, target);
+                        result = env.formatNumberToPlainText(number, (BackwardCompatibleTemplateNumberFormat) format, target);
                     } else {
-                        result = env.formatNumberToString(numberModel, format, target, true);
+                        result = env.formatNumberToPlainText(numberModel, format, target, true);
                     }
                 } catch (TemplateException e) {
                     // `e` should always be a TemplateModelException here, but to be sure: 
@@ -654,10 +654,10 @@ class BuiltInsForMultipleTypes {
                 if (cachedValue == null) {
                     try {
                         if (defaultFormat instanceof BackwardCompatibleTemplateNumberFormat) {
-                            cachedValue = env.formatNumberToString(
+                            cachedValue = env.formatNumberToPlainText(
                                     number, (BackwardCompatibleTemplateNumberFormat) defaultFormat, target);
                         } else {
-                            cachedValue = env.formatNumberToString(numberModel, defaultFormat, target, true);
+                            cachedValue = env.formatNumberToPlainText(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/c798862a/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 556d12b..8c8da51 100644
--- a/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java
+++ b/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java
@@ -43,19 +43,16 @@ class BuiltInsForOutputFormatRelated {
         
     }
     
-    static abstract class AbstractConverterBI extends BuiltInForMarkupOutputFormatRelated {
+    static abstract class AbstractConverterBI extends MarkupOutputFormatBoundBuiltIn {
 
         @Override
         protected TemplateModel calculateResult(Environment env) throws TemplateException {
             TemplateModel lhoTM = target.eval(env);
+            Object lhoMOOrStr = EvalUtil.coerceModelToStringOrMarkup(lhoTM, target, null, env);
             MarkupOutputFormat contextOF = outputFormat;
-            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!

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/BuiltInsForSequences.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInsForSequences.java b/src/main/java/freemarker/core/BuiltInsForSequences.java
index 323a85f..a12d274 100644
--- a/src/main/java/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/freemarker/core/BuiltInsForSequences.java
@@ -183,7 +183,7 @@ class BuiltInsForSequences {
                             hadItem = true;
                         }
                         try {
-                            sb.append(EvalUtil.coerceModelToString(item, null, null, env));
+                            sb.append(EvalUtil.coerceModelToStringOrUnsupportedMarkup(item, null, null, env));
                         } catch (TemplateException e) {
                             throw new _TemplateModelException(e,
                                     "\"?", key, "\" failed at index ", Integer.valueOf(idx), " with this error:\n\n",

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/BuiltInsForStringsBasic.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInsForStringsBasic.java b/src/main/java/freemarker/core/BuiltInsForStringsBasic.java
index 28aa858..cc0cba8 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsBasic.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsBasic.java
@@ -89,7 +89,7 @@ class BuiltInsForStringsBasic {
     
         @Override
         TemplateModel _eval(Environment env) throws TemplateException {
-            return new BIMethod(target.evalAndCoerceToString(env,
+            return new BIMethod(target.evalAndCoerceToStringOrUnsupportedMarkup(env,
                     "For sequences/collections (lists and such) use \"?seq_contains\" instead."));
         }
     }
@@ -217,9 +217,9 @@ class BuiltInsForStringsBasic {
         
         @Override
         TemplateModel _eval(Environment env) throws TemplateException {
-            return new BIMethod(target.evalAndCoerceToString(env,
+            return new BIMethod(target.evalAndCoerceToStringOrUnsupportedMarkup(env,
                     "For sequences/collections (lists and such) use \"?seq_index_of\" instead."));
-        } 
+        }
     }
     
     static class keep_afterBI extends BuiltInForString {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 4f02805..3cedd62 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
@@ -39,11 +39,6 @@ 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();
@@ -60,11 +55,7 @@ class BuiltInsForStringsEncoding {
         public Object getPreviousICIChainMember() {
             return prevICIObj;
         }
-
-        @Override
-        MarkupOutputFormat getMarkupOutputFormat() {
-            return HTMLOutputFormat.INSTANCE;
-        }
+        
     }
 
     static class j_stringBI extends BuiltInForString {
@@ -93,11 +84,6 @@ 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 {
@@ -149,11 +135,6 @@ class BuiltInsForStringsEncoding {
         TemplateModel calculateResult(String s, Environment env) {
             return new SimpleScalar(StringUtil.XHTMLEnc(s));
         }
-
-        @Override
-        MarkupOutputFormat getMarkupOutputFormat() {
-            return XHTMLOutputFormat.INSTANCE;
-        }
     }
 
     static class xmlBI extends BuiltInForLegacyEscaping {
@@ -161,11 +142,6 @@ 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/c798862a/src/main/java/freemarker/core/BuiltInsForStringsMisc.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInsForStringsMisc.java b/src/main/java/freemarker/core/BuiltInsForStringsMisc.java
index 246b483..8b83c2c 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsMisc.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsMisc.java
@@ -49,7 +49,7 @@ class BuiltInsForStringsMisc {
         }
     }
 
-    static class evalBI extends BuiltInForOutputFormatRelated {
+    static class evalBI extends OutputFormatBoundBuiltIn {
         
         @Override
         protected TemplateModel calculateResult(Environment env) throws TemplateException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 23a995b..a3abc31 100644
--- a/src/main/java/freemarker/core/DollarVariable.java
+++ b/src/main/java/freemarker/core/DollarVariable.java
@@ -59,8 +59,8 @@ final class DollarVariable extends Interpolation {
     void accept(Environment env) throws TemplateException, IOException {
         final TemplateModel tm = escapedExpression.eval(env);
         final Writer out = env.getOut();
-        final Object moOrStr = EvalUtil.coerceModelToMarkupOutputOrString(
-                tm, escapedExpression, null, markupOutputFormat, env);
+        final Object moOrStr = EvalUtil.coerceModelToStringOrMarkup(
+                tm, escapedExpression, null, env);
         if (moOrStr instanceof String) {
             final String s = (String) moOrStr;
             if (autoEscape) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/DynamicKeyName.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/DynamicKeyName.java b/src/main/java/freemarker/core/DynamicKeyName.java
index df9955d..b0addad 100644
--- a/src/main/java/freemarker/core/DynamicKeyName.java
+++ b/src/main/java/freemarker/core/DynamicKeyName.java
@@ -106,7 +106,7 @@ final class DynamicKeyName extends Expression {
         } 
         
         try {
-            String s = target.evalAndCoerceToString(env);
+            String s = target.evalAndCoerceToPlainText(env);
             try {
                 return new SimpleScalar(s.substring(index, index + 1));
             } catch (IndexOutOfBoundsException e) {
@@ -152,7 +152,7 @@ final class DynamicKeyName extends Expression {
         } else {
             targetSeq = null;
             try {
-                targetStr = target.evalAndCoerceToString(env);
+                targetStr = target.evalAndCoerceToPlainText(env);
             } catch (NonStringException e) {
                 throw new UnexpectedTypeException(
                         target, target.eval(env),

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 ca6d7b1..358c371 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -1033,9 +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 formatNumberToString(TemplateNumberModel number, Expression exp, boolean useTempModelExc)
+    String formatNumberToPlainText(TemplateNumberModel number, Expression exp, boolean useTempModelExc)
             throws TemplateException {
-        return formatNumberToString(number, getTemplateNumberFormat(exp, useTempModelExc), exp, useTempModelExc);
+        return formatNumberToPlainText(number, getTemplateNumberFormat(exp, useTempModelExc), exp, useTempModelExc);
     }
 
     /**
@@ -1044,12 +1044,12 @@ public final class Environment extends Configurable {
      * @param exp
      *            The blamed expression if an error occurs; it's only needed for better error messages
      */
-    String formatNumberToString(
+    String formatNumberToPlainText(
             TemplateNumberModel number, TemplateNumberFormat format, Expression exp,
             boolean useTempModelExc)
             throws TemplateException {
         try {
-            return EvalUtil.formatResultNotNull(format.formatToString(number));
+            return EvalUtil.assertFormatResultNotNull(format.formatToPlainText(number));
         } catch (TemplateValueFormatException e) {
             throw MessageUtil.newCantFormatNumberException(format, exp, e, useTempModelExc);
         }
@@ -1061,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 formatNumberToString(Number number, BackwardCompatibleTemplateNumberFormat format, Expression exp)
+    String formatNumberToPlainText(Number number, BackwardCompatibleTemplateNumberFormat format, Expression exp)
             throws TemplateModelException, _MiscTemplateException {
         try {
             return format.format(number);
@@ -1319,12 +1319,12 @@ public final class Environment extends Configurable {
      * @param tdmSourceExpr
      *            The blamed expression if an error occurs; only used for error messages.
      */
-    String formatDateToString(TemplateDateModel tdm, Expression tdmSourceExpr,
+    String formatDateToPlainText(TemplateDateModel tdm, Expression tdmSourceExpr,
             boolean useTempModelExc) throws TemplateException {
         TemplateDateFormat format = getTemplateDateFormat(tdm, tdmSourceExpr, useTempModelExc);
         
         try {
-            return EvalUtil.formatResultNotNull(format.formatToString(tdm));
+            return EvalUtil.assertFormatResultNotNull(format.formatToPlainText(tdm));
         } catch (TemplateValueFormatException e) {
             throw MessageUtil.newCantFormatDateException(format, tdmSourceExpr, e, useTempModelExc);
         }
@@ -1336,7 +1336,7 @@ public final class Environment extends Configurable {
      * @param blamedFormatterExp
      *            The blamed expression if an error occurs; only used for error messages.
      */
-    String formatDateToString(TemplateDateModel tdm, String formatString,
+    String formatDateToPlainText(TemplateDateModel tdm, String formatString,
             Expression blamedDateSourceExp, Expression blamedFormatterExp,
             boolean useTempModelExc) throws TemplateException {
         Date date = EvalUtil.modelToDate(tdm, blamedDateSourceExp);
@@ -1347,7 +1347,7 @@ public final class Environment extends Configurable {
                 useTempModelExc);
         
         try {
-            return EvalUtil.formatResultNotNull(format.formatToString(tdm));
+            return EvalUtil.assertFormatResultNotNull(format.formatToPlainText(tdm));
         } catch (TemplateValueFormatException e) {
             throw MessageUtil.newCantFormatDateException(format, blamedDateSourceExp, e, useTempModelExc);
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 6dfc68d..ca21ac5 100644
--- a/src/main/java/freemarker/core/EvalUtil.java
+++ b/src/main/java/freemarker/core/EvalUtil.java
@@ -286,8 +286,8 @@ class EvalUtil {
             boolean rightBool = ((TemplateBooleanModel) rightValue).getAsBoolean();
             cmpResult = (leftBool ? 1 : 0) - (rightBool ? 1 : 0);
         } else if (env.isClassicCompatible()) {
-            String leftSting = leftExp.evalAndCoerceToString(env);
-            String rightString = rightExp.evalAndCoerceToString(env);
+            String leftSting = leftExp.evalAndCoerceToPlainText(env);
+            String rightString = rightExp.evalAndCoerceToPlainText(env);
             cmpResult = env.getCollator().compare(leftSting, rightString);
         } else {
             if (typeMismatchMeansNotEqual) {
@@ -342,31 +342,56 @@ class EvalUtil {
     }
 
     /**
+     * Converts a value to plain text {@link String}, or a {@link TemplateMarkupOutputModel} if that's what the
+     * {@link TemplateValueFormat} involved produces.
+     * 
+     * @param seqTip
+     *            Tip to display if the value type is not coercable, but it's sequence or collection.
+     * 
      * @return Never {@code null}
      */
-    static String coerceModelToString(TemplateModel tm, Expression exp, String seqHint,
-            Environment env) throws TemplateException {
+    static Object coerceModelToStringOrMarkup(TemplateModel tm, Expression exp, String seqTip, Environment env)
+            throws TemplateException {
         if (tm instanceof TemplateNumberModel) {
-            return formatResultNotNull(env.formatNumberToString((TemplateNumberModel) tm, exp, false));
+            TemplateNumberModel tnm = (TemplateNumberModel) tm; 
+            TemplateNumberFormat format = env.getTemplateNumberFormat(exp, false);
+            try {
+                return assertFormatResultNotNull(format.format(tnm));
+            } catch (TemplateValueFormatException e) {
+                throw MessageUtil.newCantFormatNumberException(format, exp, e, false);
+            }
         } else if (tm instanceof TemplateDateModel) {
-            return formatResultNotNull(env.formatDateToString((TemplateDateModel) tm, exp, false));
-        } else {
-            return coerceModelToStringCommon(tm, exp, seqHint, false, env);
+            TemplateDateModel tdm = (TemplateDateModel) tm;
+            TemplateDateFormat format = env.getTemplateDateFormat(tdm, exp, false);
+            try {
+                return assertFormatResultNotNull(format.format(tdm));
+            } catch (TemplateValueFormatException e) {
+                throw MessageUtil.newCantFormatDateException(format, exp, e, false);
+            }
+        } else if (tm instanceof TemplateMarkupOutputModel) {
+            return tm;
+        } else { 
+            return coerceModelToTextualCommon(tm, exp, seqTip, true, env);
         }
     }
 
     /**
+     * Like {@link #coerceModelToStringOrMarkup(TemplateModel, Expression, String, Environment)}, but gives error
+     * if the result is markup. This is what you normally use where markup results can't be used.
+     *
+     * @param seqTip
+     *            Tip to display if the value type is not coercable, but it's sequence or collection.
+     * 
      * @return Never {@code null}
      */
-    static Object coerceModelToMarkupOutputOrString(TemplateModel tm, Expression exp, String seqHint,
-            MarkupOutputFormat markupOutputFormat, Environment env) throws TemplateException {
+    static String coerceModelToStringOrUnsupportedMarkup(
+            TemplateModel tm, Expression exp, String seqTip, Environment env)
+            throws TemplateException {
         if (tm instanceof TemplateNumberModel) {
             TemplateNumberModel tnm = (TemplateNumberModel) tm; 
             TemplateNumberFormat format = env.getTemplateNumberFormat(exp, false);
             try {
-                return formatResultNotNull(markupOutputFormat != null 
-                        ? format.formatToMarkupOrString(tnm, markupOutputFormat)
-                        : format.formatToString(tnm));
+                return ensureFormatResultString(format.format(tnm), exp, env);
             } catch (TemplateValueFormatException e) {
                 throw MessageUtil.newCantFormatNumberException(format, exp, e, false);
             }
@@ -374,16 +399,32 @@ class EvalUtil {
             TemplateDateModel tdm = (TemplateDateModel) tm;
             TemplateDateFormat format = env.getTemplateDateFormat(tdm, exp, false);
             try {
-                return formatResultNotNull(markupOutputFormat != null
-                        ? format.formatToMarkupOrString(tdm, markupOutputFormat)
-                        : format.formatToString(tdm));
+                return ensureFormatResultString(format.format(tdm), exp, env);
             } 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);
+            return coerceModelToTextualCommon(tm, exp, seqTip, false, env);
+        }
+    }
+
+    /**
+     * Converts a value to plain text {@link String}, even if the {@link TemplateValueFormat} involved normally produces
+     * markup. This should be used rarely, where the user clearly intend to use the plain text variant of the format.
+     * 
+     * @param seqTip
+     *            Tip to display if the value type is not coercable, but it's sequence or collection.
+     * 
+     * @return Never {@code null}
+     */
+    static String coerceModelToPlainText(TemplateModel tm, Expression exp, String seqTip,
+            Environment env) throws TemplateException {
+        if (tm instanceof TemplateNumberModel) {
+            return assertFormatResultNotNull(env.formatNumberToPlainText((TemplateNumberModel) tm, exp, false));
+        } else if (tm instanceof TemplateDateModel) {
+            return assertFormatResultNotNull(env.formatDateToPlainText((TemplateDateModel) tm, exp, false));
+        } else {
+            return coerceModelToTextualCommon(tm, exp, seqTip, false, env);
         }
     }
 
@@ -396,7 +437,7 @@ class EvalUtil {
      *            
      * @return Never {@code null}
      */
-    private static String coerceModelToStringCommon(
+    private static String coerceModelToTextualCommon(
             TemplateModel tm, Expression exp, String seqHint, boolean supportsTOM, Environment env)
             throws TemplateModelException, InvalidReferenceException, TemplateException,
                     NonStringOrTemplateOutputException, NonStringException {
@@ -456,14 +497,31 @@ class EvalUtil {
         }
     }
 
-    static String formatResultNotNull(String r) {
+    private static String ensureFormatResultString(Object formatResult, Expression exp, Environment env)
+            throws NonStringException {
+        if (formatResult instanceof String) { 
+            return (String) formatResult;
+        }
+        
+        assertFormatResultNotNull(formatResult);
+        
+        TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) formatResult;
+        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                "Value was formatted to convert it to string, but the result was markup of ouput format ",
+                new _DelayedJQuote(mo.getOutputFormat()), ".")
+                .tip("Use value?string to force formatting to plain text.")
+                .blame(exp);
+        throw new NonStringException(null, desc);
+    }
+
+    static String assertFormatResultNotNull(String r) {
         if (r != null) {
             return r;
         }
         throw new NullPointerException("TemplateValueFormatter result can't be null");
     }
 
-    static Object formatResultNotNull(Object r) {
+    static Object assertFormatResultNotNull(Object r) {
         if (r != null) {
             return r;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 c1d5355..3129e19 100644
--- a/src/main/java/freemarker/core/Expression.java
+++ b/src/main/java/freemarker/core/Expression.java
@@ -82,15 +82,37 @@ abstract public class Expression extends TemplateObject {
         return constantValue != null ? constantValue : _eval(env);
     }
     
-    String evalAndCoerceToString(Environment env) throws TemplateException {
-        return EvalUtil.coerceModelToString(eval(env), this, null, env);
+    String evalAndCoerceToPlainText(Environment env) throws TemplateException {
+        return EvalUtil.coerceModelToPlainText(eval(env), this, null, env);
     }
 
     /**
      * @param seqTip Tip to display if the value type is not coercable, but it's sequence or collection.
      */
-    String evalAndCoerceToString(Environment env, String seqTip) throws TemplateException {
-        return EvalUtil.coerceModelToString(eval(env), this, seqTip, env);
+    String evalAndCoerceToPlainText(Environment env, String seqTip) throws TemplateException {
+        return EvalUtil.coerceModelToPlainText(eval(env), this, seqTip, env);
+    }
+
+    Object evalAndCoerceToStringOrMarkup(Environment env) throws TemplateException {
+        return EvalUtil.coerceModelToStringOrMarkup(eval(env), this, null, env);
+    }
+
+    /**
+     * @param seqTip Tip to display if the value type is not coercable, but it's sequence or collection.
+     */
+    Object evalAndCoerceToStringOrMarkup(Environment env, String seqTip) throws TemplateException {
+        return EvalUtil.coerceModelToStringOrMarkup(eval(env), this, seqTip, env);
+    }
+    
+    String evalAndCoerceToStringOrUnsupportedMarkup(Environment env) throws TemplateException {
+        return EvalUtil.coerceModelToStringOrUnsupportedMarkup(eval(env), this, null, env);
+    }
+
+    /**
+     * @param seqTip Tip to display if the value type is not coercable, but it's sequence or collection.
+     */
+    String evalAndCoerceToStringOrUnsupportedMarkup(Environment env, String seqTip) throws TemplateException {
+        return EvalUtil.coerceModelToStringOrUnsupportedMarkup(eval(env), this, seqTip, env);
     }
     
     Number evalToNumber(Environment env) throws TemplateException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/HashLiteral.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/HashLiteral.java b/src/main/java/freemarker/core/HashLiteral.java
index bec56db..dcf5f4a 100644
--- a/src/main/java/freemarker/core/HashLiteral.java
+++ b/src/main/java/freemarker/core/HashLiteral.java
@@ -114,7 +114,7 @@ final class HashLiteral extends Expression {
                 for (int i = 0; i < size; i++) {
                     Expression keyExp = (Expression) keys.get(i);
                     Expression valExp = (Expression) values.get(i);
-                    String key = keyExp.evalAndCoerceToString(env);
+                    String key = keyExp.evalAndCoerceToPlainText(env);
                     TemplateModel value = valExp.eval(env);
                     if (env == null || !env.isClassicCompatible()) {
                         valExp.assertNonNull(value, env);
@@ -130,7 +130,7 @@ final class HashLiteral extends Expression {
                 for (int i = 0; i < size; i++) {
                     Expression keyExp = (Expression) keys.get(i);
                     Expression valExp = (Expression) values.get(i);
-                    String key = keyExp.evalAndCoerceToString(env);
+                    String key = keyExp.evalAndCoerceToPlainText(env);
                     TemplateModel value = valExp.eval(env);
                     if (env == null || !env.isClassicCompatible()) {
                         valExp.assertNonNull(value, env);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java b/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
index b379413..690fd0f 100644
--- a/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
+++ b/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
@@ -177,7 +177,7 @@ abstract class ISOLikeTemplateDateFormat  extends TemplateDateFormat {
     }
     
     @Override
-    public final String formatToString(TemplateDateModel dateModel) throws TemplateModelException {
+    public final String formatToPlainText(TemplateDateModel dateModel) throws TemplateModelException {
         final Date date = TemplateFormatUtil.getNonNullDate(dateModel);
         return format(
                 date,

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/Include.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Include.java b/src/main/java/freemarker/core/Include.java
index 2455b0c..f18c314 100644
--- a/src/main/java/freemarker/core/Include.java
+++ b/src/main/java/freemarker/core/Include.java
@@ -80,7 +80,7 @@ final class Include extends TemplateElement {
                 try {
                     if (parseExp instanceof StringLiteral) {
                         // Legacy
-                        parse = Boolean.valueOf(StringUtil.getYesNo(parseExp.evalAndCoerceToString(null)));
+                        parse = Boolean.valueOf(StringUtil.getYesNo(parseExp.evalAndCoerceToPlainText(null)));
                     } else {
                         try {
                             parse = Boolean.valueOf(parseExp.evalToBoolean(template.getConfiguration()));
@@ -119,7 +119,7 @@ final class Include extends TemplateElement {
     
     @Override
     void accept(Environment env) throws TemplateException, IOException {
-        final String includedTemplateName = includedTemplateNameExp.evalAndCoerceToString(env);
+        final String includedTemplateName = includedTemplateNameExp.evalAndCoerceToPlainText(env);
         final String fullIncludedTemplateName;
         try {
             fullIncludedTemplateName = env.toFullTemplateName(getTemplate().getName(), includedTemplateName);
@@ -132,7 +132,7 @@ final class Include extends TemplateElement {
         final String encoding = this.encoding != null
                 ? this.encoding
                 : (encodingExp != null
-                        ? encodingExp.evalAndCoerceToString(env)
+                        ? encodingExp.evalAndCoerceToPlainText(env)
                         : null);
         
         final boolean parse;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/Interpret.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Interpret.java b/src/main/java/freemarker/core/Interpret.java
index 47c6f79..0b947ef 100644
--- a/src/main/java/freemarker/core/Interpret.java
+++ b/src/main/java/freemarker/core/Interpret.java
@@ -43,7 +43,7 @@ import freemarker.template.TemplateTransformModel;
  * specify another parameter to the method call in which case the
  * template name suffix is the specified id instead of "anonymous_interpreted".
  */
-class Interpret extends BuiltInForOutputFormatRelated {
+class Interpret extends OutputFormatBoundBuiltIn {
     
     /**
      * Constructs a template on-the-fly and returns it embedded in a
@@ -68,7 +68,7 @@ class Interpret extends BuiltInForOutputFormatRelated {
         if (model instanceof TemplateSequenceModel) {
             sourceExpr = ((Expression) new DynamicKeyName(target, new NumberLiteral(Integer.valueOf(0))).copyLocationFrom(target));
             if (((TemplateSequenceModel) model).size() > 1) {
-                id = ((Expression) new DynamicKeyName(target, new NumberLiteral(Integer.valueOf(1))).copyLocationFrom(target)).evalAndCoerceToString(env);
+                id = ((Expression) new DynamicKeyName(target, new NumberLiteral(Integer.valueOf(1))).copyLocationFrom(target)).evalAndCoerceToPlainText(env);
             }
         } else if (model instanceof TemplateScalarModel) {
             sourceExpr = target;
@@ -78,7 +78,7 @@ class Interpret extends BuiltInForOutputFormatRelated {
                     "sequence or string", new Class[] { TemplateSequenceModel.class, TemplateScalarModel.class },
                     env);
         }
-        String templateSource = sourceExpr.evalAndCoerceToString(env);
+        String templateSource = sourceExpr.evalAndCoerceToPlainText(env);
         Template parentTemplate = env.getTemplate();
         
         final Template interpretedTemplate;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/JavaTemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/JavaTemplateDateFormat.java b/src/main/java/freemarker/core/JavaTemplateDateFormat.java
index 02a1460..1b630ae 100644
--- a/src/main/java/freemarker/core/JavaTemplateDateFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateDateFormat.java
@@ -39,7 +39,7 @@ class JavaTemplateDateFormat extends TemplateDateFormat {
     }
     
     @Override
-    public String formatToString(TemplateDateModel dateModel) throws TemplateModelException {
+    public String formatToPlainText(TemplateDateModel dateModel) throws TemplateModelException {
         return javaDateFormat.format(TemplateFormatUtil.getNonNullDate(dateModel));
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 f862c2b..27b85e2 100644
--- a/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
@@ -34,7 +34,7 @@ final class JavaTemplateNumberFormat extends BackwardCompatibleTemplateNumberFor
     }
 
     @Override
-    public String formatToString(TemplateNumberModel numberModel) throws UnformattableValueException, TemplateModelException {
+    public String formatToPlainText(TemplateNumberModel numberModel) throws UnformattableValueException, TemplateModelException {
         Number number = TemplateFormatUtil.getNonNullNumber(numberModel);
         return format(number);
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/LibraryLoad.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/LibraryLoad.java b/src/main/java/freemarker/core/LibraryLoad.java
index 0f883db..9a9c887 100644
--- a/src/main/java/freemarker/core/LibraryLoad.java
+++ b/src/main/java/freemarker/core/LibraryLoad.java
@@ -51,7 +51,7 @@ public final class LibraryLoad extends TemplateElement {
 
     @Override
     void accept(Environment env) throws TemplateException, IOException {
-        final String importedTemplateName = importedTemplateNameExp.evalAndCoerceToString(env);
+        final String importedTemplateName = importedTemplateNameExp.evalAndCoerceToPlainText(env);
         final String fullImportedTemplateName;
         try {
             fullImportedTemplateName = env.toFullTemplateName(getTemplate().getName(), importedTemplateName);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/ListLiteral.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ListLiteral.java b/src/main/java/freemarker/core/ListLiteral.java
index b4e43ae..db7b4c5 100644
--- a/src/main/java/freemarker/core/ListLiteral.java
+++ b/src/main/java/freemarker/core/ListLiteral.java
@@ -67,13 +67,13 @@ final class ListLiteral extends Expression {
                 return Collections.EMPTY_LIST;
             }
             case 1: {
-                return Collections.singletonList(((Expression) items.get(0)).evalAndCoerceToString(env));
+                return Collections.singletonList(((Expression) items.get(0)).evalAndCoerceToPlainText(env));
             }
             default: {
                 List result = new ArrayList(items.size());
                 for (ListIterator iterator = items.listIterator(); iterator.hasNext(); ) {
                     Expression exp = (Expression) iterator.next();
-                    result.add(exp.evalAndCoerceToString(env));
+                    result.add(exp.evalAndCoerceToPlainText(env));
                 }
                 return result;
             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/MarkupOutputFormatBoundBuiltIn.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/MarkupOutputFormatBoundBuiltIn.java b/src/main/java/freemarker/core/MarkupOutputFormatBoundBuiltIn.java
new file mode 100644
index 0000000..5780124
--- /dev/null
+++ b/src/main/java/freemarker/core/MarkupOutputFormatBoundBuiltIn.java
@@ -0,0 +1,46 @@
+/*
+ * 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 freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+import freemarker.template.utility.NullArgumentException;
+
+abstract class MarkupOutputFormatBoundBuiltIn extends SpecialBuiltIn {
+    
+    protected MarkupOutputFormat outputFormat;
+    
+    void bindToMarkupOutputFormat(MarkupOutputFormat outputFormat) {
+        NullArgumentException.check(outputFormat);
+        this.outputFormat = outputFormat;
+    }
+    
+    @Override
+    TemplateModel _eval(Environment env) throws TemplateException {
+        if (outputFormat == null) {
+            // The parser should prevent this situation
+            throw new NullPointerException("outputFormat was null");
+        }
+        return calculateResult(env);
+    }
+
+    protected abstract TemplateModel calculateResult(Environment env)
+            throws TemplateException;
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/NewBI.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/NewBI.java b/src/main/java/freemarker/core/NewBI.java
index d749b6f..c7842de 100644
--- a/src/main/java/freemarker/core/NewBI.java
+++ b/src/main/java/freemarker/core/NewBI.java
@@ -48,7 +48,7 @@ class NewBI extends BuiltIn {
     @Override
     TemplateModel _eval(Environment env)
             throws TemplateException {
-        return new ConstructorFunction(target.evalAndCoerceToString(env), env, target.getTemplate());
+        return new ConstructorFunction(target.evalAndCoerceToPlainText(env), env, target.getTemplate());
     }
 
     class ConstructorFunction implements TemplateMethodModelEx {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 91ded3c..40b63d5 100644
--- a/src/main/java/freemarker/core/NumberLiteral.java
+++ b/src/main/java/freemarker/core/NumberLiteral.java
@@ -42,8 +42,8 @@ final class NumberLiteral extends Expression implements TemplateNumberModel {
     }
 
     @Override
-    public String evalAndCoerceToString(Environment env) throws TemplateException {
-        return env.formatNumberToString(this, this, false);
+    public String evalAndCoerceToPlainText(Environment env) throws TemplateException {
+        return env.formatNumberToPlainText(this, this, false);
     }
 
     public Number getAsNumber() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/OutputFormatBoundBuiltIn.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/OutputFormatBoundBuiltIn.java b/src/main/java/freemarker/core/OutputFormatBoundBuiltIn.java
new file mode 100644
index 0000000..b32617b
--- /dev/null
+++ b/src/main/java/freemarker/core/OutputFormatBoundBuiltIn.java
@@ -0,0 +1,46 @@
+/*
+ * 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 freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+import freemarker.template.utility.NullArgumentException;
+
+abstract class OutputFormatBoundBuiltIn extends SpecialBuiltIn {
+    
+    protected OutputFormat outputFormat;
+    
+    void bindToOutputFormat(OutputFormat outputFormat) {
+        NullArgumentException.check(outputFormat);
+        this.outputFormat = outputFormat;
+    }
+    
+    @Override
+    TemplateModel _eval(Environment env) throws TemplateException {
+        if (outputFormat == null) {
+            // The parser should prevent this situation
+            throw new NullPointerException("outputFormat was null");
+        }
+        return calculateResult(env);
+    }
+
+    protected abstract TemplateModel calculateResult(Environment env)
+            throws TemplateException;
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/PropertySetting.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/PropertySetting.java b/src/main/java/freemarker/core/PropertySetting.java
index ab8fec1..7c9da68 100644
--- a/src/main/java/freemarker/core/PropertySetting.java
+++ b/src/main/java/freemarker/core/PropertySetting.java
@@ -120,7 +120,7 @@ final class PropertySetting extends TemplateElement {
         } else if (mval instanceof TemplateNumberModel) {
             strval = ((TemplateNumberModel) mval).getAsNumber().toString();
         } else {
-            strval = value.evalAndCoerceToString(env);
+            strval = value.evalAndCoerceToStringOrUnsupportedMarkup(env);
         }
         env.setSetting(key, strval);
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/StopInstruction.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/StopInstruction.java b/src/main/java/freemarker/core/StopInstruction.java
index 1d17326..c6a7526 100644
--- a/src/main/java/freemarker/core/StopInstruction.java
+++ b/src/main/java/freemarker/core/StopInstruction.java
@@ -37,7 +37,7 @@ final class StopInstruction extends TemplateElement {
         if (exp == null) {
             throw new StopException(env);
         }
-        throw new StopException(env, exp.evalAndCoerceToString(env));
+        throw new StopException(env, exp.evalAndCoerceToPlainText(env));
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/java/freemarker/core/StringLiteral.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/StringLiteral.java b/src/main/java/freemarker/core/StringLiteral.java
index 339da28..051c0aa 100644
--- a/src/main/java/freemarker/core/StringLiteral.java
+++ b/src/main/java/freemarker/core/StringLiteral.java
@@ -77,7 +77,7 @@ final class StringLiteral extends Expression implements TemplateScalarModel {
     
     @Override
     TemplateModel _eval(Environment env) throws TemplateException {
-        return new SimpleScalar(evalAndCoerceToString(env));
+        return new SimpleScalar(evalAndCoerceToPlainText(env));
     }
 
     public String getAsString() {
@@ -93,7 +93,7 @@ final class StringLiteral extends Expression implements TemplateScalarModel {
     }
     
     @Override
-    String evalAndCoerceToString(Environment env) throws TemplateException {
+    String evalAndCoerceToPlainText(Environment env) throws TemplateException {
         if (dynamicValue == null) {
             return value;
         } else {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 2f49d2d..daec487 100644
--- a/src/main/java/freemarker/core/TemplateDateFormat.java
+++ b/src/main/java/freemarker/core/TemplateDateFormat.java
@@ -53,28 +53,21 @@ public abstract class TemplateDateFormat extends TemplateValueFormat {
      * @throws TemplateModelException
      *             Exception thrown by the {@code dateModel} object when calling its methods.
      */
-    public abstract String formatToString(TemplateDateModel dateModel)
+    public abstract String formatToPlainText(TemplateDateModel dateModel)
             throws TemplateValueFormatException, TemplateModelException;
 
     /**
      * Formats the model to markup instead of to plain text if the result markup will be more than just plain text
      * escaped, otherwise falls back to formatting to plain text. If the markup result would be just the result of
-     * {@link #formatToString(TemplateDateModel)} escaped, it must return the {@link String} that
-     * {@link #formatToString(TemplateDateModel)} does.
+     * {@link #formatToPlainText(TemplateDateModel)} escaped, it must return the {@link String} that
+     * {@link #formatToPlainText(TemplateDateModel)} does.
      * 
-     * <p>The implementation in {@link TemplateDateFormat} simply calls {@link #formatToString(TemplateDateModel)}.
+     * <p>The implementation in {@link TemplateDateFormat} simply calls {@link #formatToPlainText(TemplateDateModel)}.
      * 
-     * @param outputFormat
-     *            When the result is a {@link TemplateMarkupOutputModel} result, it must be exactly of this output
-     *            format.
-     * 
-     * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not {@code null}. If it's a
-     *         {@link TemplateMarkupOutputModel}, then it must have the output format specified in the
-     *         {@code outputFormat} parameter.
+     * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not {@code null}.
      */
-    public Object formatToMarkupOrString(TemplateDateModel dateModel, MarkupOutputFormat<?> outputFormat)
-            throws TemplateValueFormatException, TemplateModelException {
-        return formatToString(dateModel);
+    public Object format(TemplateDateModel dateModel) throws TemplateValueFormatException, TemplateModelException {
+        return formatToPlainText(dateModel);
     }
 
     /**
@@ -94,7 +87,7 @@ public abstract class TemplateDateFormat extends TemplateValueFormat {
      * 
      * @return The interpretation of the text either as a {@link Date} or {@link TemplateDateModel}. Typically, a
      *         {@link Date}. {@link TemplateDateModel} is used if you have to attach some application-specific
-     *         meta-information thats also extracted during {@link #formatToString(TemplateDateModel)} (so if you format
+     *         meta-information thats also extracted during {@link #formatToPlainText(TemplateDateModel)} (so if you format
      *         something and then parse it, you get back an equivalent result). It can't be {@code null}. Known issue
      *         (at least in FTL 2): {@code ?date}/{@code ?time}/{@code ?datetime}, when not invoked as a method, can't
      *         return the {@link TemplateDateModel}, only the {@link Date} from inside it, hence the additional

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 69a5da0..759d969 100644
--- a/src/main/java/freemarker/core/TemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/TemplateNumberFormat.java
@@ -52,28 +52,23 @@ public abstract class TemplateNumberFormat extends TemplateValueFormat {
      * @throws TemplateModelException
      *             Exception thrown by the {@code dateModel} object when calling its methods.
      */
-    public abstract String formatToString(TemplateNumberModel numberModel)
+    public abstract String formatToPlainText(TemplateNumberModel numberModel)
             throws TemplateValueFormatException, TemplateModelException;
 
     /**
      * Formats the model to markup instead of to plain text if the result markup will be more than just plain text
      * escaped, otherwise falls back to formatting to plain text. If the markup result would be just the result of
-     * {@link #formatToString(TemplateNumberModel)} escaped, it must return the {@link String} that
-     * {@link #formatToString(TemplateNumberModel)} does.
+     * {@link #formatToPlainText(TemplateNumberModel)} escaped, it must return the {@link String} that
+     * {@link #formatToPlainText(TemplateNumberModel)} does.
      * 
-     * <p>The implementation in {@link TemplateNumberFormat} simply calls {@link #formatToString(TemplateNumberModel)}.
+     * <p>
+     * The implementation in {@link TemplateNumberFormat} simply calls {@link #formatToPlainText(TemplateNumberModel)}.
      * 
-     * @param outputFormat
-     *            When the result is a {@link TemplateMarkupOutputModel} result, it must be exactly of this output
-     *            format. Not {@code null}.
-     * 
-     * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not {@code null}. If it's a
-     *         {@link TemplateMarkupOutputModel}, then it must have the output format specified in the
-     *         {@code outputFormat} parameter.
+     * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not {@code null}.
      */
-    public Object formatToMarkupOrString(TemplateNumberModel numberModel, MarkupOutputFormat<?> outputFormat)
+    public Object format(TemplateNumberModel numberModel)
             throws TemplateValueFormatException, TemplateModelException {
-        return formatToString(numberModel);
+        return formatToPlainText(numberModel);
     }
     
     /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index 33c8ecb..e234e90 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -1665,7 +1665,7 @@ Expression AdditiveExpression() :
             if (plus) {
 	            // plus is treated separately, since it is also
 	            // used for concatenation.
-                result = new AddConcatExpression(lhs, rhs, getMarkupOutputFormat());
+                result = new AddConcatExpression(lhs, rhs);
             } else {
                 numberLiteralOnly(lhs);
                 numberLiteralOnly(rhs);
@@ -2089,19 +2089,19 @@ Expression BuiltIn(Expression lhoExp) :
             return result;
         }
 
-        if (result instanceof BuiltInForMarkupOutputFormatRelated) {
+        if (result instanceof MarkupOutputFormatBoundBuiltIn) {
             if (!(outputFormat instanceof MarkupOutputFormat)) {
                 throw new ParseException(
                         "?" + t.image + " can't be used here, as the current output format isn't a markup (escaping) "
                         + "format: " + outputFormat, template, t);
             }
-            ((BuiltInForMarkupOutputFormatRelated) result).bindToMarkupOutputFormat((MarkupOutputFormat) outputFormat);
+            ((MarkupOutputFormatBoundBuiltIn) result).bindToMarkupOutputFormat((MarkupOutputFormat) outputFormat);
             
             return result;
         }
 
-        if (result instanceof BuiltInForOutputFormatRelated) {
-            ((BuiltInForOutputFormatRelated) result).bindToOutputFormat(outputFormat);
+        if (result instanceof OutputFormatBoundBuiltIn) {
+            ((OutputFormatBoundBuiltIn) result).bindToOutputFormat(outputFormat);
             
             return result;
         }
@@ -2933,7 +2933,7 @@ TemplateElement Assign() :
 		        )
 	        )
 	        {
-	            ass = new Assignment(varName, equalsOp.kind, exp, scope, getMarkupOutputFormat());
+	            ass = new Assignment(varName, equalsOp.kind, exp, scope);
                 if (exp != null) {
                    ass.setLocation(template, nameExp, exp);
                 } else {
@@ -2973,7 +2973,7 @@ TemplateElement Assign() :
 	                )
 	            )
 	            {
-	                ass = new Assignment(varName, equalsOp.kind, exp, scope, getMarkupOutputFormat());
+	                ass = new Assignment(varName, equalsOp.kind, exp, scope);
 	                if (exp != null) {
 	                   ass.setLocation(template, nameExp, exp);
 	                } else {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index 53c2958..b8bc656 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -14563,7 +14563,7 @@ German people write: ${12345678}</programlisting>
 German people write: 12.345.678,00</programlisting>
 
           <simplesect xml:id="topic.extendedJavaDecimalFormat">
-            <title>Extended Java decimal format </title>
+            <title>Extended Java decimal format</title>
 
             <indexterm>
               <primary>extended Java decimal format</primary>
@@ -25992,14 +25992,9 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
                   number and date/time/datetime formatting results, like
                   <literal>1.23*10&lt;sup&gt;6&lt;/sup&gt;</literal>, which
                   won't be accidentally auto-escaped, as FreeMarker knows that
-                  it's already HTML. (See [TODO] as an example.) Note that
-                  formatting to markup only happens in a context that has a
-                  markup <link
-                  linkend="dgui_misc_autoescaping_outputformat">output
-                  format</link>, otherwise FreeMarker will format to plain
-                  text, just as before. Also note that no out-of-the-box
-                  (non-custom) formatter formats to markup at the
-                  moment.</para>
+                  it's already HTML. (See [TODO] as an example.) Note that no
+                  out-of-the-box format formats to markup (at the moment), but
+                  you could write such custom format.</para>
                 </listitem>
 
                 <listitem>
@@ -26250,20 +26245,17 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
 
             <listitem>
               <para><literal>TemplateNumberFormat.format</literal>/<literal>TemplateDateFormat.format</literal>
-              overloads were renamed to <literal>formatToString</literal> and
-              <literal>formatToMarkupOrString</literal>. The last has
-              different semantics then its predecessor (which was marked as
-              draft), and it's actually used by FreeMarker. Thus, it's now
-              possible to have HTML or other markup in number and
+              overloads were changed to <literal>formatToPlainText</literal>
+              and <literal>format</literal>, the last returning either String
+              or a <literal>TemplateMarkupOutputModel</literal>. Also,
+              formatting to markup is now actually used by FreeMarker. Thus,
+              it's now possible to have HTML or other markup in number and
               date/time/datetime formatting results, like
               <literal>1.23*10&lt;sup&gt;6&lt;/sup&gt;</literal>, which won't
               be accidentally auto-escaped, as FreeMarker knows that it's
-              already HTML. (See [TODO] as an example.) Note that formatting
-              to markup only happens in a context that has a markup <link
-              linkend="dgui_misc_autoescaping_outputformat">output
-              format</link>, otherwise FreeMarker will format to plain text,
-              just like before. Also note that no out-of-the-box (non-custom)
-              formatter formats to markup at the moment.</para>
+              already HTML. (See [TODO] as an example.) Note that no
+              out-of-the-box format formats to markup (at the moment), but you
+              could write such custom format.</para>
             </listitem>
 
             <listitem>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java b/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java
index e66e6f4..cc518ee 100644
--- a/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java
+++ b/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java
@@ -47,7 +47,7 @@ public class AppMetaTemplateDateFormatFactory extends TemplateDateFormatFactory
         private AppMetaTemplateDateFormat() { }
         
         @Override
-        public String formatToString(TemplateDateModel dateModel)
+        public String formatToPlainText(TemplateDateModel dateModel)
                 throws UnformattableValueException, TemplateModelException {
             String result = String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime());
             if (dateModel instanceof AppMetaTemplateDateModel) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/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 f66559a..3639a38 100644
--- a/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java
@@ -80,7 +80,7 @@ public class BaseNTemplateNumberFormatFactory extends TemplateNumberFormatFactor
         }
         
         @Override
-        public String formatToString(TemplateNumberModel numberModel)
+        public String formatToPlainText(TemplateNumberModel numberModel)
                 throws TemplateModelException, TemplateValueFormatException {
             Number n = TemplateFormatUtil.getNonNullNumber(numberModel);
             try {
@@ -90,7 +90,7 @@ public class BaseNTemplateNumberFormatFactory extends TemplateNumberFormatFactor
                     throw new UnformattableValueException(
                             n + " doesn't fit into an int, and there was no fallback format specified.");
                 } else {
-                    return fallbackFormat.formatToString(numberModel);
+                    return fallbackFormat.formatToPlainText(numberModel);
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c798862a/src/test/java/freemarker/core/CorectionToTextualTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/CorectionToTextualTest.java b/src/test/java/freemarker/core/CorectionToTextualTest.java
new file mode 100644
index 0000000..968981a
--- /dev/null
+++ b/src/test/java/freemarker/core/CorectionToTextualTest.java
@@ -0,0 +1,119 @@
+package freemarker.core;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import freemarker.template.Configuration;
+import freemarker.template.SimpleDate;
+import freemarker.template.TemplateDateModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModelException;
+import freemarker.test.TemplateTest;
+
+@SuppressWarnings("boxing")
+public class CorectionToTextualTest extends TemplateTest {
+    
+    /** 2015-09-06T12:00:00Z */
+    private static long T = 1441540800000L;
+    private static TemplateDateModel TM = new SimpleDate(new Date(T), TemplateDateModel.DATETIME);
+    
+    @Test
+    public void testBasicStringBuiltins() throws IOException, TemplateException {
+        assertOutput("${s?upperCase}", "ABC");
+        assertOutput("${n?string?lowerCase}", "1.50e+03");
+        assertErrorContains("${n?lowerCase}", "convert", "string", "markup", "text/html");
+        assertOutput("${dt?string?lowerCase}", "2015-09-06t12:00:00z");
+        assertErrorContains("${dt?lowerCase}", "convert", "string", "markup", "text/html");
+        assertOutput("${b?upperCase}", "Y");
+        assertErrorContains("${m?upperCase}", "convertible to string", "HTMLOutputModel");
+    }
+
+    @Test
+    public void testEscBuiltin() throws IOException, TemplateException {
+        Configuration cfg = getConfiguration();
+        cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
+        cfg.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
+        cfg.setBooleanFormat("<y>,<n>");
+        
+        assertOutput("${'a<b'?esc}", "a&lt;b");
+        assertOutput("${n?string?esc}", "1.50E+03");
+        assertOutput("${n?esc}", "1.50*10<sup>3</sup>");
+        assertOutput("${dt?string?esc}", "2015-09-06T12:00:00Z");
+        assertOutput("${dt?esc}", "2015-09-06<span class='T'>T</span>12:00:00Z");
+        assertOutput("${b?esc}", "&lt;y&gt;");
+        assertOutput("${m?esc}", "<p>M</p>");
+    }
+    
+    @Test
+    public void testStringOverloadedBuiltIns() throws IOException, TemplateException {
+        assertOutput("${s?contains('b')}", "y");
+        assertOutput("${n?string?contains('E')}", "y");
+        assertErrorContains("${n?contains('E')}", "convert", "string", "markup", "text/html");
+        assertErrorContains("${n?indexOf('E')}", "convert", "string", "markup", "text/html");
+        assertOutput("${dt?string?contains('0')}", "y");
+        assertErrorContains("${dt?contains('0')}", "convert", "string", "markup", "text/html");
+        assertErrorContains("${m?contains('0')}", "convertible to string", "HTMLOutputModel");
+        assertErrorContains("${m?indexOf('0')}", "convertible to string", "HTMLOutputModel");
+    }
+    
+    @Test
+    public void testMarkupStringBuiltIns() throws IOException, TemplateException {
+        assertErrorContains("${n?string?markupString}", "Expected", "markup", "string");
+        assertErrorContains("${n?markupString}", "Expected", "markup", "number");
+        assertErrorContains("${dt?markupString}", "Expected", "markup", "date");
+    }
+    
+    @Test
+    public void testSimpleInterpolation() throws IOException, TemplateException {
+        assertOutput("${s}", "abc");
+        assertOutput("${n?string}", "1.50E+03");
+        assertOutput("${n}", "1.50*10<sup>3</sup>");
+        assertOutput("${dt?string}", "2015-09-06T12:00:00Z");
+        assertOutput("${dt}", "2015-09-06<span class='T'>T</span>12:00:00Z");
+        assertOutput("${b}", "y");
+        assertOutput("${m}", "<p>M</p>");
+    }
+    
+    @Test
+    public void testConcatenation() throws IOException, TemplateException {
+        assertOutput("${s + '&'}", "abc&");
+        assertOutput("${n?string + '&'}", "1.50E+03&");
+        assertOutput("${n + '&'}", "1.50*10<sup>3</sup>&amp;");
+        assertOutput("${dt?string + '&'}", "2015-09-06T12:00:00Z&");
+        assertOutput("${dt + '&'}", "2015-09-06<span class='T'>T</span>12:00:00Z&amp;");
+        assertOutput("${b + '&'}", "y&");
+        assertOutput("${m + '&'}", "<p>M</p>&amp;");
+    }
+
+    @Test
+    public void testConcatenation2() throws IOException, TemplateException {
+        assertOutput("${'&' + s}", "&abc");
+        assertOutput("${'&' + n?string}", "&1.50E+03");
+        assertOutput("${'&' + n}", "&amp;1.50*10<sup>3</sup>");
+        assertOutput("${'&' + dt?string}", "&2015-09-06T12:00:00Z");
+        assertOutput("${'&' + dt}", "&amp;2015-09-06<span class='T'>T</span>12:00:00Z");
+        assertOutput("${'&' + b}", "&y");
+        assertOutput("${'&' + m}", "&amp;<p>M</p>");
+    }
+    
+    @Before
+    public void setup() throws TemplateModelException {
+        Configuration cfg = getConfiguration();
+        cfg.setCustomNumberFormats(Collections.singletonMap("G", PrintfGTemplateNumberFormatFactory.INSTANCE));
+        cfg.setCustomDateFormats(Collections.singletonMap("HI", HTMLISOTemplateDateFormatFactory.INSTANCE));
+        cfg.setNumberFormat("@G 3");
+        cfg.setDateTimeFormat("@HI");
+        cfg.setBooleanFormat("y,n");
+        
+        addToDataModel("s", "abc");
+        addToDataModel("n", 1500);
+        addToDataModel("dt", TM);
+        addToDataModel("b", Boolean.TRUE);
+        addToDataModel("m", HTMLOutputFormat.INSTANCE.fromMarkup("<p>M</p>"));
+    }
+
+}