Repository: incubator-freemarker Updated Branches: refs/heads/3 1f086e3f2 -> 70e47bef7
Continued work on the FM2 to FM3 converter Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/70e47bef Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/70e47bef Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/70e47bef Branch: refs/heads/3 Commit: 70e47bef7b4ae7128ee725acf98e1df87fa3d019 Parents: 1f086e3 Author: ddekany <[email protected]> Authored: Mon Jul 3 02:07:25 2017 +0200 Committer: ddekany <[email protected]> Committed: Mon Jul 3 02:07:25 2017 +0200 ---------------------------------------------------------------------- .../core/FM2ASTToFM3SourceConverter.java | 151 ++++++++++++++++--- .../freemarker/converter/FM2ToFM3Converter.java | 13 +- .../converter/FM2ToFM3ConverterTest.java | 7 + 3 files changed, 143 insertions(+), 28 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/70e47bef/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java b/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java index b1f221c..8322f95 100644 --- a/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java +++ b/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java @@ -28,10 +28,7 @@ import java.util.Set; import org.apache.freemarker.converter.ConverterException; import org.apache.freemarker.converter.ConverterUtils; import org.apache.freemarker.core.NamingConvention; -import org.apache.freemarker.core.util.FTLUtil; -import org.apache.freemarker.core.util._ClassUtil; -import org.apache.freemarker.core.util._NullArgumentException; -import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.core.util.*; import freemarker.template.Configuration; import freemarker.template.Template; @@ -67,6 +64,7 @@ import freemarker.template.utility.StringUtil; @SuppressWarnings("deprecation") public class FM2ASTToFM3SourceConverter { + private final Template template; private final String src; private final StringBuilder out; private List<Integer> rowStartPositions; @@ -78,17 +76,34 @@ public class FM2ASTToFM3SourceConverter { .build(); private final Set<String> fm3BuiltInNames = fm3Config.getSupportedBuiltInNames(); + private boolean printNextCustomDirAsFtlDir; + /** - * @param template Must have been parsed with {@link Configuration#getWhitespaceStripping()} {@code false}. + * @param fm2Cfg The {@link Configuration} used for parsing; {@link Configuration#getWhitespaceStripping()} must + * return {@code false}. */ - public static String convert(Template template, String src) throws ConverterException { - FM2ASTToFM3SourceConverter instance = new FM2ASTToFM3SourceConverter(template, src); - instance.printNode(template.getRootTreeNode()); - return instance.getOutput(); + public static Result convert(String templateName, String src, Configuration fm2Cfg) throws ConverterException { + return new FM2ASTToFM3SourceConverter(templateName, src, fm2Cfg).convert(); } - private FM2ASTToFM3SourceConverter(Template template, String src) { - _NullArgumentException.check("template", template); + private Result convert() throws ConverterException { + printDirFtl(); + printNode(template.getRootTreeNode()); + + String outAsString = out.toString(); + try { + //!!T new org.apache.freemarker.core.Template(null, s, fm3Config); + } catch (Exception e) { + throw new ConverterException( + "The result of the conversion wasn't valid FreeMarker 3 template; see cause exception", e); + } + + return new Result(template, outAsString); + } + + private FM2ASTToFM3SourceConverter(String templateName, String src, Configuration fm2Cfg) + throws ConverterException { + template = createTemplate(templateName, src, fm2Cfg); if (template.getParserConfiguration().getWhitespaceStripping()) { throw new IllegalArgumentException("The Template must have been parsed with whitespaceStripping false."); } @@ -106,15 +121,80 @@ public class FM2ASTToFM3SourceConverter { } } - private String getOutput() throws ConverterException { - String s = out.toString(); + /** + * Converts a {@link String} to a {@link Template}. + */ + private static Template createTemplate(String templateName, String src, Configuration fm2Cfg) throws + ConverterException { + if (fm2Cfg.getWhitespaceStripping()) { + throw new IllegalArgumentException("Configuration.whitespaceStripping must be false"); + } try { - //!!T new org.apache.freemarker.core.Template(null, s, fm3Config); + return new Template(templateName, src, fm2Cfg); } catch (Exception e) { - throw new ConverterException( - "The result of the conversion wasn't valid FreeMarker 3 template; see cause exception", e); + throw new ConverterException("Failed to load FreeMarker 2.3.x template", e); } - return s; + } + + // The FTL tag is not part of the AST tree, so we have to treat it differently + private void printDirFtl() throws ConverterException { + int pos = getPositionAfterWSAndExpComments(0); + if (src.length() > pos + 1 && src.charAt(pos) == tagBeginChar && src.startsWith("#ftl", pos + 1)) { + printWithConvertedExpComments(src.substring(0, pos)); + + pos += 5; // "<#ftl".length() + + int tagEnd; + String postFtlTagSkippedWS; + { + int firstNodePos; + + TemplateElement rootNode = template.getRootTreeNode(); + if (rootNode.getBeginLine() != 0) { + firstNodePos = getStartPosition(rootNode); + assertNodeContent(firstNodePos > pos, rootNode, + "Root node position should be after #ftl header"); + } else { + // Extreme case where the template only contains the #ftl header. + assertNodeContent(rootNode.getEndLine() == 0, rootNode, + "Expected 0 end line for root node"); + firstNodePos = src.length(); + } + + tagEnd = firstNodePos - 1; + while (tagEnd >= 0 && src.charAt(tagEnd) != tagEndChar) { + if (!Character.isWhitespace(src.charAt(tagEnd))) { + throw new ConverterException("Non-WS character while backtracking to #ftl tag end character."); + } + tagEnd--; + } + if (tagEnd < 0) { + throw new ConverterException("Couldn't backtrack to #ftl tag end character."); + } + + postFtlTagSkippedWS = src.substring(tagEnd + 1, firstNodePos); + } + + boolean hasSlash = src.charAt(tagEnd - 1) == '/'; + + // We need the Expression-s parsed, but they aren't part of the AST. So, we parse a template that contains + // a similar custom "ftl" directive, and the converter to print it as an #ftl directive. + FM2ASTToFM3SourceConverter customFtlDirSrcConverter = new FM2ASTToFM3SourceConverter( + template.getName(), + tagBeginChar + "@ftl" + src.substring(pos, tagEnd) + (hasSlash ? "" : "/") + tagEndChar, + template.getConfiguration()); + customFtlDirSrcConverter.printNextCustomDirAsFtlDir = true; + String fm3Content = customFtlDirSrcConverter.convert().fm3Content; + print(hasSlash + ? fm3Content + : fm3Content.substring(0, fm3Content.length() - 2) + tagEndChar); + + print(postFtlTagSkippedWS); + } + } + + private String convertFtlHeaderParamName(String name) throws ConverterException { + return name.indexOf('_') == -1 ? name : ConverterUtils.snakeCaseToCamelCase(name); } private void printNode(TemplateObject node) throws ConverterException { @@ -222,6 +302,8 @@ public class FM2ASTToFM3SourceConverter { printDirAssignmentMultiple((AssignmentInstruction) node); } else if (node instanceof AttemptBlock) { printDirAttemptRecover((AttemptBlock) node); + } else if (node instanceof AttemptBlock) { + printDirAttemptRecover((AttemptBlock) node); } else { throw new ConverterException("Unhandled AST TemplateElement class: " + node.getClass().getName()); } @@ -443,8 +525,11 @@ public class FM2ASTToFM3SourceConverter { } private void printDirCustom(UnifiedCall node) throws ConverterException { + boolean ftlDirMode = printNextCustomDirAsFtlDir; + printNextCustomDirAsFtlDir = false; + print(tagBeginChar); - print('@'); + print(ftlDirMode ? '#' : '@'); Expression callee = getParam(node, 0, ParameterRole.CALLEE, Expression.class); printExp(callee); @@ -467,9 +552,19 @@ public class FM2ASTToFM3SourceConverter { // Print named arguments: while (paramIdx < paramCount && node.getParameterRole(paramIdx) == ParameterRole.ARGUMENT_NAME) { + String paramName = getParam(node, paramIdx, ParameterRole.ARGUMENT_NAME, String.class); Expression argValue = getParam(node, paramIdx + 1, ParameterRole.ARGUMENT_VALUE, Expression.class); - printParameterSeparatorSource(lastPrintedExp, argValue); // Prints something like " someArgName=" + int pos = getEndPositionExclusive(lastPrintedExp); + pos = printWSAndExpComments(pos, ",", true); + int paramNameStartPos = pos; + pos = getPositionAfterIdentifier(pos); + assertNodeContent(pos > paramNameStartPos, node, "Parameter name in src was empty"); + if (ftlDirMode) { + paramName = convertFtlHeaderParamName(paramName); + } + print(FTLUtil.escapeIdentifier(paramName)); + printWSAndExpComments(pos, "=", false); printExp(argValue); lastPrintedExp = argValue; @@ -1385,4 +1480,22 @@ public class FM2ASTToFM3SourceConverter { } + public static class Result { + private final Template fm2Template; + private final String fm3Content; + + public Result(Template fm2Template, String fm3Content) { + this.fm2Template = fm2Template; + this.fm3Content = fm3Content; + } + + public Template getFM2Template() { + return fm2Template; + } + + public String getFM3Content() { + return fm3Content; + } + } + } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/70e47bef/freemarker-converter/src/main/java/org/apache/freemarker/converter/FM2ToFM3Converter.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/main/java/org/apache/freemarker/converter/FM2ToFM3Converter.java b/freemarker-converter/src/main/java/org/apache/freemarker/converter/FM2ToFM3Converter.java index e4c3fa5..a70c08e 100644 --- a/freemarker-converter/src/main/java/org/apache/freemarker/converter/FM2ToFM3Converter.java +++ b/freemarker-converter/src/main/java/org/apache/freemarker/converter/FM2ToFM3Converter.java @@ -93,16 +93,11 @@ public class FM2ToFM3Converter extends Converter { @Override protected void convertFile(FileConversionContext fileTransCtx) throws ConverterException, IOException { String src = IOUtils.toString(fileTransCtx.getSourceStream(), StandardCharsets.UTF_8); - Template template; - try { - template = new Template(fileTransCtx.getSourceFile().getName(), src, fm2Cfg); - } catch (Exception e) { - throw new ConverterException("Failed to load FreeMarker 2.3.x template", e); - } - - fileTransCtx.setDestinationFileName(getDestinationFileName(template)); + FM2ASTToFM3SourceConverter.Result result = FM2ASTToFM3SourceConverter.convert(fileTransCtx.getSourceFile() + .getName(), src, fm2Cfg); + fileTransCtx.setDestinationFileName(getDestinationFileName(result.getFM2Template())); fileTransCtx.getDestinationStream().write( - FM2ASTToFM3SourceConverter.convert(template, src).getBytes(getTemplateEncoding(template))); + result.getFM3Content().getBytes(getTemplateEncoding(result.getFM2Template()))); } private String getTemplateEncoding(Template template) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/70e47bef/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java b/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java index 0f74125..675a3c6 100644 --- a/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java +++ b/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java @@ -203,6 +203,13 @@ public class FM2ToFM3ConverterTest extends ConverterTest { assertConvertedSame("<#attempt >a<#recover >r</#attempt >"); assertConverted("<#attempt>a<#recover>r</#attempt>", "<#attempt>a<#recover>r</#recover>"); assertConverted("<#attempt >a<#recover >r</#attempt >", "<#attempt >a<#recover >r</#recover >"); + + assertConvertedSame("<#ftl>"); + assertConvertedSame("<#ftl>x"); + assertConvertedSame("<#ftl>x${x}"); + assertConvertedSame("<#ftl>\nx${x}"); + assertConvertedSame("\n\n <#ftl>\n\nx"); + assertConverted("<#ftl outputFormat='HTML'>x", "<#ftl output_format='HTML'>x"); } @Test
