
cat.inspiracio.orange.Programmer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of orange-maven-plugin Show documentation
Show all versions of orange-maven-plugin Show documentation
Orange-maven-plugin builds template files for use with orange-servlet.
Orange Servlet provides HTML templating with server-side Java.
The newest version!
/* Copyright 2019 Alexander Bunkenburg
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.
*/
package cat.inspiracio.orange;
import java.io.IOException;
import java.io.Writer;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.apache.maven.plugin.logging.Log;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import cat.inspiracio.html.DocumentRecurser;
/** Transforms a html document into a java class that renders it.
*
* Has state: not threadsafe. */
class Programmer extends ProgramWriter {
// state ----------------------------------------------------
private Log log;
/** Package name of the class being written.
* Corresponds to file path.
* Like "cat.inspiracio.orange.webapp.cacti"
* for /cactus/cactus.html. */
private String packageName;
/** Class name of the class being written.
* Corresponds to file name.
* Like "cactus" for /cacti/cactus.html. */
private String className;
/** Path of this html file, absolute in web app.
* Like "/index.html" or "/WEB-INF/includes/hr.html". */
private String path;
// construction --------------------------------------------
Programmer(Writer w){super(w);}
Programmer setLog(Log l){log=l;return this;}
Programmer setPath(String s){path=s;return this;}
Programmer setPackage(String p){packageName=p;return this;}
Programmer setClass(String c){className=c;return this;}
/** Generates a class for n, writing it to writer. */
Programmer generate(Node n) throws Exception{
init(n);
node(n);
close();
return this;
}
/** Everything from the start of the file to the start of the body
* of the write method. */
private Programmer init(Node n) throws IOException{
packageDeclaration();
importStatements(n);
startClass();
constructor();
startRender();
return this;
}
/** Ends the write()-method, inserts any declarations, ends the class. */
private Programmer close() throws IOException{
endRender();
declarations();
endClass();
return this;
}
// recursers -----------------------------------------------
Programmer packageDeclaration() throws IOException{
write("package ").write(packageName).writeln(';');
writeln();
return this;
}
Programmer startClass() throws IOException{
write("public class ").write(className).writeln(" extends Template{");
writeln();
indent();
return this;
}
Programmer constructor() throws IOException{
write("public ").write(className).writeln("(PageContext pc){super(pc);}");
writeln();
return this;
}
Programmer startRender() throws IOException{
writeln("@Override public final void render() throws Exception {");
indent();
return this;
}
private void importStatements(final Node n) throws IOException{
Setimports = findImports(n);
writeImports(imports);
}
/** Finds all imported classes
* @param n in this node which is document root. */
Set findImports(final Node n) throws IOException {
/* Class names for imports. Fully-qualified, alphabetic order. */
final Setimports = new TreeSet();
imports.add("cat.inspiracio.orange.Template");
imports.add("jakarta.servlet.jsp.PageContext");
/* Recurses over the element to find all class-imports. */
try {
new DocumentRecurser(){
{
node(n);
}
@Override protected DocumentRecurser open(Element e) {
if(e.hasAttribute("data-import")){
String a = e.getAttribute("data-import");
String[] is = a.split(",");
for(String i : is){
i = i.trim();
if(!empty(i))
imports.add(i);
}
e.removeAttribute("data-import");
}
return this;
}
};
} catch(Exception e){throw new IOException(e);}
return imports;
}
/** Writes imports in alphabetic order. */
Programmer writeImports(Set imports) throws IOException {
for(String i : imports)
writeln("import " + i + ";");
writeln();
return this;
}
@Override protected Programmer doctype(DocumentType type)throws IOException{
ww("");
return this;
}
/** Check for data-substitute. */
@Override protected Programmer element(Element e) throws Exception {
// In alphabetical order ...
if(e.hasAttribute("data-for"))
return dataFor(e);
if(e.hasAttribute("data-if"))
return dataIf(e);
if(e.hasAttribute("data-substitute"))
return dataSubstitute(e);
if(e.hasAttribute("data-while"))
return dataWhile(e);
super.element(e);
return this;
}
/** Renders an element with data-if attribute.
* Generates an if-statement. */
private Programmer dataIf(Element e) throws Exception {
String v = e.getAttribute("data-if");
e.removeAttribute("data-if");
log.info("data-if = " + v);
writeln("if(" + v + "){").indent();
element(e).outdent().writeln('}');
return this;
}
/** Renders an element with data-for attribute.
* Generates a for-loop. */
private Programmer dataFor(Element e) throws Exception{
String v = e.getAttribute("data-for");
e.removeAttribute("data-for");
log.info("data-for = " + v);
writeln("for(" + v + "){").indent();
element(e).outdent().writeln('}');
return this;
}
/** Renders an element with data-while attribute.
* Generates a while-loop. */
private Programmer dataWhile(Element e) throws Exception{
String v = e.getAttribute("data-while");
e.removeAttribute("data-while");
log.info("data-while = " + v);
writeln("while(" + v + "){").indent();
element(e).outdent().writeln('}');
return this;
}
/** Renders an element with data-substitute attribute.
* Generates a call to another class. */
private Programmer dataSubstitute(Element e) throws IOException{
String v = e.getAttribute("data-substitute");
log.info("data-substitute = " + v);
// path = my path inside webapp, starts with /
// v = absolute or relative to path
Resolver resolver = new Resolver(); // re-use same instance?
String className = resolver.fqcn(path, v);
write("new ").write(className).writeln("(pageContext).render();");
return this;
}
/** Writes opening tag and the attributes.
* If the element has no child nodes, the final ">" of the opening tag
* is not written, so that close(e) can write "/>" --- unless the element
* is one of the few elements that need a separate closing tag even if they
* have no children. */
@Override protected Programmer open(Element e) throws Exception {
String tag = e.getTagName();
boolean b = e.hasChildNodes() || needClosingTag(tag);
if(0==e.getAttributes().getLength()){
if(b)
ww("<" + tag + ">");
else
ww("<" + tag);
}
else {
ww("<" + tag);
attributes(e);
if(b)
ww(">");
}
return this;
}
/** Writes " key=\"value\".
*
* In value, " is escaped to "&".
*
* If the value is null or empty, writes just the key.
*
* Does not support escaped expressions \${E}.
* */
@Override protected Programmer attribute(String key, String value) throws Exception {
// no value --- normal case
if(empty(value))
return (Programmer)ww(" " + key);
// value is literally "true" or "false" --- degenerate case
if("true".equals(value))
return (Programmer)ww(" " + key);//or: key=key
if("false".equals(value))
return this;
// Value has no ${E} --- usual case, must be fast and neat
if(!value.contains("${"))
return (Programmer)ww(" " + key + "=" + quote(value)); // Escapes " in the value
// Whole value is just one ${E} which may be boolean --- a normal case
if(isExpression(value)){
String E = value.substring(2, value.length()-1);
write("attribute(").literal(key).writeln(", " + E + ");");
return this;
}
// Value is partly literal and contains expressions --- unusual case
ww(" " + key + "=\""); //opens "
Listparts=Part.parse(value);
for(Part p : parts){
if(p.isLiteral())
ww(p.getLiteral());
else
write("write(unquote(").write(p.getExpression()).writeln("));");
}
return (Programmer)ww("\"");//closes "
}
/** Is whole value just one expression, like "${karte.getGenus()}"?
* That's a frequent case. */
boolean isExpression(String value){
// I think this is not too simple.
// Correctly false for "${x} and ${y}".
// Correctly false for "${x} and \${y}".
return value!=null &&
3 parts = Part.parse(s);
for(Part p : parts)
if(p.isLiteral())
ww(escape(p.getLiteral()));
else
writeln("write(escape(" + p.getExpression() + "));");
return this;
}
/** Writes an element's closing tag.
* If the element has no children, only writes "/>" ---
* unless the element is one of the few elements that need a separate
* closing tag even if they have no child elements. */
@Override protected Programmer close(Element e) throws IOException {
String tag = e.getTagName();
boolean b = e.hasChildNodes() || needClosingTag(tag);
if(b)
return (Programmer)ww("" + tag + ">");
else
return (Programmer)ww("/>");//opt: for void elements like could output ">" rather than "/>"
}
private Programmer declarations() throws IOException{
if(declarations!=null)
for(String d : declarations)
writeln(d);
return this;
}
private Programmer endRender() throws IOException {
outdent().writeln().writeln('}').writeln();
return this;
}
private Programmer endClass() throws IOException{
outdent().writeln('}').writeln();
return this;
}
/** accumulated declarations */
private final Set declarations = new HashSet<>();
/** Writes a script element.
*
* Script elements are special:
* They have no child elements except for text,
* and the text should not be escaped. In that way,
* the javascript program in there can have < > &.
*
* @param element Must be script and must have no children
* other than text.
*/
@Override protected void script(Element element) throws Exception {
if(isDeclaration(element)){
String source = element.getTextContent();
declarations.add(source);
return;
}
if(isServerScript(element)){
// Design decision:
// I don't enclose the script in a new block: { }
// so that it can initialise variables that are used further down.
String source = element.getTextContent();
if(!empty(source))
writeln(source);//no escaping at all
return;
}
open(element);
String s = element.getTextContent(); // correct if precondition holds
if(!empty(s))
ww(s); // no escaping for html
close(element);
}
boolean isServerScript(Element e){
String type = e.getAttribute("type");
return "server/java".equals(type);
}
private boolean isDeclaration(Element e){
String type = e.getAttribute("type");
return "server/java-declarations".equals(type);
}
/** No output */
@Override protected Programmer cdata(String s) throws Exception{return this;}
/** No output */
@Override protected Programmer comment(String s) throws Exception{return this;}
/** just for easy access in tests */
protected Programmer node(Node n) throws Exception{return (Programmer)super.node(n);}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy