http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/ASTTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/ASTTest.java b/src/test/java/org/apache/freemarker/core/ASTTest.java new file mode 100644 index 0000000..9a4222c --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/ASTTest.java @@ -0,0 +1,98 @@ +/* + * 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.io.FileNotFoundException; +import java.io.IOException; + +import org.apache.freemarker.core.ASTPrinter.Options; +import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.test.util.FileTestCase; +import org.apache.freemarker.test.util.TestUtil; + +public class ASTTest extends FileTestCase { + + public ASTTest(String name) { + super(name); + } + + public void test1() throws Exception { + testAST("ast-1"); + } + + public void testRange() throws Exception { + testAST("ast-range"); + } + + public void testAssignments() throws Exception { + testAST("ast-assignments"); + } + + public void testBuiltins() throws Exception { + testAST("ast-builtins"); + } + + public void testStringLiteralInterpolation() throws Exception { + testAST("ast-strlitinterpolation"); + } + + public void testWhitespaceStripping() throws Exception { + testAST("ast-whitespacestripping"); + } + + public void testMixedContentSimplifications() throws Exception { + testAST("ast-mixedcontentsimplifications"); + } + + public void testMultipleIgnoredChildren() throws Exception { + testAST("ast-multipleignoredchildren"); + } + + public void testNestedIgnoredChildren() throws Exception { + testAST("ast-nestedignoredchildren"); + } + + public void testLocations() throws Exception { + testASTWithLocations("ast-locations"); + } + + private void testAST(String testName) throws FileNotFoundException, IOException { + testAST(testName, null); + } + + private void testASTWithLocations(String testName) throws FileNotFoundException, IOException { + Options options = new Options(); + options.setShowLocation(true); + testAST(testName, options); + } + + private void testAST(String testName, Options ops) throws FileNotFoundException, IOException { + final String templateName = testName + ".ftl"; + assertExpectedFileEqualsString( + testName + ".ast", + ASTPrinter.getASTAsString(templateName, + TestUtil.removeFTLCopyrightComment(normalizeLineBreaks(loadResource(templateName))), ops)); + } + + private String normalizeLineBreaks(final String s) throws FileNotFoundException, IOException { + return _StringUtil.replace(s, "\r\n", "\n").replace('\r', '\n'); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/AppMetaTemplateDateFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/AppMetaTemplateDateFormatFactory.java b/src/test/java/org/apache/freemarker/core/AppMetaTemplateDateFormatFactory.java new file mode 100644 index 0000000..94c8dff --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/AppMetaTemplateDateFormatFactory.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 java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import org.apache.freemarker.core.Environment; +import org.apache.freemarker.core.InvalidFormatParametersException; +import org.apache.freemarker.core.TemplateDateFormat; +import org.apache.freemarker.core.TemplateDateFormatFactory; +import org.apache.freemarker.core.TemplateFormatUtil; +import org.apache.freemarker.core.UnformattableValueException; +import org.apache.freemarker.core.UnknownDateTypeFormattingUnsupportedException; +import org.apache.freemarker.core.UnparsableValueException; +import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +public class AppMetaTemplateDateFormatFactory extends TemplateDateFormatFactory { + + public static final AppMetaTemplateDateFormatFactory INSTANCE = new AppMetaTemplateDateFormatFactory(); + + private AppMetaTemplateDateFormatFactory() { + // Defined to decrease visibility + } + + @Override + public TemplateDateFormat get(String params, int dateType, Locale locale, TimeZone timeZone, boolean zonelessInput, + Environment env) throws UnknownDateTypeFormattingUnsupportedException, InvalidFormatParametersException { + TemplateFormatUtil.checkHasNoParameters(params); + return AppMetaTemplateDateFormat.INSTANCE; + } + + private static class AppMetaTemplateDateFormat extends TemplateDateFormat { + + private static final AppMetaTemplateDateFormat INSTANCE = new AppMetaTemplateDateFormat(); + + private AppMetaTemplateDateFormat() { } + + @Override + public String formatToPlainText(TemplateDateModel dateModel) + throws UnformattableValueException, TemplateModelException { + String result = String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime()); + if (dateModel instanceof AppMetaTemplateDateModel) { + result += "/" + ((AppMetaTemplateDateModel) dateModel).getAppMeta(); + } + return result; + } + + @Override + public boolean isLocaleBound() { + return false; + } + + @Override + public boolean isTimeZoneBound() { + return false; + } + + @Override + public Object parse(String s, int dateType) throws UnparsableValueException { + int slashIdx = s.indexOf('/'); + try { + if (slashIdx != -1) { + return new AppMetaTemplateDateModel( + new Date(Long.parseLong(s.substring(0, slashIdx))), + dateType, + s.substring(slashIdx +1)); + } else { + return new Date(Long.parseLong(s)); + } + } catch (NumberFormatException e) { + throw new UnparsableValueException("Malformed long"); + } + } + + @Override + public String getDescription() { + return "millis since the epoch"; + } + + } + + public static class AppMetaTemplateDateModel implements TemplateDateModel { + + private final Date date; + private final int dateType; + private final String appMeta; + + public AppMetaTemplateDateModel(Date date, int dateType, String appMeta) { + this.date = date; + this.dateType = dateType; + this.appMeta = appMeta; + } + + @Override + public Date getAsDate() throws TemplateModelException { + return date; + } + + @Override + public int getDateType() { + return dateType; + } + + public String getAppMeta() { + return appMeta; + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/BaseNTemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/BaseNTemplateNumberFormatFactory.java b/src/test/java/org/apache/freemarker/core/BaseNTemplateNumberFormatFactory.java new file mode 100644 index 0000000..576063b --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/BaseNTemplateNumberFormatFactory.java @@ -0,0 +1,128 @@ +/* + * 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.util.Locale; + +import org.apache.freemarker.core.Environment; +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.TemplateValueFormatException; +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._NumberUtil; +import org.apache.freemarker.core.util._StringUtil; + +/** + * Shows a number in base N number system. Can only format numbers that fit into an {@code int}, + * however, optionally you can specify a fallback format. This format has one required parameter, + * the numerical system base. That can be optionally followed by "|" and a fallback format. + */ +public class BaseNTemplateNumberFormatFactory extends TemplateNumberFormatFactory { + + public static final BaseNTemplateNumberFormatFactory INSTANCE + = new BaseNTemplateNumberFormatFactory(); + + private BaseNTemplateNumberFormatFactory() { + // Defined to decrease visibility + } + + @Override + public TemplateNumberFormat get(String params, Locale locale, Environment env) + throws InvalidFormatParametersException { + TemplateNumberFormat fallbackFormat; + { + int barIdx = params.indexOf('|'); + if (barIdx != -1) { + String fallbackFormatStr = params.substring(barIdx + 1); + params = params.substring(0, barIdx); + try { + fallbackFormat = env.getTemplateNumberFormat(fallbackFormatStr, locale); + } catch (TemplateValueFormatException e) { + throw new InvalidFormatParametersException( + "Couldn't get the fallback number format (specified after the \"|\"), " + + _StringUtil.jQuote(fallbackFormatStr) + ". Reason: " + e.getMessage(), + e); + } + } else { + fallbackFormat = null; + } + } + + int base; + try { + base = Integer.parseInt(params); + } catch (NumberFormatException e) { + if (params.length() == 0) { + throw new InvalidFormatParametersException( + "A format parameter is required to specify the numerical system base."); + } + throw new InvalidFormatParametersException( + "The format paramter must be an integer, but was (shown quoted): " + + _StringUtil.jQuote(params)); + } + if (base < 2) { + throw new InvalidFormatParametersException("A base must be at least 2."); + } + return new BaseNTemplateNumberFormat(base, fallbackFormat); + } + + private static class BaseNTemplateNumberFormat extends TemplateNumberFormat { + + private final int base; + private final TemplateNumberFormat fallbackFormat; + + private BaseNTemplateNumberFormat(int base, TemplateNumberFormat fallbackFormat) { + this.base = base; + this.fallbackFormat = fallbackFormat; + } + + @Override + public String formatToPlainText(TemplateNumberModel numberModel) + throws TemplateModelException, TemplateValueFormatException { + Number n = TemplateFormatUtil.getNonNullNumber(numberModel); + try { + return Integer.toString(_NumberUtil.toIntExact(n), base); + } catch (ArithmeticException e) { + if (fallbackFormat == null) { + throw new UnformattableValueException( + n + " doesn't fit into an int, and there was no fallback format " + + "specified."); + } else { + return fallbackFormat.formatToPlainText(numberModel); + } + } + } + + @Override + public boolean isLocaleBound() { + return false; + } + + @Override + public String getDescription() { + return "base " + base; + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java b/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java new file mode 100644 index 0000000..0278a40 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java @@ -0,0 +1,65 @@ +/* + * 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.io.IOException; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +public class BreakPlacementTest extends TemplateTest { + + private static final String BREAK_NESTING_ERROR_MESSAGE_PART = "<#break> must be nested"; + + @Test + public void testValidPlacements() throws IOException, TemplateException { + assertOutput("<#assign x = 1><#switch x><#case 1>one<#break><#case 2>two</#switch>", "one"); + assertOutput("<#list 1..2 as x>${x}<#break></#list>", "1"); + assertOutput("<#list 1..2>[<#items as x>${x}<#break></#items>]</#list>", "[1]"); + assertOutput("<#list 1..2 as x>${x}<#list 1..3>B<#break>E<#items as y></#items></#list>E</#list>.", "1B."); + assertOutput("<#list 1..2 as x>${x}<#list 3..4 as x>${x}<#break></#list>;</#list>", "13;23;"); + assertOutput("<#list [1..2, 3..4, [], 5..6] as xs>[<#list xs as x>${x}<#else><#break></#list>]</#list>.", + "[12][34][."); + assertOutput("<#list [1..2, 3..4, [], 5..6] as xs>" + + "<#list xs>[<#items as x>${x}</#items>]<#else><#break></#list>" + + "</#list>.", + "[12][34]."); + assertOutput("<#forEach x in 1..2>${x}<#break></#forEach>", "1"); + } + + @Test + public void testInvalidPlacements() throws IOException, TemplateException { + assertErrorContains("<#break>", BREAK_NESTING_ERROR_MESSAGE_PART); + assertErrorContains("<#list 1..2 as x>${x}</#list><#break>", BREAK_NESTING_ERROR_MESSAGE_PART); + assertErrorContains("<#if false><#break></#if>", BREAK_NESTING_ERROR_MESSAGE_PART); + assertErrorContains("<#list xs><#break></#list>", BREAK_NESTING_ERROR_MESSAGE_PART); + assertErrorContains("<#list 1..2 as x>${x}<#else><#break></#list>", BREAK_NESTING_ERROR_MESSAGE_PART); + } + + @Test + public void testInvalidPlacementInsideMacro() throws IOException, TemplateException { + final String ftl = "<#list 1..2 as x>${x}<#macro m><#break></#macro></#list>"; + getConfiguration().setIncompatibleImprovements(Configuration.VERSION_3_0_0); + assertErrorContains(ftl, BREAK_NESTING_ERROR_MESSAGE_PART); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CamelCaseTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/CamelCaseTest.java b/src/test/java/org/apache/freemarker/core/CamelCaseTest.java new file mode 100644 index 0000000..859442c --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/CamelCaseTest.java @@ -0,0 +1,493 @@ +/* + * 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.assertTrue; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.apache.freemarker.core.ASTExpBuiltIn; +import org.apache.freemarker.core.ASTExpBuiltInVariable; +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.HTMLOutputFormat; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.UndefinedOutputFormat; +import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +public class CamelCaseTest extends TemplateTest { + + @Test + public void camelCaseSpecialVars() throws IOException, TemplateException { + getConfiguration().setOutputEncoding("utf-8"); + getConfiguration().setURLEscapingCharset("iso-8859-1"); + getConfiguration().setLocale(Locale.GERMANY); + assertOutput("${.dataModel?isHash?c}", "true"); + assertOutput("${.data_model?is_hash?c}", "true"); + assertOutput("${.localeObject.toString()}", "de_DE"); + assertOutput("${.locale_object.toString()}", "de_DE"); + assertOutput("${.templateName!'null'}", "null"); + assertOutput("${.template_name!'null'}", "null"); + assertOutput("${.currentTemplateName!'null'}", "null"); + assertOutput("${.current_template_name!'null'}", "null"); + assertOutput("${.mainTemplateName!'null'}", "null"); + assertOutput("${.main_template_name!'null'}", "null"); + assertOutput("${.outputEncoding}", "utf-8"); + assertOutput("${.output_encoding}", "utf-8"); + assertOutput("${.outputFormat}", UndefinedOutputFormat.INSTANCE.getName()); + assertOutput("${.output_format}", UndefinedOutputFormat.INSTANCE.getName()); + assertOutput("${.urlEscapingCharset}", "iso-8859-1"); + assertOutput("${.url_escaping_charset}", "iso-8859-1"); + assertOutput("${.currentNode!'-'}", "-"); + assertOutput("${.current_node!'-'}", "-"); + } + + @Test + public void camelCaseSpecialVarsInErrorMessage() throws IOException, TemplateException { + assertErrorContains("${.fooBar}", "dataModel", "\\!data_model"); + assertErrorContains("${.foo_bar}", "data_model", "\\!dataModel"); + // [2.4] If camel case will be the recommended style, then this need to be inverted: + assertErrorContains("${.foo}", "data_model", "\\!dataModel"); + + assertErrorContains("<#if x><#elseIf y></#if>${.foo}", "dataModel", "\\!data_model"); + assertErrorContains("<#if x><#elseif y></#if>${.foo}", "data_model", "\\!dataModel"); + + getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION); + assertErrorContains("${.foo}", "dataModel", "\\!data_model"); + getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION); + assertErrorContains("${.foo}", "data_model", "\\!dataModel"); + } + + @Test + public void camelCaseSettingNames() throws IOException, TemplateException { + assertOutput("<#setting booleanFormat='Y,N'>${true} <#setting booleanFormat='+,-'>${true}", "Y +"); + assertOutput("<#setting boolean_format='Y,N'>${true} <#setting boolean_format='+,-'>${true}", "Y +"); + + // Still works inside ?interpret + assertOutput("<@r\"<#setting booleanFormat='Y,N'>${true}\"?interpret />", "Y"); + } + + @Test + public void camelCaseFtlHeaderParameters() throws IOException, TemplateException { + getConfiguration().setOutputEncoding("utf-8"); + + assertOutput( + "<#ftl " + + "stripWhitespace=false " + + "stripText=true " + + "outputFormat='" + HTMLOutputFormat.INSTANCE.getName() + "' " + + "autoEsc=true " + + "nsPrefixes={} " + + ">\nx\n<#if true>\n${.outputFormat}\n</#if>\n", + "\nHTML\n"); + + assertOutput( + "<#ftl " + + "strip_whitespace=false " + + "strip_text=true " + + "output_format='" + HTMLOutputFormat.INSTANCE.getName() + "' " + + "auto_esc=true " + + "ns_prefixes={} " + + ">\nx\n<#if true>\n${.output_format}\n</#if>\n", + "\nHTML\n"); + + assertErrorContains("<#ftl strip_text=true xmlns={}>", "ns_prefixes", "\\!nsPrefixes"); + assertErrorContains("<#ftl stripText=true xmlns={}>", "nsPrefixes"); + + assertErrorContains("<#ftl stripWhitespace=true strip_text=true>", "naming convention"); + assertErrorContains("<#ftl strip_whitespace=true stripText=true>", "naming convention"); + assertErrorContains("<#ftl stripWhitespace=true>${.foo_bar}", "naming convention"); + assertErrorContains("<#ftl strip_whitespace=true>${.fooBar}", "naming convention"); + + getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION); + assertErrorContains("<#ftl strip_whitespace=true>", "naming convention"); + assertOutput("<#ftl stripWhitespace=true>${.outputEncoding}", "utf-8"); + + getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION); + assertErrorContains("<#ftl stripWhitespace=true>", "naming convention"); + assertOutput("<#ftl strip_whitespace=true>${.output_encoding}", "utf-8"); + + getConfiguration().setNamingConvention(Configuration.AUTO_DETECT_NAMING_CONVENTION); + assertOutput("<#ftl stripWhitespace=true>${.outputEncoding}", "utf-8"); + assertOutput("<#ftl encoding='iso-8859-1' stripWhitespace=true>${.outputEncoding}", "utf-8"); + assertOutput("<#ftl stripWhitespace=true encoding='iso-8859-1'>${.outputEncoding}", "utf-8"); + assertOutput("<#ftl encoding='iso-8859-1' strip_whitespace=true>${.output_encoding}", "utf-8"); + assertOutput("<#ftl strip_whitespace=true encoding='iso-8859-1'>${.output_encoding}", "utf-8"); + } + + @Test + public void camelCaseSettingNamesInErrorMessages() throws IOException, TemplateException { + assertErrorContains("<#setting fooBar=1>", "booleanFormat", "\\!boolean_format"); + assertErrorContains("<#setting foo_bar=1>", "boolean_format", "\\!booleanFormat"); + // [2.4] If camel case will be the recommended style, then this need to be inverted: + assertErrorContains("<#setting foo=1>", "boolean_format", "\\!booleanFormat"); + + assertErrorContains("<#if x><#elseIf y></#if><#setting foo=1>", "booleanFormat", "\\!boolean_format"); + assertErrorContains("<#if x><#elseif y></#if><#setting foo=1>", "boolean_format", "\\!booleanFormat"); + + getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION); + assertErrorContains("<#setting foo=1>", "booleanFormat", "\\!boolean_format"); + getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION); + assertErrorContains("<#setting foo=1>", "boolean_format", "\\!booleanFormat"); + } + + @Test + public void camelCaseIncludeParameters() throws IOException, TemplateException { + assertOutput("<#ftl stripWhitespace=true>[<#include 'noSuchTemplate' ignoreMissing=true>]", "[]"); + assertOutput("<#ftl strip_whitespace=true>[<#include 'noSuchTemplate' ignore_missing=true>]", "[]"); + assertErrorContains("<#ftl stripWhitespace=true>[<#include 'noSuchTemplate' ignore_missing=true>]", + "naming convention", "ignore_missing"); + assertErrorContains("<#ftl strip_whitespace=true>[<#include 'noSuchTemplate' ignoreMissing=true>]", + "naming convention", "ignoreMissing"); + } + + @Test + public void specialVarsHasBothNamingStyle() throws IOException, TemplateException { + assertContainsBothNamingStyles( + new HashSet(Arrays.asList(ASTExpBuiltInVariable.SPEC_VAR_NAMES)), + new NamePairAssertion() { @Override + public void assertPair(String name1, String name2) { } }); + } + + @Test + public void camelCaseBuiltIns() throws IOException, TemplateException { + assertOutput("${'x'?upperCase}", "X"); + assertOutput("${'x'?upper_case}", "X"); + } + + @Test + public void stringLiteralInterpolation() throws IOException, TemplateException { + assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, getConfiguration().getNamingConvention()); + getConfiguration().setSharedVariable("x", "x"); + + assertOutput("${'-${x?upperCase}-'} ${x?upperCase}", "-X- X"); + assertOutput("${x?upperCase} ${'-${x?upperCase}-'}", "X -X-"); + assertOutput("${'-${x?upper_case}-'} ${x?upper_case}", "-X- X"); + assertOutput("${x?upper_case} ${'-${x?upper_case}-'}", "X -X-"); + + assertErrorContains("${'-${x?upper_case}-'} ${x?upperCase}", + "naming convention", "legacy", "upperCase", "detection", "9"); + assertErrorContains("${x?upper_case} ${'-${x?upperCase}-'}", + "naming convention", "legacy", "upperCase", "detection", "5"); + assertErrorContains("${'-${x?upperCase}-'} ${x?upper_case}", + "naming convention", "camel", "upper_case"); + assertErrorContains("${x?upperCase} ${'-${x?upper_case}-'}", + "naming convention", "camel", "upper_case"); + + getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION); + assertOutput("${'-${x?upperCase}-'} ${x?upperCase}", "-X- X"); + assertErrorContains("${'-${x?upper_case}-'}", + "naming convention", "camel", "upper_case", "\\!detection"); + + getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION); + assertOutput("${'-${x?upper_case}-'} ${x?upper_case}", "-X- X"); + assertErrorContains("${'-${x?upperCase}-'}", + "naming convention", "legacy", "upperCase", "\\!detection"); + } + + @Test + public void evalAndInterpret() throws IOException, TemplateException { + assertEquals(Configuration.AUTO_DETECT_NAMING_CONVENTION, getConfiguration().getNamingConvention()); + // The naming convention detected doesn't affect the enclosing template's naming convention. + // - ?eval: + assertOutput("${\"'x'?upperCase\"?eval}${'x'?upper_case}", "XX"); + assertOutput("${\"'x'?upper_case\"?eval}${'x'?upperCase}", "XX"); + assertOutput("${'x'?upperCase}${\"'x'?upper_case\"?eval}", "XX"); + assertErrorContains("${\"'x'\n?upperCase\n?is_string\"?eval}", + "naming convention", "camel", "upperCase", "is_string", "line 2", "line 3"); + // - ?interpret: + assertOutput("<@r\"${'x'?upperCase}\"?interpret />${'x'?upper_case}", "XX"); + assertOutput("<@r\"${'x'?upper_case}\"?interpret />${'x'?upperCase}", "XX"); + assertOutput("${'x'?upper_case}<@r\"${'x'?upperCase}\"?interpret />", "XX"); + assertErrorContains("<@r\"${'x'\n?upperCase\n?is_string}\"?interpret />", + "naming convention", "camel", "upperCase", "is_string", "line 2", "line 3"); + + // Will be inherited by ?eval-ed/?interpreted fragments: + getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION); + // - ?eval: + assertErrorContains("${\"'x'?upper_case\"?eval}", "naming convention", "camel", "upper_case"); + assertOutput("${\"'x'?upperCase\"?eval}", "X"); + // - ?interpret: + assertErrorContains("<@r\"${'x'?upper_case}\"?interpret />", "naming convention", "camel", "upper_case"); + assertOutput("<@r\"${'x'?upperCase}\"?interpret />", "X"); + + // Again, will be inherited by ?eval-ed/?interpreted fragments: + getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION); + // - ?eval: + assertErrorContains("${\"'x'?upperCase\"?eval}", "naming convention", "legacy", "upperCase"); + assertOutput("${\"'x'?upper_case\"?eval}", "X"); + // - ?interpret: + assertErrorContains("<@r\"${'x'?upperCase}\"?interpret />", "naming convention", "legacy", "upperCase"); + assertOutput("<@r\"${'x'?upper_case}\"?interpret />", "X"); + } + + @Test + public void camelCaseBuiltInErrorMessage() throws IOException, TemplateException { + assertErrorContains("${'x'?upperCasw}", "upperCase", "\\!upper_case"); + assertErrorContains("${'x'?upper_casw}", "upper_case", "\\!upperCase"); + // [2.4] If camel case will be the recommended style, then this need to be inverted: + assertErrorContains("${'x'?foo}", "upper_case", "\\!upperCase"); + + assertErrorContains("<#if x><#elseIf y></#if> ${'x'?foo}", "upperCase", "\\!upper_case"); + assertErrorContains("<#if x><#elseif y></#if>${'x'?foo}", "upper_case", "\\!upperCase"); + + getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION); + assertErrorContains("${'x'?foo}", "upperCase", "\\!upper_case"); + getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION); + assertErrorContains("${'x'?foo}", "upper_case", "\\!upperCase"); + } + + @Test + public void builtInsHasBothNamingStyle() throws IOException, TemplateException { + assertContainsBothNamingStyles(getConfiguration().getSupportedBuiltInNames(), new NamePairAssertion() { + + @Override + public void assertPair(String name1, String name2) { + ASTExpBuiltIn bi1 = ASTExpBuiltIn.BUILT_INS_BY_NAME.get(name1); + ASTExpBuiltIn bi2 = ASTExpBuiltIn.BUILT_INS_BY_NAME.get(name2); + assertTrue("\"" + name1 + "\" and \"" + name2 + "\" doesn't belong to the same BI object.", + bi1 == bi2); + } + + }); + } + + private void assertContainsBothNamingStyles(Set<String> names, NamePairAssertion namePairAssertion) { + Set<String> underscoredNamesWithCamelCasePair = new HashSet<>(); + for (String name : names) { + if (_StringUtil.getIdentifierNamingConvention(name) == Configuration.CAMEL_CASE_NAMING_CONVENTION) { + String underscoredName = correctIsoBIExceptions(_StringUtil.camelCaseToUnderscored(name)); + assertTrue( + "Missing underscored variation \"" + underscoredName + "\" for \"" + name + "\".", + names.contains(underscoredName)); + assertTrue(underscoredNamesWithCamelCasePair.add(underscoredName)); + + namePairAssertion.assertPair(name, underscoredName); + } + } + for (String name : names) { + if (_StringUtil.getIdentifierNamingConvention(name) == Configuration.LEGACY_NAMING_CONVENTION) { + assertTrue("Missing camel case variation for \"" + name + "\".", + underscoredNamesWithCamelCasePair.contains(name)); + } + } + } + + private String correctIsoBIExceptions(String underscoredName) { + return underscoredName.replace("_n_z", "_nz").replace("_f_z", "_fz"); + } + + @Test + public void camelCaseDirectives() throws IOException, TemplateException { + camelCaseDirectives(false); + getConfiguration().setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX); + camelCaseDirectives(true); + } + + private void camelCaseDirectives(boolean squared) throws IOException, TemplateException { + assertOutput( + squared("<#list 1..4 as x><#if x == 1>one <#elseIf x == 2>two <#elseIf x == 3>three " + + "<#else>more</#if></#list>", squared), + "one two three more"); + assertOutput( + squared("<#list 1..4 as x><#if x == 1>one <#elseif x == 2>two <#elseif x == 3>three " + + "<#else>more</#if></#list>", squared), + "one two three more"); + + assertOutput( + squared("<#escape x as x?upperCase>${'a'}<#noEscape>${'b'}</#noEscape></#escape>", squared), + "Ab"); + assertOutput( + squared("<#escape x as x?upper_case>${'a'}<#noescape>${'b'}</#noescape></#escape>", squared), + "Ab"); + + assertOutput( + squared("<#noParse></#noparse></#noParse>", squared), + squared("</#noparse>", squared)); + assertOutput( + squared("<#noparse></#noParse></#noparse>", squared), + squared("</#noParse>", squared)); + + assertOutput( + squared("<#forEach x in 1..3>${x}</#forEach>", squared), + "123"); + assertOutput( + squared("<#foreach x in 1..3>${x}</#foreach>", squared), + "123"); + } + + private String squared(String ftl, boolean squared) { + return squared ? ftl.replace('<', '[').replace('>', ']') : ftl; + } + + @Test + public void explicitNamingConvention() throws IOException, TemplateException { + explicitNamingConvention(false); + explicitNamingConvention(true); + } + + private void explicitNamingConvention(boolean squared) throws IOException, TemplateException { + if (squared) { + getConfiguration().setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX); + } + + getConfiguration().setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION); + + assertErrorContains( + squared("<#if true>t<#elseif false>f</#if>", squared), + "naming convention", "camel", "#elseif"); + assertOutput( + squared("<#if true>t<#elseIf false>f</#if>", squared), + "t"); + + assertErrorContains( + squared("<#noparse>${x}</#noparse>", squared), + "naming convention", "camel", "#noparse"); + assertOutput( + squared("<#noParse>${x}</#noParse>", squared), + "${x}"); + + assertErrorContains( + squared("<#escape x as -x><#noescape>${1}</#noescape></#escape>", squared), + "naming convention", "camel", "#noescape"); + assertOutput( + squared("<#escape x as -x><#noEscape>${1}</#noEscape></#escape>", squared), + "1"); + + assertErrorContains( + squared("<#foreach x in 1..3>${x}</#foreach>", squared), + "naming convention", "camel", "#foreach"); + assertOutput( + squared("<#forEach x in 1..3>${x}</#forEach>", squared), + "123"); + + // --- + + getConfiguration().setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION); + + assertErrorContains( + squared("<#if true>t<#elseIf false>f</#if>", squared), + "naming convention", "legacy", "#elseIf"); + assertOutput( + squared("<#if true>t<#elseif false>f</#if>", squared), + "t"); + + assertErrorContains( + squared("<#noParse>${x}</#noParse>", squared), + "naming convention", "legacy", "#noParse"); + assertOutput( + squared("<#noparse>${x}</#noparse>", squared), + "${x}"); + + assertErrorContains( + squared("<#escape x as -x><#noEscape>${1}</#noEscape></#escape>", squared), + "naming convention", "legacy", "#noEscape"); + assertOutput( + squared("<#escape x as -x><#noescape>${1}</#noescape></#escape>", squared), + "1"); + + assertErrorContains( + squared("<#forEach x in 1..3>${x}</#forEach>", squared), + "naming convention", "legacy", "#forEach"); + assertOutput( + squared("<#foreach x in 1..3>${x}</#foreach>", squared), + "123"); + } + + @Test + public void inconsistentAutoDetectedNamingConvention() { + assertErrorContains( + "<#if x><#elseIf y><#elseif z></#if>", + "naming convention", "camel"); + assertErrorContains( + "<#if x><#elseif y><#elseIf z></#if>", + "naming convention", "legacy"); + assertErrorContains( + "<#if x><#elseIf y></#if><#noparse></#noparse>", + "naming convention", "camel"); + assertErrorContains( + "<#if x><#elseif y></#if><#noParse></#noParse>", + "naming convention", "legacy"); + assertErrorContains( + "<#if x><#elseif y><#elseIf z></#if>", + "naming convention", "legacy"); + assertErrorContains( + "<#escape x as x + 1><#noEscape></#noescape></#escape>", + "naming convention", "camel"); + assertErrorContains( + "<#escape x as x + 1><#noEscape></#noEscape><#noescape></#noescape></#escape>", + "naming convention", "camel"); + assertErrorContains( + "<#escape x as x + 1><#noescape></#noEscape></#escape>", + "naming convention", "legacy"); + assertErrorContains( + "<#escape x as x + 1><#noescape></#noescape><#noEscape></#noEscape></#escape>", + "naming convention", "legacy"); + assertErrorContains( + "<#forEach x in 1..3>${x}</#foreach>", + "naming convention", "camel"); + assertErrorContains( + "<#forEach x in 1..3>${x}</#forEach><#foreach x in 1..3>${x}</#foreach>", + "naming convention", "camel"); + assertErrorContains( + "<#foreach x in 1..3>${x}</#forEach>", + "naming convention", "legacy"); + assertErrorContains( + "<#foreach x in 1..3>${x}</#foreach><#forEach x in 1..3>${x}</#forEach>", + "naming convention", "legacy"); + + assertErrorContains("${x?upperCase?is_string}", + "naming convention", "camel", "upperCase", "is_string"); + assertErrorContains("${x?upper_case?isString}", + "naming convention", "legacy", "upper_case", "isString"); + + assertErrorContains("<#setting outputEncoding='utf-8'>${x?is_string}", + "naming convention", "camel", "outputEncoding", "is_string"); + assertErrorContains("<#setting output_encoding='utf-8'>${x?isString}", + "naming convention", "legacy", "output_encoding", "isString"); + + assertErrorContains("${x?isString}<#setting output_encoding='utf-8'>", + "naming convention", "camel", "isString", "output_encoding"); + assertErrorContains("${x?is_string}<#setting outputEncoding='utf-8'>", + "naming convention", "legacy", "is_string", "outputEncoding"); + + assertErrorContains("${.outputEncoding}${x?is_string}", + "naming convention", "camel", "outputEncoding", "is_string"); + assertErrorContains("${.output_encoding}${x?isString}", + "naming convention", "legacy", "output_encoding", "isString"); + + assertErrorContains("${x?upperCase}<#noparse></#noparse>", + "naming convention", "camel", "upperCase", "noparse"); + assertErrorContains("${x?upper_case}<#noParse></#noParse>", + "naming convention", "legacy", "upper_case", "noParse"); + } + + private interface NamePairAssertion { + + void assertPair(String name1, String name2); + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java b/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java new file mode 100644 index 0000000..b0be788 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java @@ -0,0 +1,71 @@ +/* + * 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.io.IOException; +import java.io.StringWriter; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.ParseException; +import org.apache.freemarker.core.TemplateNotFoundException; +import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException; +import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader; +import org.apache.freemarker.test.CopyrightCommentRemoverTemplateLoader; +import org.apache.freemarker.test.util.FileTestCase; + +public class CanonicalFormTest extends FileTestCase { + + public CanonicalFormTest(String name) { + super(name); + } + + public void testMacrosCanonicalForm() throws Exception { + assertCanonicalFormOf("cano-macros.ftl"); + } + + public void testIdentifierEscapingCanonicalForm() throws Exception { + assertCanonicalFormOf("cano-identifier-escaping.ftl"); + } + + public void testAssignmentCanonicalForm() throws Exception { + assertCanonicalFormOf("cano-assignments.ftl"); + } + + public void testBuiltInCanonicalForm() throws Exception { + assertCanonicalFormOf("cano-builtins.ftl"); + } + + public void testStringLiteralInterpolationCanonicalForm() throws Exception { + assertCanonicalFormOf("cano-strlitinterpolation.ftl"); + } + + private void assertCanonicalFormOf(String ftlFileName) + throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException { + Configuration cfg = new Configuration(Configuration.VERSION_3_0_0); + cfg.setTemplateLoader( + new CopyrightCommentRemoverTemplateLoader( + new ClassTemplateLoader(CanonicalFormTest.class, ""))); + StringWriter sw = new StringWriter(); + cfg.getTemplate(ftlFileName).dump(sw); + + assertExpectedFileEqualsString(ftlFileName + ".out", sw.toString()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java b/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java new file mode 100644 index 0000000..fe8da82 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/CoercionToTextualTest.java @@ -0,0 +1,137 @@ +/* + * 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.io.IOException; +import java.util.Collections; +import java.util.Date; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.HTMLOutputFormat; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.impl.SimpleDate; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Before; +import org.junit.Test; + +@SuppressWarnings("boxing") +public class CoercionToTextualTest extends TemplateTest { + + /** 2015-09-06T12:00:00Z */ + private static long T = 1441540800000L; + private static TemplateDateModel TM = new SimpleDate(new Date(T), TemplateDateModel.DATETIME); + + @Test + public void testBasicStringBuiltins() throws IOException, TemplateException { + assertOutput("${s?upperCase}", "ABC"); + assertOutput("${n?string?lowerCase}", "1.50e+03"); + assertErrorContains("${n?lowerCase}", "convert", "string", "markup", "text/html"); + assertOutput("${dt?string?lowerCase}", "2015-09-06t12:00:00z"); + assertErrorContains("${dt?lowerCase}", "convert", "string", "markup", "text/html"); + assertOutput("${b?upperCase}", "Y"); + assertErrorContains("${m?upperCase}", "convertible to string", "HTMLOutputModel"); + } + + @Test + public void testEscBuiltin() throws IOException, TemplateException { + Configuration cfg = getConfiguration(); + cfg.setOutputFormat(HTMLOutputFormat.INSTANCE); + cfg.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY); + cfg.setBooleanFormat("<y>,<n>"); + + assertOutput("${'a<b'?esc}", "a<b"); + assertOutput("${n?string?esc}", "1.50E+03"); + assertOutput("${n?esc}", "1.50*10<sup>3</sup>"); + assertOutput("${dt?string?esc}", "2015-09-06T12:00:00Z"); + assertOutput("${dt?esc}", "2015-09-06<span class='T'>T</span>12:00:00Z"); + assertOutput("${b?esc}", "<y>"); + assertOutput("${m?esc}", "<p>M</p>"); + } + + @Test + public void testStringOverloadedBuiltIns() throws IOException, TemplateException { + assertOutput("${s?contains('b')}", "y"); + assertOutput("${n?string?contains('E')}", "y"); + assertErrorContains("${n?contains('E')}", "convert", "string", "markup", "text/html"); + assertErrorContains("${n?indexOf('E')}", "convert", "string", "markup", "text/html"); + assertOutput("${dt?string?contains('0')}", "y"); + assertErrorContains("${dt?contains('0')}", "convert", "string", "markup", "text/html"); + assertErrorContains("${m?contains('0')}", "convertible to string", "HTMLOutputModel"); + assertErrorContains("${m?indexOf('0')}", "convertible to string", "HTMLOutputModel"); + } + + @Test + public void testMarkupStringBuiltIns() throws IOException, TemplateException { + assertErrorContains("${n?string?markupString}", "Expected", "markup", "string"); + assertErrorContains("${n?markupString}", "Expected", "markup", "number"); + assertErrorContains("${dt?markupString}", "Expected", "markup", "date"); + } + + @Test + public void testSimpleInterpolation() throws IOException, TemplateException { + assertOutput("${s}", "abc"); + assertOutput("${n?string}", "1.50E+03"); + assertOutput("${n}", "1.50*10<sup>3</sup>"); + assertOutput("${dt?string}", "2015-09-06T12:00:00Z"); + assertOutput("${dt}", "2015-09-06<span class='T'>T</span>12:00:00Z"); + assertOutput("${b}", "y"); + assertOutput("${m}", "<p>M</p>"); + } + + @Test + public void testConcatenation() throws IOException, TemplateException { + assertOutput("${s + '&'}", "abc&"); + assertOutput("${n?string + '&'}", "1.50E+03&"); + assertOutput("${n + '&'}", "1.50*10<sup>3</sup>&"); + assertOutput("${dt?string + '&'}", "2015-09-06T12:00:00Z&"); + assertOutput("${dt + '&'}", "2015-09-06<span class='T'>T</span>12:00:00Z&"); + assertOutput("${b + '&'}", "y&"); + assertOutput("${m + '&'}", "<p>M</p>&"); + } + + @Test + public void testConcatenation2() throws IOException, TemplateException { + assertOutput("${'&' + s}", "&abc"); + assertOutput("${'&' + n?string}", "&1.50E+03"); + assertOutput("${'&' + n}", "&1.50*10<sup>3</sup>"); + assertOutput("${'&' + dt?string}", "&2015-09-06T12:00:00Z"); + assertOutput("${'&' + dt}", "&2015-09-06<span class='T'>T</span>12:00:00Z"); + assertOutput("${'&' + b}", "&y"); + assertOutput("${'&' + m}", "&<p>M</p>"); + } + + @Before + public void setup() throws TemplateModelException { + Configuration cfg = getConfiguration(); + cfg.setCustomNumberFormats(Collections.singletonMap("G", PrintfGTemplateNumberFormatFactory.INSTANCE)); + cfg.setCustomDateFormats(Collections.singletonMap("HI", HTMLISOTemplateDateFormatFactory.INSTANCE)); + cfg.setNumberFormat("@G 3"); + cfg.setDateTimeFormat("@HI"); + cfg.setBooleanFormat("y,n"); + + addToDataModel("s", "abc"); + addToDataModel("n", 1500); + addToDataModel("dt", TM); + addToDataModel("b", Boolean.TRUE); + addToDataModel("m", HTMLOutputFormat.INSTANCE.fromMarkup("<p>M</p>")); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CombinedMarkupOutputFormatTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/CombinedMarkupOutputFormatTest.java b/src/test/java/org/apache/freemarker/core/CombinedMarkupOutputFormatTest.java new file mode 100644 index 0000000..783c437 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/CombinedMarkupOutputFormatTest.java @@ -0,0 +1,198 @@ +/* + * 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 java.io.StringWriter; + +import org.apache.freemarker.core.CombinedMarkupOutputFormat; +import org.apache.freemarker.core.HTMLOutputFormat; +import org.apache.freemarker.core.MarkupOutputFormat; +import org.apache.freemarker.core.RTFOutputFormat; +import org.apache.freemarker.core.TemplateCombinedMarkupOutputModel; +import org.apache.freemarker.core.XMLOutputFormat; +import org.apache.freemarker.core.model.TemplateModelException; +import org.junit.Test; + +public class CombinedMarkupOutputFormatTest { + + private static final CombinedMarkupOutputFormat HTML_RTF = new CombinedMarkupOutputFormat( + HTMLOutputFormat.INSTANCE, RTFOutputFormat.INSTANCE); + private static final CombinedMarkupOutputFormat XML_XML = new CombinedMarkupOutputFormat( + XMLOutputFormat.INSTANCE, XMLOutputFormat.INSTANCE); + + @Test + public void testName() { + assertEquals("HTML{RTF}", HTML_RTF.getName()); + assertEquals("XML{XML}", XML_XML.getName()); + } + + @Test + public void testOutputMO() throws TemplateModelException, IOException { + StringWriter out = new StringWriter(); + + HTML_RTF.output(HTML_RTF.fromMarkup("<pre>\\par Test "), out); + HTML_RTF.output(HTML_RTF.fromPlainTextByEscaping("foo { bar } \\ "), out); + HTML_RTF.output(HTML_RTF.fromPlainTextByEscaping("& baaz "), out); + HTML_RTF.output(HTML_RTF.fromPlainTextByEscaping("\\par & qwe"), out); + HTML_RTF.output(HTML_RTF.fromMarkup("\\par{0} End</pre>"), out); + + assertEquals( + "<pre>\\par Test " + + "foo \\{ bar \\} \\\\ " + + "& baaz " + + "\\\\par & qwe" + + "\\par{0} End</pre>", + out.toString()); + } + + @Test + public void testOutputMO2() throws TemplateModelException, IOException { + StringWriter out = new StringWriter(); + + XML_XML.output(XML_XML.fromMarkup("<pre><p> Test "), out); + XML_XML.output(XML_XML.fromPlainTextByEscaping("a & b < c"), out); + XML_XML.output(XML_XML.fromMarkup(" End</pre>"), out); + + assertEquals( + "<pre><p> Test " + + "a &amp; b &lt; c" + + " End</pre>", + out.toString()); + } + + @Test + public void testOutputMO3() throws TemplateModelException, IOException { + MarkupOutputFormat outputFormat = new CombinedMarkupOutputFormat( + RTFOutputFormat.INSTANCE, + new CombinedMarkupOutputFormat(RTFOutputFormat.INSTANCE, RTFOutputFormat.INSTANCE)); + StringWriter out = new StringWriter(); + + outputFormat.output(outputFormat.fromPlainTextByEscaping("b{}"), out); + outputFormat.output(outputFormat.fromMarkup("a{}"), out); + + assertEquals( + "b\\\\\\\\\\\\\\{\\\\\\\\\\\\\\}" + + "a{}", + out.toString()); + } + + @Test + public void testOutputString() throws TemplateModelException, IOException { + StringWriter out = new StringWriter(); + + HTML_RTF.output("a", out); + HTML_RTF.output("{", out); + HTML_RTF.output("<b>}c", out); + + assertEquals("a\\{<b>\\}c", out.toString()); + } + + @Test + public void testOutputString2() throws TemplateModelException, IOException { + StringWriter out = new StringWriter(); + + XML_XML.output("a", out); + XML_XML.output("&", out); + XML_XML.output("<b>", out); + + assertEquals("a&amp;&lt;b&gt;", out.toString()); + } + + @Test + public void testFromPlainTextByEscaping() throws TemplateModelException { + String plainText = "a\\b&c"; + TemplateCombinedMarkupOutputModel mo = HTML_RTF.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>"; + TemplateCombinedMarkupOutputModel mo = HTML_RTF.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>"; + TemplateCombinedMarkupOutputModel mo = HTML_RTF.fromMarkup(markup); + assertSame(markup, HTML_RTF.getMarkupString(mo)); + } + + { + String safe = "abc"; + TemplateCombinedMarkupOutputModel mo = HTML_RTF.fromPlainTextByEscaping(safe); + assertSame(safe, HTML_RTF.getMarkupString(mo)); + } + } + + @Test + public void testConcat() throws Exception { + assertMO( + "ab", null, + HTML_RTF.concat( + new TemplateCombinedMarkupOutputModel("a", null, HTML_RTF), + new TemplateCombinedMarkupOutputModel("b", null, HTML_RTF))); + assertMO( + null, "ab", + HTML_RTF.concat( + new TemplateCombinedMarkupOutputModel(null, "a", HTML_RTF), + new TemplateCombinedMarkupOutputModel(null, "b", HTML_RTF))); + assertMO( + null, "{<a>}\\{<b>\\}", + HTML_RTF.concat( + new TemplateCombinedMarkupOutputModel(null, "{<a>}", HTML_RTF), + new TemplateCombinedMarkupOutputModel("{<b>}", null, HTML_RTF))); + assertMO( + null, "\\{<a>\\}{<b>}", + HTML_RTF.concat( + new TemplateCombinedMarkupOutputModel("{<a>}", null, HTML_RTF), + new TemplateCombinedMarkupOutputModel(null, "{<b>}", HTML_RTF))); + } + + @Test + public void testEscaplePlainText() throws TemplateModelException { + assertEquals("", HTML_RTF.escapePlainText("")); + assertEquals("a", HTML_RTF.escapePlainText("a")); + assertEquals("\\{a\\\\b&\\}", HTML_RTF.escapePlainText("{a\\b&}")); + assertEquals("a\\\\b&", HTML_RTF.escapePlainText("a\\b&")); + assertEquals("\\{\\}&", HTML_RTF.escapePlainText("{}&")); + + assertEquals("a", XML_XML.escapePlainText("a")); + assertEquals("a&apos;b", XML_XML.escapePlainText("a'b")); + } + + private void assertMO(String pc, String mc, TemplateCombinedMarkupOutputModel mo) { + assertEquals(pc, mo.getPlainTextContent()); + assertEquals(mc, mo.getMarkupContent()); + } + + @Test + public void testGetMimeType() { + assertEquals("text/html", HTML_RTF.getMimeType()); + assertEquals("application/xml", XML_XML.getMimeType()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/ConfigurableTest.java b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java new file mode 100644 index 0000000..8e2abd8 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/ConfigurableTest.java @@ -0,0 +1,183 @@ +/* + * 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.greaterThan; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.freemarker.core.Configurable; +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.Template; +import org.apache.freemarker.core.util._StringUtil; +import org.junit.Test; + +public class ConfigurableTest { + + @Test + public void testGetSettingNamesAreSorted() throws Exception { + Configurable cfgable = createConfigurable(); + for (boolean camelCase : new boolean[] { false, true }) { + Collection<String> names = cfgable.getSettingNames(camelCase); + String prevName = null; + for (String name : names) { + if (prevName != null) { + assertThat(name, greaterThan(prevName)); + } + prevName = name; + } + } + } + + @Test + public void testStaticFieldKeysCoverAllGetSettingNames() throws Exception { + Configurable cfgable = createConfigurable(); + Collection<String> names = cfgable.getSettingNames(false); + for (String name : names) { + assertTrue("No field was found for " + name, keyFieldExists(name)); + } + } + + @Test + public void testGetSettingNamesCoversAllStaticKeyFields() throws Exception { + Configurable cfgable = createConfigurable(); + Collection<String> names = cfgable.getSettingNames(false); + + for (Field f : Configurable.class.getFields()) { + if (f.getName().endsWith("_KEY")) { + final Object name = f.get(null); + assertTrue("Missing setting name: " + name, names.contains(name)); + } + } + } + + @Test + public void testKeyStaticFieldsHasAllVariationsAndCorrectFormat() throws IllegalArgumentException, IllegalAccessException { + ConfigurableTest.testKeyStaticFieldsHasAllVariationsAndCorrectFormat(Configurable.class); + } + + @Test + public void testGetSettingNamesNameConventionsContainTheSame() throws Exception { + Configurable cfgable = createConfigurable(); + ConfigurableTest.testGetSettingNamesNameConventionsContainTheSame( + new ArrayList<>(cfgable.getSettingNames(false)), + new ArrayList<>(cfgable.getSettingNames(true))); + } + + public static void testKeyStaticFieldsHasAllVariationsAndCorrectFormat( + Class<? extends Configurable> confClass) throws IllegalArgumentException, IllegalAccessException { + // For all _KEY fields there must be a _KEY_CAMEL_CASE and a _KEY_SNAKE_CASE field. + // Their content must not contradict the expected naming convention. + // They _KEY filed value must be deducable from the field name + // The _KEY value must be the same as _KEY_SNAKE_CASE field. + // The _KEY_CAMEL_CASE converted to snake case must give the value of the _KEY_SNAKE_CASE. + for (Field field : confClass.getFields()) { + String fieldName = field.getName(); + if (fieldName.endsWith("_KEY")) { + String keyFieldValue = (String) field.get(null); + assertNotEquals(Configuration.CAMEL_CASE_NAMING_CONVENTION, + _StringUtil.getIdentifierNamingConvention(keyFieldValue)); + assertEquals(fieldName.substring(0, fieldName.length() - 4).toLowerCase(), keyFieldValue); + + try { + String keySCFieldValue = (String) confClass.getField(fieldName + "_SNAKE_CASE").get(null); + assertEquals(keyFieldValue, keySCFieldValue); + } catch (NoSuchFieldException e) { + fail("Missing ..._SNAKE_CASE field for " + fieldName); + } + + try { + String keyCCFieldValue = (String) confClass.getField(fieldName + "_CAMEL_CASE").get(null); + assertNotEquals(Configuration.LEGACY_NAMING_CONVENTION, + _StringUtil.getIdentifierNamingConvention(keyCCFieldValue)); + assertEquals(keyFieldValue, _StringUtil.camelCaseToUnderscored(keyCCFieldValue)); + } catch (NoSuchFieldException e) { + fail("Missing ..._CAMEL_CASE field for " + fieldName); + } + } + } + + // For each _KEY_SNAKE_CASE field there must be a _KEY field. + for (Field field : confClass.getFields()) { + String fieldName = field.getName(); + if (fieldName.endsWith("_KEY_SNAKE_CASE")) { + try { + confClass.getField(fieldName.substring(0, fieldName.length() - 11)).get(null); + } catch (NoSuchFieldException e) { + fail("Missing ..._KEY field for " + fieldName); + } + } + } + + // For each _KEY_CAMEL_CASE field there must be a _KEY field. + for (Field field : confClass.getFields()) { + String fieldName = field.getName(); + if (fieldName.endsWith("_KEY_CAMEL_CASE")) { + try { + confClass.getField(fieldName.substring(0, fieldName.length() - 11)).get(null); + } catch (NoSuchFieldException e) { + fail("Missing ..._KEY field for " + fieldName); + } + } + } + } + + public static void testGetSettingNamesNameConventionsContainTheSame(List<String> namesSCList, List<String> namesCCList) { + Set<String> namesSC = new HashSet<>(namesSCList); + assertEquals(namesSCList.size(), namesSC.size()); + + Set<String> namesCC = new HashSet<>(namesCCList); + assertEquals(namesCCList.size(), namesCC.size()); + + assertEquals(namesSC.size(), namesCC.size()); + + for (String nameCC : namesCC) { + final String nameSC = _StringUtil.camelCaseToUnderscored(nameCC); + if (!namesSC.contains(nameSC)) { + fail("\"" + nameCC + "\" misses corresponding snake case name, \"" + nameSC + "\"."); + } + } + } + + private Configurable createConfigurable() throws IOException { + return new Template(null, "", new Configuration(Configuration.VERSION_3_0_0)); + } + + private boolean keyFieldExists(String name) throws Exception { + try { + Configurable.class.getField(name.toUpperCase() + "_KEY"); + } catch (NoSuchFieldException e) { + return false; + } + return true; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java index 6a7b4d9..08132ad 100644 --- a/src/test/java/org/apache/freemarker/core/ConfigurationTest.java +++ b/src/test/java/org/apache/freemarker/core/ConfigurationTest.java @@ -45,28 +45,8 @@ import java.util.Map; import java.util.Set; import java.util.TimeZone; -import org.apache.freemarker.core.ast.BaseNTemplateNumberFormatFactory; -import org.apache.freemarker.core.ast.CombinedMarkupOutputFormat; -import org.apache.freemarker.core.ast.Configurable; -import org.apache.freemarker.core.ast.Configurable.SettingValueAssignmentException; -import org.apache.freemarker.core.ast.Configurable.UnknownSettingException; -import org.apache.freemarker.core.ast.ConfigurableTest; -import org.apache.freemarker.core.ast.CustomHTMLOutputFormat; -import org.apache.freemarker.core.ast.DummyOutputFormat; -import org.apache.freemarker.core.ast.Environment; -import org.apache.freemarker.core.ast.EpochMillisDivTemplateDateFormatFactory; -import org.apache.freemarker.core.ast.EpochMillisTemplateDateFormatFactory; -import org.apache.freemarker.core.ast.HTMLOutputFormat; -import org.apache.freemarker.core.ast.HexTemplateNumberFormatFactory; -import org.apache.freemarker.core.ast.MarkupOutputFormat; -import org.apache.freemarker.core.ast.OutputFormat; -import org.apache.freemarker.core.ast.ParseException; -import org.apache.freemarker.core.ast.RTFOutputFormat; -import org.apache.freemarker.core.ast.TemplateDateFormatFactory; -import org.apache.freemarker.core.ast.TemplateNumberFormatFactory; -import org.apache.freemarker.core.ast.UndefinedOutputFormat; -import org.apache.freemarker.core.ast.UnregisteredOutputFormatException; -import org.apache.freemarker.core.ast.XMLOutputFormat; +import org.apache.freemarker.core.Configurable.SettingValueAssignmentException; +import org.apache.freemarker.core.Configurable.UnknownSettingException; import org.apache.freemarker.core.model.TemplateModelException; import org.apache.freemarker.core.model.TemplateScalarModel; import org.apache.freemarker.core.model.impl.DefaultObjectWrapper; @@ -911,7 +891,7 @@ public class ConfigurationTest extends TestCase { assertTrue(cfg.getRegisteredCustomOutputFormats().isEmpty()); cfg.setSetting(Configuration.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_CAMEL_CASE, - "[org.apache.freemarker.core.ast.CustomHTMLOutputFormat(), org.apache.freemarker.core.ast.DummyOutputFormat()]"); + "[org.apache.freemarker.core.CustomHTMLOutputFormat(), org.apache.freemarker.core.DummyOutputFormat()]"); assertEquals( ImmutableList.of(CustomHTMLOutputFormat.INSTANCE, DummyOutputFormat.INSTANCE), new ArrayList(cfg.getRegisteredCustomOutputFormats())); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CoreLocaleUtilsTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/CoreLocaleUtilsTest.java b/src/test/java/org/apache/freemarker/core/CoreLocaleUtilsTest.java new file mode 100644 index 0000000..3993c1e --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/CoreLocaleUtilsTest.java @@ -0,0 +1,73 @@ +/* + * 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.util.Locale; + +import org.apache.freemarker.core.util._LocaleUtil; +import org.junit.Test; + +public class CoreLocaleUtilsTest { + + @Test + public void testGetLessSpecificLocale() { + Locale locale; + + locale = new Locale("ru", "RU", "Linux"); + assertEquals("ru_RU_Linux", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertEquals("ru_RU", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertEquals("ru", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertNull(locale); + + locale = new Locale("ch", "CH"); + assertEquals("ch_CH", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertEquals("ch", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertNull(locale); + + locale = new Locale("ja"); + assertEquals("ja", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertNull(locale); + + locale = new Locale("ja", "", ""); + assertEquals("ja", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertNull(locale); + + locale = new Locale(""); + assertEquals("", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertNull(locale); + + locale = new Locale("hu", "", "Linux"); + assertEquals("hu__Linux", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertEquals("hu", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertNull(locale); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java index 6ee16cc..1778c60 100644 --- a/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java +++ b/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java @@ -28,8 +28,6 @@ import static org.junit.Assert.fail; import java.math.BigDecimal; import java.util.Arrays; -import org.apache.freemarker.core.ast.CustomAttribute; -import org.apache.freemarker.core.ast.Environment; import org.apache.freemarker.core.util._NullWriter; import org.junit.Test; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CustomHTMLOutputFormat.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/CustomHTMLOutputFormat.java b/src/test/java/org/apache/freemarker/core/CustomHTMLOutputFormat.java new file mode 100644 index 0000000..86e69a4 --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/CustomHTMLOutputFormat.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 java.io.IOException; +import java.io.Writer; + +import org.apache.freemarker.core.CommonMarkupOutputFormat; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.util._StringUtil; + +/** + * Represents the HTML output format. + * + * @since 2.3.24 + */ +public final class CustomHTMLOutputFormat extends CommonMarkupOutputFormat<CustomTemplateHTMLModel> { + + public static final CustomHTMLOutputFormat INSTANCE = new CustomHTMLOutputFormat(); + + private CustomHTMLOutputFormat() { + // Only to decrease visibility + } + + @Override + public String getName() { + return "HTML"; + } + + @Override + public String getMimeType() { + return "text/html"; + } + + @Override + public void output(String textToEsc, Writer out) throws IOException, TemplateModelException { + // This is lazy - don't do it in reality. + out.write(escapePlainText(textToEsc)); + } + + @Override + public String escapePlainText(String plainTextContent) { + return _StringUtil.XHTMLEnc(plainTextContent.replace('x', 'X')); + } + + @Override + public boolean isLegacyBuiltInBypassed(String builtInName) { + return builtInName.equals("html") || builtInName.equals("xml") || builtInName.equals("xhtml"); + } + + @Override + protected CustomTemplateHTMLModel newTemplateMarkupOutputModel(String plainTextContent, String markupContent) { + return new CustomTemplateHTMLModel(plainTextContent, markupContent); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/test/java/org/apache/freemarker/core/CustomTemplateHTMLModel.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/freemarker/core/CustomTemplateHTMLModel.java b/src/test/java/org/apache/freemarker/core/CustomTemplateHTMLModel.java new file mode 100644 index 0000000..60a419a --- /dev/null +++ b/src/test/java/org/apache/freemarker/core/CustomTemplateHTMLModel.java @@ -0,0 +1,34 @@ +/* + * 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.core.CommonTemplateMarkupOutputModel; + +public final class CustomTemplateHTMLModel extends CommonTemplateMarkupOutputModel<CustomTemplateHTMLModel> { + + CustomTemplateHTMLModel(String plainTextContent, String markupContent) { + super(plainTextContent, markupContent); + } + + @Override + public CustomHTMLOutputFormat getOutputFormat() { + return CustomHTMLOutputFormat.INSTANCE; + } + +}
