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 2017/05/15 21:24:00 UTC
[44/51] [abbrv] [partial] incubator-freemarker git commit:
Restructured project so that freemarker-test-utils depends on freemarker-core
(and hence can provide common classes for testing templates,
and can use utility classes defined in the core). As a c
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/OutputFormatTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
new file mode 100644
index 0000000..eedb4d1
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/OutputFormatTest.java
@@ -0,0 +1,1068 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+import static org.apache.freemarker.core.ParsingConfiguration.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Collections;
+
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.PlainTextOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
+import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
+import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
+import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
+import org.apache.freemarker.core.templateresolver.OrMatcher;
+import org.apache.freemarker.core.templateresolver.impl.NullCacheStorage;
+import org.apache.freemarker.core.userpkg.CustomHTMLOutputFormat;
+import org.apache.freemarker.core.userpkg.DummyOutputFormat;
+import org.apache.freemarker.core.userpkg.SeldomEscapedOutputFormat;
+import org.apache.freemarker.test.TemplateTest;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class OutputFormatTest extends TemplateTest {
+
+ @Test
+ public void testOutputFormatSettingLayers() throws Exception {
+ addTemplate("t", "${.outputFormat}");
+ addTemplate("t.xml", "${.outputFormat}");
+ addTemplate("tWithHeader", "<#ftl outputFormat='HTML'>${.outputFormat}");
+
+ TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
+ for (OutputFormat cfgOutputFormat
+ : new OutputFormat[] { UndefinedOutputFormat.INSTANCE, RTFOutputFormat.INSTANCE } ) {
+ if (!cfgOutputFormat.equals(UndefinedOutputFormat.INSTANCE)) {
+ cfgB.setOutputFormat(cfgOutputFormat);
+ }
+ setConfiguration(cfgB.build());
+
+ assertEquals(cfgOutputFormat, getConfiguration().getOutputFormat());
+
+ {
+ Template t = getConfiguration().getTemplate("t");
+ assertEquals(cfgOutputFormat, t.getOutputFormat());
+ assertOutput(t, t.getOutputFormat().getName());
+ }
+
+ {
+ Template t = getConfiguration().getTemplate("t.xml");
+ assertEquals(XMLOutputFormat.INSTANCE, t.getOutputFormat());
+ assertOutput(t, t.getOutputFormat().getName());
+ }
+
+ {
+ Template t = getConfiguration().getTemplate("tWithHeader");
+ assertEquals(HTMLOutputFormat.INSTANCE, t.getOutputFormat());
+ assertOutput(t, t.getOutputFormat().getName());
+ }
+
+ getConfiguration().clearTemplateCache();
+ }
+ }
+
+ @Test
+ public void testStandardFileExtensions() throws Exception {
+ String commonContent = "${.outputFormat}";
+ addTemplate("t", commonContent);
+ addTemplate("t.ftl", commonContent);
+ addTemplate("t.ftlh", commonContent);
+ addTemplate("t.FTLH", commonContent);
+ addTemplate("t.fTlH", commonContent);
+ addTemplate("t.ftlx", commonContent);
+ addTemplate("t.FTLX", commonContent);
+ addTemplate("t.fTlX", commonContent);
+ addTemplate("tWithHeader.ftlx", "<#ftl outputFormat='HTML'>" + commonContent);
+
+ TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
+ for (int setupNumber = 1; setupNumber <= 3; setupNumber++) {
+ final OutputFormat cfgOutputFormat;
+ final OutputFormat ftlhOutputFormat;
+ final OutputFormat ftlxOutputFormat;
+ switch (setupNumber) {
+ case 1:
+ cfgOutputFormat = UndefinedOutputFormat.INSTANCE;
+ ftlhOutputFormat = HTMLOutputFormat.INSTANCE;
+ ftlxOutputFormat = XMLOutputFormat.INSTANCE;
+ break;
+ case 2:
+ cfgOutputFormat = RTFOutputFormat.INSTANCE;
+ cfgB.setOutputFormat(cfgOutputFormat);
+ ftlhOutputFormat = HTMLOutputFormat.INSTANCE;
+ ftlxOutputFormat = XMLOutputFormat.INSTANCE;
+ break;
+ case 3:
+ cfgOutputFormat = UndefinedOutputFormat.INSTANCE;
+ cfgB.unsetOutputFormat();
+ TemplateConfiguration.Builder tcbXML = new TemplateConfiguration.Builder();
+ tcbXML.setOutputFormat(XMLOutputFormat.INSTANCE);
+ cfgB.setTemplateConfigurations(
+ new ConditionalTemplateConfigurationFactory(
+ new OrMatcher(
+ new FileNameGlobMatcher("*.ftlh"),
+ new FileNameGlobMatcher("*.FTLH"),
+ new FileNameGlobMatcher("*.fTlH")),
+ tcbXML.build()));
+ ftlhOutputFormat = HTMLOutputFormat.INSTANCE; // can't be overidden
+ ftlxOutputFormat = XMLOutputFormat.INSTANCE;
+ break;
+ default:
+ throw new AssertionError();
+ }
+
+ setConfiguration(cfgB.build());
+ assertEquals(cfgOutputFormat, getConfiguration().getOutputFormat());
+
+ {
+ Template t = getConfiguration().getTemplate("t");
+ assertEquals(cfgOutputFormat, t.getOutputFormat());
+ assertOutput(t, t.getOutputFormat().getName());
+ }
+
+ {
+ Template t = getConfiguration().getTemplate("t.ftl");
+ assertEquals(cfgOutputFormat, t.getOutputFormat());
+ assertOutput(t, t.getOutputFormat().getName());
+ }
+
+ for (String name : new String[] { "t.ftlh", "t.FTLH", "t.fTlH" }) {
+ Template t = getConfiguration().getTemplate(name);
+ assertEquals(ftlhOutputFormat, t.getOutputFormat());
+ assertOutput(t, t.getOutputFormat().getName());
+ }
+
+ for (String name : new String[] { "t.ftlx", "t.FTLX", "t.fTlX" }) {
+ Template t = getConfiguration().getTemplate(name);
+ assertEquals(ftlxOutputFormat, t.getOutputFormat());
+ assertOutput(t, t.getOutputFormat().getName());
+ }
+
+ {
+ Template t = getConfiguration().getTemplate("tWithHeader.ftlx");
+ assertEquals(HTMLOutputFormat.INSTANCE, t.getOutputFormat());
+ assertOutput(t, t.getOutputFormat().getName());
+ }
+
+ getConfiguration().clearTemplateCache();
+ }
+ }
+
+ @Test
+ public void testStandardFileExtensionsSettingOverriding() throws Exception {
+ addTemplate("t.ftlx",
+ "${\"'\"} ${\"'\"?esc} ${\"'\"?noEsc}");
+ addTemplate("t.ftl",
+ "${'{}'} ${'{}'?esc} ${'{}'?noEsc}");
+
+ ConditionalTemplateConfigurationFactory tcfHTML = new ConditionalTemplateConfigurationFactory(
+ new FileNameGlobMatcher("t.*"),
+ new TemplateConfiguration.Builder()
+ .outputFormat(HTMLOutputFormat.INSTANCE)
+ .build());
+
+ ConditionalTemplateConfigurationFactory tcfNoAutoEsc = new ConditionalTemplateConfigurationFactory(
+ new FileNameGlobMatcher("t.*"),
+ new TemplateConfiguration.Builder()
+ .autoEscapingPolicy(DISABLE_AUTO_ESCAPING_POLICY)
+ .build());
+
+ {
+ TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
+
+ setConfiguration(cfgB.outputFormat(HTMLOutputFormat.INSTANCE).build());
+ assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it
+ setConfiguration(cfgB.templateConfigurations(tcfHTML).build());
+ assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it
+ setConfiguration(cfgB.templateConfigurations(tcfNoAutoEsc).build());
+ assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it
+ }
+
+ {
+ TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
+
+ setConfiguration(cfgB.recognizeStandardFileExtensions(false).build());
+ assertErrorContainsForNamed("t.ftlx", UndefinedOutputFormat.INSTANCE.getName());
+ setConfiguration(cfgB.outputFormat(HTMLOutputFormat.INSTANCE).build());
+ assertOutputForNamed("t.ftlx", "' ' '");
+ setConfiguration(cfgB.outputFormat(XMLOutputFormat.INSTANCE).build());
+ assertOutputForNamed("t.ftlx", "' ' '");
+ setConfiguration(cfgB.templateConfigurations(tcfHTML).build());
+ assertOutputForNamed("t.ftlx", "' ' '");
+ setConfiguration(cfgB.templateConfigurations(tcfNoAutoEsc).build());
+ assertOutputForNamed("t.ftlx", "' ' '");
+ }
+
+ {
+ TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
+ cfgB.setRecognizeStandardFileExtensions(true);
+
+ setConfiguration(cfgB.templateConfigurations(tcfHTML).build());
+ assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it
+ setConfiguration(cfgB.templateConfigurations(tcfNoAutoEsc).build());
+ assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it
+ }
+
+ {
+ TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
+
+ setConfiguration(cfgB.templateConfigurations(tcfHTML).build());
+ assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it
+ setConfiguration(cfgB.recognizeStandardFileExtensions(false).build());
+ assertOutputForNamed("t.ftlx", "' ' '");
+ }
+ }
+
+ @Test
+ public void testStandardFileExtensionsWithConstructor() throws Exception {
+ Configuration cfg = getConfiguration();
+ String commonFTL = "${'\\''}";
+ {
+ Template t = new Template("foo.ftl", commonFTL, cfg);
+ assertSame(UndefinedOutputFormat.INSTANCE, t.getOutputFormat());
+ StringWriter out = new StringWriter();
+ t.process(null, out);
+ assertEquals("'", out.toString());
+ }
+ {
+ Template t = new Template("foo.ftlx", commonFTL, cfg);
+ assertSame(XMLOutputFormat.INSTANCE, t.getOutputFormat());
+ StringWriter out = new StringWriter();
+ t.process(null, out);
+ assertEquals("'", out.toString());
+ }
+ {
+ Template t = new Template("foo.ftlh", commonFTL, cfg);
+ assertSame(HTMLOutputFormat.INSTANCE, t.getOutputFormat());
+ StringWriter out = new StringWriter();
+ t.process(null, out);
+ assertEquals("'", out.toString());
+ }
+ }
+
+ @Test
+ public void testStandardFileExtensionsFormatterImplOverriding() throws Exception {
+ addTemplate("t.ftlh", "${'a&x'}");
+
+ assertOutputForNamed("t.ftlh", "a&x");
+
+ setConfiguration(new TestConfigurationBuilder()
+ .registeredCustomOutputFormats(Collections.<OutputFormat>singleton(CustomHTMLOutputFormat.INSTANCE))
+ .build());
+ assertOutputForNamed("t.ftlh", "a&X");
+
+ setConfiguration(new TestConfigurationBuilder()
+ .registeredCustomOutputFormats(Collections.<OutputFormat>emptyList())
+ .build());
+ assertOutputForNamed("t.ftlh", "a&x");
+ }
+
+ @Test
+ public void testAutoEscapingSettingLayers() throws Exception {
+ addTemplate("t", "${'a&b'}");
+ addTemplate("tWithHeaderFalse", "<#ftl autoEsc=false>${'a&b'}");
+ addTemplate("tWithHeaderTrue", "<#ftl autoEsc=true>${'a&b'}");
+
+ TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder().outputFormat(XMLOutputFormat.INSTANCE);
+ assertEquals(ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
+
+ for (boolean cfgAutoEscaping : new boolean[] { true, false }) {
+ if (!cfgAutoEscaping) {
+ cfgB.setAutoEscapingPolicy(DISABLE_AUTO_ESCAPING_POLICY);
+ }
+ setConfiguration(cfgB.build());
+
+ {
+ Template t = getConfiguration().getTemplate("t");
+ if (cfgAutoEscaping) {
+ assertEquals(ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
+ assertOutput(t, "a&b");
+ } else {
+ assertEquals(DISABLE_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
+ assertOutput(t, "a&b");
+ }
+ }
+
+ {
+ Template t = getConfiguration().getTemplate("tWithHeaderFalse");
+ assertEquals(DISABLE_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
+ assertOutput(t, "a&b");
+ }
+
+ {
+ Template t = getConfiguration().getTemplate("tWithHeaderTrue");
+ assertEquals(ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, t.getAutoEscapingPolicy());
+ assertOutput(t, "a&b");
+ }
+
+ getConfiguration().clearTemplateCache();
+ }
+ }
+
+ @Test
+ public void testNumericalInterpolation() throws IOException, TemplateException {
+ setConfiguration(new TestConfigurationBuilder()
+ .registeredCustomOutputFormats(Collections.<OutputFormat>singleton(DummyOutputFormat.INSTANCE))
+ .build());
+ assertOutput(
+ "<#ftl outputFormat='dummy'>#{1.5}; #{1.5; m3}; ${'a.b'}",
+ "1\\.5; 1\\.500; a\\.b");
+ assertOutput(
+ "<#ftl outputFormat='dummy' autoEsc=false>#{1.5}; #{1.5; m3}; ${'a.b'}; ${'a.b'?esc}",
+ "1.5; 1.500; a.b; a\\.b");
+ assertOutput("<#ftl outputFormat='plainText'>#{1.5}", "1.5");
+ assertOutput("<#ftl outputFormat='HTML'>#{1.5}", "1.5");
+ assertOutput("#{1.5}", "1.5");
+ }
+
+ @Test
+ public void testUndefinedOutputFormat() throws IOException, TemplateException {
+ assertOutput("${'a < b'}; ${htmlPlain}; ${htmlMarkup}", "a < b; a < {h'}; <p>c");
+ assertErrorContains("${'x'?esc}", "undefined", "escaping", "?esc");
+ assertErrorContains("${'x'?noEsc}", "undefined", "escaping", "?noEsc");
+ }
+
+ @Test
+ public void testPlainTextOutputFormat() throws IOException, TemplateException {
+ assertOutput("<#ftl outputFormat='plainText'>${'a < b'}; ${htmlPlain}", "a < b; a < {h'}");
+ assertErrorContains("<#ftl outputFormat='plainText'>${htmlMarkup}", "plainText", "HTML", "conversion");
+ assertErrorContains("<#ftl outputFormat='plainText'>${'x'?esc}", "plainText", "escaping", "?esc");
+ assertErrorContains("<#ftl outputFormat='plainText'>${'x'?noEsc}", "plainText", "escaping", "?noEsc");
+ }
+
+ @Test
+ public void testAutoEscapingOnMOs() throws IOException, TemplateException {
+ for (boolean cfgAutoEscaping : new boolean[] { true, false }) {
+ String commonAutoEscFtl = "<#ftl outputFormat='HTML'>${'&'}";
+ if (cfgAutoEscaping) {
+ // Cfg default is autoEscaping true
+ assertOutput(commonAutoEscFtl, "&");
+ } else {
+ setConfiguration(createDefaultConfigurationBuilder()
+ .autoEscapingPolicy(DISABLE_AUTO_ESCAPING_POLICY)
+ .build());
+ assertOutput(commonAutoEscFtl, "&");
+ }
+
+ assertOutput(
+ "<#ftl outputFormat='RTF'>"
+ + "${rtfPlain} ${rtfMarkup} "
+ + "${htmlPlain} "
+ + "${xmlPlain}",
+ "\\\\par a & b \\par c "
+ + "a < \\{h'\\} "
+ + "a < \\{x'\\}");
+ assertOutput(
+ "<#ftl outputFormat='HTML'>"
+ + "${htmlPlain} ${htmlMarkup} "
+ + "${xmlPlain} "
+ + "${rtfPlain}",
+ "a < {h'} <p>c "
+ + "a < {x'} "
+ + "\\par a & b");
+ assertOutput(
+ "<#ftl outputFormat='XML'>"
+ + "${xmlPlain} ${xmlMarkup} "
+ + "${htmlPlain} "
+ + "${rtfPlain}",
+ "a < {x'} <p>c</p> "
+ + "a < {h'} "
+ + "\\par a & b");
+ assertErrorContains("<#ftl outputFormat='RTF'>${htmlMarkup}", "output format", "RTF", "HTML");
+ assertErrorContains("<#ftl outputFormat='RTF'>${xmlMarkup}", "output format", "RTF", "XML");
+ assertErrorContains("<#ftl outputFormat='HTML'>${rtfMarkup}", "output format", "HTML", "RTF");
+ assertErrorContains("<#ftl outputFormat='HTML'>${xmlMarkup}", "output format", "HTML", "XML");
+ assertErrorContains("<#ftl outputFormat='XML'>${rtfMarkup}", "output format", "XML", "RTF");
+ assertErrorContains("<#ftl outputFormat='XML'>${htmlMarkup}", "output format", "XML", "HTML");
+
+ for (int hasHeader = 0; hasHeader < 2; hasHeader++) {
+ assertOutput(
+ (hasHeader == 1 ? "<#ftl outputFormat='undefined'>" : "")
+ + "${xmlPlain} ${xmlMarkup} "
+ + "${htmlPlain} ${htmlMarkup} "
+ + "${rtfPlain} ${rtfMarkup}",
+ "a < {x'} <p>c</p> "
+ + "a < {h'} <p>c "
+ + "\\\\par a & b \\par c");
+ }
+ }
+ }
+
+ @Test
+ public void testStringLiteralsUseUndefinedOF() throws IOException, TemplateException {
+ String expectedOut = "& (&) &";
+ String ftl = "<#ftl outputFormat='XML'>${'&'} ${\"(${'&'})\"?noEsc} ${'&'}";
+
+ assertOutput(ftl, expectedOut);
+
+ addTemplate("t.xml", ftl);
+ assertOutputForNamed("t.xml", expectedOut);
+ }
+
+ @Test
+ public void testUnparsedTemplate() throws IOException, TemplateException {
+ String content = "<#ftl>a<#foo>b${x}";
+ {
+ Template t = Template.createPlainTextTemplate("x", content, getConfiguration());
+ Writer sw = new StringWriter();
+ t.process(null, sw);
+ assertEquals(content, sw.toString());
+ assertEquals(UndefinedOutputFormat.INSTANCE, t.getOutputFormat());
+ }
+
+ {
+ setConfiguration(new TestConfigurationBuilder().outputFormat(HTMLOutputFormat.INSTANCE).build());
+ Template t = Template.createPlainTextTemplate("x", content, getConfiguration());
+ Writer sw = new StringWriter();
+ t.process(null, sw);
+ assertEquals(content, sw.toString());
+ assertEquals(HTMLOutputFormat.INSTANCE, t.getOutputFormat());
+ }
+ }
+
+ @Test
+ public void testStringLiteralInterpolation() throws IOException, TemplateException {
+ Template t = new Template(null, "<#ftl outputFormat='XML'>${'&'} ${\"(${'&'})\"?noEsc}", getConfiguration());
+ assertEquals(XMLOutputFormat.INSTANCE, t.getOutputFormat());
+
+ assertOutput("${.outputFormat} ${'${.outputFormat}'} ${.outputFormat}",
+ "undefined undefined undefined");
+ assertOutput("<#ftl outputFormat='HTML'>${.outputFormat} ${'${.outputFormat}'} ${.outputFormat}",
+ "HTML HTML HTML");
+ assertOutput("${.outputFormat} <#outputFormat 'XML'>${'${.outputFormat}'}</#outputFormat> ${.outputFormat}",
+ "undefined XML undefined");
+ assertOutput("${'foo ${xmlPlain}'}", "foo a < {x'}");
+ assertOutput("${'${xmlMarkup}'}", "<p>c</p>");
+ assertErrorContains("${'${\"x\"?esc}'}", "?esc", "undefined");
+ assertOutput("<#ftl outputFormat='XML'>${'${xmlMarkup?esc} ${\"<\"?esc} ${\">\"} ${\"&\"?noEsc}'}",
+ "<p>c</p> < > &");
+ }
+
+ @Test
+ public void testStringBIsFail() {
+ assertErrorContains("<#ftl outputFormat='HTML'>${'<b>foo</b>'?esc?upperCase}", "string", "markup_output");
+ }
+
+ @Test
+ public void testConcatWithMOs() throws IOException, TemplateException {
+ assertOutput(
+ "${'\\'' + htmlMarkup} ${htmlMarkup + '\\''} ${htmlMarkup + htmlMarkup}",
+ "'<p>c <p>c' <p>c<p>c");
+ assertOutput(
+ "${'\\'' + htmlPlain} ${htmlPlain + '\\''} ${htmlPlain + htmlPlain}",
+ "'a < {h'} a < {h'}' a < {h'}a < {h'}");
+ assertErrorContains(
+ "<#ftl outputFormat='XML'>${'\\'' + htmlMarkup}",
+ "HTML", "XML", "conversion");
+ assertErrorContains(
+ "${xmlMarkup + htmlMarkup}",
+ "HTML", "XML", "Conversion", "common");
+ assertOutput(
+ "${xmlMarkup + htmlPlain}",
+ "<p>c</p>a < {h'}");
+ assertOutput(
+ "${xmlPlain + htmlMarkup}",
+ "a < {x'}<p>c");
+ assertOutput(
+ "${xmlPlain + htmlPlain}",
+ "a < {x'}a < {h'}");
+ assertOutput(
+ "${xmlPlain + htmlPlain + '\\''}",
+ "a < {x'}a < {h'}'");
+ assertOutput(
+ "${htmlPlain + xmlPlain + '\\''}",
+ "a < {h'}a < {x'}'");
+ assertOutput(
+ "${xmlPlain + htmlPlain + '\\''}",
+ "a < {x'}a < {h'}'");
+ assertOutput(
+ "<#ftl outputFormat='XML'>${htmlPlain + xmlPlain + '\\''}",
+ "a < {h'}a < {x'}'");
+ assertOutput(
+ "<#ftl outputFormat='RTF'>${htmlPlain + xmlPlain + '\\''}",
+ "a < \\{h'\\}a < \\{x'\\}'");
+ assertOutput(
+ "<#ftl outputFormat='XML'>${'\\'' + htmlPlain}",
+ "'a < {h'}");
+ assertOutput(
+ "<#ftl outputFormat='HTML'>${'\\'' + htmlPlain}",
+ "'a < {h'}");
+ assertOutput(
+ "<#ftl outputFormat='HTML'>${'\\'' + xmlPlain}",
+ "'a < {x'}");
+ assertOutput(
+ "<#ftl outputFormat='RTF'>${'\\'' + xmlPlain}",
+ "'a < \\{x'\\}");
+
+ assertOutput(
+ "<#assign x = '\\''><#assign x += xmlMarkup>${x}",
+ "'<p>c</p>");
+ assertOutput(
+ "<#assign x = xmlMarkup><#assign x += '\\''>${x}",
+ "<p>c</p>'");
+ assertOutput(
+ "<#assign x = xmlMarkup><#assign x += htmlPlain>${x}",
+ "<p>c</p>a < {h'}");
+ assertErrorContains(
+ "<#assign x = xmlMarkup><#assign x += htmlMarkup>${x}",
+ "HTML", "XML", "Conversion", "common");
+ }
+
+ @Test
+ public void testBlockAssignment() throws Exception {
+ for (String d : new String[] { "assign", "global", "local" }) {
+ String commonFTL =
+ "<#macro m>"
+ + "<#" + d + " x><p>${'&'}</#" + d + ">${x?isString?c} ${x} ${'&'} "
+ + "<#" + d + " x></#" + d + ">${x?isString?c}"
+ + "</#macro><@m />";
+ assertOutput(
+ commonFTL,
+ "true <p>& & true");
+ assertOutput(
+ "<#ftl outputFormat='HTML'>" + commonFTL,
+ "false <p>& & false");
+ }
+ }
+
+ @Test
+ public void testSpecialVariables() throws Exception {
+ String commonFTL = "${.outputFormat} ${.autoEsc?c}";
+
+ addTemplate("t.ftlx", commonFTL);
+ assertOutputForNamed("t.ftlx", "XML true");
+
+ addTemplate("t.ftlh", commonFTL);
+ assertOutputForNamed("t.ftlh", "HTML true");
+
+ addTemplate("t.ftl", commonFTL);
+ assertOutputForNamed("t.ftl", "undefined false");
+
+ addTemplate("tX.ftl", "<#ftl outputFormat='XML'>" + commonFTL);
+ addTemplate("tX.ftlx", commonFTL);
+ assertOutputForNamed("t.ftlx", "XML true");
+
+ addTemplate("tN.ftl", "<#ftl outputFormat='RTF' autoEsc=false>" + commonFTL);
+ assertOutputForNamed("tN.ftl", "RTF false");
+
+ assertOutput("${.output_format} ${.auto_esc?c}", "undefined false");
+ }
+
+ @Test
+ public void testEscAndNoEscBIBasics() throws IOException, TemplateException {
+ String commonFTL = "${'<x>'} ${'<x>'?esc} ${'<x>'?noEsc}";
+ addTemplate("t.ftlh", commonFTL);
+ addTemplate("t-noAuto.ftlh", "<#ftl autoEsc=false>" + commonFTL);
+ addTemplate("t.ftl", commonFTL);
+ assertOutputForNamed("t.ftlh", "<x> <x> <x>");
+ assertOutputForNamed("t-noAuto.ftlh", "<x> <x> <x>");
+ assertErrorContainsForNamed("t.ftl", "output format", "undefined");
+ }
+
+ @Test
+ public void testEscAndNoEscBIsOnMOs() throws IOException, TemplateException {
+ String xmlHdr = "<#ftl outputFormat='XML'>";
+
+ assertOutput(
+ xmlHdr + "${'&'?esc?esc} ${'&'?esc?noEsc} ${'&'?noEsc?esc} ${'&'?noEsc?noEsc}",
+ "& & & &");
+
+ for (String bi : new String[] { "esc", "noEsc" } ) {
+ assertOutput(
+ xmlHdr + "${rtfPlain?" + bi + "}",
+ "\\par a & b");
+ assertOutput(
+ xmlHdr + "<#setting numberFormat='0.0'>${1?" + bi + "}",
+ "1.0");
+ assertOutput(
+ xmlHdr + "<#setting booleanFormat='&y,&n'>${true?" + bi + "}",
+ bi.equals("esc") ? "&y" : "&y");
+ assertErrorContains(
+ xmlHdr + "${rtfMarkup?" + bi + "}",
+ "?" + bi, "output format", "RTF", "XML");
+ assertErrorContains(
+ xmlHdr + "${noSuchVar?" + bi + "}",
+ "noSuchVar", "null or missing");
+ assertErrorContains(
+ xmlHdr + "${[]?" + bi + "}",
+ "?" + bi, "xpected", "string", "sequence");
+ }
+ }
+
+ @Test
+ public void testMarkupStringBI() throws Exception {
+ assertOutput(
+ "${htmlPlain?markupString} ${htmlMarkup?markupString}",
+ "a < {h'} <p>c");
+ assertErrorContains(
+ "${noSuchVar?markupString}",
+ "noSuchVar", "null or missing");
+ assertErrorContains(
+ "${'x'?markupString}",
+ "xpected", "markup output", "string");
+ }
+
+ @Test
+ public void testOutputFormatDirective() throws Exception {
+ assertOutput(
+ "${.outputFormat}${'\\''} "
+ + "<#outputFormat 'HTML'>"
+ + "${.outputFormat}${'\\''} "
+ + "<#outputFormat 'XML'>${.outputFormat}${'\\''}</#outputFormat> "
+ + "${.outputFormat}${'\\''} "
+ + "</#outputFormat>"
+ + "${.outputFormat}${'\\''}",
+ "undefined' HTML' XML' HTML' undefined'");
+ assertOutput(
+ "<#ftl output_format='XML'>"
+ + "${.output_format}${'\\''} "
+ + "<#outputformat 'HTML'>${.output_format}${'\\''}</#outputformat> "
+ + "${.output_format}${'\\''}",
+ "XML' HTML' XML'");
+
+ // Custom format:
+ assertErrorContains(
+ "<#outputFormat 'dummy'></#outputFormat>",
+ "dummy", "nregistered");
+ setConfiguration(new TestConfigurationBuilder()
+ .registeredCustomOutputFormats(Collections.<OutputFormat>singleton(DummyOutputFormat.INSTANCE))
+ .build());
+ assertOutput(
+ "<#outputFormat 'dummy'>${.outputFormat}</#outputFormat>",
+ "dummy");
+
+ // Parse-time param expression:
+ assertOutput(
+ "<#outputFormat 'plain' + 'Text'>${.outputFormat}</#outputFormat>",
+ "plainText");
+ assertErrorContains(
+ "<#outputFormat 'plain' + someVar + 'Text'></#outputFormat>",
+ "someVar", "parse-time");
+ assertErrorContains(
+ "<#outputFormat 'plainText'?upperCase></#outputFormat>",
+ "?upperCase", "parse-time");
+ assertErrorContains(
+ "<#outputFormat true></#outputFormat>",
+ "string", "boolean");
+
+ // Naming convention:
+ assertErrorContains(
+ "<#outputFormat 'HTML'></#outputformat>",
+ "convention", "#outputFormat", "#outputformat");
+ assertErrorContains(
+ "<#outputformat 'HTML'></#outputFormat>",
+ "convention", "#outputFormat", "#outputformat");
+
+ // Empty block:
+ assertOutput(
+ "${.output_format} "
+ + "<#outputformat 'HTML'></#outputformat>"
+ + "${.output_format}",
+ "undefined undefined");
+
+ // WS stripping:
+ assertOutput(
+ "${.output_format}\n"
+ + "<#outputformat 'HTML'>\n"
+ + " x\n"
+ + "</#outputformat>\n"
+ + "${.output_format}",
+ "undefined\n x\nundefined");
+ }
+
+ @Test
+ public void testAutoEscAndNoAutoEscDirectives() throws Exception {
+ assertOutput(
+ "<#ftl outputFormat='XML'>"
+ + "${.autoEsc?c}${'&'} "
+ + "<#noAutoEsc>"
+ + "${.autoEsc?c}${'&'} "
+ + "<#autoEsc>${.autoEsc?c}${'&'}</#autoEsc> "
+ + "${.autoEsc?c}${'&'} "
+ + "</#noAutoEsc>"
+ + "${.autoEsc?c}${'&'}",
+ "true& false& true& false& true&");
+ assertOutput(
+ "<#ftl auto_esc=false output_format='XML'>"
+ + "${.auto_esc?c}${'&'} "
+ + "<#autoesc>${.auto_esc?c}${'&'}</#autoesc> "
+ + "${.auto_esc?c}${'&'}",
+ "false& true& false&");
+
+ // Bad came case:
+ assertErrorContains(
+ "<#noAutoesc></#noAutoesc>",
+ "Unknown directive");
+ assertErrorContains(
+ "<#noautoEsc></#noautoEsc>",
+ "Unknown directive");
+
+ setConfiguration(new TestConfigurationBuilder()
+ .outputFormat(XMLOutputFormat.INSTANCE)
+ .build());
+
+ // Empty block:
+ assertOutput(
+ "${.auto_esc?c} "
+ + "<#noautoesc></#noautoesc>"
+ + "${.auto_esc?c}",
+ "true true");
+
+ // WS stripping:
+ assertOutput(
+ "${.auto_esc?c}\n"
+ + "<#noautoesc>\n"
+ + " x\n"
+ + "</#noautoesc>\n"
+ + "${.auto_esc?c}",
+ "true\n x\ntrue");
+
+
+ // Naming convention:
+ assertErrorContains(
+ "<#autoEsc></#autoesc>",
+ "convention", "#autoEsc", "#autoesc");
+ assertErrorContains(
+ "<#autoesc></#autoEsc>",
+ "convention", "#autoEsc", "#autoesc");
+ assertErrorContains(
+ "<#noAutoEsc></#noautoesc>",
+ "convention", "#noAutoEsc", "#noautoesc");
+ assertErrorContains(
+ "<#noautoesc></#noAutoEsc>",
+ "convention", "#noAutoEsc", "#noautoesc");
+ }
+
+ @Test
+ public void testExplicitAutoEscBannedForNonMarkup() throws Exception {
+ // While this restriction is technically unnecessary, we can catch a dangerous and probably common user
+ // misunderstanding.
+ assertErrorContains("<#ftl autoEsc=true>", "can't do escaping", "undefined");
+ assertErrorContains("<#ftl outputFormat='plainText' autoEsc=true>", "can't do escaping", "plainText");
+ assertErrorContains("<#ftl autoEsc=true outputFormat='plainText'>", "can't do escaping", "plainText");
+ assertOutput("<#ftl autoEsc=true outputFormat='HTML'>", "");
+ assertOutput("<#ftl outputFormat='HTML' autoEsc=true>", "");
+ assertOutput("<#ftl autoEsc=false>", "");
+
+ assertErrorContains("<#autoEsc></#autoEsc>", "can't do escaping", "undefined");
+ assertErrorContains("<#ftl outputFormat='plainText'><#autoEsc></#autoEsc>", "can't do escaping", "plainText");
+ assertOutput("<#ftl outputFormat='plainText'><#outputFormat 'XML'><#autoEsc></#autoEsc></#outputFormat>", "");
+ assertOutput("<#ftl outputFormat='HTML'><#autoEsc></#autoEsc>", "");
+ assertOutput("<#noAutoEsc></#noAutoEsc>", "");
+ }
+
+ @Test
+ public void testAutoEscPolicy() throws Exception {
+ TestConfigurationBuilder cfgB = createDefaultConfigurationBuilder();
+ cfgB.setRegisteredCustomOutputFormats(ImmutableList.<OutputFormat>of(
+ SeldomEscapedOutputFormat.INSTANCE, DummyOutputFormat.INSTANCE));
+ assertEquals(ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy());
+
+ String commonFTL = "${'.'} ${.autoEsc?c}";
+ String notEsced = ". false";
+ String esced = "\\. true";
+
+ for (int autoEscPolicy : new int[] {
+ ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY,
+ ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY,
+ DISABLE_AUTO_ESCAPING_POLICY }) {
+ cfgB.setAutoEscapingPolicy(autoEscPolicy);
+
+ String sExpted = autoEscPolicy == ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY ? esced : notEsced;
+ cfgB.setOutputFormat(SeldomEscapedOutputFormat.INSTANCE);
+ setConfiguration(cfgB.build());
+ assertOutput(commonFTL, sExpted);
+ cfgB.setOutputFormat(UndefinedOutputFormat.INSTANCE);
+ setConfiguration(cfgB.build());
+ assertOutput("<#ftl outputFormat='seldomEscaped'>" + commonFTL, sExpted);
+ assertOutput("<#outputFormat 'seldomEscaped'>" + commonFTL + "</#outputFormat>", sExpted);
+
+ String dExpted = autoEscPolicy == DISABLE_AUTO_ESCAPING_POLICY ? notEsced : esced;
+ cfgB.setOutputFormat(DummyOutputFormat.INSTANCE);
+ setConfiguration(cfgB.build());
+ assertOutput(commonFTL, dExpted);
+ cfgB.setOutputFormat(UndefinedOutputFormat.INSTANCE);
+ setConfiguration(cfgB.build());
+ assertOutput("<#ftl outputFormat='dummy'>" + commonFTL, dExpted);
+ assertOutput("<#outputFormat 'dummy'>" + commonFTL + "</#outputFormat>", dExpted);
+
+ cfgB.setOutputFormat(DummyOutputFormat.INSTANCE);
+ setConfiguration(cfgB.build());
+ assertOutput(
+ commonFTL
+ + "<#outputFormat 'seldomEscaped'>"
+ + commonFTL
+ + "<#outputFormat 'dummy'>"
+ + commonFTL
+ + "</#outputFormat>"
+ + commonFTL
+ + "<#outputFormat 'plainText'>"
+ + commonFTL
+ + "</#outputFormat>"
+ + commonFTL
+ + "<#noAutoEsc>"
+ + commonFTL
+ + "</#noAutoEsc>"
+ + commonFTL
+ + "<#autoEsc>"
+ + commonFTL
+ + "</#autoEsc>"
+ + commonFTL
+ + "</#outputFormat>"
+ + commonFTL
+ + "<#noAutoEsc>"
+ + commonFTL
+ + "</#noAutoEsc>"
+ + commonFTL
+ + "<#autoEsc>"
+ + commonFTL
+ + "</#autoEsc>"
+ + commonFTL
+ ,
+ dExpted
+ + sExpted
+ + dExpted
+ + sExpted
+ + notEsced
+ + sExpted
+ + notEsced
+ + sExpted
+ + esced
+ + sExpted
+ + dExpted
+ + notEsced
+ + dExpted
+ + esced
+ + dExpted);
+ }
+ }
+
+ @Test
+ public void testDynamicParsingBIsInherticContextOutputFormat() throws Exception {
+ // Dynamic parser BI-s are supposed to use the ParsingConfiguration of the calling template, and ignore anything
+ // inside the calling template itself. Except, the outputFormat and autoEscapingPolicy has to come from the
+ // calling lexical context.
+
+ String commonFTL
+ = "Eval: ${'.outputFormat'?eval}; "
+ + "Interpret: <#assign ipd = r\"${.outputFormat} ${'{&}'}\"?interpret><@ipd/>";
+ addTemplate("t.ftlh", commonFTL);
+ addTemplate("t2.ftlh", "<#outputFormat 'RTF'>" + commonFTL + "</#outputFormat>");
+
+ assertOutputForNamed(
+ "t.ftlh",
+ "Eval: HTML; Interpret: HTML {&}");
+ assertOutputForNamed(
+ "t2.ftlh",
+ "Eval: RTF; Interpret: RTF \\{&\\}");
+ assertOutput(
+ commonFTL,
+ "Eval: undefined; Interpret: undefined {&}");
+ assertOutput(
+ "<#ftl outputFormat='RTF'>" + commonFTL + "\n"
+ + "<#outputFormat 'XML'>" + commonFTL + "</#outputFormat>",
+ "Eval: RTF; Interpret: RTF \\{&\\}\n"
+ + "Eval: XML; Interpret: XML {&}");
+
+ // parser.autoEscapingPolicy is inherited too:
+ assertOutput(
+ "<#ftl autoEsc=false outputFormat='XML'>"
+ + commonFTL + " ${'.autoEsc'?eval?c}",
+ "Eval: XML; Interpret: XML {&} false");
+ assertOutput(
+ "<#ftl outputFormat='XML'>"
+ + "<#noAutoEsc>" + commonFTL + " ${'.autoEsc'?eval?c}</#noAutoEsc>",
+ "Eval: XML; Interpret: XML {&} false");
+ assertOutput(
+ "<#ftl autoEsc=false outputFormat='XML'>"
+ + "<#noAutoEsc>" + commonFTL + " ${'.autoEsc'?eval?c}</#noAutoEsc>",
+ "Eval: XML; Interpret: XML {&} false");
+ assertOutput(
+ "<#ftl autoEsc=false outputFormat='XML'>"
+ + "<#autoEsc>" + commonFTL + " ${'.autoEsc'?eval?c}</#autoEsc>",
+ "Eval: XML; Interpret: XML {&} true");
+ assertOutput(
+ "${.outputFormat}<#assign ftl='<#ftl outputFormat=\\'RTF\\'>$\\{.outputFormat}'> <@ftl?interpret/>",
+ "undefined RTF");
+ assertOutput(
+ "${.outputFormat}<#outputFormat 'RTF'>"
+ + "<#assign ftl='$\\{.outputFormat}'> <@ftl?interpret/> ${'.outputFormat'?eval}"
+ + "</#outputFormat>",
+ "undefined RTF RTF");
+ }
+
+ @Test
+ public void testBannedBIsWhenAutoEscaping() throws Exception {
+ for (String biName : new String[] { "html", "xhtml", "rtf", "xml" }) {
+ String commonFTL = "${'x'?" + biName + "}";
+ assertOutput(commonFTL, "x");
+ assertErrorContains("<#ftl outputFormat='HTML'>" + commonFTL,
+ "?" + biName, "HTML", "double-escaping");
+ assertErrorContains("<#ftl outputFormat='HTML'>${'${\"x\"?" + biName + "}'}",
+ "?" + biName, "HTML", "double-escaping");
+ assertOutput("<#ftl outputFormat='plainText'>" + commonFTL, "x");
+ assertOutput("<#ftl outputFormat='HTML' autoEsc=false>" + commonFTL, "x");
+ assertOutput("<#ftl outputFormat='HTML'><#noAutoEsc>" + commonFTL + "</#noAutoEsc>", "x");
+ assertOutput("<#ftl outputFormat='HTML'><#outputFormat 'plainText'>" + commonFTL + "</#outputFormat>",
+ "x");
+ }
+ }
+
+ @Test
+ public void testLegacyEscaperBIsBypassMOs() throws Exception {
+ assertOutput("${htmlPlain?html} ${htmlMarkup?html}", "a < {h'} <p>c");
+ assertErrorContains("${xmlPlain?html}", "?html", "string", "markup_output", "XML");
+ assertErrorContains("${xmlMarkup?html}", "?html", "string", "markup_output", "XML");
+ assertErrorContains("${rtfPlain?html}", "?html", "string", "markup_output", "RTF");
+ assertErrorContains("${rtfMarkup?html}", "?html", "string", "markup_output", "RTF");
+
+ assertOutput("${htmlPlain?xhtml} ${htmlMarkup?xhtml}", "a < {h'} <p>c");
+ assertErrorContains("${xmlPlain?xhtml}", "?xhtml", "string", "markup_output", "XML");
+ assertErrorContains("${xmlMarkup?xhtml}", "?xhtml", "string", "markup_output", "XML");
+ assertErrorContains("${rtfPlain?xhtml}", "?xhtml", "string", "markup_output", "RTF");
+ assertErrorContains("${rtfMarkup?xhtml}", "?xhtml", "string", "markup_output", "RTF");
+
+ assertOutput("${xmlPlain?xml} ${xmlMarkup?xml}", "a < {x'} <p>c</p>");
+ assertOutput("${htmlPlain?xml} ${htmlMarkup?xml}", "a < {h'} <p>c");
+ assertErrorContains("${rtfPlain?xml}", "?xml", "string", "markup_output", "RTF");
+ assertErrorContains("${rtfMarkup?xml}", "?xml", "string", "markup_output", "RTF");
+
+ assertOutput("${rtfPlain?rtf} ${rtfMarkup?rtf}", "\\\\par a & b \\par c");
+ assertErrorContains("${xmlPlain?rtf}", "?rtf", "string", "markup_output", "XML");
+ assertErrorContains("${xmlMarkup?rtf}", "?rtf", "string", "markup_output", "XML");
+ assertErrorContains("${htmlPlain?rtf}", "?rtf", "string", "markup_output", "HTML");
+ assertErrorContains("${htmlMarkup?rtf}", "?rtf", "string", "markup_output", "HTML");
+ }
+
+ @Test
+ public void testBannedDirectivesWhenAutoEscaping() throws Exception {
+ String commonFTL = "<#escape x as x?html>x</#escape>";
+ assertOutput(commonFTL, "x");
+ assertErrorContains("<#ftl outputFormat='HTML'>" + commonFTL, "escape", "HTML", "double-escaping");
+ assertOutput("<#ftl outputFormat='plainText'>" + commonFTL, "x");
+ assertOutput("<#ftl outputFormat='HTML' autoEsc=false>" + commonFTL, "x");
+ assertOutput("<#ftl outputFormat='HTML'><#noAutoEsc>" + commonFTL + "</#noAutoEsc>", "x");
+ assertOutput("<#ftl outputFormat='HTML'><#outputFormat 'plainText'>" + commonFTL + "</#outputFormat>", "x");
+ }
+
+ @Test
+ public void testCombinedOutputFormats() throws Exception {
+ assertOutput(
+ "<#outputFormat 'XML{HTML}'>${'\\''}</#outputFormat>",
+ "&#39;");
+ assertOutput(
+ "<#outputFormat 'HTML{RTF{XML}}'>${'<a=\\'{}\\' />'}</#outputFormat>",
+ "&lt;a=&apos;\\{\\}&apos; /&gt;");
+
+ String commonFtl = "${'\\''} <#outputFormat '{HTML}'>${'\\''}</#outputFormat>";
+ String commonOutput = "' &#39;";
+ assertOutput(
+ "<#outputFormat 'XML'>" + commonFtl + "</#outputFormat>",
+ commonOutput);
+ assertOutput(
+ "<#ftl outputFormat='XML'>" + commonFtl,
+ commonOutput);
+ addTemplate("t.ftlx", commonFtl);
+ assertOutputForNamed(
+ "t.ftlx",
+ commonOutput);
+
+ assertErrorContains(
+ commonFtl,
+ ParseException.class, "{...}", "markup", UndefinedOutputFormat.INSTANCE.getName());
+ assertErrorContains(
+ "<#ftl outputFormat='plainText'>" + commonFtl,
+ ParseException.class, "{...}", "markup", PlainTextOutputFormat.INSTANCE.getName());
+ assertErrorContains(
+ "<#ftl outputFormat='RTF'><#outputFormat '{plainText}'></#outputFormat>",
+ ParseException.class, "{...}", "markup", PlainTextOutputFormat.INSTANCE.getName());
+ assertErrorContains(
+ "<#ftl outputFormat='RTF'><#outputFormat '{noSuchFormat}'></#outputFormat>",
+ ParseException.class, "noSuchFormat", "registered");
+ assertErrorContains(
+ "<#outputFormat 'noSuchFormat{HTML}'></#outputFormat>",
+ ParseException.class, "noSuchFormat", "registered");
+ assertErrorContains(
+ "<#outputFormat 'HTML{noSuchFormat}'></#outputFormat>",
+ ParseException.class, "noSuchFormat", "registered");
+ }
+
+ @Test
+ public void testHasContentBI() throws Exception {
+ assertOutput("${htmlMarkup?hasContent?c} ${htmlPlain?hasContent?c}", "true true");
+ assertOutput("<#ftl outputFormat='HTML'>${''?esc?hasContent?c} ${''?noEsc?hasContent?c}", "false false");
+ }
+
+ @Test
+ public void testMissingVariables() throws Exception {
+ for (String ftl : new String[] {
+ "${noSuchVar}",
+ "<#ftl outputFormat='XML'>${noSuchVar}",
+ "<#ftl outputFormat='XML'>${noSuchVar?esc}",
+ "<#ftl outputFormat='XML'>${'x'?esc + noSuchVar}"
+ }) {
+ assertErrorContains(ftl, InvalidReferenceException.class, "noSuchVar", "null or missing");
+ }
+ }
+
+ @Test
+ public void testIsMarkupOutputBI() throws Exception {
+ addToDataModel("m1", HTMLOutputFormat.INSTANCE.fromPlainTextByEscaping("x"));
+ addToDataModel("m2", HTMLOutputFormat.INSTANCE.fromMarkup("x"));
+ addToDataModel("s", "x");
+ assertOutput("${m1?isMarkupOutput?c} ${m2?isMarkupOutput?c} ${s?isMarkupOutput?c}", "true true false");
+ assertOutput("${m1?is_markup_output?c}", "true");
+ }
+
+ private TestConfigurationBuilder createDefaultConfigurationBuilder() throws TemplateModelException {
+ return new TestConfigurationBuilder()
+ .templateConfigurations(
+ new ConditionalTemplateConfigurationFactory(
+ new FileNameGlobMatcher("*.xml"),
+ new TemplateConfiguration.Builder()
+ .outputFormat(XMLOutputFormat.INSTANCE)
+ .build()))
+ .cacheStorage(NullCacheStorage.INSTANCE); // Prevent caching as we change the cfgB between build().
+ }
+
+ @Before
+ public void addCommonDataModelVariables() throws TemplateModelException {
+ addToDataModel("rtfPlain", RTFOutputFormat.INSTANCE.fromPlainTextByEscaping("\\par a & b"));
+ addToDataModel("rtfMarkup", RTFOutputFormat.INSTANCE.fromMarkup("\\par c"));
+ addToDataModel("htmlPlain", HTMLOutputFormat.INSTANCE.fromPlainTextByEscaping("a < {h'}"));
+ addToDataModel("htmlMarkup", HTMLOutputFormat.INSTANCE.fromMarkup("<p>c"));
+ addToDataModel("xmlPlain", XMLOutputFormat.INSTANCE.fromPlainTextByEscaping("a < {x'}"));
+ addToDataModel("xmlMarkup", XMLOutputFormat.INSTANCE.fromMarkup("<p>c</p>"));
+ }
+
+ @Override
+ protected Configuration createDefaultConfiguration() throws TemplateModelException {
+ return createDefaultConfigurationBuilder().build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParseTimeParameterBIErrorMessagesTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParseTimeParameterBIErrorMessagesTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParseTimeParameterBIErrorMessagesTest.java
new file mode 100644
index 0000000..77b46de
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParseTimeParameterBIErrorMessagesTest.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 org.apache.freemarker.core;
+
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+public class ParseTimeParameterBIErrorMessagesTest extends TemplateTest {
+
+ @Test
+ public void testThen() throws Exception {
+ assertErrorContains("${true?then}", "expecting", "\"(\"");
+ assertErrorContains("${true?then + 1}", "expecting", "\"(\"");
+ assertErrorContains("${true?then()}", "?then", "2 parameters");
+ assertErrorContains("${true?then(1)}", "?then", "2 parameters");
+ assertOutput("${true?then(1, 2)}", "1");
+ assertErrorContains("${true?then(1, 2, 3)}", "?then", "2 parameters");
+ }
+
+ @Test
+ public void testSwitch() throws Exception {
+ assertErrorContains("${true?switch}", "expecting", "\"(\"");
+ assertErrorContains("${true?switch + 1}", "expecting", "\"(\"");
+ assertErrorContains("${true?switch()}", "at least 2 parameters");
+ assertErrorContains("${true?switch(true)}", "at least 2 parameters");
+ assertOutput("${true?switch(true, 1)}", "1");
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
new file mode 100644
index 0000000..8f20d6c
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+import org.junit.Test;
+
+public class ParsingErrorMessagesTest {
+
+ private Configuration cfg = new TestConfigurationBuilder()
+ .tagSyntax(ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX)
+ .build();
+
+ @Test
+ public void testNeedlessInterpolation() {
+ assertErrorContains("<#if ${x} == 3></#if>", "instead of ${");
+ assertErrorContains("<#if ${x == 3}></#if>", "instead of ${");
+ assertErrorContains("<@foo ${x == 3} />", "instead of ${");
+ }
+
+ @Test
+ public void testWrongDirectiveNames() {
+ assertErrorContains("<#foo />", "nknown directive", "#foo");
+ assertErrorContains("<#set x = 1 />", "nknown directive", "#set", "#assign");
+ assertErrorContains("<#iterator></#iterator>", "nknown directive", "#iterator", "#list");
+ }
+
+ @Test
+ public void testBug402() {
+ assertErrorContains("<#list 1..i as k>${k}<#list>", "existing directive", "malformed", "#list");
+ assertErrorContains("<#assign>", "existing directive", "malformed", "#assign");
+ assertErrorContains("</#if x>", "existing directive", "malformed", "#if");
+ assertErrorContains("<#compress x>", "existing directive", "malformed", "#compress");
+ }
+
+ @Test
+ public void testUnclosedDirectives() {
+ assertErrorContains("<#macro x>", "#macro", "unclosed");
+ assertErrorContains("<#function x>", "#macro", "unclosed");
+ assertErrorContains("<#assign x>", "#assign", "unclosed");
+ assertErrorContains("<#macro m><#local x>", "#local", "unclosed");
+ assertErrorContains("<#global x>", "#global", "unclosed");
+ assertErrorContains("<@foo>", "@...", "unclosed");
+ assertErrorContains("<#list xs as x>", "#list", "unclosed");
+ assertErrorContains("<#list xs as x><#if x>", "#if", "unclosed");
+ assertErrorContains("<#list xs as x><#if x><#if q><#else>", "#if", "unclosed");
+ assertErrorContains("<#list xs as x><#if x><#if q><#else><#macro x>qwe", "#macro", "unclosed");
+ assertErrorContains("${(blah", "\"(\"", "unclosed");
+ assertErrorContains("${blah", "\"{\"", "unclosed");
+ }
+
+ @Test
+ public void testInterpolatingClosingsErrors() {
+ assertErrorContains("${x", "unclosed");
+ assertErrorContains("<#assign x = x}>", "\"}\"", "open");
+ // TODO assertErrorContains("<#assign x = '${x'>", "unclosed");
+ }
+
+ private void assertErrorContains(String ftl, String... expectedSubstrings) {
+ assertErrorContains(false, ftl, expectedSubstrings);
+ assertErrorContains(true, ftl, expectedSubstrings);
+ }
+
+ private void assertErrorContains(boolean squareTags, String ftl, String... expectedSubstrings) {
+ try {
+ if (squareTags) {
+ ftl = ftl.replace('<', '[').replace('>', ']');
+ }
+ new Template("adhoc", ftl, cfg);
+ fail("The template had to fail");
+ } catch (ParseException e) {
+ String msg = e.getMessage();
+ for (String needle: expectedSubstrings) {
+ if (needle.startsWith("\\!")) {
+ String netNeedle = needle.substring(2);
+ if (msg.contains(netNeedle)) {
+ fail("The message shouldn't contain substring " + _StringUtil.jQuote(netNeedle) + ":\n" + msg);
+ }
+ } else if (!msg.contains(needle)) {
+ fail("The message didn't contain substring " + _StringUtil.jQuote(needle) + ":\n" + msg);
+ }
+ }
+ showError(e);
+ } catch (IOException e) {
+ // Won't happen
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void showError(Throwable e) {
+ //System.out.println(e);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java
new file mode 100644
index 0000000..702a254
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjectWrapperTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+import static org.apache.freemarker.test.hamcerst.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.impl.DefaultArrayAdapter;
+import org.apache.freemarker.core.model.impl.DefaultListAdapter;
+import org.apache.freemarker.core.model.impl.DefaultMapAdapter;
+import org.apache.freemarker.core.model.impl.DefaultNonListCollectionAdapter;
+import org.apache.freemarker.core.model.impl.DefaultObjectWrapperTest.TestBean;
+import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
+import org.apache.freemarker.core.model.impl.SimpleDate;
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.junit.Test;
+
+public class RestrictedObjectWrapperTest {
+
+ @Test
+ public void testBasics() throws TemplateModelException {
+ PostConstruct.class.toString();
+ RestrictedObjectWrapper ow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+ testCustomizationCommonPart(ow);
+ assertTrue(ow.wrap(Collections.emptyMap()) instanceof DefaultMapAdapter);
+ assertTrue(ow.wrap(Collections.emptyList()) instanceof DefaultListAdapter);
+ assertTrue(ow.wrap(new boolean[] { }) instanceof DefaultArrayAdapter);
+ assertTrue(ow.wrap(new HashSet()) instanceof DefaultNonListCollectionAdapter);
+ }
+
+ @SuppressWarnings("boxing")
+ private void testCustomizationCommonPart(RestrictedObjectWrapper ow) throws TemplateModelException {
+ assertTrue(ow.wrap("x") instanceof SimpleScalar);
+ assertTrue(ow.wrap(1.5) instanceof SimpleNumber);
+ assertTrue(ow.wrap(new Date()) instanceof SimpleDate);
+ assertEquals(TemplateBooleanModel.TRUE, ow.wrap(true));
+
+ try {
+ ow.wrap(new TestBean());
+ fail();
+ } catch (TemplateModelException e) {
+ assertThat(e.getMessage(), containsStringIgnoringCase("type"));
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java
new file mode 100644
index 0000000..43ff3bf
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/RestrictedObjetWrapperTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateCollectionModelEx;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx2;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class RestrictedObjetWrapperTest {
+
+ @Test
+ public void testDoesNotAllowAPIBuiltin() throws TemplateModelException {
+ RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+
+ TemplateModelWithAPISupport map = (TemplateModelWithAPISupport) sow.wrap(new HashMap());
+ try {
+ map.getAPI();
+ fail();
+ } catch (TemplateException e) {
+ assertThat(e.getMessage(), containsString("?api"));
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void testCanWrapBasicTypes() throws TemplateModelException {
+ RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+ assertTrue(sow.wrap("s") instanceof TemplateScalarModel);
+ assertTrue(sow.wrap(1) instanceof TemplateNumberModel);
+ assertTrue(sow.wrap(true) instanceof TemplateBooleanModel);
+ assertTrue(sow.wrap(new Date()) instanceof TemplateDateModel);
+ assertTrue(sow.wrap(new ArrayList()) instanceof TemplateSequenceModel);
+ assertTrue(sow.wrap(new String[0]) instanceof TemplateSequenceModel);
+ assertTrue(sow.wrap(new ArrayList().iterator()) instanceof TemplateCollectionModel);
+ assertTrue(sow.wrap(new HashSet()) instanceof TemplateCollectionModelEx);
+ assertTrue(sow.wrap(new HashMap()) instanceof TemplateHashModelEx2);
+ assertNull(sow.wrap(null));
+ }
+
+ @Test
+ public void testWontWrapDOM() throws SAXException, IOException, ParserConfigurationException,
+ TemplateModelException {
+ DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ InputSource is = new InputSource();
+ is.setCharacterStream(new StringReader("<doc><sub a='1' /></doc>"));
+ Document doc = db.parse(is);
+
+ RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+ try {
+ sow.wrap(doc);
+ fail();
+ } catch (TemplateModelException e) {
+ assertThat(e.getMessage(), containsString("won't wrap"));
+ }
+ }
+
+ @Test
+ public void testWontWrapGenericObjects() {
+ RestrictedObjectWrapper sow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+ try {
+ sow.wrap(new File("/x"));
+ fail();
+ } catch (TemplateModelException e) {
+ assertThat(e.getMessage(), containsString("won't wrap"));
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java
new file mode 100644
index 0000000..cf14b93
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java
@@ -0,0 +1,371 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+import static org.junit.Assert.*;
+
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.util._DateUtil;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+public class SQLTimeZoneTest extends TemplateTest {
+
+ private final static TimeZone GMT_P02 = TimeZone.getTimeZone("GMT+02");
+
+ private TimeZone lastDefaultTimeZone;
+
+ private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
+ {
+ df.setTimeZone(_DateUtil.UTC);
+ }
+
+ // Values that JDBC in GMT+02 would produce
+ private final java.sql.Date sqlDate = new java.sql.Date(utcToLong("2014-07-11T22:00:00")); // 2014-07-12
+ private final Time sqlTime = new Time(utcToLong("1970-01-01T10:30:05")); // 12:30:05
+ private final Timestamp sqlTimestamp = new Timestamp(utcToLong("2014-07-12T10:30:05")); // 2014-07-12T12:30:05
+ private final Date javaDate = new Date(utcToLong("2014-07-12T10:30:05")); // 2014-07-12T12:30:05
+ private final Date javaDayErrorDate = new Date(utcToLong("2014-07-11T22:00:00")); // 2014-07-12T12:30:05
+
+ public TimeZone getLastDefaultTimeZone() {
+ return lastDefaultTimeZone;
+ }
+
+ public void setLastDefaultTimeZone(TimeZone lastDefaultTimeZone) {
+ this.lastDefaultTimeZone = lastDefaultTimeZone;
+ }
+
+ public java.sql.Date getSqlDate() {
+ return sqlDate;
+ }
+
+ public Time getSqlTime() {
+ return sqlTime;
+ }
+
+ public Timestamp getSqlTimestamp() {
+ return sqlTimestamp;
+ }
+
+ public Date getJavaDate() {
+ return javaDate;
+ }
+
+ public Date getJavaDayErrorDate() {
+ return javaDayErrorDate;
+ }
+
+ private static final String FTL =
+ "${sqlDate} ${sqlTime} ${sqlTimestamp} ${javaDate?datetime}\n"
+ + "${sqlDate?string.iso_fz} ${sqlTime?string.iso_fz} "
+ + "${sqlTimestamp?string.iso_fz} ${javaDate?datetime?string.iso_fz}\n"
+ + "${sqlDate?string.xs_fz} ${sqlTime?string.xs_fz} "
+ + "${sqlTimestamp?string.xs_fz} ${javaDate?datetime?string.xs_fz}\n"
+ + "${sqlDate?string.xs} ${sqlTime?string.xs} "
+ + "${sqlTimestamp?string.xs} ${javaDate?datetime?string.xs}\n"
+ + "<#setting time_zone='GMT'>\n"
+ + "${sqlDate} ${sqlTime} ${sqlTimestamp} ${javaDate?datetime}\n"
+ + "${sqlDate?string.iso_fz} ${sqlTime?string.iso_fz} "
+ + "${sqlTimestamp?string.iso_fz} ${javaDate?datetime?string.iso_fz}\n"
+ + "${sqlDate?string.xs_fz} ${sqlTime?string.xs_fz} "
+ + "${sqlTimestamp?string.xs_fz} ${javaDate?datetime?string.xs_fz}\n"
+ + "${sqlDate?string.xs} ${sqlTime?string.xs} "
+ + "${sqlTimestamp?string.xs} ${javaDate?datetime?string.xs}\n";
+
+ private static final String OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2
+ = "2014-07-12 12:30:05 2014-07-12T12:30:05 2014-07-12T12:30:05\n"
+ + "2014-07-12 12:30:05+02:00 2014-07-12T12:30:05+02:00 2014-07-12T12:30:05+02:00\n"
+ + "2014-07-12+02:00 12:30:05+02:00 2014-07-12T12:30:05+02:00 2014-07-12T12:30:05+02:00\n"
+ + "2014-07-12 12:30:05 2014-07-12T12:30:05+02:00 2014-07-12T12:30:05+02:00\n";
+
+ private static final String OUTPUT_BEFORE_SETTING_GMT_CFG_GMT1_SQL_DIFFERENT
+ = "2014-07-12 12:30:05 2014-07-12T11:30:05 2014-07-12T11:30:05\n"
+ + "2014-07-12 12:30:05+02:00 2014-07-12T11:30:05+01:00 2014-07-12T11:30:05+01:00\n"
+ + "2014-07-12+02:00 12:30:05+02:00 2014-07-12T11:30:05+01:00 2014-07-12T11:30:05+01:00\n"
+ + "2014-07-12 12:30:05 2014-07-12T11:30:05+01:00 2014-07-12T11:30:05+01:00\n";
+
+ private static final String OUTPUT_BEFORE_SETTING_GMT_CFG_GMT1_SQL_SAME
+ = "2014-07-11 11:30:05 2014-07-12T11:30:05 2014-07-12T11:30:05\n"
+ + "2014-07-11 11:30:05+01:00 2014-07-12T11:30:05+01:00 2014-07-12T11:30:05+01:00\n"
+ + "2014-07-11+01:00 11:30:05+01:00 2014-07-12T11:30:05+01:00 2014-07-12T11:30:05+01:00\n"
+ + "2014-07-11 11:30:05 2014-07-12T11:30:05+01:00 2014-07-12T11:30:05+01:00\n";
+
+ private static final String OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME
+ = "2014-07-11 10:30:05 2014-07-12T10:30:05 2014-07-12T10:30:05\n"
+ + "2014-07-11 10:30:05Z 2014-07-12T10:30:05Z 2014-07-12T10:30:05Z\n"
+ + "2014-07-11Z 10:30:05Z 2014-07-12T10:30:05Z 2014-07-12T10:30:05Z\n"
+ + "2014-07-11 10:30:05 2014-07-12T10:30:05Z 2014-07-12T10:30:05Z\n";
+
+ private static final String OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT
+ = "2014-07-12 12:30:05 2014-07-12T10:30:05 2014-07-12T10:30:05\n"
+ + "2014-07-12 12:30:05+02:00 2014-07-12T10:30:05Z 2014-07-12T10:30:05Z\n"
+ + "2014-07-12+02:00 12:30:05+02:00 2014-07-12T10:30:05Z 2014-07-12T10:30:05Z\n"
+ + "2014-07-12 12:30:05 2014-07-12T10:30:05Z 2014-07-12T10:30:05Z\n";
+
+ @Test
+ public void testWithDefaultTZAndNullSQL() throws Exception {
+ TimeZone prevSysDefTz = TimeZone.getDefault();
+ TimeZone.setDefault(GMT_P02);
+ try {
+ Configuration.ExtendableBuilder<?> cfgB = createConfigurationBuilder();
+ cfgB.unsetTimeZone();
+ setConfiguration(cfgB.build());
+
+ assertNull(getConfiguration().getSQLDateAndTimeTimeZone());
+ assertEquals(TimeZone.getDefault(), getConfiguration().getTimeZone());
+
+ assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME);
+ } finally {
+ TimeZone.setDefault(prevSysDefTz);
+ }
+ }
+
+ @Test
+ public void testWithDefaultTZAndGMT2SQL() throws Exception {
+ TimeZone prevSysDefTz = TimeZone.getDefault();
+ TimeZone.setDefault(GMT_P02);
+ try {
+ Configuration.ExtendableBuilder<?> cfgB = createConfigurationBuilder();
+ cfgB.sqlDateAndTimeTimeZone(GMT_P02).unsetTimeZone();
+ setConfiguration(cfgB.build());
+
+ assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT);
+ } finally {
+ TimeZone.setDefault(prevSysDefTz);
+ }
+ }
+
+ @Test
+ public void testWithGMT1AndNullSQL() throws Exception {
+ setConfiguration(createConfigurationBuilder()
+ .timeZone(TimeZone.getTimeZone("GMT+01:00"))
+ .build());
+ assertNull(getConfiguration().getSQLDateAndTimeTimeZone());
+
+ assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT1_SQL_SAME + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME);
+ }
+
+ @Test
+ public void testWithGMT1AndGMT2SQL() throws Exception {
+ setConfiguration(createConfigurationBuilder()
+ .sqlDateAndTimeTimeZone(GMT_P02)
+ .timeZone(TimeZone.getTimeZone("GMT+01:00"))
+ .build());
+
+ assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT1_SQL_DIFFERENT + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT);
+ }
+
+ @Test
+ public void testWithGMT2AndNullSQL() throws Exception {
+ setConfiguration(createConfigurationBuilder()
+ .timeZone(TimeZone.getTimeZone("GMT+02"))
+ .build());
+ assertNull(getConfiguration().getSQLDateAndTimeTimeZone());
+
+ assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME);
+ }
+
+ @Test
+ public void testWithGMT2AndGMT2SQL() throws Exception {
+ setConfiguration(createConfigurationBuilder()
+ .sqlDateAndTimeTimeZone(GMT_P02)
+ .timeZone(TimeZone.getTimeZone("GMT+02"))
+ .build());
+
+ assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT);
+ }
+
+ @Test
+ public void testCacheFlushings() throws Exception {
+ Configuration.ExtendableBuilder<?> cfgB = createConfigurationBuilder()
+ .timeZone(_DateUtil.UTC)
+ .dateFormat("yyyy-MM-dd E")
+ .timeFormat("HH:mm:ss E")
+ .dateTimeFormat("yyyy-MM-dd'T'HH:mm:ss E");
+
+ setConfiguration(cfgB.build());
+ assertOutput(
+ "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n"
+ + "<#setting locale='de'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n",
+ "2014-07-11 Fri, 10:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n"
+ + "2014-07-11 Fr, 10:30:05 Do, 2014-07-12T10:30:05 Sa, 2014-07-12T10:30:05 Sa, 2014-07-12 Sa, 10:30:05 Sa\n");
+ assertOutput(
+ "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n"
+ + "<#setting date_format='yyyy-MM-dd'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n",
+ "2014-07-11 Fri, 10:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n"
+ + "2014-07-11, 10:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12, 10:30:05 Sat\n");
+ assertOutput(
+ "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n"
+ + "<#setting time_format='HH:mm:ss'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n",
+ "2014-07-11 Fri, 10:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n"
+ + "2014-07-11 Fri, 10:30:05, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05\n");
+ assertOutput(
+ "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n"
+ + "<#setting datetime_format='yyyy-MM-dd\\'T\\'HH:mm:ss'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n",
+ "2014-07-11 Fri, 10:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n"
+ + "2014-07-11 Fri, 10:30:05 Thu, 2014-07-12T10:30:05, 2014-07-12T10:30:05, 2014-07-12 Sat, 10:30:05 Sat\n");
+
+ setConfiguration(cfgB.sqlDateAndTimeTimeZone(GMT_P02).build());
+ assertOutput(
+ "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n"
+ + "<#setting locale='de'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n",
+ "2014-07-12 Sat, 12:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n"
+ + "2014-07-12 Sa, 12:30:05 Do, 2014-07-12T10:30:05 Sa, 2014-07-12T10:30:05 Sa, 2014-07-12 Sa, 10:30:05 Sa\n");
+ assertOutput(
+ "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n"
+ + "<#setting date_format='yyyy-MM-dd'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n",
+ "2014-07-12 Sat, 12:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n"
+ + "2014-07-12, 12:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12, 10:30:05 Sat\n");
+ assertOutput(
+ "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n"
+ + "<#setting time_format='HH:mm:ss'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n",
+ "2014-07-12 Sat, 12:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n"
+ + "2014-07-12 Sat, 12:30:05, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05\n");
+ assertOutput(
+ "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n"
+ + "<#setting datetime_format='yyyy-MM-dd\\'T\\'HH:mm:ss'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}, ${javaDate?date}, ${javaDate?time}\n",
+ "2014-07-12 Sat, 12:30:05 Thu, 2014-07-12T10:30:05 Sat, 2014-07-12T10:30:05 Sat, 2014-07-12 Sat, 10:30:05 Sat\n"
+ + "2014-07-12 Sat, 12:30:05 Thu, 2014-07-12T10:30:05, 2014-07-12T10:30:05, 2014-07-12 Sat, 10:30:05 Sat\n");
+ }
+
+ @Test
+ public void testDateAndTimeBuiltInsHasNoEffect() throws Exception {
+ setConfiguration(createConfigurationBuilder()
+ .timeZone(_DateUtil.UTC)
+ .sqlDateAndTimeTimeZone(GMT_P02)
+ .build());
+
+ assertOutput(
+ "${javaDayErrorDate?date} ${javaDayErrorDate?time} ${sqlTimestamp?date} ${sqlTimestamp?time} "
+ + "${sqlDate?date} ${sqlTime?time}\n"
+ + "<#setting time_zone='GMT+02'>\n"
+ + "${javaDayErrorDate?date} ${javaDayErrorDate?time} ${sqlTimestamp?date} ${sqlTimestamp?time} "
+ + "${sqlDate?date} ${sqlTime?time}\n"
+ + "<#setting time_zone='GMT-11'>\n"
+ + "${javaDayErrorDate?date} ${javaDayErrorDate?time} ${sqlTimestamp?date} ${sqlTimestamp?time} "
+ + "${sqlDate?date} ${sqlTime?time}\n",
+ "2014-07-11 22:00:00 2014-07-12 10:30:05 2014-07-12 12:30:05\n"
+ + "2014-07-12 00:00:00 2014-07-12 12:30:05 2014-07-12 12:30:05\n"
+ + "2014-07-11 11:00:00 2014-07-11 23:30:05 2014-07-12 12:30:05\n");
+ }
+
+ @Test
+ public void testChangeSettingInTemplate() throws Exception {
+ setConfiguration(createConfigurationBuilder()
+ .timeZone(_DateUtil.UTC)
+ .build());
+
+ assertNull(getConfiguration().getSQLDateAndTimeTimeZone());
+
+ assertOutput(
+ "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n"
+ + "<#setting sql_date_and_time_time_zone='GMT+02'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n"
+ + "<#setting sql_date_and_time_time_zone='null'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n"
+ + "<#setting time_zone='GMT+03'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n"
+ + "<#setting sql_date_and_time_time_zone='GMT+02'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n"
+ + "<#setting sql_date_and_time_time_zone='GMT-11'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n"
+ + "<#setting date_format='xs fz'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n"
+ + "<#setting time_format='xs fz'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n"
+ + "<#setting datetime_format='iso m'>\n"
+ + "${sqlDate}, ${sqlTime}, ${sqlTimestamp}, ${javaDate?datetime}\n",
+ "2014-07-11, 10:30:05, 2014-07-12T10:30:05, 2014-07-12T10:30:05\n"
+ + "2014-07-12, 12:30:05, 2014-07-12T10:30:05, 2014-07-12T10:30:05\n"
+ + "2014-07-11, 10:30:05, 2014-07-12T10:30:05, 2014-07-12T10:30:05\n"
+ + "2014-07-12, 13:30:05, 2014-07-12T13:30:05, 2014-07-12T13:30:05\n"
+ + "2014-07-12, 12:30:05, 2014-07-12T13:30:05, 2014-07-12T13:30:05\n"
+ + "2014-07-11, 23:30:05, 2014-07-12T13:30:05, 2014-07-12T13:30:05\n"
+ + "2014-07-11-11:00, 23:30:05, 2014-07-12T13:30:05, 2014-07-12T13:30:05\n"
+ + "2014-07-11-11:00, 23:30:05-11:00, 2014-07-12T13:30:05, 2014-07-12T13:30:05\n"
+ + "2014-07-11-11:00, 23:30:05-11:00, 2014-07-12T13:30+03:00, 2014-07-12T13:30+03:00\n");
+ }
+
+ @Test
+ public void testFormatUTCFlagHasNoEffect() throws Exception {
+ setConfiguration(createConfigurationBuilder()
+ .sqlDateAndTimeTimeZone(GMT_P02)
+ .timeZone(TimeZone.getTimeZone("GMT-01"))
+ .build());
+
+ assertOutput(
+ "<#setting date_format='xs fz'><#setting time_format='xs fz'>\n"
+ + "${sqlDate}, ${sqlTime}, ${javaDate?time}\n"
+ + "<#setting date_format='xs fz u'><#setting time_format='xs fz u'>\n"
+ + "${sqlDate}, ${sqlTime}, ${javaDate?time}\n"
+ + "<#setting sql_date_and_time_time_zone='GMT+03'>\n"
+ + "${sqlDate}, ${sqlTime}, ${javaDate?time}\n"
+ + "<#setting sql_date_and_time_time_zone='null'>\n"
+ + "${sqlDate}, ${sqlTime}, ${javaDate?time}\n"
+ + "<#setting date_format='xs fz'><#setting time_format='xs fz'>\n"
+ + "${sqlDate}, ${sqlTime}, ${javaDate?time}\n"
+ + "<#setting date_format='xs fz fu'><#setting time_format='xs fz fu'>\n"
+ + "${sqlDate}, ${sqlTime}, ${javaDate?time}\n",
+ "2014-07-12+02:00, 12:30:05+02:00, 09:30:05-01:00\n"
+ + "2014-07-12+02:00, 12:30:05+02:00, 10:30:05Z\n"
+ + "2014-07-12+03:00, 13:30:05+03:00, 10:30:05Z\n"
+ + "2014-07-11-01:00, 09:30:05-01:00, 10:30:05Z\n"
+ + "2014-07-11-01:00, 09:30:05-01:00, 09:30:05-01:00\n"
+ + "2014-07-11Z, 10:30:05Z, 10:30:05Z\n");
+ }
+
+ private Configuration.ExtendableBuilder<?> createConfigurationBuilder() {
+ return new Configuration.Builder(Configuration.VERSION_3_0_0)
+ .locale(Locale.US)
+ .dateFormat("yyyy-MM-dd")
+ .timeFormat("HH:mm:ss")
+ .dateTimeFormat("yyyy-MM-dd'T'HH:mm:ss");
+ }
+
+ @Override
+ protected Object createDataModel() {
+ return this;
+ }
+
+ private long utcToLong(String isoDateTime) {
+ try {
+ return df.parse(isoDateTime).getTime();
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}