Author: mrdon Date: Sat Jun 21 03:50:43 2008 New Revision: 670181 URL: http://svn.apache.org/viewvc?rev=670181&view=rev Log: Adding new Struts 2 JST Plugin that renders pages using Trimpath's JST
Added: struts/sandbox/trunk/struts2-jst-plugin/ struts/sandbox/trunk/struts2-jst-plugin/LICENSE.txt struts/sandbox/trunk/struts2-jst-plugin/NOTICE.txt struts/sandbox/trunk/struts2-jst-plugin/README.txt struts/sandbox/trunk/struts2-jst-plugin/pom.xml struts/sandbox/trunk/struts2-jst-plugin/src/ struts/sandbox/trunk/struts2-jst-plugin/src/main/ struts/sandbox/trunk/struts2-jst-plugin/src/main/java/ struts/sandbox/trunk/struts2-jst-plugin/src/main/java/org/ struts/sandbox/trunk/struts2-jst-plugin/src/main/java/org/apache/ struts/sandbox/trunk/struts2-jst-plugin/src/main/java/org/apache/struts2/ struts/sandbox/trunk/struts2-jst-plugin/src/main/java/org/apache/struts2/jst/ struts/sandbox/trunk/struts2-jst-plugin/src/main/java/org/apache/struts2/jst/JSTResult.java struts/sandbox/trunk/struts2-jst-plugin/src/main/resources/ struts/sandbox/trunk/struts2-jst-plugin/src/main/resources/struts-plugin.xml struts/sandbox/trunk/struts2-jst-plugin/src/main/resources/trimpath-template-1.0.38.js struts/sandbox/trunk/struts2-jst-plugin/src/test/ struts/sandbox/trunk/struts2-jst-plugin/src/test/java/ struts/sandbox/trunk/struts2-jst-plugin/src/test/java/org/ struts/sandbox/trunk/struts2-jst-plugin/src/test/java/org/apache/ struts/sandbox/trunk/struts2-jst-plugin/src/test/java/org/apache/struts2/ struts/sandbox/trunk/struts2-jst-plugin/src/test/java/org/apache/struts2/jst/ struts/sandbox/trunk/struts2-jst-plugin/src/test/java/org/apache/struts2/jst/JSTResultTest.java struts/sandbox/trunk/struts2-jst-plugin/src/test/resources/ struts/sandbox/trunk/struts2-jst-plugin/src/test/resources/hello-name.js struts/sandbox/trunk/struts2-jst-plugin/src/test/resources/hello.js Added: struts/sandbox/trunk/struts2-jst-plugin/LICENSE.txt URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jst-plugin/LICENSE.txt?rev=670181&view=auto ============================================================================== --- struts/sandbox/trunk/struts2-jst-plugin/LICENSE.txt (added) +++ struts/sandbox/trunk/struts2-jst-plugin/LICENSE.txt Sat Jun 21 03:50:43 2008 @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. Added: struts/sandbox/trunk/struts2-jst-plugin/NOTICE.txt URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jst-plugin/NOTICE.txt?rev=670181&view=auto ============================================================================== --- struts/sandbox/trunk/struts2-jst-plugin/NOTICE.txt (added) +++ struts/sandbox/trunk/struts2-jst-plugin/NOTICE.txt Sat Jun 21 03:50:43 2008 @@ -0,0 +1,6 @@ +Apache Struts + +Copyright 2006 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/ Added: struts/sandbox/trunk/struts2-jst-plugin/README.txt URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jst-plugin/README.txt?rev=670181&view=auto ============================================================================== --- struts/sandbox/trunk/struts2-jst-plugin/README.txt (added) +++ struts/sandbox/trunk/struts2-jst-plugin/README.txt Sat Jun 21 03:50:43 2008 @@ -0,0 +1,10 @@ +README.txt - plugin + +This is an "empty" Struts 2 plugin that you can use as the basis for a new +plugin. + +For more on Struts plugins, see + +* http://struts.apache.org/2.x/docs/plugins.html + +---------------------------------------------------------------------------- Added: struts/sandbox/trunk/struts2-jst-plugin/pom.xml URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jst-plugin/pom.xml?rev=670181&view=auto ============================================================================== --- struts/sandbox/trunk/struts2-jst-plugin/pom.xml (added) +++ struts/sandbox/trunk/struts2-jst-plugin/pom.xml Sat Jun 21 03:50:43 2008 @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.struts</groupId> + <artifactId>struts2-plugins</artifactId> + <version>2.1.2</version> + </parent> + <artifactId>struts2-jst-plugin</artifactId> + <version>1.0-SNAPSHOT</version> + <name>Struts 2 JST Plugin</name> + + <dependencies> + <dependency> + <groupId>org.apache.struts</groupId> + <artifactId>struts2-core</artifactId> + <version>2.1.2</version> + </dependency> + <dependency> + <groupId>rhino</groupId> + <artifactId>js</artifactId> + <version>1.6R7</version> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-js2j</artifactId> + <version>0.1-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + <version>2.4</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>3.8.1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>mockobjects</groupId> + <artifactId>mockobjects-core</artifactId> + <version>0.09</version> + <scope>test</scope> + </dependency> + + + </dependencies> + + <build> + <defaultGoal>install</defaultGoal> + </build> + +</project> Added: struts/sandbox/trunk/struts2-jst-plugin/src/main/java/org/apache/struts2/jst/JSTResult.java URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jst-plugin/src/main/java/org/apache/struts2/jst/JSTResult.java?rev=670181&view=auto ============================================================================== --- struts/sandbox/trunk/struts2-jst-plugin/src/main/java/org/apache/struts2/jst/JSTResult.java (added) +++ struts/sandbox/trunk/struts2-jst-plugin/src/main/java/org/apache/struts2/jst/JSTResult.java Sat Jun 21 03:50:43 2008 @@ -0,0 +1,218 @@ +/* + * 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.struts2.jst; + +import org.apache.struts2.ServletActionContext; +import org.apache.struts2.StrutsConstants; +import org.apache.struts2.dispatcher.StrutsResultSupport; +import org.apache.commons.js2j.SugarWrapFactory; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.Script; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.Result; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.FileManager; +import com.opensymphony.xwork2.inject.Inject; + +import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletContext; +import java.io.*; +import java.util.HashMap; + + +/** + * Result that uses Trimpath's Javascript Templates to render HTML + */ +public class JSTResult extends StrutsResultSupport { + private ServletContext servletContext; + private ScriptableObject rootScope; + private Script processTemplateScript; + private String contentType = "text/html"; + private String defaultEncoding; + + /** + * Constructs a result and loads the common scripts + * + * @param ctx The servlet context + * @param enc The default encoding for the templates + */ + @Inject + public JSTResult(@Inject ServletContext ctx, + @Inject(StrutsConstants.STRUTS_I18N_ENCODING) String enc) { + this.servletContext = ctx; + this.defaultEncoding = enc; + + // Find the embedded trimpath file + String jst = null; + try { + jst = readStream(getClass().getResourceAsStream("/trimpath-template-1.0.38.js")); + } catch (IOException e) { + throw new RuntimeException("Missing or invalid trimpath template file", e); + } + + // Loads the root context + Context cx = Context.enter(); + try { + rootScope = cx.initStandardObjects(); + + // Load trimpath into the root scope + cx.evaluateString(rootScope, jst, "trimpath", 1, null); + + // Compile the script used for processing templates + processTemplateScript = cx.compileString("var template = TrimPath.parseTemplate(userTemplate, null);\n" + + " if (template != null)\n" + + " template.process(context, null);", "generate-template", 1, null); + } finally { + // Exit from the context. + Context.exit(); + } + } + + protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { + String output = processTemplate(finalLocation, invocation); + HttpServletResponse response = ServletActionContext.getResponse(); + response.setContentType(getContentType()); + Writer writer = response.getWriter(); + writer.write(output); + writer.flush(); + } + + String processTemplate(String finalLocation, ActionInvocation invocation) throws IOException { + String template = readTemplate(finalLocation); + String output = null; + + Context cx = Context.enter(); + try { + + // Create a new scope for this request + Scriptable scope = cx.newObject(rootScope); + scope.setPrototype(rootScope); + scope.setParentScope(null); + + // Use this nice wrap faactory that adds support, among other things, for Maps as if they were Javascript maps + cx.setWrapFactory(new SugarWrapFactory()); + + // Set the user template and context into the scope + Object wrappedOut = Context.javaToJS(template, scope); + ScriptableObject.putProperty(scope, "userTemplate", wrappedOut); + Object wrappedStack = Context.javaToJS(new ValueStackMap(invocation.getStack()), scope); + ScriptableObject.putProperty(scope, "context", wrappedStack); + + // Process the template + Object result = processTemplateScript.exec(cx, scope); + + // Convert the result to a String + output = Context.toString(result); + } finally { + Context.exit(); + } + return output; + } + + /** + * Reads a template from the servlet context, uses traditional file management with optional caching + * + * @param finalLocation The template path, relative to the servlet context + * @return The template as a String + * @throws IOException If the template cannot be read + */ + private String readTemplate(String finalLocation) throws IOException { + InputStream in = FileManager.loadFile(servletContext.getResource(finalLocation)); + return readStream(in); + } + + /** + * Read an input stream into a String + * + * @param in The input stream + * @return The string result + * @throws IOException If the stream cannot be read + */ + private String readStream(InputStream in) throws IOException { + StringBuilder sb = new StringBuilder(); + char[] buffer = new char[1024]; + Reader reader = new BufferedReader(new InputStreamReader(in, getEncoding())); + int len = 0; + while ((len = reader.read(buffer)) > 0) { + sb.append(buffer, 0, len); + } + return sb.toString(); + } + + /** + * Retrieve the encoding for this template. + * + * @return The encoding for loading templates + */ + protected String getEncoding() { + String encoding = defaultEncoding; + if (encoding == null) { + encoding = System.getProperty("file.encoding"); + } + if (encoding == null) { + encoding = "UTF-8"; + } + return encoding; + } + + /** + * @return The content type for the response + */ + public String getContentType() { + return contentType; + } + + /** + * @param contentType The http result content type + */ + public void setContentType(String contentType) { + this.contentType = contentType; + } + + /** + * Wraps the value stack in a Map, so Javascript can access it easily + */ + static class ValueStackMap extends HashMap<String,Object> { + private ValueStack stack; + + public ValueStackMap(ValueStack stack) { + this.stack = stack; + } + + @Override + public Object get(Object o) { + Object result = super.get(o); + if (result == null) + return stack.findValue(o.toString()); + else + return result; + } + + @Override + public boolean containsKey(Object o) { + boolean result = super.containsKey(o); + if (!result) + return stack.findValue(o.toString()) != null; + else + return result; + } + } +} Added: struts/sandbox/trunk/struts2-jst-plugin/src/main/resources/struts-plugin.xml URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jst-plugin/src/main/resources/struts-plugin.xml?rev=670181&view=auto ============================================================================== --- struts/sandbox/trunk/struts2-jst-plugin/src/main/resources/struts-plugin.xml (added) +++ struts/sandbox/trunk/struts2-jst-plugin/src/main/resources/struts-plugin.xml Sat Jun 21 03:50:43 2008 @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<!DOCTYPE struts PUBLIC + "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" + "http://struts.apache.org/dtds/struts-2.0.dtd"> + +<struts> + + <package name="jst-default"> + <result-types> + <result-type name="jst" class="org.apache.struts2.jst.JSTResult"/> + </result-types> + </package> + +</struts> Added: struts/sandbox/trunk/struts2-jst-plugin/src/main/resources/trimpath-template-1.0.38.js URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jst-plugin/src/main/resources/trimpath-template-1.0.38.js?rev=670181&view=auto ============================================================================== --- struts/sandbox/trunk/struts2-jst-plugin/src/main/resources/trimpath-template-1.0.38.js (added) +++ struts/sandbox/trunk/struts2-jst-plugin/src/main/resources/trimpath-template-1.0.38.js Sat Jun 21 03:50:43 2008 @@ -0,0 +1,397 @@ +/** + * TrimPath Template. Release 1.0.38. + * Copyright (C) 2004, 2005 Metaha. + * + * TrimPath Template is licensed under the GNU General Public License + * and the Apache License, Version 2.0, as follows: + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Licensed 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. + */ +var TrimPath; + +// TODO: Debugging mode vs stop-on-error mode - runtime flag. +// TODO: Handle || (or) characters and backslashes. +// TODO: Add more modifiers. + +(function() { // Using a closure to keep global namespace clean. + if (TrimPath == null) + TrimPath = new Object(); + if (TrimPath.evalEx == null) + TrimPath.evalEx = function(src) { return eval(src); }; + + var UNDEFINED; + if (Array.prototype.pop == null) // IE 5.x fix from Igor Poteryaev. + Array.prototype.pop = function() { + if (this.length === 0) {return UNDEFINED;} + return this[--this.length]; + }; + if (Array.prototype.push == null) // IE 5.x fix from Igor Poteryaev. + Array.prototype.push = function() { + for (var i = 0; i < arguments.length; ++i) {this[this.length] = arguments[i];} + return this.length; + }; + + TrimPath.parseTemplate = function(tmplContent, optTmplName, optEtc) { + if (optEtc == null) + optEtc = TrimPath.parseTemplate_etc; + var funcSrc = parse(tmplContent, optTmplName, optEtc); + var func = TrimPath.evalEx(funcSrc, optTmplName, 1); + if (func != null) + return new optEtc.Template(optTmplName, tmplContent, funcSrc, func, optEtc); + return null; + } + + try { + String.prototype.process = function(context, optFlags) { + var template = TrimPath.parseTemplate(this, null); + if (template != null) + return template.process(context, optFlags); + return this; + } + } catch (e) { // Swallow exception, such as when String.prototype is sealed. + } + + TrimPath.parseTemplate_etc = {}; // Exposed for extensibility. + TrimPath.parseTemplate_etc.statementTag = "forelse|for|if|elseif|else|var|macro"; + TrimPath.parseTemplate_etc.statementDef = { // Lookup table for statement tags. + "if" : { delta: 1, prefix: "if (", suffix: ") {", paramMin: 1 }, + "else" : { delta: 0, prefix: "} else {" }, + "elseif" : { delta: 0, prefix: "} else if (", suffix: ") {", paramDefault: "true" }, + "/if" : { delta: -1, prefix: "}" }, + "for" : { delta: 1, paramMin: 3, + prefixFunc : function(stmtParts, state, tmplName, etc) { + if (stmtParts[2] != "in") + throw new etc.ParseError(tmplName, state.line, "bad for loop statement: " + stmtParts.join(' ')); + var iterVar = stmtParts[1]; + var listVar = "__LIST__" + iterVar; + return [ "var ", listVar, " = ", stmtParts[3], ";", + // Fix from Ross Shaull for hash looping, make sure that we have an array of loop lengths to treat like a stack. + "var __LENGTH_STACK__;", + "if (typeof(__LENGTH_STACK__) == 'undefined' || !__LENGTH_STACK__.length) __LENGTH_STACK__ = new Array();", + "__LENGTH_STACK__[__LENGTH_STACK__.length] = 0;", // Push a new for-loop onto the stack of loop lengths. + "if ((", listVar, ") != null) { ", + "var ", iterVar, "_ct = 0;", // iterVar_ct variable, added by B. Bittman + "for (var ", iterVar, "_index in ", listVar, ") { ", + iterVar, "_ct++;", + "if (typeof(", listVar, "[", iterVar, "_index]) == 'function') {continue;}", // IE 5.x fix from Igor Poteryaev. + "__LENGTH_STACK__[__LENGTH_STACK__.length - 1]++;", + "var ", iterVar, " = ", listVar, "[", iterVar, "_index];" ].join(""); + } }, + "forelse" : { delta: 0, prefix: "} } if (__LENGTH_STACK__[__LENGTH_STACK__.length - 1] == 0) { if (", suffix: ") {", paramDefault: "true" }, + "/for" : { delta: -1, prefix: "} }; delete __LENGTH_STACK__[__LENGTH_STACK__.length - 1];" }, // Remove the just-finished for-loop from the stack of loop lengths. + "var" : { delta: 0, prefix: "var ", suffix: ";" }, + "macro" : { delta: 1, + prefixFunc : function(stmtParts, state, tmplName, etc) { + var macroName = stmtParts[1].split('(')[0]; + return [ "var ", macroName, " = function", + stmtParts.slice(1).join(' ').substring(macroName.length), + "{ var _OUT_arr = []; var _OUT = { write: function(m) { if (m) _OUT_arr.push(m); } }; " ].join(''); + } }, + "/macro" : { delta: -1, prefix: " return _OUT_arr.join(''); };" } + } + TrimPath.parseTemplate_etc.modifierDef = { + "eat" : function(v) { return ""; }, + "escape" : function(s) { return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); }, + "capitalize" : function(s) { return String(s).toUpperCase(); }, + "default" : function(s, d) { return s != null ? s : d; } + } + TrimPath.parseTemplate_etc.modifierDef.h = TrimPath.parseTemplate_etc.modifierDef.escape; + + TrimPath.parseTemplate_etc.Template = function(tmplName, tmplContent, funcSrc, func, etc) { + this.process = function(context, flags) { + if (context == null) + context = {}; + if (context._MODIFIERS == null) + context._MODIFIERS = {}; + if (context.defined == null) + context.defined = function(str) { return (context[str] != undefined); }; + for (var k in etc.modifierDef) { + if (context._MODIFIERS[k] == null) + context._MODIFIERS[k] = etc.modifierDef[k]; + } + if (flags == null) + flags = {}; + var resultArr = []; + var resultOut = { write: function(m) { resultArr.push(m); } }; + try { + func(resultOut, context, flags); + } catch (e) { + if (flags.throwExceptions == true) + throw e; + var result = new String(resultArr.join("") + "[ERROR: " + e.toString() + (e.message ? '; ' + e.message : '') + "]"); + result["exception"] = e; + return result; + } + return resultArr.join(""); + } + this.name = tmplName; + this.source = tmplContent; + this.sourceFunc = funcSrc; + this.toString = function() { return "TrimPath.Template [" + tmplName + "]"; } + } + TrimPath.parseTemplate_etc.ParseError = function(name, line, message) { + this.name = name; + this.line = line; + this.message = message; + } + TrimPath.parseTemplate_etc.ParseError.prototype.toString = function() { + return ("TrimPath template ParseError in " + this.name + ": line " + this.line + ", " + this.message); + } + + var parse = function(body, tmplName, etc) { + body = cleanWhiteSpace(body); + var funcText = [ "var TrimPath_Template_TEMP = function(_OUT, _CONTEXT, _FLAGS) { with (_CONTEXT) {" ]; + var state = { stack: [], line: 1 }; // TODO: Fix line number counting. + var endStmtPrev = -1; + while (endStmtPrev + 1 < body.length) { + var begStmt = endStmtPrev; + // Scan until we find some statement markup. + begStmt = body.indexOf("{", begStmt + 1); + while (begStmt >= 0) { + var endStmt = body.indexOf('}', begStmt + 1); + var stmt = body.substring(begStmt, endStmt); + var blockrx = stmt.match(/^\{(cdata|minify|eval)/); // From B. Bittman, minify/eval/cdata implementation. + if (blockrx) { + var blockType = blockrx[1]; + var blockMarkerBeg = begStmt + blockType.length + 1; + var blockMarkerEnd = body.indexOf('}', blockMarkerBeg); + if (blockMarkerEnd >= 0) { + var blockMarker; + if( blockMarkerEnd - blockMarkerBeg <= 0 ) { + blockMarker = "{/" + blockType + "}"; + } else { + blockMarker = body.substring(blockMarkerBeg + 1, blockMarkerEnd); + } + + var blockEnd = body.indexOf(blockMarker, blockMarkerEnd + 1); + if (blockEnd >= 0) { + emitSectionText(body.substring(endStmtPrev + 1, begStmt), funcText); + + var blockText = body.substring(blockMarkerEnd + 1, blockEnd); + if (blockType == 'cdata') { + emitText(blockText, funcText); + } else if (blockType == 'minify') { + emitText(scrubWhiteSpace(blockText), funcText); + } else if (blockType == 'eval') { + if (blockText != null && blockText.length > 0) // From B. Bittman, eval should not execute until process(). + funcText.push('_OUT.write( (function() { ' + blockText + ' })() );'); + } + begStmt = endStmtPrev = blockEnd + blockMarker.length - 1; + } + } + } else if (body.charAt(begStmt - 1) != '$' && // Not an expression or backslashed, + body.charAt(begStmt - 1) != '\\') { // so check if it is a statement tag. + var offset = (body.charAt(begStmt + 1) == '/' ? 2 : 1); // Close tags offset of 2 skips '/'. + // 10 is larger than maximum statement tag length. + if (body.substring(begStmt + offset, begStmt + 10 + offset).search(TrimPath.parseTemplate_etc.statementTag) == 0) + break; // Found a match. + } + begStmt = body.indexOf("{", begStmt + 1); + } + if (begStmt < 0) // In "a{for}c", begStmt will be 1. + break; + var endStmt = body.indexOf("}", begStmt + 1); // In "a{for}c", endStmt will be 5. + if (endStmt < 0) + break; + emitSectionText(body.substring(endStmtPrev + 1, begStmt), funcText); + emitStatement(body.substring(begStmt, endStmt + 1), state, funcText, tmplName, etc); + endStmtPrev = endStmt; + } + emitSectionText(body.substring(endStmtPrev + 1), funcText); + if (state.stack.length != 0) + throw new etc.ParseError(tmplName, state.line, "unclosed, unmatched statement(s): " + state.stack.join(",")); + funcText.push("}}; TrimPath_Template_TEMP"); + return funcText.join(""); + } + + var emitStatement = function(stmtStr, state, funcText, tmplName, etc) { + var parts = stmtStr.slice(1, -1).split(' '); + var stmt = etc.statementDef[parts[0]]; // Here, parts[0] == for/if/else/... + if (stmt == null) { // Not a real statement. + emitSectionText(stmtStr, funcText); + return; + } + if (stmt.delta < 0) { + if (state.stack.length <= 0) + throw new etc.ParseError(tmplName, state.line, "close tag does not match any previous statement: " + stmtStr); + state.stack.pop(); + } + if (stmt.delta > 0) + state.stack.push(stmtStr); + + if (stmt.paramMin != null && + stmt.paramMin >= parts.length) + throw new etc.ParseError(tmplName, state.line, "statement needs more parameters: " + stmtStr); + if (stmt.prefixFunc != null) + funcText.push(stmt.prefixFunc(parts, state, tmplName, etc)); + else + funcText.push(stmt.prefix); + if (stmt.suffix != null) { + if (parts.length <= 1) { + if (stmt.paramDefault != null) + funcText.push(stmt.paramDefault); + } else { + for (var i = 1; i < parts.length; i++) { + if (i > 1) + funcText.push(' '); + funcText.push(parts[i]); + } + } + funcText.push(stmt.suffix); + } + } + + var emitSectionText = function(text, funcText) { + if (text.length <= 0) + return; + var nlPrefix = 0; // Index to first non-newline in prefix. + var nlSuffix = text.length - 1; // Index to first non-space/tab in suffix. + while (nlPrefix < text.length && (text.charAt(nlPrefix) == '\n')) + nlPrefix++; + while (nlSuffix >= 0 && (text.charAt(nlSuffix) == ' ' || text.charAt(nlSuffix) == '\t')) + nlSuffix--; + if (nlSuffix < nlPrefix) + nlSuffix = nlPrefix; + if (nlPrefix > 0) { + funcText.push('if (_FLAGS.keepWhitespace == true) _OUT.write("'); + var s = text.substring(0, nlPrefix).replace('\n', '\\n'); // A macro IE fix from BJessen. + if (s.charAt(s.length - 1) == '\n') + s = s.substring(0, s.length - 1); + funcText.push(s); + funcText.push('");'); + } + var lines = text.substring(nlPrefix, nlSuffix + 1).split('\n'); + for (var i = 0; i < lines.length; i++) { + emitSectionTextLine(lines[i], funcText); + if (i < lines.length - 1) + funcText.push('_OUT.write("\\n");\n'); + } + if (nlSuffix + 1 < text.length) { + funcText.push('if (_FLAGS.keepWhitespace == true) _OUT.write("'); + var s = text.substring(nlSuffix + 1).replace('\n', '\\n'); + if (s.charAt(s.length - 1) == '\n') + s = s.substring(0, s.length - 1); + funcText.push(s); + funcText.push('");'); + } + } + + var emitSectionTextLine = function(line, funcText) { + var endMarkPrev = '}'; + var endExprPrev = -1; + while (endExprPrev + endMarkPrev.length < line.length) { + var begMark = "${", endMark = "}"; + var begExpr = line.indexOf(begMark, endExprPrev + endMarkPrev.length); // In "a${b}c", begExpr == 1 + if (begExpr < 0) + break; + if (line.charAt(begExpr + 2) == '%') { + begMark = "${%"; + endMark = "%}"; + } + var endExpr = line.indexOf(endMark, begExpr + begMark.length); // In "a${b}c", endExpr == 4; + if (endExpr < 0) + break; + emitText(line.substring(endExprPrev + endMarkPrev.length, begExpr), funcText); + // Example: exprs == 'firstName|default:"John Doe"|capitalize'.split('|') + var exprArr = line.substring(begExpr + begMark.length, endExpr).replace(/\|\|/g, "#@@#").split('|'); + for (var k in exprArr) { + if (exprArr[k].replace) // IE 5.x fix from Igor Poteryaev. + exprArr[k] = exprArr[k].replace(/#@@#/g, '||'); + } + funcText.push('_OUT.write('); + emitExpression(exprArr, exprArr.length - 1, funcText); + funcText.push(');'); + endExprPrev = endExpr; + endMarkPrev = endMark; + } + emitText(line.substring(endExprPrev + endMarkPrev.length), funcText); + } + + var emitText = function(text, funcText) { + if (text == null || + text.length <= 0) + return; + text = text.replace(/\\/g, '\\\\'); + text = text.replace(/\n/g, '\\n'); + text = text.replace(/"/g, '\\"'); + funcText.push('_OUT.write("'); + funcText.push(text); + funcText.push('");'); + } + + var emitExpression = function(exprArr, index, funcText) { + // Ex: foo|a:x|b:y1,y2|c:z1,z2 is emitted as c(b(a(foo,x),y1,y2),z1,z2) + var expr = exprArr[index]; // Ex: exprArr == [firstName,capitalize,default:"John Doe"] + if (index <= 0) { // Ex: expr == 'default:"John Doe"' + funcText.push(expr); + return; + } + var parts = expr.split(':'); + funcText.push('_MODIFIERS["'); + funcText.push(parts[0]); // The parts[0] is a modifier function name, like capitalize. + funcText.push('"]('); + emitExpression(exprArr, index - 1, funcText); + if (parts.length > 1) { + funcText.push(','); + funcText.push(parts[1]); + } + funcText.push(')'); + } + + var cleanWhiteSpace = function(result) { + result = result.replace(/\t/g, " "); + result = result.replace(/\r\n/g, "\n"); + result = result.replace(/\r/g, "\n"); + result = result.replace(/^(\s*\S*(\s+\S+)*)\s*$/, '$1'); // Right trim by Igor Poteryaev. + return result; + } + + var scrubWhiteSpace = function(result) { + result = result.replace(/^\s+/g, ""); + result = result.replace(/\s+$/g, ""); + result = result.replace(/\s+/g, " "); + result = result.replace(/^(\s*\S*(\s+\S+)*)\s*$/, '$1'); // Right trim by Igor Poteryaev. + return result; + } + + // The DOM helper functions depend on DOM/DHTML, so they only work in a browser. + // However, these are not considered core to the engine. + // + TrimPath.parseDOMTemplate = function(elementId, optDocument, optEtc) { + if (optDocument == null) + optDocument = document; + var element = optDocument.getElementById(elementId); + var content = element.value; // Like textarea.value. + if (content == null) + content = element.innerHTML; // Like textarea.innerHTML. + content = content.replace(/</g, "<").replace(/>/g, ">"); + return TrimPath.parseTemplate(content, elementId, optEtc); + } + + TrimPath.processDOMTemplate = function(elementId, context, optFlags, optDocument, optEtc) { + return TrimPath.parseDOMTemplate(elementId, optDocument, optEtc).process(context, optFlags); + } +}) (); Added: struts/sandbox/trunk/struts2-jst-plugin/src/test/java/org/apache/struts2/jst/JSTResultTest.java URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jst-plugin/src/test/java/org/apache/struts2/jst/JSTResultTest.java?rev=670181&view=auto ============================================================================== --- struts/sandbox/trunk/struts2-jst-plugin/src/test/java/org/apache/struts2/jst/JSTResultTest.java (added) +++ struts/sandbox/trunk/struts2-jst-plugin/src/test/java/org/apache/struts2/jst/JSTResultTest.java Sat Jun 21 03:50:43 2008 @@ -0,0 +1,67 @@ +/* + * 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.struts2.jst; + +import junit.framework.TestCase; +import com.mockobjects.dynamic.Mock; +import com.mockobjects.dynamic.C; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.util.ValueStack; + +import javax.servlet.ServletContext; +import java.io.IOException; +import java.util.Map; +import java.util.Collections; + +public class JSTResultTest extends TestCase { + + public void testResult() throws Exception { + + testTemplate("/hello.js", "hello"); + testTemplate("/hello-name.js", "hello bob", Collections.<String, Object>singletonMap("name", "bob")); + } + + private void testTemplate(String templatePath, String response) throws IOException { + testTemplate(templatePath, response, Collections.<String, Object>emptyMap()); + } + + private void testTemplate(String templatePath, String response, Map<String,Object> context) throws IOException { + Mock mockServletContext = new Mock(ServletContext.class); + Mock mockActionInvocation = new Mock(ActionInvocation.class); + Mock mockValueStack = new Mock(ValueStack.class); + + + JSTResult result = new JSTResult((ServletContext) mockServletContext.proxy(), null); + + mockValueStack.matchAndReturn("findValue", C.args(C.eq("_MODIFIERS")), null); + mockValueStack.matchAndReturn("findValue", C.args(C.eq("defined")), null); + mockValueStack.matchAndReturn("findValue", C.args(C.eq("_OUT")), null); + for (Map.Entry<String,Object> entry : context.entrySet()) { + mockValueStack.expectAndReturn("findValue", C.args(C.eq(entry.getKey())), entry.getValue()); + mockValueStack.expectAndReturn("findValue", C.args(C.eq(entry.getKey())), entry.getValue()); + } + mockActionInvocation.expectAndReturn("getStack", mockValueStack.proxy()); + + mockServletContext.expectAndReturn("getResource", C.args(C.eq(templatePath)), getClass().getResource(templatePath)); + String output = result.processTemplate(templatePath, (ActionInvocation) mockActionInvocation.proxy()); + assertEquals(response, output); + mockServletContext.verify(); + } + +} Added: struts/sandbox/trunk/struts2-jst-plugin/src/test/resources/hello-name.js URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jst-plugin/src/test/resources/hello-name.js?rev=670181&view=auto ============================================================================== --- struts/sandbox/trunk/struts2-jst-plugin/src/test/resources/hello-name.js (added) +++ struts/sandbox/trunk/struts2-jst-plugin/src/test/resources/hello-name.js Sat Jun 21 03:50:43 2008 @@ -0,0 +1 @@ +hello ${name} \ No newline at end of file Added: struts/sandbox/trunk/struts2-jst-plugin/src/test/resources/hello.js URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jst-plugin/src/test/resources/hello.js?rev=670181&view=auto ============================================================================== --- struts/sandbox/trunk/struts2-jst-plugin/src/test/resources/hello.js (added) +++ struts/sandbox/trunk/struts2-jst-plugin/src/test/resources/hello.js Sat Jun 21 03:50:43 2008 @@ -0,0 +1 @@ +hello \ No newline at end of file