All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cat.inspiracio.orange.Programmer Maven / Gradle / Ivy

Go to download

Orange-maven-plugin builds template files for use with orange-servlet. Orange Servlet provides HTML templating with server-side Java.

There is a newer version: 5.0.0
Show newest version
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("");
		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