Skip to content

Instantly share code, notes, and snippets.

@ndemengel
Created November 13, 2016 19:59
Show Gist options
  • Save ndemengel/13130b50ffbd9cbe6c1da335c1ad026b to your computer and use it in GitHub Desktop.
Save ndemengel/13130b50ffbd9cbe6c1da335c1ad026b to your computer and use it in GitHub Desktop.
JSP pseudo-interpretation in JS
<p>should be shown 1</p>
<p>should be shown 2</p>
<p>should be shown 3</p>
<form modelAttribute="formModel">
<input type="number" value="form model member value" id="formModelMember" name="formModelMember" data-plop="name 1"/>
<input type="date" value="toto">
<input type="number" value="42">
<input type="number" value="form model member value" id="formModelMember" name="formModelMember" data-plop="name 2"/>
<input type="date" value="toto">
<input type="number" value="42">
notavar
A VALUE TO &amp;SCAPE
<!-- unsupported JSP tag removed: <un:supported jsp="tag"> --><!-- unsupported JSP tag removed: </un:supported> -->
<a title="i18n_message.code" href="#">Link</a>
<!-- @include sibling1: -->
<p>Text from sibling.jsp with some toto</p>
<dl>
<dt>Param 1</dt><dd>static</dd>
<dt>Param 2</dt><dd>dynamic, resolved</dd>
</dl>
<p>anewvar: anoldvalue</p>
<p>multilineSet: foo
bar</p>
<p>escapedSet: A VALUE TO &amp;SCAPE</p>
<!-- jsp:include sibling1: -->
<p>Text from sibling.jsp with some toto</p>
<dl>
<dt>Param 1</dt><dd></dd>
<dt>Param 2</dt><dd></dd>
</dl>
<p>I support "empty"!</p>
<p>I support "not empty"!</p>
<p>I support "or" too!</p>
<input type="radio" name="someRadioButtonPath" value="1" id="someRadioButtonPath1"/>
<span>1</span>
<span>2</span>
<span>3</span>
<p>i18n_message.with.args,arg1,arg2</p>
<span>fooINCLUDEDVAR</span>
<span>INCLUDEDVARbar</span>
<span>fooINCLUDEDVARbar</span>
</form>
'use strict';
const fs = require('fs');
const path = require('path');
const expect = require('./').expect;
const jsp = require('./jsp');
describe('Jsp Translator', () => {
describe('<c:choose>', () => {
it('should work with only a "when" clause', () => {
// given
let attributes = {someCondition: true};
const template = '<c:choose>' +
'<c:when test="${someCondition}">' +
'it is true' +
'</c:when>' +
'</c:choose>';
// then
expect(jsp.resolve(template, attributes)).to.equal('it is true');
// and given
attributes = {someCondition: false};
// then
expect(jsp.resolve(template, attributes)).to.equal('');
});
it('should work with 1 "when" clause and 1 "otherwise" clause', () => {
// given
let attributes = {someCondition: true};
const template = '<c:choose>' +
'<c:when test="${someCondition}">' +
'it is true' +
'</c:when>' +
'<c:otherwise>' +
'it is false' +
'</c:otherwise>' +
'</c:choose>';
// then
expect(jsp.resolve(template, attributes)).to.equal('it is true');
// and given
attributes = {someCondition: false};
// then
expect(jsp.resolve(template, attributes)).to.equal('it is false');
});
it('should work with several "when" clauses and 1 "otherwise" clause', () => {
// given
let attributes = {someNumber: 1};
const template = '<c:choose>' +
'<c:when test="${someNumber == 1}">' +
'one' +
'</c:when>' +
'<c:when test="${someNumber == 2}">' +
'two' +
'</c:when>' +
'<c:otherwise>' +
'other' +
'</c:otherwise>' +
'</c:choose>';
// then
expect(jsp.resolve(template, attributes)).to.equal('one');
// and given
attributes = {someNumber: 2};
// then
expect(jsp.resolve(template, attributes)).to.equal('two');
// and given
attributes = {someNumber: 42};
// then
expect(jsp.resolve(template, attributes)).to.equal('other');
});
it('should work with string representation of "true"', () => {
// given
let attributes = {};
const template = '<c:choose>' +
'<c:when test="true">' +
'it is true' +
'</c:when>' +
'<c:otherwise>' +
'it is false' +
'</c:otherwise>' +
'</c:choose>';
// then
expect(jsp.resolve(template, attributes)).to.equal('it is true');
});
it('should work with string representation of "false"', () => {
// given
let attributes = {};
const template = '<c:choose>' +
'<c:when test="false">' +
'it is true' +
'</c:when>' +
'<c:otherwise>' +
'it is false' +
'</c:otherwise>' +
'</c:choose>';
// then
expect(jsp.resolve(template, attributes)).to.equal('it is false');
});
});
describe('<c:forEach>', () => {
it('should loop over an array of items', () => {
// given
const attributes = {
someItems: [
{prop1: 'a', prop2: 'b'},
{prop1: 'c', prop2: 'd'},
{prop1: 'e', prop2: 'f'}
]
};
const template = '<c:forEach items="${someItems}" var="thing">' +
'<li>${thing.prop1} - ${thing.prop2}</li>' +
'</c:forEach>';
// then
expect(jsp.resolve(template, attributes)).to.equal(
'<li>a - b</li>' +
'<li>c - d</li>' +
'<li>e - f</li>'
);
});
it('should loop over a range of numbers', () => {
// given
const attributes = {};
const template = '<c:forEach begin="2" end="5" var="num">' +
'<li>${num}</li>' +
'</c:forEach>';
// then
expect(jsp.resolve(template, attributes)).to.equal(
'<li>2</li>' +
'<li>3</li>' +
'<li>4</li>' +
'<li>5</li>'
);
});
it('should provide looping status for an array', () => {
// given
const attributes = {
someItems: ['A', 'B', 'C']
};
const template = '<c:forEach items="${someItems}" var="el" varStatus="loop">' +
'[${el},${loop.index},${loop.first},${loop.last}]' +
'</c:forEach>';
// then
expect(jsp.resolve(template, attributes)).to.equal(
'[A,0,true,false]' +
'[B,1,false,false]' +
'[C,2,false,true]'
);
});
it('should provide looping status for a range', () => {
// given
const attributes = {
someItems: ['A', 'B', 'C']
};
const template = '<c:forEach begin="100" end="102" var="el" varStatus="loop">' +
'[${el},${loop.index},${loop.first},${loop.last}]' +
'</c:forEach>';
// then
expect(jsp.resolve(template, attributes)).to.equal(
'[100,0,true,false]' +
'[101,1,false,false]' +
'[102,2,false,true]'
);
});
});
describe('<c:if>', () => {
it('should work with a simple condition', () => {
// given
let attributes = {someCondition: true};
const template = '<c:if test="${!someCondition}">' +
'it is true' +
'</c:if>';
// then
expect(jsp.resolve(template, attributes)).to.equal('');
// and given
attributes = {someCondition: false};
// then
expect(jsp.resolve(template, attributes)).to.equal('it is true');
});
it('should work with string representation of "true"', () => {
// given
let attributes = {};
const template = '<c:if test="true">' +
'it is true' +
'</c:if>';
// then
expect(jsp.resolve(template, attributes)).to.equal('it is true');
});
it('should work with string representation of "false"', () => {
// given
let attributes = {};
const template = '<c:if test="false">' +
'it is true' +
'</c:if>';
// then
expect(jsp.resolve(template, attributes)).to.equal('');
});
});
describe('<c:out>', () => {
it('should escape HTML in given value attribute', () => {
// given
let attributes = {someHtml: '<p>not a paragraph</p>'};
const template = '<c:out value="${someHtml}"/>';
// then
expect(jsp.resolve(template, attributes)).to.equal('&lt;p&gt;not a paragraph&lt;/p&gt;');
});
});
describe('<c:set>', () => {
it('should set a variable to a static value', () => {
// given
const attributes = {};
const template = '<c:set var="someVar" value="someValue"/>' +
'${someVar}';
// then
expect(jsp.resolve(template, attributes)).to.equal('someValue');
});
it('should set a variable to a dynamic value', () => {
// given
const attributes = {someVar: 'foo'};
const template = '<c:set var="someOtherVar" value="A${someVar}B"/>' +
'${someOtherVar}';
// then
expect(jsp.resolve(template, attributes)).to.equal('AfooB');
});
it('should set a variable using element content', () => {
// given
const attributes = {foo: 'bar'};
const template = `<c:set var="someVar">
A
\${foo}
B
</c:set>
\${someVar}`;
// then
expect(jsp.resolve(template, attributes)).to.equal(`
A
bar
B`);
});
});
// and so on... Skipped: tests for form:*, jsp:* spring:*
it('should translate a big soup of JSP tags', () => {
const attributes = {
bar: 'toto',
formModel: {
formModelMember: 'form model member value'
},
myVar: 'someVal',
myVar2: 'someOtherVal',
things: [
{name: 'name 1'},
{name: 'name 2'}
],
avar: 'A VALUE TO &SCAPE',
anoldvar: 'anoldvalue',
emptyVar: null,
emptyVar2: '',
nonEmptyVar: "I'm not empty",
radioPath: "someRadioButtonPath",
one: 1
};
const jspPath = path.resolve(__dirname, './main.jsp');
const expectedHtml = fs.readFileSync(path.resolve(__dirname, 'expected.html')).toString();
expect(jsp.resolveFile(jspPath, attributes)).to.equal(expectedHtml);
});
});
'use strict';
const fs = require('fs');
const path = require('path');
const _ = require('lodash');
function unwrapElExpression(value) {
if (value === null || value === undefined) {
return '';
}
if (value.indexOf('${') !== 0) {
if (value.indexOf('}') !== value.length - 1) {
return value.replace(/^(.*?)\$\{([^}]*?)}([^}]*?)$/, '"$1" + $2 + "$3"');
}
return value.replace(/^(.*?)\$\{([^}]*?)}$/, '"$1" + $2');
}
if (value.indexOf('}') !== value.length - 1) {
return value.replace(/^\$\{([^}]*?)}([^}]*?)$/, '$1 + "$2"');
}
return value.replace(/^\$\{([^}]*?)}$/, '$1');
}
function resolve(jsp, attributes, context) {
context = context || {jspDir: __dirname};
const ATTRIBUTE = /(?:\s+?([\w-]+?=".*?"))/g;
const EL_EXPRESSION = /\$\{([^}]*?)\}/g;
const FORM_INPUT = /(<form:\w+\s+.*?\b)path=".*?"(.*?\/?>)/g;
const FORM = /(<\/?)form:/g;
const INCLUDE_DIRECTIVE = /<%@\s*include\s+file="(.*?)"\s*%>/g;
const JSP_DIRECTIVE_OR_COMMENT = /<%(@|--).*?(--)?%>/g;
const JSP_TAG = /<\/?(\w+:\w+)(\s+[^>]*?)?\s*\/?>/g;
function readAttributes(str) {
const attrs = {};
let res;
while ((res = ATTRIBUTE.exec(str)) !== null) {
const kv = res[1].split('=');
const key = kv[0];
let value = kv.slice(1).join('=');
if (value) {
value = value.replace(/^"(.*?)"$/, '$1');
attrs[key] = replaceElOperators(value);
} else {
attrs[key] = null;
}
}
return attrs;
}
function replaceElOperators(jsp) {
let res;
const buf = [];
let idx = 0;
while ((res = EL_EXPRESSION.exec(jsp)) !== null) {
buf.push(jsp.substring(idx, res.index));
buf.push('${', res[1].replace(/\bnot\s+empty\s+/g, '!!')
.replace(/\bempty\s+/g, '!')
.replace(/\s+and\s+/, ' && ')
.replace(/\s+or\s+/, ' || '), '}');
idx = EL_EXPRESSION.lastIndex;
}
buf.push(jsp.substring(idx));
return buf.join('');
}
function replaceFormInputs(jsp) {
let res;
const buf = [];
let idx = 0;
while ((res = FORM_INPUT.exec(jsp)) !== null) {
buf.push(jsp.substring(idx, res.index));
buf.push(res[1]);
const attrs = readAttributes(res[0]);
if (!attrs.id) {
buf.push(' id="', attrs.path, '"');
}
if (!attrs.name) {
buf.push(' name="', attrs.path, '"');
}
buf.push(res[2]);
idx = FORM_INPUT.lastIndex;
}
buf.push(jsp.substring(idx));
return buf.join('');
}
function resolveInclude(file, attrs) {
if ((context.ignoredInclusions || []).indexOf(file) !== -1) {
return '';
}
let jspDir = context.jspDir;
if (!path.isAbsolute(file)) {
const fileRelDir = path.dirname(context.jspFile.substr(context.jspDir.length + 1));
if (fileRelDir) {
jspDir = path.join(jspDir, fileRelDir);
}
}
if (file.startsWith('/WEB-INF/jsp/')) {
jspDir = context.jspRoot;
file = file.substr('/WEB-INF/jsp/'.length);
}
const filePath = path.resolve(jspDir, file);
return resolveFile(filePath, attrs, {
jspRoot: context.jspRoot,
ignoredInclusions: context.ignoredInclusions
});
}
function replaceJspTags(jsp) {
let res;
let buf = [];
let bufBackup;
let idx = 0;
let currentJspParams, currentJspPageToInclude, currentChoose, currentModelAttribute, currentSpringArgs;
function jspInclude() {
const inclusionAttrs = _.assign({
param: currentJspParams
}, attributes);
const str = resolveInclude(currentJspPageToInclude, inclusionAttrs);
currentJspPageToInclude = null;
currentJspParams = null;
return str;
}
const STRATEGIES = {
DEFAULT: {
open: (buf, res) => {
buf.push('<!-- unsupported JSP tag removed: ', res[0], ' -->');
},
close: (buf, res) => {
buf.push('<!-- unsupported JSP tag removed: ', res[0], ' -->');
}
},
'c:choose': {
open: () => {
currentChoose = {};
},
close: () => {
buf.push('<% } %>');
}
},
'c:otherwise': {
open: buf => {
buf.push('<% } else { %>');
},
close: _buf => {
}
},
'c:when': {
open: (buf, res, attrs) => {
if (currentChoose.firstWhenPassed) {
buf.push('<% } else if (', unwrapElExpression(attrs.test), ') { %>');
} else {
buf.push('<% if (', unwrapElExpression(attrs.test), ') { %>');
}
currentChoose.firstWhenPassed = true;
},
close: _buf => {
}
},
'c:forEach': {
open: (buf, res, attrs) => {
let items;
if (attrs.items) {
items = unwrapElExpression(attrs.items);
} else {
items = '_.range(' + unwrapElExpression(attrs.begin) + ', ' + unwrapElExpression(attrs.end) + ' + 1)';
}
if (attrs.varStatus) {
buf.push('<% var _forEachItems = ', items, '; ',
'var ', attrs.varStatus, ' = { begin: 0, end: _forEachItems.length - 1 }; ',
'_forEachItems.forEach(function (', attrs.var, ', _forEachIdx) { ',
attrs.varStatus, '.index = _forEachIdx;',
attrs.varStatus, '.first = _forEachIdx === 0;',
attrs.varStatus, '.last = _forEachIdx === _forEachItems.length - 1;',
'%>');
} else {
buf.push('<% ', items, '.forEach(function (', attrs.var, ') { %>');
}
},
close: buf => {
buf.push('<% }); %>');
}
},
'c:if': {
open: (buf, res, attrs) => {
buf.push('<% if (', unwrapElExpression(attrs.test), ') { %>');
},
close: buf => {
buf.push('<% } %>');
}
},
'c:set': {
mayReadContent: true,
open: (buf, res, attrs) => {
if (typeof attrs.value === 'string') {
if (attrs.value.indexOf('${') !== -1) {
buf.push('<% var ', attrs.var, ' = ', unwrapElExpression(attrs.value), '; %>');
} else {
buf.push('<% var ', attrs.var, ' = "', attrs.value, '"; %>');
}
} else {
buf.push('<% var ', attrs.var, ' = ');
}
},
close: (buf, res, setContent) => {
if (!setContent) {
return;
}
setContent = setContent.trim();
if (setContent.indexOf('${') !== -1) {
if (setContent.indexOf('${') !== 0) {
if (setContent.indexOf('}') !== setContent.length - 1) {
buf.push(setContent.replace(/^([\S\s]*?)\$\{([^}]*?)}([^}]*)$/m, '`$1` + $2 + `$3`'));
} else {
buf.push(setContent.replace(/^([\S\s]*?)\$\{([^}]*)}$/m, '`$1` + $2'));
}
}
else if (setContent.indexOf('}') !== setContent.length - 1) {
buf.push(setContent.replace(/^\$\{([^}]*?)}([^}]*)$/m, '$1 + `$2`'));
} else {
buf.push(setContent.replace(/^\$\{([^}]*)}$/m, '$1'));
}
buf.push('; %>');
} else {
let match = setContent.match(/^<%-\s*(.*?)\s*%>$/);
if (match) {
buf.push('_.escape(', match[1], '); %>');
} else {
match = setContent.match(/^<%=?\s*(.*?)\s*%>$/);
if (match) {
buf.push(match[1], '; %>');
} else {
buf.push('"', setContent.replace(/\n/g, '\\n').replace(/\r/g, '\\r'), '"; %>');
}
}
}
}
},
'c:out': {
open: (buf, res, attrs) => {
if (attrs.value.indexOf('${') === 0) {
buf.push('<%- ', unwrapElExpression(attrs.value), ' %>');
} else {
buf.push(attrs.value);
}
}
},
'form:form': {
open: (buf, res, attrs) => {
if (attrs.modelAttribute) {
currentModelAttribute = attrs.modelAttribute;
}
// handled later
buf.push(res[0]);
},
close: (buf, res) => {
currentModelAttribute = null;
// handled later
buf.push(res[0]);
}
},
'form:input': {
open: (buf, res, attrs) => {
if (attrs.path) {
let path = attrs.path;
if (currentModelAttribute) {
if (path.indexOf('${') === 0) {
path = currentModelAttribute + '[' + unwrapElExpression(path) + ']';
} else {
path = currentModelAttribute + '.' + path;
}
}
let value = attrs.value;
if (value) {
buf.push(res[0]);
} else {
if (path.indexOf('${') === 0) {
value = ''; // not implemented, too complex
} else {
value = '${' + path + '}';
}
buf.push(res[0].replace(/\bpath=/, 'value="' + value + '" path='));
}
}
else {
// handled later
buf.push(res[0]);
}
},
close: (buf, res) => {
// handled later
buf.push(res[0]);
}
},
'form:radiobutton': {
open: (buf, res, attrs) => {
res[0] = res[0].replace('radiobutton', 'input type="radio"');
STRATEGIES['form:input'].open(buf, res, attrs);
},
close: (buf, res) => {
STRATEGIES['form:input'].close(buf, res);
}
},
'jsp:include': {
open: (buf, res, attrs, selfClosing) => {
currentJspPageToInclude = attrs.page;
currentJspParams = {};
if (selfClosing) {
buf.push(jspInclude());
}
},
close: buf => {
buf.push(jspInclude());
}
},
'jsp:param': {
mayReadContent: true,
open: (buf, res, attrs) => {
currentJspParams[attrs.name] = attrs.value;
}
},
'spring:argument': {
mayReadContent: true,
open: (buf, res, attrs) => {
if (typeof attrs.value !== 'undefined') {
currentSpringArgs.push(attrs.value);
}
},
close: (buf, res, argContent) => {
currentSpringArgs.push(argContent);
}
},
'spring:message': (function () {
let messageCode;
let messageVar;
function writeResult(args, buf) {
args = args ? ',' + args : '';
if (messageVar) {
if (messageCode.indexOf('${') !== -1) {
messageCode = unwrapElExpression(messageCode);
buf.push('<% var ', messageVar, ' = "i18n_" + ', messageCode, ' + "' + args + '"; %>');
} else {
buf.push('<% var ', messageVar, ' = "i18n_', messageCode, args + '"; %>');
}
} else {
if (messageCode.indexOf('${') !== -1) {
messageCode = unwrapElExpression(messageCode);
buf.push('<%= "i18n_" + ', messageCode, ' + "' + args + '" %>');
} else {
buf.push('<%= "i18n_', messageCode, args + '" %>');
}
}
}
return {
open: (buf, res, attrs, selfClosing) => {
messageCode = attrs.code;
messageVar = attrs.var;
currentSpringArgs = [];
if (selfClosing) {
writeResult(attrs.arguments, buf);
}
},
close: buf => {
writeResult(currentSpringArgs.join(','), buf);
}
};
})()
};
while ((res = JSP_TAG.exec(jsp)) !== null) {
buf.push(jsp.substring(idx, res.index));
const tag = res[0];
const tagName = res[1];
const closing = tag.indexOf('</') === 0;
if (!closing) {
const selfClosing = tag.indexOf('/>') === tag.length - 2;
const attrs = readAttributes(tag);
const strategy = STRATEGIES[tagName] || STRATEGIES.DEFAULT;
const open = strategy.open || STRATEGIES.DEFAULT.open;
open(buf, res, attrs, selfClosing);
if (strategy.mayReadContent && !selfClosing) {
bufBackup = buf;
buf = [];
}
}
else {
const strategy = STRATEGIES[tagName] || STRATEGIES.DEFAULT;
const close = strategy.close || STRATEGIES.DEFAULT.close;
if (bufBackup) {
const content = buf.join('');
buf = bufBackup;
bufBackup = null;
close(buf, res, content);
} else {
close(buf, res);
}
}
idx = JSP_TAG.lastIndex;
}
buf.push(jsp.substring(idx));
return buf.join('');
}
function replaceIncludes(jsp) {
let res;
const buf = [];
let idx = 0;
while ((res = INCLUDE_DIRECTIVE.exec(jsp)) !== null) {
buf.push(jsp.substring(idx, res.index));
buf.push(resolveInclude(res[1], attributes));
idx = INCLUDE_DIRECTIVE.lastIndex;
}
buf.push(jsp.substring(idx));
return buf.join('');
}
function convert(jsp) {
jsp = replaceElOperators(jsp);
jsp = replaceJspTags(jsp);
jsp = replaceFormInputs(jsp);
jsp = jsp.replace(FORM, '$1');
jsp = replaceIncludes(jsp);
return jsp.replace(JSP_DIRECTIVE_OR_COMMENT, '');
}
return _.template(convert(jsp))(attributes)
.replace(/\n+\s*\n+/g, '\n');
}
function resolveFile(jspFile, attributes, cfg) {
cfg = cfg || {};
cfg.jspDir = cfg.jspRoot;
if (!cfg.jspDir) {
cfg.jspDir = path.dirname(jspFile);
cfg.jspRoot = cfg.jspDir;
}
cfg.jspFile = jspFile;
const jsp = fs.readFileSync(jspFile).toString();
return resolve(jsp, attributes, cfg);
}
module.exports = {
resolve,
resolveFile
};
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%-- some comment --%>
<c:choose>
<c:when test="${myVar != 'someVal'}">
<p>should not be shown</p>
</c:when>
<c:when test="${myVar2 == 'someOtherVal'}">
<p>should be shown 1</p>
</c:when>
<c:otherwise>
<p>should also not be shown</p>
</c:otherwise>
</c:choose>
<c:choose>
<c:when test="${myVar == 'someVal'}">
<p>should be shown 2</p>
</c:when>
<c:when test="${myVar != 'someVal'}">
<p>should not be shown</p>
</c:when>
<c:otherwise>
<p>should also not be shown</p>
</c:otherwise>
</c:choose>
<c:choose>
<c:when test="${false}">
<p>should not be shown</p>
</c:when>
<c:otherwise>
<p>should be shown 3</p>
</c:otherwise>
</c:choose>
<form:form modelAttribute="formModel">
<c:forEach items="${things}" var="thing">
<form:input type="number" path="formModelMember" data-plop="${thing.name}"/>
<c:if test="${myVar == 'someVal'}">
<input type="date" value="${bar}">
</c:if>
<c:if test="${myVar2 != 'someVal'}">
<input type="number" value="42">
</c:if>
</c:forEach>
<c:out value="notavar"/>
<c:out value="${avar}"/>
<un:supported jsp="tag"></un:supported>
<a title="<spring:message code="message.code" javaScriptEscape="true"/>" href="#">Link</a>
<!-- @include sibling1: -->
<%@ include file="sibling1.jsp" %>
<c:set var="anewvar" value="${anoldvar}"/>
<p>anewvar: ${anewvar}</p>
<c:set var="multilineSet">
foo
bar
</c:set>
<p>multilineSet: ${multilineSet}</p>
<c:set var="escapedSet">
<c:out value="${avar}"/>
</c:set>
<p>escapedSet: ${escapedSet}</p>
<!-- jsp:include sibling1: -->
<jsp:include page="sibling1.jsp"/>
<c:if test="${empty emptyVar and empty emptyVar2}">
<p>I support "empty"!</p>
</c:if>
<c:if test="${not empty nonEmptyVar}">
<p>I support "not empty"!</p>
</c:if>
<p>I support "${false or true ? 'or' : 'error'}" too!</p>
<form:radiobutton path="${radioPath}" value="1" id="${radioPath}1"/>
<c:forEach begin="${one}" end="3" var="idx">
<span>${idx}</span>
</c:forEach>
<p><spring:message code="message.with.args" arguments="arg1,arg2"/></p>
<c:set var="includedVar" value="INCLUDEDVAR"/>
<span><c:out value="foo${includedVar}"/></span>
<span><c:out value="${includedVar}bar"/></span>
<span><c:out value="foo${includedVar}bar"/></span>
</form:form>
<p>Text from sibling.jsp with some <c:out value="${bar}"/></p>
<c:set var="dynamicParam" value="dynamic, resolved"/>
<jsp:include page="sibling2.jsp">
<jsp:param name="param1" value="static"/>
<jsp:param name="param2" value="${dynamicParam}"/>
</jsp:include>
<dl>
<dt>Param 1</dt><dd>${param.param1}</dd>
<dt>Param 2</dt><dd>${param.param2}</dd>
</dl>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment