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/14 10:52:48 UTC

[05/51] [partial] incubator-freemarker git commit: Migrated from Ant to Gradle, and modularized the project. This is an incomplete migration; there are some TODO-s in the build scripts, and release related tasks are still missing. What works: Building th

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());
+    }
+
+}