http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java new file mode 100644 index 0000000..2a0ae9d --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/util/StringUtilTest.java @@ -0,0 +1,403 @@ +/* + * 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.util; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.regex.Pattern; + +import org.hamcrest.Matchers; +import org.junit.Test; + +public class StringUtilTest { + + @Test + public void testV2319() { + assertEquals("\\n\\r\\f\\b\\t\\x00\\x19", _StringUtil.javaScriptStringEnc("\n\r\f\b\t\u0000\u0019")); + } + + @Test + public void testControlChars() { + assertEsc( + "\n\r\f\b\t \u0000\u0019\u001F \u007F\u0080\u009F \u2028\u2029", + "\\n\\r\\f\\b\\t \\x00\\x19\\x1F \\x7F\\x80\\x9F \\u2028\\u2029", + "\\n\\r\\f\\b\\t \\u0000\\u0019\\u001F \\u007F\\u0080\\u009F \\u2028\\u2029"); + } + + @Test + public void testHtmlChars() { + assertEsc( + "<safe>/>->]> </foo> <!-- --> <![CDATA[ ]]> <?php?>", + "<safe>/>->]> <\\/foo> \\x3C!-- --\\> \\x3C![CDATA[ ]]\\> \\x3C?php?>", + "<safe>/>->]> <\\/foo> \\u003C!-- --\\u003E \\u003C![CDATA[ ]]\\u003E \\u003C?php?>"); + assertEsc("<!c", "\\x3C!c", "\\u003C!c"); + assertEsc("c<!", "c\\x3C!", "c\\u003C!"); + assertEsc("c<", "c\\x3C", "c\\u003C"); + assertEsc("c<c", "c<c", "c<c"); + assertEsc("<c", "<c", "<c"); + assertEsc(">", "\\>", "\\u003E"); + assertEsc("->", "-\\>", "-\\u003E"); + assertEsc("-->", "--\\>", "--\\u003E"); + assertEsc("c-->", "c--\\>", "c--\\u003E"); + assertEsc("-->c", "--\\>c", "--\\u003Ec"); + assertEsc("]>", "]\\>", "]\\u003E"); + assertEsc("]]>", "]]\\>", "]]\\u003E"); + assertEsc("c]]>", "c]]\\>", "c]]\\u003E"); + assertEsc("]]>c", "]]\\>c", "]]\\u003Ec"); + assertEsc("c->", "c->", "c->"); + assertEsc("c>", "c>", "c>"); + assertEsc("-->", "--\\>", "--\\u003E"); + assertEsc("/", "\\/", "\\/"); + assertEsc("/c", "\\/c", "\\/c"); + assertEsc("</", "<\\/", "<\\/"); + assertEsc("</c", "<\\/c", "<\\/c"); + assertEsc("c/", "c/", "c/"); + } + + @Test + public void testJSChars() { + assertEsc("\"", "\\\"", "\\\""); + assertEsc("'", "\\'", "'"); + assertEsc("\\", "\\\\", "\\\\"); + } + + @Test + public void testSameStringsReturned() { + String s = "==> I/m <safe>!"; + assertTrue(s == _StringUtil.jsStringEnc(s, false)); // "==" because is must return the same object + assertTrue(s == _StringUtil.jsStringEnc(s, true)); + + s = ""; + assertTrue(s == _StringUtil.jsStringEnc(s, false)); + assertTrue(s == _StringUtil.jsStringEnc(s, true)); + + s = "\u00E1rv\u00EDzt\u0171r\u0151 \u3020"; + assertEquals(s, _StringUtil.jsStringEnc(s, false)); + assertTrue(s == _StringUtil.jsStringEnc(s, false)); + assertTrue(s == _StringUtil.jsStringEnc(s, true)); + } + + @Test + public void testOneOffs() { + assertEsc("c\"c\"cc\"\"c", "c\\\"c\\\"cc\\\"\\\"c", "c\\\"c\\\"cc\\\"\\\"c"); + assertEsc("\"c\"cc\"", "\\\"c\\\"cc\\\"", "\\\"c\\\"cc\\\""); + assertEsc("c/c/cc//c", "c/c/cc//c", "c/c/cc//c"); + assertEsc("c<c<cc<<c", "c<c<cc<<c", "c<c<cc<<c"); + assertEsc("/<", "\\/\\x3C", "\\/\\u003C"); + assertEsc(">", "\\>", "\\u003E"); + assertEsc("]>", "]\\>", "]\\u003E"); + assertEsc("->", "-\\>", "-\\u003E"); + } + + private void assertEsc(String s, String javaScript, String json) { + assertEquals(javaScript, _StringUtil.jsStringEnc(s, false)); + assertEquals(json, _StringUtil.jsStringEnc(s, true)); + } + + @Test + public void testTrim() { + assertSame(_CollectionUtil.EMPTY_CHAR_ARRAY, _StringUtil.trim(_CollectionUtil.EMPTY_CHAR_ARRAY)); + assertSame(_CollectionUtil.EMPTY_CHAR_ARRAY, _StringUtil.trim(" \t\u0001 ".toCharArray())); + { + char[] cs = "foo".toCharArray(); + assertSame(cs, cs); + } + assertArrayEquals("foo".toCharArray(), _StringUtil.trim("foo ".toCharArray())); + assertArrayEquals("foo".toCharArray(), _StringUtil.trim(" foo".toCharArray())); + assertArrayEquals("foo".toCharArray(), _StringUtil.trim(" foo ".toCharArray())); + assertArrayEquals("foo".toCharArray(), _StringUtil.trim("\t\tfoo \r\n".toCharArray())); + assertArrayEquals("x".toCharArray(), _StringUtil.trim(" x ".toCharArray())); + assertArrayEquals("x y z".toCharArray(), _StringUtil.trim(" x y z ".toCharArray())); + } + + @Test + public void testIsTrimmedToEmpty() { + assertTrue(_StringUtil.isTrimmableToEmpty("".toCharArray())); + assertTrue(_StringUtil.isTrimmableToEmpty("\r\r\n\u0001".toCharArray())); + assertFalse(_StringUtil.isTrimmableToEmpty("x".toCharArray())); + assertFalse(_StringUtil.isTrimmableToEmpty(" x ".toCharArray())); + } + + @Test + public void testJQuote() { + assertEquals("null", _StringUtil.jQuote(null)); + assertEquals("\"foo\"", _StringUtil.jQuote("foo")); + assertEquals("\"123\"", _StringUtil.jQuote(Integer.valueOf(123))); + assertEquals("\"foo's \\\"bar\\\"\"", + _StringUtil.jQuote("foo's \"bar\"")); + assertEquals("\"\\n\\r\\t\\u0001\"", + _StringUtil.jQuote("\n\r\t\u0001")); + assertEquals("\"<\\nb\\rc\\td\\u0001>\"", + _StringUtil.jQuote("<\nb\rc\td\u0001>")); + } + + @Test + public void testJQuoteNoXSS() { + assertEquals("null", _StringUtil.jQuoteNoXSS(null)); + assertEquals("\"foo\"", _StringUtil.jQuoteNoXSS("foo")); + assertEquals("\"123\"", _StringUtil.jQuoteNoXSS(Integer.valueOf(123))); + assertEquals("\"foo's \\\"bar\\\"\"", + _StringUtil.jQuoteNoXSS("foo's \"bar\"")); + assertEquals("\"\\n\\r\\t\\u0001\"", + _StringUtil.jQuoteNoXSS("\n\r\t\u0001")); + assertEquals("\"\\u003C\\nb\\rc\\td\\u0001>\"", + _StringUtil.jQuoteNoXSS("<\nb\rc\td\u0001>")); + assertEquals("\"\\u003C\\nb\\rc\\td\\u0001>\"", + _StringUtil.jQuoteNoXSS((Object) "<\nb\rc\td\u0001>")); + } + + @Test + public void testGlobToRegularExpression() { + assertGlobMatches("a/b/c.ftl", "a/b/c.ftl"); + assertGlobDoesNotMatch("/a/b/cxftl", "/a/b/c.ftl", "a/b/C.ftl"); + + assertGlobMatches("a/b/*.ftl", "a/b/.ftl", "a/b/x.ftl", "a/b/xx.ftl"); + assertGlobDoesNotMatch("a/b/*.ftl", "a/c/x.ftl", "a/b/c/x.ftl", "/a/b/x.ftl", "a/b/xxftl"); + + assertGlobMatches("a/b/?.ftl", "a/b/x.ftl"); + assertGlobDoesNotMatch("a/b/?.ftl", "a/c/x.ftl", "a/b/.ftl", "a/b/xx.ftl", "a/b/xxftl"); + + assertGlobMatches("a/**/c.ftl", "a/b/c.ftl", "a/c.ftl", "a/b/b2/b3/c.ftl", "a//c.ftl"); + assertGlobDoesNotMatch("a/**/c.ftl", "x/b/c.ftl", "a/b/x.ftl"); + + assertGlobMatches("**/c.ftl", "a/b/c.ftl", "c.ftl", "/c.ftl", "///c.ftl"); + assertGlobDoesNotMatch("**/c.ftl", "a/b/x.ftl"); + + assertGlobMatches("a/b/**", "a/b/c.ftl", "a/b/c2/c.ftl", "a/b/", "a/b/c/"); + assertGlobDoesNotMatch("a/b.ftl"); + + assertGlobMatches("**", "a/b/c.ftl", ""); + + assertGlobMatches("\\[\\{\\*\\?\\}\\]\\\\", "[{*?}]\\"); + assertGlobDoesNotMatch("\\[\\{\\*\\?\\}\\]\\\\", "[{xx}]\\"); + + assertGlobMatches("a/b/\\?.ftl", "a/b/?.ftl"); + assertGlobDoesNotMatch("a/b/\\?.ftl", "a/b/x.ftl"); + + assertGlobMatches("\\?\\?.ftl", "??.ftl"); + assertGlobMatches("\\\\\\\\", "\\\\"); + assertGlobMatches("\\\\\\\\?", "\\\\x"); + assertGlobMatches("x\\", "x"); + + assertGlobMatches("???*", "123", "1234", "12345"); + assertGlobDoesNotMatch("???*", "12", "1", ""); + + assertGlobMatches("**/a??/b*.ftl", "a11/b1.ftl", "x/a11/b123.ftl", "x/y/a11/b.ftl"); + assertGlobDoesNotMatch("**/a??/b*.ftl", "a1/b1.ftl", "x/a11/c123.ftl"); + + assertFalse(_StringUtil.globToRegularExpression("ab*").matcher("aBc").matches()); + assertTrue(_StringUtil.globToRegularExpression("ab*", true).matcher("aBc").matches()); + assertTrue(_StringUtil.globToRegularExpression("ab", true).matcher("aB").matches()); + assertTrue(_StringUtil.globToRegularExpression("\u00E1b*", true).matcher("\u00C1bc").matches()); + + try { + _StringUtil.globToRegularExpression("x**/y"); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), Matchers.containsString("**")); + } + + try { + _StringUtil.globToRegularExpression("**y"); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), Matchers.containsString("**")); + } + + try { + _StringUtil.globToRegularExpression("[ab]c"); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), Matchers.containsString("unsupported")); + } + + try { + _StringUtil.globToRegularExpression("{aa,bb}c"); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), Matchers.containsString("unsupported")); + } + } + + private void assertGlobMatches(String glob, String... ss) { + Pattern pattern = _StringUtil.globToRegularExpression(glob); + for (String s : ss) { + if (!pattern.matcher(s).matches()) { + fail("Glob " + glob + " (regexp: " + pattern + ") doesn't match " + s); + } + } + } + + private void assertGlobDoesNotMatch(String glob, String... ss) { + Pattern pattern = _StringUtil.globToRegularExpression(glob); + for (String s : ss) { + if (pattern.matcher(s).matches()) { + fail("Glob " + glob + " (regexp: " + pattern + ") matches " + s); + } + } + } + + @Test + public void testHTMLEnc() { + String s = ""; + assertSame(s, _StringUtil.XMLEncNA(s)); + + s = "asd"; + assertSame(s, _StringUtil.XMLEncNA(s)); + + assertEquals("a&b<c>d"e'f", _StringUtil.XMLEncNA("a&b<c>d\"e'f")); + assertEquals("<", _StringUtil.XMLEncNA("<")); + assertEquals("<a", _StringUtil.XMLEncNA("<a")); + assertEquals("<a>", _StringUtil.XMLEncNA("<a>")); + assertEquals("a>", _StringUtil.XMLEncNA("a>")); + assertEquals("<>", _StringUtil.XMLEncNA("<>")); + assertEquals("a<>b", _StringUtil.XMLEncNA("a<>b")); + } + + @Test + public void testXHTMLEnc() throws IOException { + String s = ""; + assertSame(s, _StringUtil.XHTMLEnc(s)); + + s = "asd"; + assertSame(s, _StringUtil.XHTMLEnc(s)); + + testXHTMLEnc("a&b<c>d"e'f", "a&b<c>d\"e'f"); + testXHTMLEnc("<", "<"); + testXHTMLEnc("<a", "<a"); + testXHTMLEnc("<a>", "<a>"); + testXHTMLEnc("a>", "a>"); + testXHTMLEnc("<>", "<>"); + testXHTMLEnc("a<>b", "a<>b"); + } + + private void testXHTMLEnc(String expected, String in) throws IOException { + assertEquals(expected, _StringUtil.XHTMLEnc(in)); + + StringWriter sw = new StringWriter(); + _StringUtil.XHTMLEnc(in, sw); + assertEquals(expected, sw.toString()); + } + + @Test + public void testXMLEnc() throws IOException { + String s = ""; + assertSame(s, _StringUtil.XMLEnc(s)); + + s = "asd"; + assertSame(s, _StringUtil.XMLEnc(s)); + + testXMLEnc("a&b<c>d"e'f", "a&b<c>d\"e'f"); + testXMLEnc("<", "<"); + testXMLEnc("<a", "<a"); + testXMLEnc("<a>", "<a>"); + testXMLEnc("a>", "a>"); + testXMLEnc("<>", "<>"); + testXMLEnc("a<>b", "a<>b"); + } + + private void testXMLEnc(String expected, String in) throws IOException { + assertEquals(expected, _StringUtil.XMLEnc(in)); + + StringWriter sw = new StringWriter(); + _StringUtil.XMLEnc(in, sw); + assertEquals(expected, sw.toString()); + } + + @Test + public void testXMLEncQAttr() throws IOException { + String s = ""; + assertSame(s, _StringUtil.XMLEncQAttr(s)); + + s = "asd"; + assertSame(s, _StringUtil.XMLEncQAttr(s)); + + assertEquals("a&b<c>d"e'f", _StringUtil.XMLEncQAttr("a&b<c>d\"e'f")); + assertEquals("<", _StringUtil.XMLEncQAttr("<")); + assertEquals("<a", _StringUtil.XMLEncQAttr("<a")); + assertEquals("<a>", _StringUtil.XMLEncQAttr("<a>")); + assertEquals("a>", _StringUtil.XMLEncQAttr("a>")); + assertEquals("<>", _StringUtil.XMLEncQAttr("<>")); + assertEquals("a<>b", _StringUtil.XMLEncQAttr("a<>b")); + } + + @Test + public void testXMLEncNQG() throws IOException { + String s = ""; + assertSame(s, _StringUtil.XMLEncNQG(s)); + + s = "asd"; + assertSame(s, _StringUtil.XMLEncNQG(s)); + + assertEquals("a&b<c>d\"e'f", _StringUtil.XMLEncNQG("a&b<c>d\"e'f")); + assertEquals("<", _StringUtil.XMLEncNQG("<")); + assertEquals("<a", _StringUtil.XMLEncNQG("<a")); + assertEquals("<a>", _StringUtil.XMLEncNQG("<a>")); + assertEquals("a>", _StringUtil.XMLEncNQG("a>")); + assertEquals("<>", _StringUtil.XMLEncNQG("<>")); + assertEquals("a<>b", _StringUtil.XMLEncNQG("a<>b")); + + assertEquals(">", _StringUtil.XMLEncNQG(">")); + assertEquals("]>", _StringUtil.XMLEncNQG("]>")); + assertEquals("]]>", _StringUtil.XMLEncNQG("]]>")); + assertEquals("x]]>", _StringUtil.XMLEncNQG("x]]>")); + assertEquals("x]>", _StringUtil.XMLEncNQG("x]>")); + assertEquals("]x>", _StringUtil.XMLEncNQG("]x>")); + } + + @Test + public void testRTFEnc() throws IOException { + String s = ""; + assertSame(s, _StringUtil.RTFEnc(s)); + + s = "asd"; + assertSame(s, _StringUtil.RTFEnc(s)); + + testRTFEnc("a\\{b\\}c\\\\d", "a{b}c\\d"); + testRTFEnc("\\{", "{"); + testRTFEnc("\\{a", "{a"); + testRTFEnc("\\{a\\}", "{a}"); + testRTFEnc("a\\}", "a}"); + testRTFEnc("\\{\\}", "{}"); + testRTFEnc("a\\{\\}b", "a{}b"); + } + + private void testRTFEnc(String expected, String in) throws IOException { + assertEquals(expected, _StringUtil.RTFEnc(in)); + + StringWriter sw = new StringWriter(); + _StringUtil.RTFEnc(in, sw); + assertEquals(expected, sw.toString()); + } + + @Test + public void testNormalizeEOLs() { + assertNull(_StringUtil.normalizeEOLs(null)); + assertEquals("", _StringUtil.normalizeEOLs("")); + assertEquals("x", _StringUtil.normalizeEOLs("x")); + assertEquals("x\ny", _StringUtil.normalizeEOLs("x\ny")); + assertEquals("x\ny", _StringUtil.normalizeEOLs("x\r\ny")); + assertEquals("x\ny", _StringUtil.normalizeEOLs("x\ry")); + assertEquals("\n\n\n\n\n\n", _StringUtil.normalizeEOLs("\n\r\r\r\n\r\n\r")); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java new file mode 100644 index 0000000..8900d2b --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java @@ -0,0 +1,365 @@ +/* + * 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.valueformat; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collections; +import java.util.Locale; +import java.util.Map; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.Environment; +import org.apache.freemarker.core.Template; +import org.apache.freemarker.core.TemplateConfiguration; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateDirectiveBody; +import org.apache.freemarker.core.model.TemplateDirectiveModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateNumberModel; +import org.apache.freemarker.core.model.impl.SimpleNumber; +import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory; +import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher; +import org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory; +import org.apache.freemarker.core.userpkg.BaseNTemplateNumberFormatFactory; +import org.apache.freemarker.core.userpkg.HexTemplateNumberFormatFactory; +import org.apache.freemarker.core.userpkg.LocaleSensitiveTemplateNumberFormatFactory; +import org.apache.freemarker.core.userpkg.PrintfGTemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.impl.AliasTemplateNumberFormatFactory; +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Ignore; +import org.junit.Test; + +import com.google.common.collect.ImmutableMap; + +@SuppressWarnings("boxing") +public class NumberFormatTest extends TemplateTest { + + @Test + public void testUnknownCustomFormat() throws Exception { + { + setConfigurationWithNumberFormat("@noSuchFormat"); + Throwable exc = assertErrorContains("${1}", "\"@noSuchFormat\"", "\"noSuchFormat\""); + assertThat(exc.getCause(), instanceOf(UndefinedCustomFormatException.class)); + } + + { + setConfigurationWithNumberFormat("number"); + Throwable exc = assertErrorContains("${1?string('@noSuchFormat2')}", + "\"@noSuchFormat2\"", "\"noSuchFormat2\""); + assertThat(exc.getCause(), instanceOf(UndefinedCustomFormatException.class)); + } + } + + @Test + public void testStringBI() throws Exception { + setConfigurationWithNumberFormat(null); + assertOutput("${11} ${11?string.@hex} ${12} ${12?string.@hex}", "11 b 12 c"); + } + + @Test + public void testSetting() throws Exception { + setConfigurationWithNumberFormat("@hex"); + assertOutput("${11?string.number} ${11} ${12?string.number} ${12}", "11 b 12 c"); + } + + @Test + public void testSetting2() throws Exception { + setConfigurationWithNumberFormat(null); + assertOutput( + "<#setting numberFormat='@hex'>${11?string.number} ${11} ${12?string.number} ${12} ${13?string}" + + "<#setting numberFormat='@loc'>${11?string.number} ${11} ${12?string.number} ${12} ${13?string}", + "11 b 12 c d" + + "11 11_en_US 12 12_en_US 13_en_US"); + } + + @Test + public void testUnformattableNumber() throws Exception { + setConfigurationWithNumberFormat("@hex"); + assertErrorContains("${1.1}", "hexadecimal int", "doesn't fit into an int"); + } + + @Test + public void testLocaleSensitive() throws Exception { + setConfigurationWithNumberFormat("@loc"); + assertOutput("${1.1}", "1.1_en_US"); + setConfigurationWithNumberFormat("@loc", null, null, Locale.GERMANY); + assertOutput("${1.1}", "1.1_de_DE"); + } + + @Test + public void testLocaleSensitive2() throws Exception { + setConfigurationWithNumberFormat("@loc"); + assertOutput("${1.1} <#setting locale='de_DE'>${1.1}", "1.1_en_US 1.1_de_DE"); + } + + @Test + public void testCustomParameterized() throws Exception { + setConfigurationWithNumberFormat("@base 2"); + assertOutput("${11}", "1011"); + assertOutput("${11?string}", "1011"); + assertOutput("${11?string.@base_3}", "102"); + + assertErrorContains("${11?string.@base_xyz}", "\"@base_xyz\"", "\"xyz\""); + setConfigurationWithNumberFormat("@base"); + assertErrorContains("${11}", "\"@base\"", "format parameter is required"); + } + + @Test + public void testCustomWithFallback() throws Exception { + Configuration cfg = getConfiguration(); + setConfigurationWithNumberFormat("@base 2|0.0#"); + assertOutput("${11}", "1011"); + assertOutput("${11.34}", "11.34"); + assertOutput("${11?string('@base 3|0.00')}", "102"); + assertOutput("${11.2?string('@base 3|0.00')}", "11.20"); + } + + @Test + public void testEnvironmentGetters() throws Exception { + setConfigurationWithNumberFormat(null); + + Template t = new Template(null, "", getConfiguration()); + Environment env = t.createProcessingEnvironment(null, null); + + TemplateNumberFormat defF = env.getTemplateNumberFormat(); + // + TemplateNumberFormat explF = env.getTemplateNumberFormat("0.00"); + assertEquals("1.25", explF.formatToPlainText(new SimpleNumber(1.25))); + // + TemplateNumberFormat expl2F = env.getTemplateNumberFormat("@loc"); + assertEquals("1.25_en_US", expl2F.formatToPlainText(new SimpleNumber(1.25))); + + TemplateNumberFormat explFFr = env.getTemplateNumberFormat("0.00", Locale.FRANCE); + assertNotSame(explF, explFFr); + assertEquals("1,25", explFFr.formatToPlainText(new SimpleNumber(1.25))); + // + TemplateNumberFormat expl2FFr = env.getTemplateNumberFormat("@loc", Locale.FRANCE); + assertEquals("1.25_fr_FR", expl2FFr.formatToPlainText(new SimpleNumber(1.25))); + + assertSame(env.getTemplateNumberFormat(), defF); + // + assertSame(env.getTemplateNumberFormat("0.00"), explF); + // + assertSame(env.getTemplateNumberFormat("@loc"), expl2F); + } + + /** + * ?string formats lazily (at least in 2.3.x), so it must make a snapshot of the format inputs when it's called. + */ + @Test + @Ignore // [FM3] We want to rework BI-s so that lazy evaluation won't be needed. Then this will go away too. + public void testStringBIDoesSnapshot() throws Exception { + // TemplateNumberModel-s shouldn't change, but we have to keep BC when that still happens. + final MutableTemplateNumberModel nm = new MutableTemplateNumberModel(); + nm.setNumber(123); + addToDataModel("n", nm); + addToDataModel("incN", new TemplateDirectiveModel() { + + @Override + public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) + throws TemplateException, IOException { + nm.setNumber(nm.getAsNumber().intValue() + 1); + } + }); + assertOutput( + "<#assign s1 = n?string>" + + "<#setting numberFormat='@loc'>" + + "<#assign s2 = n?string>" + + "<#setting numberFormat='@hex'>" + + "<#assign s3 = n?string>" + + "${s1} ${s2} ${s3}", + "123 123_en_US 7b"); + assertOutput( + "<#assign s1 = n?string>" + + "<@incN />" + + "<#assign s2 = n?string>" + + "${s1} ${s2}", + "123 124"); + } + + @Test + public void testNullInModel() throws Exception { + addToDataModel("n", new MutableTemplateNumberModel()); + assertErrorContains("${n}", "nothing inside it"); + assertErrorContains("${n?string}", "nothing inside it"); + } + + @Test + public void testAtPrefix() throws Exception { + Configuration cfg = getConfiguration(); + + setConfigurationWithNumberFormat("@hex"); + assertOutput("${10}", "a"); + setConfigurationWithNumberFormat("'@'0"); + assertOutput("${10}", "@10"); + setConfigurationWithNumberFormat("@@0"); + assertOutput("${10}", "@@10"); + + setConfigurationWithNumberFormat( + "@hex", Collections.<String, TemplateNumberFormatFactory>emptyMap()); + assertErrorContains("${10}", "custom", "\"hex\""); + + setConfigurationWithNumberFormat( + "'@'0", Collections.<String, TemplateNumberFormatFactory>emptyMap()); + assertOutput("${10}", "@10"); + + setConfigurationWithNumberFormat( + "@@0", Collections.<String, TemplateNumberFormatFactory>emptyMap()); + assertOutput("${10}", "@@10"); + } + + @Test + public void testAlieses() throws Exception { + setConfigurationWithNumberFormat( + "'@'0", + ImmutableMap.of( + "f", new AliasTemplateNumberFormatFactory("0.#'f'"), + "d", new AliasTemplateNumberFormatFactory("0.0#"), + "hex", HexTemplateNumberFormatFactory.INSTANCE), + new ConditionalTemplateConfigurationFactory( + new FileNameGlobMatcher("*2*"), + new TemplateConfiguration.Builder() + .customNumberFormats(ImmutableMap.<String, TemplateNumberFormatFactory>of( + "d", new AliasTemplateNumberFormatFactory("0.#'d'"), + "i", new AliasTemplateNumberFormatFactory("@hex"))) + .build())); + + String commonFtl = "${1?string.@f} ${1?string.@d} " + + "<#setting locale='fr_FR'>${1.5?string.@d} " + + "<#attempt>${10?string.@i}<#recover>E</#attempt>"; + addTemplate("t1.ftl", commonFtl); + addTemplate("t2.ftl", commonFtl); + + assertOutputForNamed("t1.ftl", "1f 1.0 1,5 E"); + assertOutputForNamed("t2.ftl", "1f 1d 1,5d a"); + } + + @Test + public void testAlieses2() throws Exception { + setConfigurationWithNumberFormat( + "@n", + ImmutableMap.<String, TemplateNumberFormatFactory>of( + "n", new AliasTemplateNumberFormatFactory("0.0", + ImmutableMap.of( + new Locale("en"), "0.0'_en'", + Locale.UK, "0.0'_en_GB'", + Locale.FRANCE, "0.0'_fr_FR'")))); + assertOutput( + "<#setting locale='en_US'>${1} " + + "<#setting locale='en_GB'>${1} " + + "<#setting locale='en_GB_Win'>${1} " + + "<#setting locale='fr_FR'>${1} " + + "<#setting locale='hu_HU'>${1}", + "1.0_en 1.0_en_GB 1.0_en_GB 1,0_fr_FR 1,0"); + } + + @Test + public void testMarkupFormat() throws IOException, TemplateException { + setConfigurationWithNumberFormat("@printfG_3"); + + String commonFTL = "${1234567} ${'cat:' + 1234567} ${0.0000123}"; + String commonOutput = "1.23*10<sup>6</sup> cat:1.23*10<sup>6</sup> 1.23*10<sup>-5</sup>"; + assertOutput(commonFTL, commonOutput); + assertOutput("<#ftl outputFormat='HTML'>" + commonFTL, commonOutput); + assertOutput("<#escape x as x?html>" + commonFTL + "</#escape>", commonOutput); + assertOutput("<#escape x as x?xhtml>" + commonFTL + "</#escape>", commonOutput); + assertOutput("<#escape x as x?xml>" + commonFTL + "</#escape>", commonOutput); + assertOutput("${\"" + commonFTL + "\"}", "1.23*10<sup>6</sup> cat:1.23*10<sup>6</sup> 1.23*10<sup>-5</sup>"); + assertErrorContains("<#ftl outputFormat='plainText'>" + commonFTL, "HTML", "plainText", "conversion"); + } + + @Test + public void testPrintG() throws IOException, TemplateException { + setConfigurationWithNumberFormat(null); + for (Number n : new Number[] { + 1234567, 1234567L, 1234567d, 1234567f, BigInteger.valueOf(1234567), BigDecimal.valueOf(1234567) }) { + addToDataModel("n", n); + + assertOutput("${n?string.@printfG}", "1.23457E+06"); + assertOutput("${n?string.@printfG_3}", "1.23E+06"); + assertOutput("${n?string.@printfG_7}", "1234567"); + assertOutput("${0.0000123?string.@printfG}", "1.23000E-05"); + } + } + + private void setConfigurationWithNumberFormat( + String numberFormat, + Map<String, TemplateNumberFormatFactory> customNumberFormats, + TemplateConfigurationFactory templateConfigurationFactory, + Locale locale) { + TestConfigurationBuilder cfgB = new TestConfigurationBuilder(Configuration.VERSION_3_0_0); + + if (numberFormat != null) { + cfgB.setNumberFormat(numberFormat); + } + cfgB.setCustomNumberFormats( + customNumberFormats != null ? customNumberFormats + : ImmutableMap.of( + "hex", HexTemplateNumberFormatFactory.INSTANCE, + "loc", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE, + "base", BaseNTemplateNumberFormatFactory.INSTANCE, + "printfG", PrintfGTemplateNumberFormatFactory.INSTANCE)); + if (locale != null) { + cfgB.setLocale(locale); + } + if (templateConfigurationFactory != null) { + cfgB.setTemplateConfigurations(templateConfigurationFactory); + } + + setConfiguration(cfgB.build()); + } + + private void setConfigurationWithNumberFormat(String numberFormat) { + setConfigurationWithNumberFormat(numberFormat, null, null, null); + } + + private void setConfigurationWithNumberFormat( + String numberFormat, Map<String, TemplateNumberFormatFactory> customNumberFormats) { + setConfigurationWithNumberFormat(numberFormat, customNumberFormats, null, null); + } + + private void setConfigurationWithNumberFormat( + String numberFormat, Map<String, TemplateNumberFormatFactory> customNumberFormats, + TemplateConfigurationFactory templateConfigurationFactory) { + setConfigurationWithNumberFormat(numberFormat, customNumberFormats, templateConfigurationFactory, null); + } + + private static class MutableTemplateNumberModel implements TemplateNumberModel { + + private Number number; + + public void setNumber(Number number) { + this.number = number; + } + + @Override + public Number getAsNumber() throws TemplateModelException { + return number; + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java new file mode 100644 index 0000000..76c0bfc --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatTest.java @@ -0,0 +1,343 @@ +/* + * 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.valueformat.impl; + +import static org.apache.freemarker.test.hamcerst.Matchers.*; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.ParseException; +import java.util.Locale; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +public class ExtendedDecimalFormatTest extends TemplateTest { + + private static final Locale LOC = Locale.US; + private static final DecimalFormatSymbols SYMS = DecimalFormatSymbols.getInstance(LOC); + + @Test + public void testNonExtended() throws ParseException { + for (String fStr : new String[] { "0.00", "0.###", "#,#0.###", "#0.####", "0.0;m", "0.0;", + "0'x'", "0'x';'m'", "0';'", "0';';m", "0';';'#'m';'", "0';;'", "" }) { + assertFormatsEquivalent(new DecimalFormat(fStr, SYMS), ExtendedDecimalFormatParser.parse(fStr, LOC)); + } + + try { + new DecimalFormat(";"); + fail(); + } catch (IllegalArgumentException e) { + // Expected + } + try { + ExtendedDecimalFormatParser.parse(";", LOC); + } catch (ParseException e) { + // Expected + } + } + + @Test + public void testNonExtended2() throws ParseException { + assertFormatsEquivalent(new DecimalFormat("0.0", SYMS), ExtendedDecimalFormatParser.parse("0.0;", LOC)); + assertFormatsEquivalent(new DecimalFormat("0.0", SYMS), ExtendedDecimalFormatParser.parse("0.0;;", LOC)); + assertFormatsEquivalent(new DecimalFormat("0.0;m", SYMS), ExtendedDecimalFormatParser.parse("0.0;m;", LOC)); + assertFormatsEquivalent(new DecimalFormat("", SYMS), ExtendedDecimalFormatParser.parse(";;", LOC)); + assertFormatsEquivalent(new DecimalFormat("0'x'", SYMS), ExtendedDecimalFormatParser.parse("0'x';;", LOC)); + assertFormatsEquivalent(new DecimalFormat("0'x';'m'", SYMS), + ExtendedDecimalFormatParser.parse("0'x';'m';", LOC)); + assertFormatsEquivalent(new DecimalFormat("0';'", SYMS), ExtendedDecimalFormatParser.parse("0';';;", LOC)); + assertFormatsEquivalent(new DecimalFormat("0';';m", SYMS), ExtendedDecimalFormatParser.parse("0';';m;", LOC)); + assertFormatsEquivalent(new DecimalFormat("0';';'#'m';'", SYMS), + ExtendedDecimalFormatParser.parse("0';';'#'m';';", LOC)); + assertFormatsEquivalent(new DecimalFormat("0';;'", SYMS), + ExtendedDecimalFormatParser.parse("0';;';;", LOC)); + + try { + new DecimalFormat(";m"); + fail(); + } catch (IllegalArgumentException e) { + // Expected + } + try { + new DecimalFormat("; ;"); + fail(); + } catch (IllegalArgumentException e) { + // Expected + } + try { + ExtendedDecimalFormatParser.parse("; ;", LOC); + fail(); + } catch (ParseException e) { + // Expected + } + try { + ExtendedDecimalFormatParser.parse(";m", LOC); + fail(); + } catch (ParseException e) { + // Expected + } + try { + ExtendedDecimalFormatParser.parse(";m;", LOC); + fail(); + } catch (ParseException e) { + // Expected + } + } + + @SuppressWarnings("boxing") + @Test + public void testExtendedParamsParsing() throws ParseException { + for (String fs : new String[] { + "00.##;; decimalSeparator='D'", + "00.##;;decimalSeparator=D", + "00.##;; decimalSeparator = D ", "00.##;; decimalSeparator = 'D' " }) { + assertFormatted(fs, 1.125, "01D12"); + } + for (String fs : new String[] { + ",#0.0;; decimalSeparator=D, groupingSeparator=_", + ",#0.0;;decimalSeparator=D,groupingSeparator=_", + ",#0.0;; decimalSeparator = D , groupingSeparator = _ ", + ",#0.0;; decimalSeparator='D', groupingSeparator='_'" + }) { + assertFormatted(fs, 12345, "1_23_45D0"); + } + + assertFormatted("0.0;;infinity=infinity", Double.POSITIVE_INFINITY, "infinity"); + assertFormatted("0.0;;infinity='infinity'", Double.POSITIVE_INFINITY, "infinity"); + assertFormatted("0.0;;infinity=\"infinity\"", Double.POSITIVE_INFINITY, "infinity"); + assertFormatted("0.0;;infinity=''", Double.POSITIVE_INFINITY, ""); + assertFormatted("0.0;;infinity=\"\"", Double.POSITIVE_INFINITY, ""); + assertFormatted("0.0;;infinity='x''y'", Double.POSITIVE_INFINITY, "x'y"); + assertFormatted("0.0;;infinity=\"x'y\"", Double.POSITIVE_INFINITY, "x'y"); + assertFormatted("0.0;;infinity='x\"\"y'", Double.POSITIVE_INFINITY, "x\"\"y"); + assertFormatted("0.0;;infinity=\"x''y\"", Double.POSITIVE_INFINITY, "x''y"); + assertFormatted("0.0;;decimalSeparator=''''", 1, "1'0"); + assertFormatted("0.0;;decimalSeparator=\"'\"", 1, "1'0"); + assertFormatted("0.0;;decimalSeparator='\"'", 1, "1\"0"); + assertFormatted("0.0;;decimalSeparator=\"\"\"\"", 1, "1\"0"); + + try { + ExtendedDecimalFormatParser.parse(";;decimalSeparator=D,", LOC); + fail(); + } catch (java.text.ParseException e) { + assertThat(e.getMessage(), + allOf(containsStringIgnoringCase("expected a(n) name"), containsString(" end of "))); + } + try { + ExtendedDecimalFormatParser.parse(";;foo=D,", LOC); + fail(); + } catch (java.text.ParseException e) { + assertThat(e.getMessage(), + allOf(containsString("\"foo\""), containsString("name"))); + } + try { + ExtendedDecimalFormatParser.parse(";;decimalSeparator='D", LOC); + fail(); + } catch (java.text.ParseException e) { + assertThat(e.getMessage(), + allOf(containsString("quotation"), containsString("closed"))); + } + try { + ExtendedDecimalFormatParser.parse(";;decimalSeparator=\"D", LOC); + fail(); + } catch (java.text.ParseException e) { + assertThat(e.getMessage(), + allOf(containsString("quotation"), containsString("closed"))); + } + try { + ExtendedDecimalFormatParser.parse(";;decimalSeparator='D'groupingSeparator=G", LOC); + fail(); + } catch (java.text.ParseException e) { + assertThat(e.getMessage(), allOf( + containsString("separator"), containsString("whitespace"), containsString("comma"))); + } + try { + ExtendedDecimalFormatParser.parse(";;decimalSeparator=., groupingSeparator=G", LOC); + fail(); + } catch (java.text.ParseException e) { + assertThat(e.getMessage(), allOf( + containsStringIgnoringCase("expected a(n) value"), containsString("., gr[...]"))); + } + try { + ExtendedDecimalFormatParser.parse("0.0;;decimalSeparator=''", LOC); + fail(); + } catch (java.text.ParseException e) { + assertThat(e.getMessage(), allOf( + containsStringIgnoringCase("\"decimalSeparator\""), containsString("exactly 1 char"))); + } + try { + ExtendedDecimalFormatParser.parse("0.0;;multipier=ten", LOC); + fail(); + } catch (java.text.ParseException e) { + assertThat(e.getMessage(), allOf( + containsString("\"multipier\""), containsString("\"ten\""), containsString("integer"))); + } + } + + @SuppressWarnings("boxing") + @Test + public void testExtendedParamsEffect() throws ParseException { + assertFormatted("0", + 1.5, "2", 2.5, "2", 3.5, "4", 1.4, "1", 1.6, "2", -1.4, "-1", -1.5, "-2", -2.5, "-2", -1.6, "-2"); + assertFormatted("0;; roundingMode=halfEven", + 1.5, "2", 2.5, "2", 3.5, "4", 1.4, "1", 1.6, "2", -1.4, "-1", -1.5, "-2", -2.5, "-2", -1.6, "-2"); + assertFormatted("0;; roundingMode=halfUp", + 1.5, "2", 2.5, "3", 3.5, "4", 1.4, "1", 1.6, "2", -1.4, "-1", -1.5, "-2", -2.5, "-3", -1.6, "-2"); + assertFormatted("0;; roundingMode=halfDown", + 1.5, "1", 2.5, "2", 3.5, "3", 1.4, "1", 1.6, "2", -1.4, "-1", -1.5, "-1", -2.5, "-2", -1.6, "-2"); + assertFormatted("0;; roundingMode=floor", + 1.5, "1", 2.5, "2", 3.5, "3", 1.4, "1", 1.6, "1", -1.4, "-2", -1.5, "-2", -2.5, "-3", -1.6, "-2"); + assertFormatted("0;; roundingMode=ceiling", + 1.5, "2", 2.5, "3", 3.5, "4", 1.4, "2", 1.6, "2", -1.4, "-1", -1.5, "-1", -2.5, "-2", -1.6, "-1"); + assertFormatted("0;; roundingMode=up", + 1.5, "2", 2.5, "3", 3.5, "4", 1.4, "2", 1.6, "2", -1.4, "-2", -1.5, "-2", -2.5, "-3", -1.6, "-2"); + assertFormatted("0;; roundingMode=down", + 1.5, "1", 2.5, "2", 3.5, "3", 1.4, "1", 1.6, "1", -1.4, "-1", -1.5, "-1", -2.5, "-2", -1.6, "-1"); + assertFormatted("0;; roundingMode=unnecessary", 2, "2"); + try { + assertFormatted("0;; roundingMode=unnecessary", 2.5, "2"); + fail(); + } catch (ArithmeticException e) { + // Expected + } + + assertFormatted("0.##;; multipier=100", 12.345, "1234.5"); + assertFormatted("0.##;; multipier=1000", 12.345, "12345"); + + assertFormatted(",##0.##;; groupingSeparator=_ decimalSeparator=D", 12345.1, "12_345D1", 1, "1"); + + assertFormatted("0.##E0;; exponentSeparator='*10^'", 12345.1, "1.23*10^4"); + + assertFormatted("0.##;; minusSign=m", -1, "m1", 1, "1"); + + assertFormatted("0.##;; infinity=foo", Double.POSITIVE_INFINITY, "foo", Double.NEGATIVE_INFINITY, "-foo"); + + assertFormatted("0.##;; nan=foo", Double.NaN, "foo"); + + assertFormatted("0%;; percent='c'", 0.75, "75c"); + + assertFormatted("0\u2030;; perMill='m'", 0.75, "750m"); + + assertFormatted("0.00;; zeroDigit='@'", 10.5, "A@.E@"); + + assertFormatted("0;; currencyCode=USD", 10, "10"); + assertFormatted("0 \u00A4;; currencyCode=USD", 10, "10 $"); + assertFormatted("0 \u00A4\u00A4;; currencyCode=USD", 10, "10 USD"); + assertFormatted(Locale.GERMANY, "0 \u00A4;; currencyCode=EUR", 10, "10 \u20AC"); + assertFormatted(Locale.GERMANY, "0 \u00A4\u00A4;; currencyCode=EUR", 10, "10 EUR"); + try { + assertFormatted("0;; currencyCode=USDX", 10, "10"); + } catch (ParseException e) { + assertThat(e.getMessage(), containsString("ISO 4217")); + } + assertFormatted("0 \u00A4;; currencyCode=USD currencySymbol=bucks", 10, "10 bucks"); + // Order doesn't mater: + assertFormatted("0 \u00A4;; currencySymbol=bucks currencyCode=USD", 10, "10 bucks"); + // International symbol isn't affected: + assertFormatted("0 \u00A4\u00A4;; currencyCode=USD currencySymbol=bucks", 10, "10 USD"); + + assertFormatted("0.0 \u00A4;; monetaryDecimalSeparator=m", 10.5, "10m5 $"); + assertFormatted("0.0 kg;; monetaryDecimalSeparator=m", 10.5, "10.5 kg"); + assertFormatted("0.0 \u00A4;; decimalSeparator=d", 10.5, "10.5 $"); + assertFormatted("0.0 kg;; decimalSeparator=d", 10.5, "10d5 kg"); + assertFormatted("0.0 \u00A4;; monetaryDecimalSeparator=m decimalSeparator=d", 10.5, "10m5 $"); + assertFormatted("0.0 kg;; monetaryDecimalSeparator=m decimalSeparator=d", 10.5, "10d5 kg"); + } + + @Test + public void testLocale() throws ParseException { + assertEquals("1000.0", ExtendedDecimalFormatParser.parse("0.0", Locale.US).format(1000)); + assertEquals("1000,0", ExtendedDecimalFormatParser.parse("0.0", Locale.FRANCE).format(1000)); + assertEquals("1_000.0", ExtendedDecimalFormatParser.parse(",000.0;;groupingSeparator=_", Locale.US).format(1000)); + assertEquals("1_000,0", ExtendedDecimalFormatParser.parse(",000.0;;groupingSeparator=_", Locale.FRANCE).format(1000)); + } + + @Test + public void testTemplates() throws IOException, TemplateException { + TestConfigurationBuilder cfgB = new TestConfigurationBuilder(); + + setConfiguration(cfgB.numberFormat(",000.#").build()); + assertOutput("${1000.15} ${1000.25}", "1,000.2 1,000.2"); + setConfiguration(cfgB.numberFormat(",000.#;; roundingMode=halfUp groupingSeparator=_").build());; + assertOutput("${1000.15} ${1000.25}", "1_000.2 1_000.3"); + setConfiguration(cfgB.locale(Locale.GERMANY).build());; + assertOutput("${1000.15} ${1000.25}", "1_000,2 1_000,3"); + setConfiguration(cfgB.locale(Locale.US).build());; + assertOutput( + "${1000.15}; " + + "${1000.15?string(',##.#;;groupingSeparator=\" \"')}; " + + "<#setting locale='de_DE'>${1000.15}; " + + "<#setting numberFormat='0.0;;roundingMode=down'>${1000.15}", + "1_000.2; 10 00.2; 1_000,2; 1000,1"); + assertErrorContains("${1?string('#E')}", + TemplateException.class, "\"#E\"", "format string", "exponential"); + assertErrorContains("<#setting numberFormat='#E'>${1}", + TemplateException.class, "\"#E\"", "format string", "exponential"); + assertErrorContains("<#setting numberFormat=';;foo=bar'>${1}", + TemplateException.class, "\"foo\"", "supported"); + assertErrorContains("<#setting numberFormat='0;;roundingMode=unnecessary'>${1.5}", + TemplateException.class, "can't format", "1.5", "UNNECESSARY"); + } + + private void assertFormatted(String formatString, Object... numberAndExpectedOutput) throws ParseException { + assertFormatted(LOC, formatString, numberAndExpectedOutput); + } + + private void assertFormatted(Locale loc, String formatString, Object... numberAndExpectedOutput) throws ParseException { + if (numberAndExpectedOutput.length % 2 != 0) { + throw new IllegalArgumentException(); + } + + DecimalFormat df = ExtendedDecimalFormatParser.parse(formatString, loc); + Number num = null; + for (int i = 0; i < numberAndExpectedOutput.length; i++) { + if (i % 2 == 0) { + num = (Number) numberAndExpectedOutput[i]; + } else { + assertEquals(numberAndExpectedOutput[i], df.format(num)); + } + } + } + + private void assertFormatsEquivalent(DecimalFormat dfExpected, DecimalFormat dfActual) { + for (int signum : new int[] { 1, -1 }) { + assertFormatsEquivalent(dfExpected, dfActual, 0); + assertFormatsEquivalent(dfExpected, dfActual, signum * 0.5); + assertFormatsEquivalent(dfExpected, dfActual, signum * 0.25); + assertFormatsEquivalent(dfExpected, dfActual, signum * 0.125); + assertFormatsEquivalent(dfExpected, dfActual, signum); + assertFormatsEquivalent(dfExpected, dfActual, signum * 10); + assertFormatsEquivalent(dfExpected, dfActual, signum * 100); + assertFormatsEquivalent(dfExpected, dfActual, signum * 1000); + assertFormatsEquivalent(dfExpected, dfActual, signum * 10000); + assertFormatsEquivalent(dfExpected, dfActual, signum * 100000); + } + } + + private void assertFormatsEquivalent(DecimalFormat dfExpected, DecimalFormat dfActual, double n) { + assertEquals(dfExpected.format(n), dfActual.format(n)); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/dom/DOMSiblingTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/dom/DOMSiblingTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/dom/DOMSiblingTest.java new file mode 100644 index 0000000..56a15eb --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/dom/DOMSiblingTest.java @@ -0,0 +1,99 @@ +/* + * 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.dom; + +import java.io.IOException; + +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.XMLLoader; +import org.junit.Before; +import org.junit.Test; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public class DOMSiblingTest extends TemplateTest { + + @Before + public void setUp() throws SAXException, IOException, ParserConfigurationException { + InputSource is = new InputSource(getClass().getResourceAsStream("DOMSiblingTest.xml")); + addToDataModel("doc", XMLLoader.toModel(is)); + } + + @Test + public void testBlankPreviousSibling() throws IOException, TemplateException { + assertOutput("${doc.person.name?previousSibling}", "\n "); + assertOutput("${doc.person.name?previous_sibling}", "\n "); + } + + @Test + public void testNonBlankPreviousSibling() throws IOException, TemplateException { + assertOutput("${doc.person.address?previousSibling}", "12th August"); + } + + @Test + public void testBlankNextSibling() throws IOException, TemplateException { + assertOutput("${doc.person.name?nextSibling}", "\n "); + assertOutput("${doc.person.name?next_sibling}", "\n "); + } + + @Test + public void testNonBlankNextSibling() throws IOException, TemplateException { + assertOutput("${doc.person.dob?nextSibling}", "Chennai, India"); + } + + @Test + public void testNullPreviousSibling() throws IOException, TemplateException { + assertOutput("${doc.person?previousSibling?? ?c}", "false"); + } + + @Test + public void testSignificantPreviousSibling() throws IOException, TemplateException { + assertOutput("${doc.person.name.@@previous_sibling_element}", "male"); + } + + @Test + public void testSignificantNextSibling() throws IOException, TemplateException { + assertOutput("${doc.person.name.@@next_sibling_element}", "12th August"); + } + + @Test + public void testNullSignificantPreviousSibling() throws IOException, TemplateException { + assertOutput("${doc.person.phone.@@next_sibling_element?size}", "0"); + } + + @Test + public void testSkippingCommentNode() throws IOException, TemplateException { + assertOutput("${doc.person.profession.@@previous_sibling_element}", "Chennai, India"); + } + + @Test + public void testSkippingEmptyCDataNode() throws IOException, TemplateException { + assertOutput("${doc.person.hobby.@@previous_sibling_element}", "Software Engineer"); + } + + @Test + public void testValidCDataNode() throws IOException, TemplateException { + assertOutput("${doc.person.phone.@@previous_sibling_element?size}", "0"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/dom/DOMSimplifiersTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/dom/DOMSimplifiersTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/dom/DOMSimplifiersTest.java new file mode 100644 index 0000000..f135873 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/dom/DOMSimplifiersTest.java @@ -0,0 +1,201 @@ +/* + * 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.dom; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.freemarker.test.XMLLoader; +import org.junit.Test; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Comment; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; +import org.xml.sax.SAXException; + +public class DOMSimplifiersTest { + + private static final String COMMON_TEST_XML + = "<!DOCTYPE a []><?p?><a>x<![CDATA[y]]><!--c--><?p?>z<?p?><b><!--c--></b><c></c>" + + "<d>a<e>c</e>b<!--c--><!--c--><!--c--><?p?><?p?><?p?></d>" + + "<f><![CDATA[1]]>2</f></a><!--c-->"; + + private static final String TEXT_MERGE_CONTENT = + "<a>" + + "a<!--c--><s/>" + + "<!--c-->a<s/>" + + "a<!--c-->b<s/>" + + "<!--c-->a<!--c-->b<!--c--><s/>" + + "a<b>b</b>c<s/>" + + "a<b>b</b><!--c-->c<s/>" + + "a<!--c-->1<b>b<!--c--></b>c<!--c-->1<s/>" + + "a<!--c-->1<b>b<!--c-->c</b>d<!--c-->1<s/>" + + "a<!--c-->1<b>b<!--c-->c</b>d<!--c-->1<s/>" + + "a<!--c-->1<b>b<!--c-->1<e>c<!--c-->1</e>d<!--c-->1</b>e<!--c-->1<s/>" + + "</a>"; + private static final String TEXT_MERGE_EXPECTED = + "<a>" + + "%a<s/>" + + "%a<s/>" + + "%ab<s/>" + + "%ab<s/>" + + "%a<b>%b</b>%c<s/>" + + "%a<b>%b</b>%c<s/>" + + "%a1<b>%b</b>%c1<s/>" + + "%a1<b>%bc</b>%d1<s/>" + + "%a1<b>%bc</b>%d1<s/>" + + "%a1<b>%b1<e>%c1</e>%d1</b>%e1<s/>" + + "</a>"; + + @Test + public void testTest() throws Exception { + String expected = "<!DOCTYPE ...><?p?><a>%x<![CDATA[y]]><!--c--><?p?>%z<?p?><b><!--c--></b><c/>" + + "<d>%a<e>%c</e>%b<!--c--><!--c--><!--c--><?p?><?p?><?p?></d>" + + "<f><![CDATA[1]]>%2</f></a><!--c-->"; + assertEquals(expected, toString(XMLLoader.toDOM(COMMON_TEST_XML))); + } + + @Test + public void testMergeAdjacentText() throws Exception { + Document dom = XMLLoader.toDOM(COMMON_TEST_XML); + NodeModel.mergeAdjacentText(dom); + assertEquals( + "<!DOCTYPE ...><?p?><a>%xy<!--c--><?p?>%z<?p?><b><!--c--></b><c/>" + + "<d>%a<e>%c</e>%b<!--c--><!--c--><!--c--><?p?><?p?><?p?></d>" + + "<f><![CDATA[12]]></f></a><!--c-->", + toString(dom)); + } + + @Test + public void testRemoveComments() throws Exception { + Document dom = XMLLoader.toDOM(COMMON_TEST_XML); + NodeModel.removeComments(dom); + assertEquals( + "<!DOCTYPE ...><?p?><a>%x<![CDATA[y]]><?p?>%z<?p?><b/><c/>" + + "<d>%a<e>%c</e>%b<?p?><?p?><?p?></d>" + + "<f><![CDATA[1]]>%2</f></a>", + toString(dom)); + } + + @Test + public void testRemovePIs() throws Exception { + Document dom = XMLLoader.toDOM(COMMON_TEST_XML); + NodeModel.removePIs(dom); + assertEquals( + "<!DOCTYPE ...><a>%x<![CDATA[y]]><!--c-->%z<b><!--c--></b><c/>" + + "<d>%a<e>%c</e>%b<!--c--><!--c--><!--c--></d>" + + "<f><![CDATA[1]]>%2</f></a><!--c-->", + toString(dom)); + } + + @Test + public void testSimplify() throws Exception { + testSimplify( + "<!DOCTYPE ...><a>%xyz<b/><c/>" + + "<d>%a<e>%c</e>%b</d><f><![CDATA[12]]></f></a>", + COMMON_TEST_XML); + } + + @Test + public void testSimplify2() throws Exception { + testSimplify(TEXT_MERGE_EXPECTED, TEXT_MERGE_CONTENT); + } + + @Test + public void testSimplify3() throws Exception { + testSimplify("<a/>", "<a/>"); + } + + private void testSimplify(String expected, String content) + throws SAXException, IOException, ParserConfigurationException { + { + Document dom = XMLLoader.toDOM(content); + NodeModel.simplify(dom); + assertEquals(expected, toString(dom)); + } + + // Must be equivalent: + { + Document dom = XMLLoader.toDOM(content); + NodeModel.removeComments(dom); + NodeModel.removePIs(dom); + NodeModel.mergeAdjacentText(dom); + assertEquals(expected, toString(dom)); + } + + // Must be equivalent: + { + Document dom = XMLLoader.toDOM(content); + NodeModel.removeComments(dom); + NodeModel.removePIs(dom); + NodeModel.simplify(dom); + assertEquals(expected, toString(dom)); + } + } + + private String toString(Document doc) { + StringBuilder sb = new StringBuilder(); + toString(doc, sb); + return sb.toString(); + } + + private void toString(Node node, StringBuilder sb) { + if (node instanceof Document) { + childrenToString(node, sb); + } else if (node instanceof Element) { + if (node.hasChildNodes()) { + sb.append("<").append(node.getNodeName()).append(">"); + childrenToString(node, sb); + sb.append("</").append(node.getNodeName()).append(">"); + } else { + sb.append("<").append(node.getNodeName()).append("/>"); + } + } else if (node instanceof Text) { + if (node instanceof CDATASection) { + sb.append("<![CDATA[").append(node.getNodeValue()).append("]]>"); + } else { + sb.append("%").append(node.getNodeValue()); + } + } else if (node instanceof Comment) { + sb.append("<!--").append(node.getNodeValue()).append("-->"); + } else if (node instanceof ProcessingInstruction) { + sb.append("<?").append(node.getNodeName()).append("?>"); + } else if (node instanceof DocumentType) { + sb.append("<!DOCTYPE ...>"); + } else { + throw new IllegalStateException("Unhandled node type: " + node.getClass().getName()); + } + } + + private void childrenToString(Node node, StringBuilder sb) { + Node child = node.getFirstChild(); + while (child != null) { + toString(child, sb); + child = child.getNextSibling(); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/dom/DOMTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/dom/DOMTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/dom/DOMTest.java new file mode 100644 index 0000000..847f503 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/dom/DOMTest.java @@ -0,0 +1,159 @@ +/* + * 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.dom; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.StringReader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.XMLLoader; +import org.junit.Test; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public class DOMTest extends TemplateTest { + + @Test + public void xpathDetectionBugfix() throws Exception { + addDocToDataModel("<root><a>A</a><b>B</b><c>C</c></root>"); + assertOutput("${doc.root.b['following-sibling::c']}", "C"); + assertOutput("${doc.root.b['following-sibling::*']}", "C"); + } + + @Test + public void xmlnsPrefixes() throws Exception { + addDocToDataModel("<root xmlns='http://example.com/ns1' xmlns:ns2='http://example.com/ns2'>" + + "<a>A</a><ns2:b>B</ns2:b><c a1='1' ns2:a2='2'/></root>"); + + String ftlHeader = "<#ftl ns_prefixes={'D':'http://example.com/ns1', 'n2':'http://example.com/ns2'}>"; + + // @@markup: + assertOutput("${doc.@@markup}", + "<a:root xmlns:a=\"http://example.com/ns1\" xmlns:b=\"http://example.com/ns2\">" + + "<a:a>A</a:a><b:b>B</b:b><a:c a1=\"1\" b:a2=\"2\" />" + + "</a:root>"); + assertOutput(ftlHeader + + "${doc.@@markup}", + "<root xmlns=\"http://example.com/ns1\" xmlns:n2=\"http://example.com/ns2\">" + + "<a>A</a><n2:b>B</n2:b><c a1=\"1\" n2:a2=\"2\" /></root>"); + assertOutput("<#ftl ns_prefixes={'D':'http://example.com/ns1'}>" + + "${doc.@@markup}", + "<root xmlns=\"http://example.com/ns1\" xmlns:a=\"http://example.com/ns2\">" + + "<a>A</a><a:b>B</a:b><c a1=\"1\" a:a2=\"2\" /></root>"); + + // When there's no matching prefix declared via the #ftl header, return null for qname: + assertOutput("${doc?children[0].@@qname!'null'}", "null"); + assertOutput("${doc?children[0]?children[1].@@qname!'null'}", "null"); + assertOutput("${doc?children[0]?children[2]['@*'][1].@@qname!'null'}", "null"); + + // When we have prefix declared in the #ftl header: + assertOutput(ftlHeader + "${doc?children[0].@@qname}", "root"); + assertOutput(ftlHeader + "${doc?children[0]?children[1].@@qname}", "n2:b"); + assertOutput(ftlHeader + "${doc?children[0]?children[2].@@qname}", "c"); + assertOutput(ftlHeader + "${doc?children[0]?children[2]['@*'][0].@@qname}", "a1"); + assertOutput(ftlHeader + "${doc?children[0]?children[2]['@*'][1].@@qname}", "n2:a2"); + // Unfortunately these include the xmlns attributes, but that would be non-BC to fix now: + assertThat(getOutput(ftlHeader + "${doc?children[0].@@start_tag}"), startsWith("<root")); + assertThat(getOutput(ftlHeader + "${doc?children[0]?children[1].@@start_tag}"), startsWith("<n2:b")); + } + + @Test + public void namespaceUnaware() throws Exception { + addNSUnawareDocToDataModel("<root><x:a>A</x:a><:>B</:><xyz::c>C</xyz::c></root>"); + assertOutput("${doc.root['x:a']}", "A"); + assertOutput("${doc.root[':']}", "B"); + try { + assertOutput("${doc.root['xyz::c']}", "C"); + fail(); + } catch (TemplateException e) { + assertThat(e.getMessage(), containsString("xyz")); + } + } + + private void addDocToDataModel(String xml) throws SAXException, IOException, ParserConfigurationException { + addToDataModel("doc", XMLLoader.toModel(new InputSource(new StringReader(xml)))); + } + + private void addDocToDataModelNoSimplification(String xml) throws SAXException, IOException, ParserConfigurationException { + addToDataModel("doc", XMLLoader.toModel(new InputSource(new StringReader(xml)), false)); + } + + private void addNSUnawareDocToDataModel(String xml) throws ParserConfigurationException, SAXException, IOException { + DocumentBuilderFactory newFactory = DocumentBuilderFactory.newInstance(); + newFactory.setNamespaceAware(false); + DocumentBuilder builder = newFactory.newDocumentBuilder(); + Document doc = builder.parse(new InputSource(new StringReader(xml))); + addToDataModel("doc", doc); + } + + @Test + public void testInvalidAtAtKeyErrors() throws Exception { + addDocToDataModel("<r><multipleMatches /><multipleMatches /></r>"); + assertErrorContains("${doc.r.@@invalid_key}", "Unsupported @@ key", "@invalid_key"); + assertErrorContains("${doc.@@start_tag}", "@@start_tag", "not supported", "document"); + assertErrorContains("${doc.@@}", "\"@@\"", "not supported", "document"); + assertErrorContains("${doc.r.noMatch.@@invalid_key}", "Unsupported @@ key", "@invalid_key"); + assertErrorContains("${doc.r.multipleMatches.@@invalid_key}", "Unsupported @@ key", "@invalid_key"); + assertErrorContains("${doc.r.noMatch.@@attributes_markup}", "single XML node", "@@attributes_markup"); + assertErrorContains("${doc.r.multipleMatches.@@attributes_markup}", "single XML node", "@@attributes_markup"); + } + + @Test + public void testAtAtSiblingElement() throws Exception { + addDocToDataModel("<r><a/><b/></r>"); + assertOutput("${doc.r.@@previous_sibling_element?size}", "0"); + assertOutput("${doc.r.@@next_sibling_element?size}", "0"); + assertOutput("${doc.r.a.@@previous_sibling_element?size}", "0"); + assertOutput("${doc.r.a.@@next_sibling_element.@@qname}", "b"); + assertOutput("${doc.r.b.@@previous_sibling_element.@@qname}", "a"); + assertOutput("${doc.r.b.@@next_sibling_element?size}", "0"); + + addDocToDataModel("<r>\r\n\t <a/>\r\n\t <b/>\r\n\t </r>"); + assertOutput("${doc.r.@@previous_sibling_element?size}", "0"); + assertOutput("${doc.r.@@next_sibling_element?size}", "0"); + assertOutput("${doc.r.a.@@previous_sibling_element?size}", "0"); + assertOutput("${doc.r.a.@@next_sibling_element.@@qname}", "b"); + assertOutput("${doc.r.b.@@previous_sibling_element.@@qname}", "a"); + assertOutput("${doc.r.b.@@next_sibling_element?size}", "0"); + + addDocToDataModel("<r>t<a/>t<b/>t</r>"); + assertOutput("${doc.r.a.@@previous_sibling_element?size}", "0"); + assertOutput("${doc.r.a.@@next_sibling_element?size}", "0"); + assertOutput("${doc.r.b.@@previous_sibling_element?size}", "0"); + assertOutput("${doc.r.b.@@next_sibling_element?size}", "0"); + + addDocToDataModelNoSimplification("<r><a/> <!-- --><?pi?> <b/></r>"); + assertOutput("${doc.r.a.@@next_sibling_element.@@qname}", "b"); + assertOutput("${doc.r.b.@@previous_sibling_element.@@qname}", "a"); + + addDocToDataModelNoSimplification("<r><a/> <!-- -->t<!-- --> <b/></r>"); + assertOutput("${doc.r.a.@@next_sibling_element?size}", "0"); + assertOutput("${doc.r.b.@@previous_sibling_element?size}", "0"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/AutoEscapingExample.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/AutoEscapingExample.java b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/AutoEscapingExample.java new file mode 100644 index 0000000..036bace --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/AutoEscapingExample.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.manualtest; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +public class AutoEscapingExample extends TemplateTest { + + @Test + public void testInfoBox() throws Exception { + assertOutputForNamed("AutoEscapingExample-infoBox.ftlh"); + } + + @Test + public void testCapture() throws Exception { + assertOutputForNamed("AutoEscapingExample-capture.ftlh"); + } + + @Test + public void testMarkup() throws Exception { + assertOutputForNamed("AutoEscapingExample-markup.ftlh"); + } + + @Test + public void testConvert() throws Exception { + assertOutputForNamed("AutoEscapingExample-convert.ftlh"); + } + + @Test + public void testConvert2() throws Exception { + assertOutputForNamed("AutoEscapingExample-convert2.ftl"); + } + + @Test + public void testStringLiteral() throws Exception { + assertOutputForNamed("AutoEscapingExample-stringLiteral.ftlh"); + } + + @Test + public void testStringLiteral2() throws Exception { + assertOutputForNamed("AutoEscapingExample-stringLiteral2.ftlh"); + } + + @Test + public void testStringConcat() throws Exception { + assertOutputForNamed("AutoEscapingExample-stringConcat.ftlh"); + } + + @Override + protected Configuration createDefaultConfiguration() throws Exception { + return new TestConfigurationBuilder(AutoEscapingExample.class).build(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java new file mode 100644 index 0000000..40c1297 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/ConfigureOutputFormatExamples.java @@ -0,0 +1,105 @@ +/* + * 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.manualtest; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.apache.freemarker.core.TemplateConfiguration; +import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat; +import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat; +import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat; +import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory; +import org.apache.freemarker.core.templateresolver.FileExtensionMatcher; +import org.apache.freemarker.core.templateresolver.FirstMatchTemplateConfigurationFactory; +import org.apache.freemarker.core.templateresolver.OrMatcher; +import org.apache.freemarker.core.templateresolver.PathGlobMatcher; +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +public class ConfigureOutputFormatExamples extends TemplateTest { + + @Test + public void test() throws Exception { + addTemplate("mail/t.ftl", ""); + addTemplate("t.html", ""); + addTemplate("t.htm", ""); + addTemplate("t.xml", ""); + addTemplate("t.rtf", ""); + + example2(true); + example2(false); + example3(true); + example3(false); + } + + private void example2(boolean javaCfg) throws IOException { + setConfiguration( + javaCfg + ? new TestConfigurationBuilder() + .templateConfigurations( + new ConditionalTemplateConfigurationFactory( + new PathGlobMatcher("mail/**"), + new TemplateConfiguration.Builder() + .outputFormat(HTMLOutputFormat.INSTANCE) + .build())) + .build() + : new TestConfigurationBuilder() + .settings(loadPropertiesFile("ConfigureOutputFormatExamples1.properties")) + .build()); + assertEquals(HTMLOutputFormat.INSTANCE, getConfiguration().getTemplate("mail/t.ftl").getOutputFormat()); + } + + private void example3(boolean javaCfg) throws IOException { + setConfiguration( + javaCfg + ? new TestConfigurationBuilder() + .templateConfigurations( + new FirstMatchTemplateConfigurationFactory( + new ConditionalTemplateConfigurationFactory( + new FileExtensionMatcher("xml"), + new TemplateConfiguration.Builder() + .outputFormat(XMLOutputFormat.INSTANCE) + .build()), + new ConditionalTemplateConfigurationFactory( + new OrMatcher( + new FileExtensionMatcher("html"), + new FileExtensionMatcher("htm")), + new TemplateConfiguration.Builder() + .outputFormat(HTMLOutputFormat.INSTANCE) + .build()), + new ConditionalTemplateConfigurationFactory( + new FileExtensionMatcher("rtf"), + new TemplateConfiguration.Builder() + .outputFormat(RTFOutputFormat.INSTANCE) + .build())) + .allowNoMatch(true)) + .build() + : new TestConfigurationBuilder() + .settings(loadPropertiesFile("ConfigureOutputFormatExamples2.properties")) + .build()); + assertEquals(HTMLOutputFormat.INSTANCE, getConfiguration().getTemplate("t.html").getOutputFormat()); + assertEquals(HTMLOutputFormat.INSTANCE, getConfiguration().getTemplate("t.htm").getOutputFormat()); + assertEquals(XMLOutputFormat.INSTANCE, getConfiguration().getTemplate("t.xml").getOutputFormat()); + assertEquals(RTFOutputFormat.INSTANCE, getConfiguration().getTemplate("t.rtf").getOutputFormat()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java new file mode 100644 index 0000000..f38bb14 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/CustomFormatsExample.java @@ -0,0 +1,84 @@ +/* + * 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.manualtest; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Date; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.userpkg.BaseNTemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; +import org.apache.freemarker.core.valueformat.impl.AliasTemplateDateFormatFactory; +import org.apache.freemarker.core.valueformat.impl.AliasTemplateNumberFormatFactory; +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +import com.google.common.collect.ImmutableMap; + +@SuppressWarnings("boxing") +public class CustomFormatsExample extends TemplateTest { + + @Test + public void aliases1() throws IOException, TemplateException { + setConfiguration(new TestConfigurationBuilder(this.getClass()) + .customNumberFormats(ImmutableMap.<String, TemplateNumberFormatFactory>of( + "price", new AliasTemplateNumberFormatFactory(",000.00"), + "weight", new AliasTemplateNumberFormatFactory("0.##;; roundingMode=halfUp"))) + .customDateFormats(ImmutableMap.<String, TemplateDateFormatFactory>of( + "fileDate", new AliasTemplateDateFormatFactory("dd/MMM/yy hh:mm a"), + "logEventTime", new AliasTemplateDateFormatFactory("iso ms u") + )) + .build()); + + addToDataModel("p", 10000); + addToDataModel("w", new BigDecimal("10.305")); + addToDataModel("fd", new Date(1450904944213L)); + addToDataModel("let", new Date(1450904944213L)); + + assertOutputForNamed("CustomFormatsExample-alias1.ftlh"); + } + + @Test + public void aliases2() throws IOException, TemplateException { + setConfiguration(new TestConfigurationBuilder(this.getClass()) + .customNumberFormats(ImmutableMap.of( + "base", BaseNTemplateNumberFormatFactory.INSTANCE, + "oct", new AliasTemplateNumberFormatFactory("@base 8"))) + .build()); + + assertOutputForNamed("CustomFormatsExample-alias2.ftlh"); + } + + @Test + public void modelAware() throws IOException, TemplateException { + setConfiguration(new TestConfigurationBuilder(this.getClass()) + .customNumberFormats(ImmutableMap.<String, TemplateNumberFormatFactory>of( + "ua", UnitAwareTemplateNumberFormatFactory.INSTANCE)) + .numberFormat("@ua 0.####;; roundingMode=halfUp") + .build()); + + addToDataModel("weight", new UnitAwareTemplateNumberModel(1.5, "kg")); + + assertOutputForNamed("CustomFormatsExample-modelAware.ftlh"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/GettingStartedExample.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/GettingStartedExample.java b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/GettingStartedExample.java new file mode 100644 index 0000000..a676bc4 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/manualtest/GettingStartedExample.java @@ -0,0 +1,69 @@ +/* + * 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.manualtest; + +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import org.apache.freemarker.core.Configuration; +import org.apache.freemarker.core.Template; +import org.apache.freemarker.core.TemplateExceptionHandler; +import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader; +import org.junit.Test; + +public class GettingStartedExample { + + @Test + public void main() throws Exception { + /* ------------------------------------------------------------------------ */ + /* You should do this ONLY ONCE in the whole application life-cycle: */ + + /* Create the configuration singleton (using builder pattern) */ + Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0) + .templateLoader(new ClassTemplateLoader(GettingStartedExample.class, "")) + .sourceEncoding(StandardCharsets.UTF_8) + .templateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER) + .logTemplateExceptions(false) + .build(); + + /* ------------------------------------------------------------------------ */ + /* You usually do these for MULTIPLE TIMES in the application life-cycle: */ + + /* Create a data-model */ + Map<String, Object> root = new HashMap(); + root.put("user", "Big Joe"); + Product latest = new Product(); + latest.setUrl("products/greenmouse.html"); + latest.setName("green mouse"); + root.put("latestProduct", latest); + + /* Get the template (uses cache internally) */ + Template temp = cfg.getTemplate("test.ftlh"); + + /* Merge data-model with template */ + Writer out = new OutputStreamWriter(System.out); + temp.process(root, out); + // Note: Depending on what `out` is, you may need to call `out.close()`. + // This is usually the case for file output, but not for servlet output. + } + +}