http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/OutputFormatTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/OutputFormatTest.java b/src/test/java/org/apache/freemarker/core/OutputFormatTest.java new file mode 100644 index 0000000..0918436 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/OutputFormatTest.java @@ -0,0 +1,1031 @@ +/* + * 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.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Collections; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.HTMLOutputFormat; +import org.apache.freemarker.core.InvalidReferenceException; +import org.apache.freemarker.core.OutputFormat; +import org.apache.freemarker.core.ParseException; +import org.apache.freemarker.core.PlainTextOutputFormat; +import org.apache.freemarker.core.RTFOutputFormat; +import org.apache.freemarker.core.Template; +import org.apache.freemarker.core.TemplateConfiguration; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.UndefinedOutputFormat; +import org.apache.freemarker.core.XMLOutputFormat; +import org.apache.freemarker.core.model.TemplateModelException; +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.test.TemplateTest; +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}"); + + Configuration cfg = getConfiguration(); + for (OutputFormat cfgOutputFormat + : new OutputFormat[] { UndefinedOutputFormat.INSTANCE, RTFOutputFormat.INSTANCE } ) { + if (!cfgOutputFormat.equals(UndefinedOutputFormat.INSTANCE)) { + cfg.setOutputFormat(cfgOutputFormat); + } + + assertEquals(cfgOutputFormat, cfg.getOutputFormat()); + + { + Template t = cfg.getTemplate("t"); + assertEquals(cfgOutputFormat, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + { + Template t = cfg.getTemplate("t.xml"); + assertEquals(XMLOutputFormat.INSTANCE, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + { + Template t = cfg.getTemplate("tWithHeader"); + assertEquals(HTMLOutputFormat.INSTANCE, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + cfg.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); + + Configuration cfg = getConfiguration(); + 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; + cfg.setOutputFormat(cfgOutputFormat); + ftlhOutputFormat = HTMLOutputFormat.INSTANCE; + ftlxOutputFormat = XMLOutputFormat.INSTANCE; + break; + case 3: + cfgOutputFormat = UndefinedOutputFormat.INSTANCE; + cfg.unsetOutputFormat(); + TemplateConfiguration tcXml = new TemplateConfiguration(); + tcXml.setOutputFormat(XMLOutputFormat.INSTANCE); + cfg.setTemplateConfigurations( + new ConditionalTemplateConfigurationFactory( + new OrMatcher( + new FileNameGlobMatcher("*.ftlh"), + new FileNameGlobMatcher("*.FTLH"), + new FileNameGlobMatcher("*.fTlH")), + tcXml)); + ftlhOutputFormat = HTMLOutputFormat.INSTANCE; // can't be overidden + ftlxOutputFormat = XMLOutputFormat.INSTANCE; + break; + default: + throw new AssertionError(); + } + + assertEquals(cfgOutputFormat, cfg.getOutputFormat()); + + { + Template t = cfg.getTemplate("t"); + assertEquals(cfgOutputFormat, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + { + Template t = cfg.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 = cfg.getTemplate(name); + assertEquals(ftlhOutputFormat, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + for (String name : new String[] { "t.ftlx", "t.FTLX", "t.fTlX" }) { + Template t = cfg.getTemplate(name); + assertEquals(ftlxOutputFormat, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + { + Template t = cfg.getTemplate("tWithHeader.ftlx"); + assertEquals(HTMLOutputFormat.INSTANCE, t.getOutputFormat()); + assertOutput(t, t.getOutputFormat().getName()); + } + + cfg.clearTemplateCache(); + } + } + + @Test + public void testStandardFileExtensionsSettingOverriding() throws Exception { + addTemplate("t.ftlx", + "${\"'\"} ${\"'\"?esc} ${\"'\"?noEsc}"); + addTemplate("t.ftl", + "${'{}'} ${'{}'?esc} ${'{}'?noEsc}"); + + TemplateConfiguration tcHTML = new TemplateConfiguration(); + tcHTML.setOutputFormat(HTMLOutputFormat.INSTANCE); + ConditionalTemplateConfigurationFactory tcfHTML = new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("t.*"), tcHTML); + + TemplateConfiguration tcNoAutoEsc = new TemplateConfiguration(); + tcNoAutoEsc.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY); + ConditionalTemplateConfigurationFactory tcfNoAutoEsc = new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("t.*"), tcNoAutoEsc); + + Configuration cfg = getConfiguration(); + cfg.setOutputFormat(HTMLOutputFormat.INSTANCE); + assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it + cfg.setTemplateConfigurations(tcfHTML); + assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it + cfg.setTemplateConfigurations(tcfNoAutoEsc); + assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it + + cfg.setTemplateConfigurations(null); + cfg.unsetOutputFormat(); + cfg.setRecognizeStandardFileExtensions(false); + assertErrorContainsForNamed("t.ftlx", UndefinedOutputFormat.INSTANCE.getName()); + cfg.setOutputFormat(HTMLOutputFormat.INSTANCE); + assertOutputForNamed("t.ftlx", "' ' '"); + cfg.setOutputFormat(XMLOutputFormat.INSTANCE); + assertOutputForNamed("t.ftlx", "' ' '"); + cfg.setTemplateConfigurations(tcfHTML); + assertOutputForNamed("t.ftlx", "' ' '"); + cfg.setTemplateConfigurations(tcfNoAutoEsc); + assertOutputForNamed("t.ftlx", "' ' '"); + + cfg.setRecognizeStandardFileExtensions(true); + cfg.setTemplateConfigurations(tcfHTML); + assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it + cfg.setTemplateConfigurations(tcfNoAutoEsc); + assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it + + cfg.setTemplateConfigurations(null); + cfg.unsetOutputFormat(); + cfg.setTemplateConfigurations(tcfHTML); + assertOutputForNamed("t.ftlx", "' ' '"); // Can't override it + cfg.setRecognizeStandardFileExtensions(false); + 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"); + getConfiguration().setRegisteredCustomOutputFormats(Collections.singleton(CustomHTMLOutputFormat.INSTANCE)); + assertOutputForNamed("t.ftlh", "a&X"); + getConfiguration().setRegisteredCustomOutputFormats(Collections.<OutputFormat>emptyList()); + 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'}"); + + Configuration cfg = getConfiguration(); + + assertEquals(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy()); + + cfg.setOutputFormat(XMLOutputFormat.INSTANCE); + + for (boolean cfgAutoEscaping : new boolean[] { true, false }) { + if (!cfgAutoEscaping) { + cfg.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY); + } + + { + Template t = cfg.getTemplate("t"); + if (cfgAutoEscaping) { + assertTrue(t.getAutoEscaping()); + assertOutput(t, "a&b"); + } else { + assertFalse(t.getAutoEscaping()); + assertOutput(t, "a&b"); + } + } + + { + Template t = cfg.getTemplate("tWithHeaderFalse"); + assertFalse(t.getAutoEscaping()); + assertOutput(t, "a&b"); + } + + { + Template t = cfg.getTemplate("tWithHeaderTrue"); + assertTrue(t.getAutoEscaping()); + assertOutput(t, "a&b"); + } + + cfg.clearTemplateCache(); + } + } + + @Test + public void testNumericalInterpolation() throws IOException, TemplateException { + getConfiguration().setRegisteredCustomOutputFormats(Collections.singleton(DummyOutputFormat.INSTANCE)); + 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 (int autoEsc = 0; autoEsc < 2; autoEsc++) { + String commonAutoEscFtl = "<#ftl outputFormat='HTML'>${'&'}"; + if (autoEsc == 0) { + // Cfg default is autoEscaping true + assertOutput(commonAutoEscFtl, "&"); + } else { + getConfiguration().setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY); + 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.getPlainTextTemplate("x", content, getConfiguration()); + Writer sw = new StringWriter(); + t.process(null, sw); + assertEquals(content, sw.toString()); + assertEquals(UndefinedOutputFormat.INSTANCE, t.getOutputFormat()); + } + + { + getConfiguration().setOutputFormat(HTMLOutputFormat.INSTANCE); + Template t = Template.getPlainTextTemplate("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"); + getConfiguration().setRegisteredCustomOutputFormats(Collections.singleton(DummyOutputFormat.INSTANCE)); + 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"); + + getConfiguration().setOutputFormat(XMLOutputFormat.INSTANCE); + + // 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 { + Configuration cfg = getConfiguration(); + cfg.setRegisteredCustomOutputFormats(ImmutableList.of( + SeldomEscapedOutputFormat.INSTANCE, DummyOutputFormat.INSTANCE)); + assertEquals(Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfg.getAutoEscapingPolicy()); + + String commonFTL = "${'.'} ${.autoEsc?c}"; + String notEsced = ". false"; + String esced = "\\. true"; + + for (int autoEscPolicy : new int[] { + Configuration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, + Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, + Configuration.DISABLE_AUTO_ESCAPING_POLICY }) { + cfg.setAutoEscapingPolicy(autoEscPolicy); + + String sExpted = autoEscPolicy == Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY ? esced : notEsced; + cfg.setOutputFormat(SeldomEscapedOutputFormat.INSTANCE); + assertOutput(commonFTL, sExpted); + cfg.setOutputFormat(UndefinedOutputFormat.INSTANCE); + assertOutput("<#ftl outputFormat='seldomEscaped'>" + commonFTL, sExpted); + assertOutput("<#outputFormat 'seldomEscaped'>" + commonFTL + "</#outputFormat>", sExpted); + + String dExpted = autoEscPolicy == Configuration.DISABLE_AUTO_ESCAPING_POLICY ? notEsced : esced; + cfg.setOutputFormat(DummyOutputFormat.INSTANCE); + assertOutput(commonFTL, dExpted); + cfg.setOutputFormat(UndefinedOutputFormat.INSTANCE); + assertOutput("<#ftl outputFormat='dummy'>" + commonFTL, dExpted); + assertOutput("<#outputFormat 'dummy'>" + commonFTL + "</#outputFormat>", dExpted); + + cfg.setOutputFormat(DummyOutputFormat.INSTANCE); + 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 parserConfiguration of the calling template, and ignore anything + // inside the calling template itself. Except, the outputFormat 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" }) { + getConfiguration().setIncompatibleImprovements(Configuration.VERSION_3_0_0); + + 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"); + } + + @Override + protected Configuration createConfiguration() throws TemplateModelException { + Configuration cfg = new Configuration(Configuration.VERSION_3_0_0); + + TemplateConfiguration xmlTC = new TemplateConfiguration(); + xmlTC.setOutputFormat(XMLOutputFormat.INSTANCE); + cfg.setTemplateConfigurations( + new ConditionalTemplateConfigurationFactory(new FileNameGlobMatcher("*.xml"), xmlTC)); + + cfg.setSharedVariable("rtfPlain", RTFOutputFormat.INSTANCE.fromPlainTextByEscaping("\\par a & b")); + cfg.setSharedVariable("rtfMarkup", RTFOutputFormat.INSTANCE.fromMarkup("\\par c")); + cfg.setSharedVariable("htmlPlain", HTMLOutputFormat.INSTANCE.fromPlainTextByEscaping("a < {h'}")); + cfg.setSharedVariable("htmlMarkup", HTMLOutputFormat.INSTANCE.fromMarkup("<p>c")); + cfg.setSharedVariable("xmlPlain", XMLOutputFormat.INSTANCE.fromPlainTextByEscaping("a < {x'}")); + cfg.setSharedVariable("xmlMarkup", XMLOutputFormat.INSTANCE.fromMarkup("<p>c</p>")); + + return cfg; + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/ParseTimeParameterBIErrorMessagesTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/ParseTimeParameterBIErrorMessagesTest.java b/src/test/java/org/apache/freemarker/core/ParseTimeParameterBIErrorMessagesTest.java new file mode 100644 index 0000000..cbeabc5 --- /dev/null +++ b/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/7d784b2b/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java b/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java new file mode 100644 index 0000000..99af659 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/ParsingErrorMessagesTest.java @@ -0,0 +1,119 @@ +/* + * 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.fail; + +import java.io.IOException; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.ParseException; +import org.apache.freemarker.core.Template; +import org.apache.freemarker.core.util._StringUtil; +import org.junit.Test; + +public class ParsingErrorMessagesTest { + + private Configuration cfg = new Configuration(Configuration.VERSION_3_0_0); + { + cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX); + } + + @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 tempalte 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/7d784b2b/src/test/java/org/apache/freemarker/core/PrintfGTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/PrintfGTemplateNumberFormatFactory.java b/src/test/java/org/apache/freemarker/core/PrintfGTemplateNumberFormatFactory.java new file mode 100644 index 0000000..0408b48 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/PrintfGTemplateNumberFormatFactory.java @@ -0,0 +1,138 @@ +/* + * 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 java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Locale; + +import org.apache.freemarker.core.Environment; +import org.apache.freemarker.core.HTMLOutputFormat; +import org.apache.freemarker.core.InvalidFormatParametersException; +import org.apache.freemarker.core.TemplateFormatUtil; +import org.apache.freemarker.core.TemplateNumberFormat; +import org.apache.freemarker.core.TemplateNumberFormatFactory; +import org.apache.freemarker.core.UnformattableValueException; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateNumberModel; +import org.apache.freemarker.core.util._StringUtil; + +/** + * Formats like {@code %G} in {@code printf}, with the specified number of significant digits. Also has special + * formatter for HTML output format, where it uses the HTML "sup" element for exponents. + */ +public class PrintfGTemplateNumberFormatFactory extends TemplateNumberFormatFactory { + + public static final PrintfGTemplateNumberFormatFactory INSTANCE = new PrintfGTemplateNumberFormatFactory(); + + private PrintfGTemplateNumberFormatFactory() { + // Defined to decrease visibility + } + + @Override + public TemplateNumberFormat get(String params, Locale locale, Environment env) + throws InvalidFormatParametersException { + Integer significantDigits; + if (!params.isEmpty()) { + try { + significantDigits = Integer.valueOf(params); + } catch (NumberFormatException e) { + throw new InvalidFormatParametersException( + "The format parameter must be an integer, but was (shown quoted) " + + _StringUtil.jQuote(params) + "."); + } + } else { + // Use the default of %G + significantDigits = null; + } + return new PrintfGTemplateNumberFormat(significantDigits, locale); + } + + private static class PrintfGTemplateNumberFormat extends TemplateNumberFormat { + + private final Locale locale; + private final String printfFormat; + + private PrintfGTemplateNumberFormat(Integer significantDigits, Locale locale) { + printfFormat = "%" + (significantDigits != null ? "." + significantDigits : "") + "G"; + this.locale = locale; + } + + @Override + public String formatToPlainText(TemplateNumberModel numberModel) + throws UnformattableValueException, TemplateModelException { + final Number n = TemplateFormatUtil.getNonNullNumber(numberModel); + + // printf %G only accepts Double, BigDecimal and Float + final Number gCompatibleN; + if (n instanceof Double || n instanceof BigDecimal || n instanceof Float) { + gCompatibleN = n; + } else { + if (n instanceof BigInteger) { + gCompatibleN = new BigDecimal((BigInteger) n); + } else if (n instanceof Long) { + gCompatibleN = BigDecimal.valueOf(n.longValue()); + } else { + gCompatibleN = Double.valueOf(n.doubleValue()); + } + } + + return String.format(locale, printfFormat, gCompatibleN); + } + + @Override + public Object format(TemplateNumberModel numberModel) + throws UnformattableValueException, TemplateModelException { + String strResult = formatToPlainText(numberModel); + + int expIdx = strResult.indexOf('E'); + if (expIdx == -1) { + return strResult; + } + + String expStr = strResult.substring(expIdx + 1); + int expSignifNumBegin = 0; + while (expSignifNumBegin < expStr.length() && isExpSignifNumPrefix(expStr.charAt(expSignifNumBegin))) { + expSignifNumBegin++; + } + + return HTMLOutputFormat.INSTANCE.fromMarkup( + strResult.substring(0, expIdx) + + "*10<sup>" + + (expStr.charAt(0) == '-' ? "-" : "") + expStr.substring(expSignifNumBegin) + + "</sup>"); + } + + private boolean isExpSignifNumPrefix(char c) { + return c == '+' || c == '-' || c == '0'; + } + + @Override + public boolean isLocaleBound() { + return true; + } + + @Override + public String getDescription() { + return "printf " + printfFormat; + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/RTFOutputFormatTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/RTFOutputFormatTest.java b/src/test/java/org/apache/freemarker/core/RTFOutputFormatTest.java new file mode 100644 index 0000000..3b2c878 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/RTFOutputFormatTest.java @@ -0,0 +1,129 @@ +/* + * 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.RTFOutputFormat.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.StringWriter; + +import org.apache.freemarker.core.TemplateRTFOutputModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.junit.Test; + +public class RTFOutputFormatTest { + + @Test + public void testOutputMO() throws TemplateModelException, IOException { + StringWriter out = new StringWriter(); + + INSTANCE.output(INSTANCE.fromMarkup("\\par Test "), out); + INSTANCE.output(INSTANCE.fromPlainTextByEscaping("foo { bar } \\ "), out); + INSTANCE.output(INSTANCE.fromPlainTextByEscaping("baaz "), out); + INSTANCE.output(INSTANCE.fromPlainTextByEscaping("\\par qweqwe"), out); + INSTANCE.output(INSTANCE.fromMarkup("\\par{0} End"), out); + + assertEquals( + "\\par Test " + + "foo \\{ bar \\} \\\\ " + + "baaz " + + "\\\\par qweqwe" + + "\\par{0} End", + out.toString()); + } + + @Test + public void testOutputString() throws TemplateModelException, IOException { + StringWriter out = new StringWriter(); + + INSTANCE.output("a", out); + INSTANCE.output("{", out); + INSTANCE.output("b}c", out); + + assertEquals("a\\{b\\}c", out.toString()); + } + + @Test + public void testFromPlainTextByEscaping() throws TemplateModelException { + String plainText = "a\\b"; + TemplateRTFOutputModel mo = INSTANCE.fromPlainTextByEscaping(plainText); + assertSame(plainText, mo.getPlainTextContent()); + assertNull(mo.getMarkupContent()); // Not the MO's duty to calculate it! + } + + @Test + public void testFromMarkup() throws TemplateModelException { + String markup = "a \\par b"; + TemplateRTFOutputModel mo = INSTANCE.fromMarkup(markup); + assertSame(markup, mo.getMarkupContent()); + assertNull(mo.getPlainTextContent()); // Not the MO's duty to calculate it! + } + + @Test + public void testGetMarkup() throws TemplateModelException { + { + String markup = "a \\par b"; + TemplateRTFOutputModel mo = INSTANCE.fromMarkup(markup); + assertSame(markup, INSTANCE.getMarkupString(mo)); + } + + { + String safe = "abc"; + TemplateRTFOutputModel mo = INSTANCE.fromPlainTextByEscaping(safe); + assertSame(safe, INSTANCE.getMarkupString(mo)); + } + } + + @Test + public void testConcat() throws Exception { + assertMO( + "ab", null, + INSTANCE.concat(new TemplateRTFOutputModel("a", null), new TemplateRTFOutputModel("b", null))); + assertMO( + null, "ab", + INSTANCE.concat(new TemplateRTFOutputModel(null, "a"), new TemplateRTFOutputModel(null, "b"))); + assertMO( + null, "{a}\\{b\\}", + INSTANCE.concat(new TemplateRTFOutputModel(null, "{a}"), new TemplateRTFOutputModel("{b}", null))); + assertMO( + null, "\\{a\\}{b}", + INSTANCE.concat(new TemplateRTFOutputModel("{a}", null), new TemplateRTFOutputModel(null, "{b}"))); + } + + @Test + public void testEscaplePlainText() { + assertEquals("", INSTANCE.escapePlainText("")); + assertEquals("a", INSTANCE.escapePlainText("a")); + assertEquals("\\{a\\\\b\\}", INSTANCE.escapePlainText("{a\\b}")); + assertEquals("a\\\\b", INSTANCE.escapePlainText("a\\b")); + assertEquals("\\{\\}", INSTANCE.escapePlainText("{}")); + } + + private void assertMO(String pc, String mc, TemplateRTFOutputModel mo) { + assertEquals(pc, mo.getPlainTextContent()); + assertEquals(mc, mo.getMarkupContent()); + } + + @Test + public void testGetMimeType() { + assertEquals("application/rtf", INSTANCE.getMimeType()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java b/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java new file mode 100644 index 0000000..ba18c08 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/SQLTimeZoneTest.java @@ -0,0 +1,362 @@ +/* + * 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.assertEquals; +import static org.junit.Assert.assertNull; + +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.Configuration; +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 cfg = getConfiguration(); + assertNull(cfg.getSQLDateAndTimeTimeZone()); + assertEquals(TimeZone.getDefault(), cfg.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 cfg = getConfiguration(); + cfg.setSQLDateAndTimeTimeZone(GMT_P02); + + 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 { + Configuration cfg = getConfiguration(); + assertNull(cfg.getSQLDateAndTimeTimeZone()); + cfg.setTimeZone(TimeZone.getTimeZone("GMT+01:00")); + + assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT1_SQL_SAME + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME); + } + + @Test + public void testWithGMT1AndGMT2SQL() throws Exception { + Configuration cfg = getConfiguration(); + cfg.setSQLDateAndTimeTimeZone(GMT_P02); + cfg.setTimeZone(TimeZone.getTimeZone("GMT+01:00")); + + assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT1_SQL_DIFFERENT + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT); + } + + @Test + public void testWithGMT2AndNullSQL() throws Exception { + Configuration cfg = getConfiguration(); + assertNull(cfg.getSQLDateAndTimeTimeZone()); + cfg.setTimeZone(TimeZone.getTimeZone("GMT+02")); + + assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_SAME); + } + + @Test + public void testWithGMT2AndGMT2SQL() throws Exception { + Configuration cfg = getConfiguration(); + cfg.setSQLDateAndTimeTimeZone(GMT_P02); + cfg.setTimeZone(TimeZone.getTimeZone("GMT+02")); + + assertOutput(FTL, OUTPUT_BEFORE_SETTING_GMT_CFG_GMT2 + OUTPUT_AFTER_SETTING_GMT_CFG_SQL_DIFFERENT); + } + + @Test + public void testCacheFlushings() throws Exception { + Configuration cfg = getConfiguration(); + cfg.setTimeZone(_DateUtil.UTC); + cfg.setDateFormat("yyyy-MM-dd E"); + cfg.setTimeFormat("HH:mm:ss E"); + cfg.setDateTimeFormat("yyyy-MM-dd'T'HH:mm:ss E"); + + 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"); + + cfg.setSQLDateAndTimeTimeZone(GMT_P02); + 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 { + Configuration cfg = getConfiguration(); + cfg.setTimeZone(_DateUtil.UTC); + cfg.setSQLDateAndTimeTimeZone(GMT_P02); + 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 { + Configuration cfg = getConfiguration(); + cfg.setTimeZone(_DateUtil.UTC); + + assertNull(cfg.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 { + Configuration cfg = getConfiguration(); + cfg.setSQLDateAndTimeTimeZone(GMT_P02); + cfg.setTimeZone(TimeZone.getTimeZone("GMT-01")); + + 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"); + } + + @Override + protected Configuration createConfiguration() { + Configuration cfg = new Configuration(Configuration.VERSION_3_0_0); + cfg.setLocale(Locale.US); + cfg.setDateFormat("yyyy-MM-dd"); + cfg.setTimeFormat("HH:mm:ss"); + cfg.setDateTimeFormat("yyyy-MM-dd'T'HH:mm:ss"); + return cfg; + } + + @Override + protected Object createDataModel() { + return this; + } + + private long utcToLong(String isoDateTime) { + try { + return df.parse(isoDateTime).getTime(); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + +}
