
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.
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;
import cat.inspiracio.html.DocumentWriter;
/** Transforms a html document into a java class that renders it.
*
* Has state: not threadsafe. */
class Programmer extends DocumentWriter{
// 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;
/** Is the cursor at the start of a new line? */
private boolean newline=true;
/** How many tabs at the start of the line? */
private int indentation=0;
// construction --------------------------------------------
Programmer(Writer w){super(w);}
void setLog(Log l){log=l;}
void setPackage(String p){packageName=p;}
void setClass(String c){className=c;}
void generate(Node n) throws Exception{
init(n);
node(n);
close();
}
private void init(Node n) throws IOException{
packageDeclaration();
importStatements(n);
startClass();
constructor();
startWrite();
}
/** Ends the write()-methods, inserts any declaration, ends the class.
* @throws IOException */
private void close() throws IOException{
endWrite();
declarations();
endClass();
}
// recursers -----------------------------------------------
private void startClass() throws IOException{
write("public class ").write(className).writeln(" extends Template{");
writeln();
indent();
}
private void constructor() throws IOException{
write("public ").write(className).writeln("(){}");
writeln();
}
private void packageDeclaration() throws IOException{
write("package ").write(packageName).writeln(';');
writeln();
}
private void startWrite() throws IOException{
writeln("@Override public final void write() throws Exception {");
indent();
}
private void importStatements(final Node n) throws IOException{
Setimports=findImports(n);
writeImports(imports);
}
/** Finds all imported classes.
* Package-visibility for tests. */
SetfindImports(final Node n) throws IOException{
/** Class names for imports. Fully-qualified. */
final Setimports=new TreeSet();
imports.add("cat.inspiracio.orange.Template");
/** 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();
imports.add(i);
}
e.removeAttribute("data-import");
}
return this;
}
};
}catch(Exception e){throw new IOException(e);}
return imports;
}
private void writeImports(Setimports) throws IOException{
for(String i : imports)
writeln("import " + i + ";");
writeln();
}
@Override protected Programmer doctype(DocumentType type)throws IOException{return ww("");}
/** Check for data-substitute. */
@Override protected Programmer element(Element e) throws Exception{
if(e.hasAttribute("data-if"))
return dataIf(e);
if(e.hasAttribute("data-for"))
return dataFor(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);
return writeln("if(" + v + "){").indent().element(e).outdent().writeln('}');
}
/** 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);
return writeln("for(" + v + "){").indent().element(e).outdent().writeln('}');
}
/** 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);
return writeln("while(" + v + "){").indent().element(e).outdent().writeln('}');
}
/** Renders an element with data-substitute attribute.
* Generates a call to another class. */
private Programmer dataSubstitute(Element e) throws IOException{
//optimise: already resolve here
String v=e.getAttribute("data-substitute");
log.info("data-substitute = " + v);
return write("substitute(").literal(v).writeln(");");
}
/** 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 no support escaped expressions \${E}.
* */
@Override protected Programmer attribute(String key, String value) throws Exception {
//no value --- normal case
if(empty(value))
return ww(" " + key);
//value is literally "true" or "false" --- degenerate case
if("true".equals(value))
return ww(" " + key);//or: key=key
if("false".equals(value))
return this;
//Value has no ${E} --- usual case, must be fast and neat
if(-1==value.indexOf("${"))
return 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);
return write("attribute(").literal(key).writeln(", " + e + ");");
}
//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(escape(").write(p.getExpression()).writeln("));");
}
return ww("\""); //closes "
}
/** Is whole value just one expression, like "${karte.getGenus()}"?
* That's a frequent case. */
private boolean isExpression(String value){
//XXX Too simple. Fails for "${x} and ${y}".
return value!=null &&
3parts=Part.parse(s);
for(Part p : parts)
if(p.isLiteral())
ww(escape(p.getLiteral()));
else
writeln("write(" + p.getExpression() + ");");//need some toString(Object)?
return this;
}
private boolean whitespace(String s){return s.trim().isEmpty();}
/** escape for html: < & */
private String escape(String s){
if(s==null)
return null;
return s.replace("&", "&").replace("<", "<");
}
/** 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 ww("" + tag + ">");
else
return 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 endWrite() throws IOException{
return outdent().writeln().writeln('}').writeln();
}
private Programmer endClass() throws IOException{
return outdent().writeln('}').writeln();
}
private 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:
//We 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);
}
private boolean isServerScript(Element e){
String type=e.getAttribute("type");
return "server/java".equals(type);
}
private boolean isDeclaration(Element e){return e.hasAttribute("data-declare");}
/** No output */
@Override protected Programmer cdata(String s) throws Exception{return this;}
/** No output */
@Override protected Programmer comment(String s) throws Exception{return this;}
/** Quotes a value so that it can be an attribute's value.
* Escapes " by " and encloses in ". */
protected String quote(String value){
//if(okUnquoted(value))return value;
//optimise usual case
if(contains(value, '"'))
value=value.replaceAll("\"", """);
return '"' + value + '"';
}
/** Is it ok to use this attribute value without quotes in html5?
*
* https://www.w3.org/TR/html-markup/syntax.html#syntax-attributes
* Must not contain any literal space characters.
* Must not contain any """, "'", "=", ">", "<", or "`", characters.
* Must not be the empty string.
*
* I'm not using it because it's a bit dangerous. Example:
* becomes .
* After unquoted attribute:
* -another attribute, fine it starts with space
* -close element, */
@SuppressWarnings("unused")
private boolean okUnquoted(String s){
if(s.isEmpty())
return false;
for(int i=0; i':
case '<':
case '`':
return false;
}
}
return true;
}
/** Does the string contain this character? */
private boolean contains(String value, char c){return 0<=value.indexOf(c);}
// helpers -------------------------------------------------
private Programmer indent() throws IOException{
flush();
indentation++;
return this;
}
private Programmer outdent() throws IOException{
flush();
indentation--;
return this;
}
/** If at start of new line, write the indentation. */
private void dent() throws IOException{
if(newline)
for(int i=0; i
© 2015 - 2025 Weber Informatics LLC | Privacy Policy