You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/05/15 21:23:51 UTC
[35/51] [abbrv] [partial] incubator-freemarker git commit:
Restructured project so that freemarker-test-utils depends on freemarker-core
(and hence can provide common classes for testing templates,
and can use utility classes defined in the core). As a c
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/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.
+ }
+
+}