http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/en_US/docgen-originals/figures/model2sketch_with_alpha.png ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/en_US/docgen-originals/figures/model2sketch_with_alpha.png b/freemarker-core/src/manual/en_US/docgen-originals/figures/model2sketch_with_alpha.png new file mode 100644 index 0000000..ce120cc Binary files /dev/null and b/freemarker-core/src/manual/en_US/docgen-originals/figures/model2sketch_with_alpha.png differ
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/en_US/docgen-originals/figures/odg-convert-howto.txt ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/en_US/docgen-originals/figures/odg-convert-howto.txt b/freemarker-core/src/manual/en_US/docgen-originals/figures/odg-convert-howto.txt new file mode 100644 index 0000000..e55acec --- /dev/null +++ b/freemarker-core/src/manual/en_US/docgen-originals/figures/odg-convert-howto.txt @@ -0,0 +1,43 @@ +/* + * 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. + */ + +Converting to SVG: +1. Open the ODG file with Libeoffice/OpenOffice Draw +2. Ctrl+A to select all objects +3. File/Export..., chose SVG format, and then tick "Selection" +4. Check the result. If contour lines at the right and bottom edge of the + figure are partically clipped (stroke width is halved), set a stroke with + other than 0 for all shapes. + +Converting to a decent quality (though non-transparent) PNG: +1. Open the ODG file with Libeoffice/OpenOffice Draw +2. Export to PDF +3. Open PDF in Adobe Acrobat Reader +4. Go to Adobe Acrobat Reader preferences and set it to not use subpixel + anti-aliasing, just normal anti-aliasing. They used to call this LCD vs + Monitor mode. +5. Zoom in/out until you get the desired size in pixels, take a + screen shot, crop it in some image editor, save it as PNG. + +Converting to transparent but somewhat ugly PNG: +1. Convert to SVG as described earlier +2. Use Apache Batik Rasterizer command line utility like: + $BARIK_INSTALLATION\batik-rasterizer-1.8.jar -dpi 72 -m image/png ${FIGURE}.svg + If Batik fails (as it doesn't support all SVG features), use Inkscape. + Of course avoid supixel anti-aliasing, as it's not device independent. http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/en_US/docgen-originals/figures/overview.odg ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/en_US/docgen-originals/figures/overview.odg b/freemarker-core/src/manual/en_US/docgen-originals/figures/overview.odg new file mode 100644 index 0000000..0533b7c Binary files /dev/null and b/freemarker-core/src/manual/en_US/docgen-originals/figures/overview.odg differ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/en_US/docgen-originals/figures/tree_with_alpha.png ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/en_US/docgen-originals/figures/tree_with_alpha.png b/freemarker-core/src/manual/en_US/docgen-originals/figures/tree_with_alpha.png new file mode 100644 index 0000000..dc4fba8 Binary files /dev/null and b/freemarker-core/src/manual/en_US/docgen-originals/figures/tree_with_alpha.png differ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/en_US/docgen.cjson ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/en_US/docgen.cjson b/freemarker-core/src/manual/en_US/docgen.cjson new file mode 100644 index 0000000..076e8f3 --- /dev/null +++ b/freemarker-core/src/manual/en_US/docgen.cjson @@ -0,0 +1,132 @@ +//charset: UTF-8 + +// 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. + +deployUrl: "http://freemarker.org/docs/" +onlineTrackerHTML: "docgen-misc/googleAnalytics.html" +searchKey: "003127866208504630097:arjqbv_znfw" +validation: { + programlistingsRequireRole + // programlistingsRequireLanguage + maximumProgramlistingWidth: 100 +} +showXXELogo +generateEclipseTOC +// eclipse: { +// link_to: "freemarker-toc.xml#ManualLink" +// } + +removeNodesWhenOnline: [ "preface" ] + +copyrightHolder: "The Apache Software Foundation" +copyrightHolderSite: "http://apache.org/" +copyrightSuffix: "Apache FreeMarker, FreeMarker, Apache Incubator, Apache, the Apache FreeMarker logo are trademarks of The Apache Software Foundation." +copyrightStartYear: 1999 +copyrightCommentFile: "docgen-misc/copyrightComment.txt" + +seoMeta: { + "dgui_quickstart": { + "title": "Getting Started with template writing" + } + "pgui_quickstart": { + "title": "Getting Started with the Java API" + } +} + +logo: { + href: "http://freemarker.org" + src: logo.png, + alt: "FreeMarker" +} + +olinks: { + homepage: "http://freemarker.org/" + api: "api/index.html" + + // Homepage links: + freemarkerdownload: "http://freemarker.org/freemarkerdownload.html" + contribute: "http://freemarker.org/contribute.html" + history: "http://freemarker.org/history.html" + what-is-freemarker: "http://freemarker.org/" + mailing-lists: "http://freemarker.org/mailing-lists.html" + + // External URL-s: + onlineTemplateTester: "http://freemarker-online.kenshoo.com/" + twitter: "https://twitter.com/freemarker" + sourceforgeProject: "https://sourceforge.net/projects/freemarker/" + githubProject: "https://github.com/freemarker/freemarker" + newBugReport: "https://issues.apache.org/jira/browse/FREEMARKER/" + newStackOverflowQuestion: "http://stackoverflow.com/questions/ask?tags=freemarker" +} + +internalBookmarks: { + "Alpha. index": alphaidx + "Glossary": gloss + "Expressions": exp_cheatsheet + "?builtins": ref_builtins_alphaidx + "#directives": ref_directive_alphaidx + ".spec_vars": ref_specvar + "FAQ": app_faq +} + +tabs: { + "Home": "olink:homepage" + "Manual": "" // Empty => We are here + "Java API": "olink:api" +} + +// Available icons: +// .icon-heart +// .icon-bug +// .icon-download +// .icon-star +secondaryTabs: { + "Contribute": { class: "icon-heart", href: "olink:contribute" } + "Report a Bug": { class: "icon-bug", href: "olink:newBugReport" } + "Download": { class: "icon-download", href: "olink:freemarkerdownload" } +} + +footerSiteMap: { + "Overview": { + "What is FreeMarker?": "olink:what-is-freemarker" + "Download": "olink:freemarkerdownload" + "Version history": "id:app_versions" + "About us": "olink:history" + "License": "id:app_license" + } + "Handy stuff": { + "Try template online": "olink:onlineTemplateTester" + "Expressions cheatsheet": "id:exp_cheatsheet" + "#directives": "id:ref_directive_alphaidx" + "?built_ins": "id:ref_builtins_alphaidx" + ".special_vars": "id:ref_specvar" + } + "Community": { + "FreeMarker on Github": "olink:githubProject" + "Follow us on Twitter": "olink:twitter" + "Report a bug": "olink:newBugReport" + "Ask a question": "olink:newStackOverflowQuestion" + "Mailing lists": "olink:mailing-lists" + } +} + +socialLinks: { + "Github": { class: "github", href: "olink:githubProject" } + "Twitter": { class: "twitter", href: "olink:twitter" } + "Stack Overflow": { class: "stack-overflow", href: "olink:newStackOverflowQuestion" } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/en_US/favicon.png ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/en_US/favicon.png b/freemarker-core/src/manual/en_US/favicon.png new file mode 100644 index 0000000..ce0de20 Binary files /dev/null and b/freemarker-core/src/manual/en_US/favicon.png differ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/en_US/figures/model2sketch.png ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/en_US/figures/model2sketch.png b/freemarker-core/src/manual/en_US/figures/model2sketch.png new file mode 100644 index 0000000..93f9a6b Binary files /dev/null and b/freemarker-core/src/manual/en_US/figures/model2sketch.png differ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/en_US/figures/overview.png ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/en_US/figures/overview.png b/freemarker-core/src/manual/en_US/figures/overview.png new file mode 100644 index 0000000..b32e0bd Binary files /dev/null and b/freemarker-core/src/manual/en_US/figures/overview.png differ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/en_US/figures/tree.png ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/en_US/figures/tree.png b/freemarker-core/src/manual/en_US/figures/tree.png new file mode 100644 index 0000000..dcd9bf3 Binary files /dev/null and b/freemarker-core/src/manual/en_US/figures/tree.png differ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/en_US/logo.png ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/en_US/logo.png b/freemarker-core/src/manual/en_US/logo.png new file mode 100644 index 0000000..193dc11 Binary files /dev/null and b/freemarker-core/src/manual/en_US/logo.png differ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/zh_CN/book.xml ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/zh_CN/book.xml b/freemarker-core/src/manual/zh_CN/book.xml new file mode 100644 index 0000000..c26677f --- /dev/null +++ b/freemarker-core/src/manual/zh_CN/book.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<book conformance="docgen" version="5.0" xml:lang="en" + xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + xmlns:ns5="http://www.w3.org/1999/xhtml" + xmlns:ns4="http://www.w3.org/2000/svg" + xmlns:ns3="http://www.w3.org/1998/Math/MathML" + xmlns:ns="http://docbook.org/ns/docbook"> + <info> + <title>Apache FreeMarker æå</title> + + <titleabbrev>æå</titleabbrev> + + <productname>Freemarker 3.0.0</productname> + </info> + + <preface role="index.html" xml:id="preface"> + <title>TODO</title> + + <para>TODO... Eventually, we might copy the FM2 Manual and rework + it.</para> + + <para>Anchors to satisfy Docgen:</para> + + <itemizedlist> + <listitem> + <para xml:id="app_versions">app_versions</para> + </listitem> + + <listitem> + <para xml:id="app_license">app_license</para> + </listitem> + + <listitem> + <para xml:id="exp_cheatsheet">exp_cheatsheet</para> + </listitem> + + <listitem> + <para xml:id="ref_directive_alphaidx">ref_directive_alphaidx</para> + </listitem> + + <listitem> + <para xml:id="ref_builtins_alphaidx">ref_builtins_alphaidx</para> + </listitem> + + <listitem> + <para xml:id="ref_specvar">ref_specvar</para> + </listitem> + + <listitem> + <para xml:id="alphaidx">alphaidx</para> + </listitem> + + <listitem> + <para xml:id="gloss">gloss</para> + </listitem> + + <listitem> + <para xml:id="app_faq">app_faq</para> + </listitem> + </itemizedlist> + </preface> +</book> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/zh_CN/docgen-help/README ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/zh_CN/docgen-help/README b/freemarker-core/src/manual/zh_CN/docgen-help/README new file mode 100644 index 0000000..6ebc928 --- /dev/null +++ b/freemarker-core/src/manual/zh_CN/docgen-help/README @@ -0,0 +1,2 @@ +Put the locale-specific or translated guides to editors here. +For the non-localized guides see the similar folder of the en_US Manual. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/zh_CN/docgen-misc/googleAnalytics.html ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/zh_CN/docgen-misc/googleAnalytics.html b/freemarker-core/src/manual/zh_CN/docgen-misc/googleAnalytics.html new file mode 100644 index 0000000..759564e --- /dev/null +++ b/freemarker-core/src/manual/zh_CN/docgen-misc/googleAnalytics.html @@ -0,0 +1,14 @@ +<!-- + This snippet was generated by Google Analytics. + Thus, the standard FreeMarker copyright comment was intentionally omitted. + <#DO_NOT_UPDATE_COPYRIGHT> +--> +<script> + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + + ga('create', 'UA-55420501-1', 'auto'); + ga('send', 'pageview'); +</script> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/zh_CN/docgen-originals/figures/README ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/zh_CN/docgen-originals/figures/README b/freemarker-core/src/manual/zh_CN/docgen-originals/figures/README new file mode 100644 index 0000000..f3a8221 --- /dev/null +++ b/freemarker-core/src/manual/zh_CN/docgen-originals/figures/README @@ -0,0 +1,2 @@ +Put the translated originals (sources) of the figures used in the manual here. +For figures that aren't translated, see the similar folder of the en_US Manual. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/zh_CN/docgen.cjson ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/zh_CN/docgen.cjson b/freemarker-core/src/manual/zh_CN/docgen.cjson new file mode 100644 index 0000000..ecff859 --- /dev/null +++ b/freemarker-core/src/manual/zh_CN/docgen.cjson @@ -0,0 +1,130 @@ +//charset: UTF-8 + +// 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. + +deployUrl: "http://freemarker.org/docs/" +onlineTrackerHTML: "docgen-misc/googleAnalytics.html" +searchKey: "014728049242975963158:8awjt03uofm" +validation: { + programlistingsRequireRole + // programlistingsRequireLanguage + maximumProgramlistingWidth: 100 +} +showXXELogo +generateEclipseTOC +// eclipse: { +// link_to: "freemarker-toc.xml#ManualLink" +// } + +removeNodesWhenOnline: [ "preface" ] + +copyrightHolder: "The Apache Software Foundation" +copyrightStartYear: 1999 +copyrightCommentFile: "../en_US/docgen-misc/copyrightComment.txt" + +seoMeta: { + "dgui_quickstart": { + "title": "Getting Started with template writing" + } + "pgui_quickstart": { + "title": "Getting Started with the Java API" + } +} + +logo: { + href: "http://freemarker.org" + src: logo.png, + alt: "FreeMarker" +} + +olinks: { + homepage: "http://freemarker.org/" + api: "api/index.html" + + // Homepage links: + freemarkerdownload: "http://freemarker.org/freemarkerdownload.html" + contribute: "http://freemarker.org/contribute.html" + history: "http://freemarker.org/history.html" + what-is-freemarker: "http://freemarker.org/" + mailing-lists: "http://freemarker.org/mailing-lists.html" + + // External URL-s: + onlineTemplateTester: "http://freemarker-online.kenshoo.com/" + twitter: "https://twitter.com/freemarker" + sourceforgeProject: "https://sourceforge.net/projects/freemarker/" + githubProject: "https://github.com/freemarker/freemarker" + newBugReport: "https://sourceforge.net/p/freemarker/bugs/new/" + newStackOverflowQuestion: "http://stackoverflow.com/questions/ask?tags=freemarker" +} + +internalBookmarks: { + "Alpha. index": alphaidx + "Glossary": gloss + "Expressions": exp_cheatsheet + "?builtins": ref_builtins_alphaidx + "#directives": ref_directive_alphaidx + ".spec_vars": ref_specvar + "FAQ": app_faq +} + +tabs: { + "Home": "olink:homepage" + "Manual": "" // Empty => We are here + "Java API": "olink:api" +} + +// Available icons: +// .icon-heart +// .icon-bug +// .icon-download +// .icon-star +secondaryTabs: { + "Contribute": { class: "icon-heart", href: "olink:contribute" } + "Report a Bug": { class: "icon-bug", href: "olink:newBugReport" } + "Download": { class: "icon-download", href: "olink:freemarkerdownload" } +} + +footerSiteMap: { + "Overview": { + "What is FreeMarker?": "olink:what-is-freemarker" + "Download": "olink:freemarkerdownload" + "Version history": "id:app_versions" + "About us": "olink:history" + "License": "id:app_license" + } + "Handy stuff": { + "Try template online": "olink:onlineTemplateTester" + "Expressions cheatsheet": "id:exp_cheatsheet" + "#directives": "id:ref_directive_alphaidx" + "?built_ins": "id:ref_builtins_alphaidx" + ".special_vars": "id:ref_specvar" + } + "Community": { + "FreeMarker on Github": "olink:githubProject" + "Follow us on Twitter": "olink:twitter" + "Report a bug": "olink:newBugReport" + "Ask a question": "olink:newStackOverflowQuestion" + "Mailing lists": "olink:mailing-lists" + } +} + +socialLinks: { + "Github": { class: "github", href: "olink:githubProject" } + "Twitter": { class: "twitter", href: "olink:twitter" } + "Stack Overflow": { class: "stack-overflow", href: "olink:newStackOverflowQuestion" } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/zh_CN/favicon.png ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/zh_CN/favicon.png b/freemarker-core/src/manual/zh_CN/favicon.png new file mode 100644 index 0000000..ce0de20 Binary files /dev/null and b/freemarker-core/src/manual/zh_CN/favicon.png differ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/zh_CN/figures/model2sketch.png ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/zh_CN/figures/model2sketch.png b/freemarker-core/src/manual/zh_CN/figures/model2sketch.png new file mode 100644 index 0000000..93f9a6b Binary files /dev/null and b/freemarker-core/src/manual/zh_CN/figures/model2sketch.png differ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/zh_CN/figures/overview.png ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/zh_CN/figures/overview.png b/freemarker-core/src/manual/zh_CN/figures/overview.png new file mode 100644 index 0000000..b32e0bd Binary files /dev/null and b/freemarker-core/src/manual/zh_CN/figures/overview.png differ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/zh_CN/figures/tree.png ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/zh_CN/figures/tree.png b/freemarker-core/src/manual/zh_CN/figures/tree.png new file mode 100644 index 0000000..dcd9bf3 Binary files /dev/null and b/freemarker-core/src/manual/zh_CN/figures/tree.png differ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/manual/zh_CN/logo.png ---------------------------------------------------------------------- diff --git a/freemarker-core/src/manual/zh_CN/logo.png b/freemarker-core/src/manual/zh_CN/logo.png new file mode 100644 index 0000000..193dc11 Binary files /dev/null and b/freemarker-core/src/manual/zh_CN/logo.png differ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/ASTBasedErrorMessagesTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/ASTBasedErrorMessagesTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/ASTBasedErrorMessagesTest.java new file mode 100644 index 0000000..10d63b3 --- /dev/null +++ b/freemarker-core/src/test/java/org/apache/freemarker/core/ASTBasedErrorMessagesTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.util.Map; + +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +public class ASTBasedErrorMessagesTest extends TemplateTest { + + @Test + public void testInvalidRefBasic() { + assertErrorContains("${foo}", "foo", "specify a default"); + assertErrorContains("${map[foo]}", "foo", "\\!map[", "specify a default"); + } + + @Test + public void testInvalidRefDollar() { + assertErrorContains("${$x}", "$x", "must not start with \"$\"", "specify a default"); + assertErrorContains("${map.$x}", "map.$x", "must not start with \"$\"", "specify a default"); + } + + @Test + public void testInvalidRefAfterDot() { + assertErrorContains("${map.foo.bar}", "map.foo", "\\!foo.bar", "after the last dot", "specify a default"); + } + + @Test + public void testInvalidRefInSquareBrackets() { + assertErrorContains("${map['foo']}", "map", "final [] step", "specify a default"); + } + + @Test + public void testInvalidRefSize() { + assertErrorContains("${map.size()}", "map.size", "?size", "specify a default"); + assertErrorContains("${map.length()}", "map.length", "?length", "specify a default"); + } + + @Override + protected Object createDataModel() { + Map<String, Object> dataModel = createCommonTestValuesDataModel(); + dataModel.put("overloads", new Overloads()); + return dataModel; + } + + public static class Overloads { + + @SuppressWarnings("unused") + public void m(String s) {} + + @SuppressWarnings("unused") + public void m(int i) {} + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/ASTPrinter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/ASTPrinter.java b/freemarker-core/src/test/java/org/apache/freemarker/core/ASTPrinter.java new file mode 100644 index 0000000..3518b29 --- /dev/null +++ b/freemarker-core/src/test/java/org/apache/freemarker/core/ASTPrinter.java @@ -0,0 +1,438 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.util.FTLUtil; +import org.apache.freemarker.core.util._ClassUtil; +import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.test.TestConfigurationBuilder; + +/** + * Static methods and command-line tool for printing the AST of a template. + */ +public class ASTPrinter { + + private final Configuration cfg; + private int successfulCounter; + private int failedCounter; + + static public void main(String[] args) throws IOException { + if (args.length == 0) { + usage(); + System.exit(-1); + } + + ASTPrinter astp = new ASTPrinter(); + if (args[0].equalsIgnoreCase("-r")) { + astp.mainRecursive(args); + } else { + astp.mainSingleTemplate(args); + } + } + + private ASTPrinter() { + cfg = new TestConfigurationBuilder(Configuration.VERSION_3_0_0).build(); + } + + private void mainSingleTemplate(String[] args) throws IOException, FileNotFoundException { + final String templateFileName; + final String templateContent; + if (args[0].startsWith("ftl:")) { + templateFileName = null; + templateContent = args[0]; + } else { + templateFileName = args[0]; + templateContent = null; + } + + Template t = new Template( + templateFileName, + templateFileName == null ? new StringReader(templateContent) : new FileReader(templateFileName), + cfg); + + p(getASTAsString(t)); + } + + private void mainRecursive(String[] args) throws IOException { + if (args.length != 4) { + p("Number of arguments must be 4, but was: " + args.length); + usage(); + System.exit(-1); + } + + final String srcDirPath = args[1].trim(); + File srcDir = new File(srcDirPath); + if (!srcDir.isDirectory()) { + p("This should be an existing directory: " + srcDirPath); + System.exit(-1); + } + + Pattern fnPattern; + try { + fnPattern = Pattern.compile(args[2]); + } catch (PatternSyntaxException e) { + p(_StringUtil.jQuote(args[2]) + " is not a valid regular expression"); + System.exit(-1); + return; + } + + final String dstDirPath = args[3].trim(); + File dstDir = new File(dstDirPath); + if (!dstDir.isDirectory()) { + p("This should be an existing directory: " + dstDirPath); + System.exit(-1); + } + + long startTime = System.currentTimeMillis(); + recurse(srcDir, fnPattern, dstDir); + long endTime = System.currentTimeMillis(); + + p("Templates successfully processed " + successfulCounter + ", failed " + failedCounter + + ". Time taken: " + (endTime - startTime) / 1000.0 + " s"); + } + + private void recurse(File srcDir, Pattern fnPattern, File dstDir) throws IOException { + File[] files = srcDir.listFiles(); + if (files == null) { + throw new IOException("Failed to kust directory: " + srcDir); + } + for (File file : files) { + if (file.isDirectory()) { + recurse(file, fnPattern, new File(dstDir, file.getName())); + } else { + if (fnPattern.matcher(file.getName()).matches()) { + File dstFile = new File(dstDir, file.getName()); + String res; + try { + Template t = new Template(file.getPath().replace('\\', '/'), loadIntoString(file), cfg); + res = getASTAsString(t); + successfulCounter++; + } catch (ParseException e) { + res = "<<<FAILED>>>\n" + e.getMessage(); + failedCounter++; + p(""); + p("-------------------------failed-------------------------"); + p("Error message was saved into: " + dstFile.getAbsolutePath()); + p(""); + p(e.getMessage()); + } + save(res, dstFile); + } + } + } + } + + private String loadIntoString(File file) throws IOException { + long ln = file.length(); + if (ln < 0) { + throw new IOException("Failed to get the length of " + file); + } + byte[] buffer = new byte[(int) ln]; + InputStream in = new FileInputStream(file); + try { + int offset = 0; + int bytesRead; + while (offset < buffer.length) { + bytesRead = in.read(buffer, offset, buffer.length - offset); + if (bytesRead == -1) { + throw new IOException("Unexpected end of file: " + file); + } + offset += bytesRead; + } + } finally { + in.close(); + } + + try { + return decode(buffer, StandardCharsets.UTF_8); + } catch (CharacterCodingException e) { + return decode(buffer, StandardCharsets.ISO_8859_1); + } + } + + private String decode(byte[] buffer, Charset charset) throws CharacterCodingException { + return charset.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT) + .decode(ByteBuffer.wrap(buffer)).toString(); + } + + private void save(String astStr, File file) throws IOException { + File parentDir = file.getParentFile(); + if (!parentDir.isDirectory() && !parentDir.mkdirs()) { + throw new IOException("Failed to invoke parent directory: " + parentDir); + } + + Writer w = new BufferedWriter(new FileWriter(file)); + try { + w.write(astStr); + } finally { + w.close(); + } + } + + private static void usage() { + p("Prints template Abstract Syntax Tree (AST) as plain text."); + p("Usage:"); + p(" java org.apache.freemarker.core.PrintAST <templateFile>"); + p(" java org.apache.freemarker.core.PrintAST ftl:<templateSource>"); + p(" java org.apache.freemarker.core.PrintAST -r <src-directory> <regexp> <dst-directory>"); + } + + private static final String INDENTATION = " "; + + public static String getASTAsString(String ftl) throws IOException { + return getASTAsString(ftl, (Options) null); + } + + public static String getASTAsString(String ftl, Options opts) throws IOException { + return getASTAsString(null, ftl, opts); + } + + public static String getASTAsString(String templateName, String ftl) throws IOException { + return getASTAsString(templateName, ftl, null); + } + + public static String getASTAsString(String templateName, String ftl, Options opts) throws IOException { + Template t = new Template(templateName, ftl, new TestConfigurationBuilder().build()); + return getASTAsString(t, opts); + } + + public static String getASTAsString(Template t) throws IOException { + return getASTAsString(t, null); + } + + public static String getASTAsString(Template t, Options opts) throws IOException { + validateAST(t); + + StringWriter out = new StringWriter(); + printNode(t.getRootASTNode(), "", null, opts != null ? opts : Options.DEFAULT_INSTANCE, out); + return out.toString(); + } + + public static void validateAST(Template t) throws InvalidASTException { + final ASTElement node = t.getRootASTNode(); + if (node.getParent() != null) { + throw new InvalidASTException("Root node parent must be null." + + "\nRoot node: " + node.dump(false) + + "\nParent" + + ": " + node.getParent().getClass() + ", " + node.getParent().dump(false)); + } + validateAST(node); + } + + private static void validateAST(ASTElement te) { + int childCount = te.getChildCount(); + for (int i = 0; i < childCount; i++) { + ASTElement child = te.getChild(i); + ASTElement parentElement = child.getParent(); + // As ASTImplicitParent.accept does nothing but returns its children, it's optimized out in the final + // AST tree. While it will be present as a child, the parent element also will have children + // that contains the children of the ASTImplicitParent directly. + if (parentElement instanceof ASTImplicitParent && parentElement.getParent() != null) { + parentElement = parentElement.getParent(); + } + if (parentElement != te) { + throw new InvalidASTException("Wrong parent node." + + "\nNode: " + child.dump(false) + + "\nExpected parent: " + te.dump(false) + + "\nActual parent: " + parentElement.dump(false)); + } + if (child.getIndex() != i) { + throw new InvalidASTException("Wrong node index." + + "\nNode: " + child.dump(false) + + "\nExpected index: " + i + + "\nActual index: " + child.getIndex()); + } + } + if (te instanceof ASTImplicitParent && te.getChildCount() < 2) { + throw new InvalidASTException("Mixed content with child count less than 2 should removed by optimizatoin, " + + "but found one with " + te.getChildCount() + " child(ren)."); + } + ASTElement[] children = te.getChildBuffer(); + if (children != null) { + if (childCount == 0) { + throw new InvalidASTException( + "Children must be null when childCount is 0." + + "\nNode: " + te.dump(false)); + } + for (int i = 0; i < te.getChildCount(); i++) { + if (children[i] == null) { + throw new InvalidASTException( + "Child can't be null at index " + i + + "\nNode: " + te.dump(false)); + } + } + for (int i = te.getChildCount(); i < children.length; i++) { + if (children[i] != null) { + throw new InvalidASTException( + "Children can't be non-null at index " + i + + "\nNode: " + te.dump(false)); + } + } + } else { + if (childCount != 0) { + throw new InvalidASTException( + "Children mustn't be null when child count isn't 0." + + "\nNode: " + te.dump(false)); + } + } + } + + private static void printNode(Object node, String ind, ParameterRole paramRole, Options opts, Writer out) throws IOException { + if (node instanceof ASTNode) { + ASTNode tObj = (ASTNode) node; + + printNodeLineStart(paramRole, ind, out); + out.write(tObj.getNodeTypeSymbol()); + printNodeLineEnd(node, out, opts); + + if (opts.getShowConstantValue() && node instanceof ASTExpression) { + TemplateModel tm = ((ASTExpression) node).constantValue; + if (tm != null) { + out.write(INDENTATION); + out.write(ind); + out.write("= const "); + out.write(FTLUtil.getTypeDescription(tm)); + out.write(' '); + out.write(tm.toString()); + out.write('\n'); + } + } + + int paramCnt = tObj.getParameterCount(); + for (int i = 0; i < paramCnt; i++) { + ParameterRole role = tObj.getParameterRole(i); + if (role == null) throw new NullPointerException("parameter role"); + Object value = tObj.getParameterValue(i); + printNode(value, ind + INDENTATION, role, opts, out); + } + if (tObj instanceof ASTElement) { + Enumeration enu = ((ASTElement) tObj).children(); + while (enu.hasMoreElements()) { + printNode(enu.nextElement(), INDENTATION + ind, null, opts, out); + } + } + } else { + printNodeLineStart(paramRole, ind, out); + out.write(_StringUtil.jQuote(node)); + printNodeLineEnd(node, out, opts); + } + } + + protected static void printNodeLineEnd(Object node, Writer out, Options opts) throws IOException { + boolean commentStared = false; + if (opts.getShowJavaClass()) { + out.write(" // "); + commentStared = true; + out.write(_ClassUtil.getShortClassNameOfObject(node, true)); + } + if (opts.getShowLocation() && node instanceof ASTNode) { + if (!commentStared) { + out.write(" // "); + commentStared = true; + } else { + out.write("; "); + } + ASTNode tObj = (ASTNode) node; + out.write("Location " + tObj.beginLine + ":" + tObj.beginColumn + "-" + tObj.endLine + ":" + tObj.endColumn); + } + out.write('\n'); + } + + private static void printNodeLineStart(ParameterRole paramRole, String ind, Writer out) throws IOException { + out.write(ind); + if (paramRole != null) { + out.write("- "); + out.write(paramRole.toString()); + out.write(": "); + } + } + + public static class Options { + + private final static Options DEFAULT_INSTANCE = new Options(); + + private boolean showJavaClass = true; + private boolean showConstantValue = false; + private boolean showLocation = false; + + public boolean getShowJavaClass() { + return showJavaClass; + } + + public void setShowJavaClass(boolean showJavaClass) { + this.showJavaClass = showJavaClass; + } + + public boolean getShowConstantValue() { + return showConstantValue; + } + + public void setShowConstantValue(boolean showConstantValue) { + this.showConstantValue = showConstantValue; + } + + public boolean getShowLocation() { + return showLocation; + } + + public void setShowLocation(boolean showLocation) { + this.showLocation = showLocation; + } + + } + + private static void p(Object obj) { + System.out.println(obj); + } + + public static class InvalidASTException extends RuntimeException { + + public InvalidASTException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidASTException(String message) { + super(message); + } + + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/ASTTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/ASTTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/ASTTest.java new file mode 100644 index 0000000..96f173a --- /dev/null +++ b/freemarker-core/src/test/java/org/apache/freemarker/core/ASTTest.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.apache.freemarker.core.ASTPrinter.Options; +import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.test.util.FileTestCase; +import org.apache.freemarker.test.TestUtil; + +public class ASTTest extends FileTestCase { + + public ASTTest(String name) { + super(name); + } + + public void test1() throws Exception { + testAST("ast-1"); + } + + public void testRange() throws Exception { + testAST("ast-range"); + } + + public void testAssignments() throws Exception { + testAST("ast-assignments"); + } + + public void testBuiltins() throws Exception { + testAST("ast-builtins"); + } + + public void testStringLiteralInterpolation() throws Exception { + testAST("ast-strlitinterpolation"); + } + + public void testWhitespaceStripping() throws Exception { + testAST("ast-whitespacestripping"); + } + + public void testMixedContentSimplifications() throws Exception { + testAST("ast-mixedcontentsimplifications"); + } + + public void testMultipleIgnoredChildren() throws Exception { + testAST("ast-multipleignoredchildren"); + } + + public void testNestedIgnoredChildren() throws Exception { + testAST("ast-nestedignoredchildren"); + } + + public void testLocations() throws Exception { + testASTWithLocations("ast-locations"); + } + + private void testAST(String testName) throws FileNotFoundException, IOException { + testAST(testName, null); + } + + private void testASTWithLocations(String testName) throws FileNotFoundException, IOException { + Options options = new Options(); + options.setShowLocation(true); + testAST(testName, options); + } + + private void testAST(String testName, Options ops) throws FileNotFoundException, IOException { + final String templateName = testName + ".ftl"; + assertExpectedFileEqualsString( + testName + ".ast", + ASTPrinter.getASTAsString(templateName, + TestUtil.removeFTLCopyrightComment( + normalizeLineBreaks( + loadTestTextResource( + getTestFileURL( + getExpectedContentFileDirectoryResourcePath(), templateName))) + ), ops)); + } + + private String normalizeLineBreaks(final String s) throws FileNotFoundException, IOException { + return _StringUtil.replace(s, "\r\n", "\n").replace('\r', '\n'); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/ActualNamingConvetionTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/ActualNamingConvetionTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/ActualNamingConvetionTest.java new file mode 100644 index 0000000..57e40fa --- /dev/null +++ b/freemarker-core/src/test/java/org/apache/freemarker/core/ActualNamingConvetionTest.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +public class ActualNamingConvetionTest { + + @Test + public void testUndetectable() throws IOException { + final String ftl = "<#if true>${x?size}</#if>"; + assertEquals(getActualNamingConvention(ftl, + ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION), ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION); + assertEquals(getActualNamingConvention(ftl, + ParsingConfiguration.LEGACY_NAMING_CONVENTION), ParsingConfiguration.LEGACY_NAMING_CONVENTION); + assertEquals(getActualNamingConvention(ftl, + ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION), ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION); + } + + @Test + public void testLegacyDetected() throws IOException { + final String ftl = "${x?upper_case}"; + assertEquals(getActualNamingConvention(ftl, + ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION), ParsingConfiguration.LEGACY_NAMING_CONVENTION); + assertEquals(getActualNamingConvention(ftl, + ParsingConfiguration.LEGACY_NAMING_CONVENTION), ParsingConfiguration.LEGACY_NAMING_CONVENTION); + } + + @Test + public void testCamelCaseDetected() throws IOException { + final String ftl = "${x?upperCase}"; + assertEquals(getActualNamingConvention(ftl, + ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION), ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION); + assertEquals(getActualNamingConvention(ftl, + ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION), ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION); + } + + private int getActualNamingConvention(String ftl, int namingConvention) throws IOException { + return new Template(null, ftl, + new TestConfigurationBuilder().namingConvention(namingConvention).build()) + .getActualNamingConvention(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/ActualTagSyntaxTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/ActualTagSyntaxTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/ActualTagSyntaxTest.java new file mode 100644 index 0000000..88f0646 --- /dev/null +++ b/freemarker-core/src/test/java/org/apache/freemarker/core/ActualTagSyntaxTest.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import static org.apache.freemarker.core.ParsingConfiguration.*; +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +public class ActualTagSyntaxTest { + + @Test + public void testWithFtlHeader() throws IOException { + testWithFtlHeader(AUTO_DETECT_TAG_SYNTAX); + testWithFtlHeader(ANGLE_BRACKET_TAG_SYNTAX); + testWithFtlHeader(SQUARE_BRACKET_TAG_SYNTAX); + } + + private void testWithFtlHeader(int cfgTagSyntax) throws IOException { + assertEquals(getActualTagSyntax("[#ftl]foo", cfgTagSyntax), SQUARE_BRACKET_TAG_SYNTAX); + assertEquals(getActualTagSyntax("<#ftl>foo", cfgTagSyntax), ANGLE_BRACKET_TAG_SYNTAX); + } + + @Test + public void testUndecidable() throws IOException { + assertEquals(getActualTagSyntax("foo", AUTO_DETECT_TAG_SYNTAX), ANGLE_BRACKET_TAG_SYNTAX); + assertEquals(getActualTagSyntax("foo", ANGLE_BRACKET_TAG_SYNTAX), ANGLE_BRACKET_TAG_SYNTAX); + assertEquals(getActualTagSyntax("foo", SQUARE_BRACKET_TAG_SYNTAX), SQUARE_BRACKET_TAG_SYNTAX); + } + + @Test + public void testDecidableWithoutFtlHeader() throws IOException { + assertEquals(getActualTagSyntax("foo<#if true></#if>", AUTO_DETECT_TAG_SYNTAX), ANGLE_BRACKET_TAG_SYNTAX); + assertEquals(getActualTagSyntax("foo<#if true></#if>", ANGLE_BRACKET_TAG_SYNTAX), ANGLE_BRACKET_TAG_SYNTAX); + assertEquals(getActualTagSyntax("foo<#if true></#if>", SQUARE_BRACKET_TAG_SYNTAX), SQUARE_BRACKET_TAG_SYNTAX); + + assertEquals(getActualTagSyntax("foo[#if true][/#if]", AUTO_DETECT_TAG_SYNTAX), SQUARE_BRACKET_TAG_SYNTAX); + assertEquals(getActualTagSyntax("foo[#if true][/#if]", ANGLE_BRACKET_TAG_SYNTAX), ANGLE_BRACKET_TAG_SYNTAX); + assertEquals(getActualTagSyntax("foo[#if true][/#if]", SQUARE_BRACKET_TAG_SYNTAX), SQUARE_BRACKET_TAG_SYNTAX); + } + + private int getActualTagSyntax(String ftl, int cfgTagSyntax) throws IOException { + return new Template( + null, ftl, + new TestConfigurationBuilder().tagSyntax(cfgTagSyntax).build()).getActualTagSyntax(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java new file mode 100644 index 0000000..61ba02b --- /dev/null +++ b/freemarker-core/src/test/java/org/apache/freemarker/core/BreakPlacementTest.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.io.IOException; + +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +public class BreakPlacementTest extends TemplateTest { + + private static final String BREAK_NESTING_ERROR_MESSAGE_PART = "<#break> must be nested"; + + @Test + public void testValidPlacements() throws IOException, TemplateException { + assertOutput("<#assign x = 1><#switch x><#case 1>one<#break><#case 2>two</#switch>", "one"); + assertOutput("<#list 1..2 as x>${x}<#break></#list>", "1"); + assertOutput("<#list 1..2>[<#items as x>${x}<#break></#items>]</#list>", "[1]"); + assertOutput("<#list 1..2 as x>${x}<#list 1..3>B<#break>E<#items as y></#items></#list>E</#list>.", "1B."); + assertOutput("<#list 1..2 as x>${x}<#list 3..4 as x>${x}<#break></#list>;</#list>", "13;23;"); + assertOutput("<#list [1..2, 3..4, [], 5..6] as xs>[<#list xs as x>${x}<#else><#break></#list>]</#list>.", + "[12][34][."); + assertOutput("<#list [1..2, 3..4, [], 5..6] as xs>" + + "<#list xs>[<#items as x>${x}</#items>]<#else><#break></#list>" + + "</#list>.", + "[12][34]."); + } + + @Test + public void testInvalidPlacements() throws IOException, TemplateException { + assertErrorContains("<#break>", BREAK_NESTING_ERROR_MESSAGE_PART); + assertErrorContains("<#list 1..2 as x>${x}</#list><#break>", BREAK_NESTING_ERROR_MESSAGE_PART); + assertErrorContains("<#if false><#break></#if>", BREAK_NESTING_ERROR_MESSAGE_PART); + assertErrorContains("<#list xs><#break></#list>", BREAK_NESTING_ERROR_MESSAGE_PART); + assertErrorContains("<#list 1..2 as x>${x}<#else><#break></#list>", BREAK_NESTING_ERROR_MESSAGE_PART); + assertErrorContains("<#list 1..2 as x>${x}<#macro m><#break></#macro></#list>", BREAK_NESTING_ERROR_MESSAGE_PART); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/CamelCaseTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/CamelCaseTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/CamelCaseTest.java new file mode 100644 index 0000000..95572ad --- /dev/null +++ b/freemarker-core/src/test/java/org/apache/freemarker/core/CamelCaseTest.java @@ -0,0 +1,486 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.freemarker.core; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat; +import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat; +import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +public class CamelCaseTest extends TemplateTest { + + @Test + public void camelCaseSpecialVars() throws IOException, TemplateException { + setConfiguration(new TestConfigurationBuilder() + .outputEncoding(StandardCharsets.UTF_8) + .urlEscapingCharset(StandardCharsets.ISO_8859_1) + .locale(Locale.GERMANY) + .build()); + assertOutput("${.dataModel?isHash?c}", "true"); + assertOutput("${.data_model?is_hash?c}", "true"); + assertOutput("${.localeObject.toString()}", "de_DE"); + assertOutput("${.locale_object.toString()}", "de_DE"); + assertOutput("${.templateName!'null'}", "null"); + assertOutput("${.template_name!'null'}", "null"); + assertOutput("${.currentTemplateName!'null'}", "null"); + assertOutput("${.current_template_name!'null'}", "null"); + assertOutput("${.mainTemplateName!'null'}", "null"); + assertOutput("${.main_template_name!'null'}", "null"); + assertOutput("${.outputEncoding}", StandardCharsets.UTF_8.name()); + assertOutput("${.output_encoding}", StandardCharsets.UTF_8.name()); + assertOutput("${.outputFormat}", UndefinedOutputFormat.INSTANCE.getName()); + assertOutput("${.output_format}", UndefinedOutputFormat.INSTANCE.getName()); + assertOutput("${.urlEscapingCharset}", StandardCharsets.ISO_8859_1.name()); + assertOutput("${.url_escaping_charset}", StandardCharsets.ISO_8859_1.name()); + assertOutput("${.currentNode!'-'}", "-"); + assertOutput("${.current_node!'-'}", "-"); + } + + @Test + public void camelCaseSpecialVarsInErrorMessage() throws IOException, TemplateException { + assertErrorContains("${.fooBar}", "dataModel", "\\!data_model"); + assertErrorContains("${.foo_bar}", "data_model", "\\!dataModel"); + // [2.4] If camel case will be the recommended style, then this need to be inverted: + assertErrorContains("${.foo}", "data_model", "\\!dataModel"); + + assertErrorContains("<#if x><#elseIf y></#if>${.foo}", "dataModel", "\\!data_model"); + assertErrorContains("<#if x><#elseif y></#if>${.foo}", "data_model", "\\!dataModel"); + + setConfigurationToCamelCaseNamingConvention(); + assertErrorContains("${.foo}", "dataModel", "\\!data_model"); + + setConfigurationToLegacyCaseNamingConvention(); + assertErrorContains("${.foo}", "data_model", "\\!dataModel"); + } + + @Test + public void camelCaseSettingNames() throws IOException, TemplateException { + assertOutput("<#setting booleanFormat='Y,N'>${true} <#setting booleanFormat='+,-'>${true}", "Y +"); + assertOutput("<#setting boolean_format='Y,N'>${true} <#setting boolean_format='+,-'>${true}", "Y +"); + + // Still works inside ?interpret + assertOutput("<@r\"<#setting booleanFormat='Y,N'>${true}\"?interpret />", "Y"); + } + + @Test + public void camelCaseFtlHeaderParameters() throws IOException, TemplateException { + assertOutput( + "<#ftl " + + "stripWhitespace=false " + + "stripText=true " + + "outputFormat='" + HTMLOutputFormat.INSTANCE.getName() + "' " + + "autoEsc=true " + + "nsPrefixes={} " + + ">\nx\n<#if true>\n${.outputFormat}\n</#if>\n", + "\nHTML\n"); + + assertOutput( + "<#ftl " + + "strip_whitespace=false " + + "strip_text=true " + + "output_format='" + HTMLOutputFormat.INSTANCE.getName() + "' " + + "auto_esc=true " + + "ns_prefixes={} " + + ">\nx\n<#if true>\n${.output_format}\n</#if>\n", + "\nHTML\n"); + + assertErrorContains("<#ftl strip_text=true xmlns={}>", "ns_prefixes", "\\!nsPrefixes"); + assertErrorContains("<#ftl stripText=true xmlns={}>", "nsPrefixes"); + + assertErrorContains("<#ftl stripWhitespace=true strip_text=true>", "naming convention"); + assertErrorContains("<#ftl strip_whitespace=true stripText=true>", "naming convention"); + assertErrorContains("<#ftl stripWhitespace=true>${.foo_bar}", "naming convention"); + assertErrorContains("<#ftl strip_whitespace=true>${.fooBar}", "naming convention"); + + setConfiguration(new TestConfigurationBuilder() + .namingConvention(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION) + .outputEncoding(StandardCharsets.UTF_8) + .build()); + assertErrorContains("<#ftl strip_whitespace=true>", "naming convention"); + assertOutput("<#ftl stripWhitespace=true>${.outputEncoding}", StandardCharsets.UTF_8.name()); + + setConfiguration(new TestConfigurationBuilder() + .namingConvention(ParsingConfiguration.LEGACY_NAMING_CONVENTION) + .outputEncoding(StandardCharsets.UTF_8) + .build()); + assertErrorContains("<#ftl stripWhitespace=true>", "naming convention"); + assertOutput("<#ftl strip_whitespace=true>${.output_encoding}", StandardCharsets.UTF_8.name()); + + setConfiguration(new TestConfigurationBuilder() + .namingConvention(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION) + .outputEncoding(StandardCharsets.UTF_8) + .build()); + assertOutput("<#ftl stripWhitespace=true>${.outputEncoding}", StandardCharsets.UTF_8.name()); + assertOutput("<#ftl encoding='iso-8859-1' stripWhitespace=true>${.outputEncoding}", StandardCharsets.UTF_8.name()); + assertOutput("<#ftl stripWhitespace=true encoding='iso-8859-1'>${.outputEncoding}", StandardCharsets.UTF_8.name()); + assertOutput("<#ftl encoding='iso-8859-1' strip_whitespace=true>${.output_encoding}", StandardCharsets.UTF_8.name()); + assertOutput("<#ftl strip_whitespace=true encoding='iso-8859-1'>${.output_encoding}", StandardCharsets.UTF_8.name()); + } + + @Test + public void camelCaseSettingNamesInErrorMessages() throws IOException, TemplateException { + assertErrorContains("<#setting fooBar=1>", "booleanFormat", "\\!boolean_format"); + assertErrorContains("<#setting foo_bar=1>", "boolean_format", "\\!booleanFormat"); + // [2.4] If camel case will be the recommended style, then this need to be inverted: + assertErrorContains("<#setting foo=1>", "boolean_format", "\\!booleanFormat"); + + assertErrorContains("<#if x><#elseIf y></#if><#setting foo=1>", "booleanFormat", "\\!boolean_format"); + assertErrorContains("<#if x><#elseif y></#if><#setting foo=1>", "boolean_format", "\\!booleanFormat"); + + setConfigurationToCamelCaseNamingConvention(); + assertErrorContains("<#setting foo=1>", "booleanFormat", "\\!boolean_format"); + + setConfigurationToLegacyCaseNamingConvention(); + assertErrorContains("<#setting foo=1>", "boolean_format", "\\!booleanFormat"); + } + + @Test + public void camelCaseIncludeParameters() throws IOException, TemplateException { + assertOutput("<#ftl stripWhitespace=true>[<#include 'noSuchTemplate' ignoreMissing=true>]", "[]"); + assertOutput("<#ftl strip_whitespace=true>[<#include 'noSuchTemplate' ignore_missing=true>]", "[]"); + assertErrorContains("<#ftl stripWhitespace=true>[<#include 'noSuchTemplate' ignore_missing=true>]", + "naming convention", "ignore_missing"); + assertErrorContains("<#ftl strip_whitespace=true>[<#include 'noSuchTemplate' ignoreMissing=true>]", + "naming convention", "ignoreMissing"); + } + + @Test + public void specialVarsHasBothNamingStyle() throws IOException, TemplateException { + assertContainsBothNamingStyles( + new HashSet(Arrays.asList(ASTExpBuiltInVariable.SPEC_VAR_NAMES)), + new NamePairAssertion() { @Override + public void assertPair(String name1, String name2) { } }); + } + + @Test + public void camelCaseBuiltIns() throws IOException, TemplateException { + assertOutput("${'x'?upperCase}", "X"); + assertOutput("${'x'?upper_case}", "X"); + } + + @Test + public void stringLiteralInterpolation() throws IOException, TemplateException { + assertEquals(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION, getConfiguration().getNamingConvention()); + addToDataModel("x", "x"); + + assertOutput("${'-${x?upperCase}-'} ${x?upperCase}", "-X- X"); + assertOutput("${x?upperCase} ${'-${x?upperCase}-'}", "X -X-"); + assertOutput("${'-${x?upper_case}-'} ${x?upper_case}", "-X- X"); + assertOutput("${x?upper_case} ${'-${x?upper_case}-'}", "X -X-"); + + assertErrorContains("${'-${x?upper_case}-'} ${x?upperCase}", + "naming convention", "legacy", "upperCase", "detection", "9"); + assertErrorContains("${x?upper_case} ${'-${x?upperCase}-'}", + "naming convention", "legacy", "upperCase", "detection", "5"); + assertErrorContains("${'-${x?upperCase}-'} ${x?upper_case}", + "naming convention", "camel", "upper_case"); + assertErrorContains("${x?upperCase} ${'-${x?upper_case}-'}", + "naming convention", "camel", "upper_case"); + + setConfigurationToCamelCaseNamingConvention(); + assertOutput("${'-${x?upperCase}-'} ${x?upperCase}", "-X- X"); + assertErrorContains("${'-${x?upper_case}-'}", + "naming convention", "camel", "upper_case", "\\!detection"); + + setConfigurationToLegacyCaseNamingConvention(); + assertOutput("${'-${x?upper_case}-'} ${x?upper_case}", "-X- X"); + assertErrorContains("${'-${x?upperCase}-'}", + "naming convention", "legacy", "upperCase", "\\!detection"); + } + + @Test + public void evalAndInterpret() throws IOException, TemplateException { + assertEquals(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION, getConfiguration().getNamingConvention()); + // The naming convention detected doesn't affect the enclosing template's naming convention. + // - ?eval: + assertOutput("${\"'x'?upperCase\"?eval}${'x'?upper_case}", "XX"); + assertOutput("${\"'x'?upper_case\"?eval}${'x'?upperCase}", "XX"); + assertOutput("${'x'?upperCase}${\"'x'?upper_case\"?eval}", "XX"); + assertErrorContains("${\"'x'\n?upperCase\n?is_string\"?eval}", + "naming convention", "camel", "upperCase", "is_string", "line 2", "line 3"); + // - ?interpret: + assertOutput("<@r\"${'x'?upperCase}\"?interpret />${'x'?upper_case}", "XX"); + assertOutput("<@r\"${'x'?upper_case}\"?interpret />${'x'?upperCase}", "XX"); + assertOutput("${'x'?upper_case}<@r\"${'x'?upperCase}\"?interpret />", "XX"); + assertErrorContains("<@r\"${'x'\n?upperCase\n?is_string}\"?interpret />", + "naming convention", "camel", "upperCase", "is_string", "line 2", "line 3"); + + // Will be inherited by ?eval-ed/?interpreted fragments: + setConfigurationToCamelCaseNamingConvention(); + // - ?eval: + assertErrorContains("${\"'x'?upper_case\"?eval}", "naming convention", "camel", "upper_case"); + assertOutput("${\"'x'?upperCase\"?eval}", "X"); + // - ?interpret: + assertErrorContains("<@r\"${'x'?upper_case}\"?interpret />", "naming convention", "camel", "upper_case"); + assertOutput("<@r\"${'x'?upperCase}\"?interpret />", "X"); + + // Again, will be inherited by ?eval-ed/?interpreted fragments: + setConfigurationToLegacyCaseNamingConvention(); + // - ?eval: + assertErrorContains("${\"'x'?upperCase\"?eval}", "naming convention", "legacy", "upperCase"); + assertOutput("${\"'x'?upper_case\"?eval}", "X"); + // - ?interpret: + assertErrorContains("<@r\"${'x'?upperCase}\"?interpret />", "naming convention", "legacy", "upperCase"); + assertOutput("<@r\"${'x'?upper_case}\"?interpret />", "X"); + } + + private void setConfigurationToLegacyCaseNamingConvention() { + setConfiguration(new TestConfigurationBuilder() + .namingConvention(ParsingConfiguration.LEGACY_NAMING_CONVENTION) + .build()); + } + + @Test + public void camelCaseBuiltInErrorMessage() throws IOException, TemplateException { + assertErrorContains("${'x'?upperCasw}", "upperCase", "\\!upper_case"); + assertErrorContains("${'x'?upper_casw}", "upper_case", "\\!upperCase"); + // [2.4] If camel case will be the recommended style, then this need to be inverted: + assertErrorContains("${'x'?foo}", "upper_case", "\\!upperCase"); + + assertErrorContains("<#if x><#elseIf y></#if> ${'x'?foo}", "upperCase", "\\!upper_case"); + assertErrorContains("<#if x><#elseif y></#if>${'x'?foo}", "upper_case", "\\!upperCase"); + + setConfigurationToCamelCaseNamingConvention(); + assertErrorContains("${'x'?foo}", "upperCase", "\\!upper_case"); + setConfigurationToLegacyCaseNamingConvention(); + assertErrorContains("${'x'?foo}", "upper_case", "\\!upperCase"); + } + + private void setConfigurationToCamelCaseNamingConvention() { + setConfiguration(new TestConfigurationBuilder() + .namingConvention(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION) + .build()); + } + + @Test + public void builtInsHasBothNamingStyle() throws IOException, TemplateException { + assertContainsBothNamingStyles(getConfiguration().getSupportedBuiltInNames(), new NamePairAssertion() { + + @Override + public void assertPair(String name1, String name2) { + ASTExpBuiltIn bi1 = ASTExpBuiltIn.BUILT_INS_BY_NAME.get(name1); + ASTExpBuiltIn bi2 = ASTExpBuiltIn.BUILT_INS_BY_NAME.get(name2); + assertTrue("\"" + name1 + "\" and \"" + name2 + "\" doesn't belong to the same BI object.", + bi1 == bi2); + } + + }); + } + + private void assertContainsBothNamingStyles(Set<String> names, NamePairAssertion namePairAssertion) { + Set<String> underscoredNamesWithCamelCasePair = new HashSet<>(); + for (String name : names) { + if (_StringUtil.getIdentifierNamingConvention(name) == ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION) { + String underscoredName = correctIsoBIExceptions(_StringUtil.camelCaseToUnderscored(name)); + assertTrue( + "Missing underscored variation \"" + underscoredName + "\" for \"" + name + "\".", + names.contains(underscoredName)); + assertTrue(underscoredNamesWithCamelCasePair.add(underscoredName)); + + namePairAssertion.assertPair(name, underscoredName); + } + } + for (String name : names) { + if (_StringUtil.getIdentifierNamingConvention(name) == ParsingConfiguration.LEGACY_NAMING_CONVENTION) { + assertTrue("Missing camel case variation for \"" + name + "\".", + underscoredNamesWithCamelCasePair.contains(name)); + } + } + } + + private String correctIsoBIExceptions(String underscoredName) { + return underscoredName.replace("_n_z", "_nz").replace("_f_z", "_fz"); + } + + @Test + public void camelCaseDirectives() throws IOException, TemplateException { + camelCaseDirectives(false); + setConfiguration(new TestConfigurationBuilder() + .tagSyntax(ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX) + .build()); + camelCaseDirectives(true); + } + + private void camelCaseDirectives(boolean squared) throws IOException, TemplateException { + assertOutput( + squared("<#list 1..4 as x><#if x == 1>one <#elseIf x == 2>two <#elseIf x == 3>three " + + "<#else>more</#if></#list>", squared), + "one two three more"); + assertOutput( + squared("<#list 1..4 as x><#if x == 1>one <#elseif x == 2>two <#elseif x == 3>three " + + "<#else>more</#if></#list>", squared), + "one two three more"); + + assertOutput( + squared("<#escape x as x?upperCase>${'a'}<#noEscape>${'b'}</#noEscape></#escape>", squared), + "Ab"); + assertOutput( + squared("<#escape x as x?upper_case>${'a'}<#noescape>${'b'}</#noescape></#escape>", squared), + "Ab"); + + assertOutput( + squared("<#noParse></#noparse></#noParse>", squared), + squared("</#noparse>", squared)); + assertOutput( + squared("<#noparse></#noParse></#noparse>", squared), + squared("</#noParse>", squared)); + } + + private String squared(String ftl, boolean squared) { + return squared ? ftl.replace('<', '[').replace('>', ']') : ftl; + } + + @Test + public void explicitNamingConvention() throws IOException, TemplateException { + explicitNamingConvention(false); + explicitNamingConvention(true); + } + + private void explicitNamingConvention(boolean squared) throws IOException, TemplateException { + int tagSyntax = squared ? ParsingConfiguration.AUTO_DETECT_TAG_SYNTAX + : ParsingConfiguration.ANGLE_BRACKET_TAG_SYNTAX; + setConfiguration(new TestConfigurationBuilder() + .tagSyntax(tagSyntax) + .namingConvention(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION) + .build()); + + assertErrorContains( + squared("<#if true>t<#elseif false>f</#if>", squared), + "naming convention", "camel", "#elseif"); + assertOutput( + squared("<#if true>t<#elseIf false>f</#if>", squared), + "t"); + + assertErrorContains( + squared("<#noparse>${x}</#noparse>", squared), + "naming convention", "camel", "#noparse"); + assertOutput( + squared("<#noParse>${x}</#noParse>", squared), + "${x}"); + + assertErrorContains( + squared("<#escape x as -x><#noescape>${1}</#noescape></#escape>", squared), + "naming convention", "camel", "#noescape"); + assertOutput( + squared("<#escape x as -x><#noEscape>${1}</#noEscape></#escape>", squared), + "1"); + + // --- + + setConfiguration(new TestConfigurationBuilder() + .tagSyntax(tagSyntax) + .namingConvention(ParsingConfiguration.LEGACY_NAMING_CONVENTION) + .build()); + + assertErrorContains( + squared("<#if true>t<#elseIf false>f</#if>", squared), + "naming convention", "legacy", "#elseIf"); + assertOutput( + squared("<#if true>t<#elseif false>f</#if>", squared), + "t"); + + assertErrorContains( + squared("<#noParse>${x}</#noParse>", squared), + "naming convention", "legacy", "#noParse"); + assertOutput( + squared("<#noparse>${x}</#noparse>", squared), + "${x}"); + + assertErrorContains( + squared("<#escape x as -x><#noEscape>${1}</#noEscape></#escape>", squared), + "naming convention", "legacy", "#noEscape"); + assertOutput( + squared("<#escape x as -x><#noescape>${1}</#noescape></#escape>", squared), + "1"); + } + + @Test + public void inconsistentAutoDetectedNamingConvention() { + assertErrorContains( + "<#if x><#elseIf y><#elseif z></#if>", + "naming convention", "camel"); + assertErrorContains( + "<#if x><#elseif y><#elseIf z></#if>", + "naming convention", "legacy"); + assertErrorContains( + "<#if x><#elseIf y></#if><#noparse></#noparse>", + "naming convention", "camel"); + assertErrorContains( + "<#if x><#elseif y></#if><#noParse></#noParse>", + "naming convention", "legacy"); + assertErrorContains( + "<#if x><#elseif y><#elseIf z></#if>", + "naming convention", "legacy"); + assertErrorContains( + "<#escape x as x + 1><#noEscape></#noescape></#escape>", + "naming convention", "camel"); + assertErrorContains( + "<#escape x as x + 1><#noEscape></#noEscape><#noescape></#noescape></#escape>", + "naming convention", "camel"); + assertErrorContains( + "<#escape x as x + 1><#noescape></#noEscape></#escape>", + "naming convention", "legacy"); + assertErrorContains( + "<#escape x as x + 1><#noescape></#noescape><#noEscape></#noEscape></#escape>", + "naming convention", "legacy"); + + assertErrorContains("${x?upperCase?is_string}", + "naming convention", "camel", "upperCase", "is_string"); + assertErrorContains("${x?upper_case?isString}", + "naming convention", "legacy", "upper_case", "isString"); + + assertErrorContains("<#setting outputEncoding='utf-8'>${x?is_string}", + "naming convention", "camel", "outputEncoding", "is_string"); + assertErrorContains("<#setting output_encoding='utf-8'>${x?isString}", + "naming convention", "legacy", "output_encoding", "isString"); + + assertErrorContains("${x?isString}<#setting output_encoding='utf-8'>", + "naming convention", "camel", "isString", "output_encoding"); + assertErrorContains("${x?is_string}<#setting outputEncoding='utf-8'>", + "naming convention", "legacy", "is_string", "outputEncoding"); + + assertErrorContains("${.outputEncoding}${x?is_string}", + "naming convention", "camel", "outputEncoding", "is_string"); + assertErrorContains("${.output_encoding}${x?isString}", + "naming convention", "legacy", "output_encoding", "isString"); + + assertErrorContains("${x?upperCase}<#noparse></#noparse>", + "naming convention", "camel", "upperCase", "noparse"); + assertErrorContains("${x?upper_case}<#noParse></#noParse>", + "naming convention", "legacy", "upper_case", "noParse"); + } + + private interface NamePairAssertion { + + void assertPair(String name1, String name2); + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java b/freemarker-core/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java new file mode 100644 index 0000000..c78c90e --- /dev/null +++ b/freemarker-core/src/test/java/org/apache/freemarker/core/CanonicalFormTest.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.io.IOException; +import java.io.StringWriter; + +import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader; +import org.apache.freemarker.test.CopyrightCommentRemoverTemplateLoader; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.apache.freemarker.test.util.FileTestCase; + +public class CanonicalFormTest extends FileTestCase { + + public CanonicalFormTest(String name) { + super(name); + } + + public void testMacrosCanonicalForm() throws Exception { + assertCanonicalFormOf("cano-macros.ftl"); + } + + public void testIdentifierEscapingCanonicalForm() throws Exception { + assertCanonicalFormOf("cano-identifier-escaping.ftl"); + } + + public void testAssignmentCanonicalForm() throws Exception { + assertCanonicalFormOf("cano-assignments.ftl"); + } + + public void testBuiltInCanonicalForm() throws Exception { + assertCanonicalFormOf("cano-builtins.ftl"); + } + + public void testStringLiteralInterpolationCanonicalForm() throws Exception { + assertCanonicalFormOf("cano-strlitinterpolation.ftl"); + } + + private void assertCanonicalFormOf(String ftlFileName) + throws IOException { + Configuration cfg = new TestConfigurationBuilder() + .templateLoader( + new CopyrightCommentRemoverTemplateLoader( + new ClassTemplateLoader(CanonicalFormTest.class, ""))) + .build(); + StringWriter sw = new StringWriter(); + cfg.getTemplate(ftlFileName).dump(sw); + assertExpectedFileEqualsString(ftlFileName + ".out", sw.toString()); + } + +}