2015-03-23 23:09:56 +00:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2015 Ziver
|
|
|
|
|
*
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
|
*
|
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
|
* THE SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package zutil.parser;
|
|
|
|
|
|
2015-04-03 22:49:45 +00:00
|
|
|
import zutil.io.file.FileUtil;
|
2015-03-25 14:38:49 +00:00
|
|
|
import zutil.log.LogUtil;
|
2015-03-23 23:09:56 +00:00
|
|
|
import zutil.struct.MutableInt;
|
|
|
|
|
|
2015-04-03 22:49:45 +00:00
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.lang.reflect.Array;
|
2015-03-25 23:24:15 +00:00
|
|
|
import java.lang.reflect.Field;
|
2015-04-03 22:49:45 +00:00
|
|
|
import java.util.*;
|
2015-03-25 23:24:15 +00:00
|
|
|
import java.util.logging.Level;
|
2015-03-25 14:38:49 +00:00
|
|
|
import java.util.logging.Logger;
|
2015-03-23 23:09:56 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Class for generating dynamic text/code from set data.
|
|
|
|
|
* The syntax is similar to the javascript mustache library.
|
|
|
|
|
*
|
2015-03-24 21:45:23 +00:00
|
|
|
* <br /><br />
|
2015-03-23 23:09:56 +00:00
|
|
|
* Supported tags:
|
2015-03-24 21:45:23 +00:00
|
|
|
* <ul>
|
|
|
|
|
* <li><b> {{key}} </b><br>
|
2015-03-25 23:24:15 +00:00
|
|
|
* <b> {{obj.attr}} </b><br>
|
2015-03-26 23:01:38 +00:00
|
|
|
* Will be replaced with the string from the key.</li>
|
2015-03-24 21:45:23 +00:00
|
|
|
* <li><b> {{#key}}...{{/key}} </b><br>
|
2015-03-27 19:03:32 +00:00
|
|
|
* <b> {{#obj.attr}}...{{/obj.attr}} </b><br>
|
|
|
|
|
* Will display content between the tags if:
|
|
|
|
|
* key is defined,
|
2015-04-03 22:49:45 +00:00
|
|
|
* if the key references a list or array the content will be iterated
|
2015-03-27 19:03:32 +00:00
|
|
|
* for every element, the element can be referenced by the tag {{.}},
|
2015-04-03 22:49:45 +00:00
|
|
|
* if key is a boolean with the value true,
|
|
|
|
|
* if key is a Integer with the value anything other then 0.</li>
|
2015-03-27 19:03:32 +00:00
|
|
|
* <li><b> {{^key}}</b><br>
|
|
|
|
|
* <b> {{^obj.attr}}...{{/obj.attr}} </b><br>
|
|
|
|
|
* A negative condition, will display content if:
|
|
|
|
|
* the key is undefined,
|
|
|
|
|
* the key is a empty list,
|
2015-04-03 22:49:45 +00:00
|
|
|
* the key is a zero length array,
|
|
|
|
|
* the key is a false boolean,
|
|
|
|
|
* the key is a 0 Integer</li>
|
2015-03-26 23:01:38 +00:00
|
|
|
* <li><b>{{! ignore me }}</b><br>
|
|
|
|
|
* Comment, will be ignored.</li>
|
2015-03-24 21:45:23 +00:00
|
|
|
* </ul>
|
2015-03-23 23:09:56 +00:00
|
|
|
*
|
2015-03-27 19:07:00 +00:00
|
|
|
* TODO: {{> file}}: include file
|
2015-03-27 19:03:32 +00:00
|
|
|
* TODO: {{=<% %>=}}: change delimiter
|
2015-04-03 22:49:45 +00:00
|
|
|
* TODO: {{obj.func()}}: execute functions
|
2015-03-27 19:03:32 +00:00
|
|
|
*
|
2015-03-24 21:45:23 +00:00
|
|
|
* @author Ziver koc
|
2015-03-23 23:09:56 +00:00
|
|
|
*/
|
|
|
|
|
public class Templator {
|
2015-03-25 14:38:49 +00:00
|
|
|
private static final Logger log = LogUtil.getLogger();
|
2015-04-03 22:49:45 +00:00
|
|
|
|
2015-03-23 23:09:56 +00:00
|
|
|
private HashMap<String,Object> data;
|
|
|
|
|
private TemplateEntity tmplRoot;
|
|
|
|
|
|
2015-04-03 22:49:45 +00:00
|
|
|
// File metadata
|
|
|
|
|
private File file;
|
|
|
|
|
private long lastModified;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A template file will be read from the disk. The template will
|
|
|
|
|
* be regenerated if the file changes.
|
|
|
|
|
*/
|
|
|
|
|
public Templator(File tmpl) throws IOException {
|
|
|
|
|
this.data = new HashMap<String, Object>();
|
|
|
|
|
this.file = tmpl;
|
|
|
|
|
parseTemplate(FileUtil.getContent(file));
|
|
|
|
|
this.lastModified = file.lastModified();
|
|
|
|
|
}
|
2015-03-23 23:09:56 +00:00
|
|
|
public Templator(String tmpl){
|
|
|
|
|
this.data = new HashMap<String, Object>();
|
|
|
|
|
parseTemplate(tmpl);
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-03 22:49:45 +00:00
|
|
|
|
2015-03-26 23:01:38 +00:00
|
|
|
public void set(String key, Object data){
|
2015-03-23 23:09:56 +00:00
|
|
|
this.data.put(key, data);
|
|
|
|
|
}
|
2015-04-03 22:49:45 +00:00
|
|
|
public Object get(String key){
|
|
|
|
|
return this.data.get(key);
|
|
|
|
|
}
|
|
|
|
|
public void remove(String key){
|
|
|
|
|
this.data.remove(key);
|
|
|
|
|
}
|
2015-03-23 23:09:56 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Will clear all data attributes
|
|
|
|
|
*/
|
|
|
|
|
public void clear(){
|
|
|
|
|
data.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String compile(){
|
2015-04-03 22:49:45 +00:00
|
|
|
if(file != null && lastModified != file.lastModified()){
|
|
|
|
|
try {
|
|
|
|
|
log.info("Template file changed. Regenerating template...");
|
|
|
|
|
parseTemplate(FileUtil.getContent(file));
|
|
|
|
|
this.lastModified = file.lastModified();
|
|
|
|
|
} catch(IOException e) {
|
|
|
|
|
log.log(Level.WARNING, "Unable to regenerate template", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-23 23:09:56 +00:00
|
|
|
StringBuilder str = new StringBuilder();
|
|
|
|
|
if(tmplRoot != null)
|
|
|
|
|
tmplRoot.compile(str);
|
|
|
|
|
return str.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Will pare or re-parse the source template.
|
|
|
|
|
*/
|
|
|
|
|
private void parseTemplate(String tmpl){
|
2015-03-25 14:38:49 +00:00
|
|
|
tmplRoot = parseTemplate(new TemplateNode(), tmpl, new MutableInt(), null);
|
2015-03-23 23:09:56 +00:00
|
|
|
}
|
2015-03-25 14:38:49 +00:00
|
|
|
private TemplateNode parseTemplate(TemplateNode root, String tmpl, MutableInt m, String parentTag){
|
2015-03-23 23:09:56 +00:00
|
|
|
StringBuilder data = new StringBuilder();
|
|
|
|
|
boolean tagOpen = false;
|
|
|
|
|
|
|
|
|
|
for(; m.i<tmpl.length(); ++m.i){
|
|
|
|
|
char c = tmpl.charAt(m.i);
|
2015-03-24 13:56:53 +00:00
|
|
|
String d = ""+ c + (m.i+1<tmpl.length() ? tmpl.charAt(m.i+1) : ' ');
|
|
|
|
|
switch( d ){
|
|
|
|
|
case "{{":
|
2015-03-26 23:01:38 +00:00
|
|
|
root.add(new TemplateStaticString(data.toString()));
|
2015-03-24 13:56:53 +00:00
|
|
|
data.delete(0, data.length());
|
|
|
|
|
tagOpen = true;
|
|
|
|
|
++m.i;
|
2015-03-23 23:09:56 +00:00
|
|
|
break;
|
2015-03-24 13:56:53 +00:00
|
|
|
case "}}":
|
|
|
|
|
if(!tagOpen){ // Tag not opened, incorrect enclosure
|
|
|
|
|
data.append(c);
|
|
|
|
|
continue;
|
2015-03-23 23:09:56 +00:00
|
|
|
}
|
2015-03-24 13:56:53 +00:00
|
|
|
tagOpen = false;
|
|
|
|
|
++m.i;
|
2015-03-24 21:45:23 +00:00
|
|
|
String tagName = data.toString();
|
2015-03-24 13:56:53 +00:00
|
|
|
data.delete(0, data.length());
|
2015-03-24 21:45:23 +00:00
|
|
|
switch(tagName.charAt(0)) {
|
2015-03-26 23:01:38 +00:00
|
|
|
case '#': // Condition
|
2015-03-25 14:38:49 +00:00
|
|
|
++m.i;
|
2015-03-26 23:01:38 +00:00
|
|
|
root.add(parseTemplate(new TemplateCondition(tagName.substring(1)),
|
2015-03-25 14:38:49 +00:00
|
|
|
tmpl, m, tagName));
|
2015-03-24 21:45:23 +00:00
|
|
|
break;
|
2015-03-26 23:01:38 +00:00
|
|
|
case '^': // Negative condition
|
|
|
|
|
++m.i;
|
|
|
|
|
root.add(parseTemplate(new TemplateNegativeCondition(tagName.substring(1)),
|
|
|
|
|
tmpl, m, tagName));
|
|
|
|
|
break;
|
|
|
|
|
case '/': // End tag
|
2015-03-25 14:43:14 +00:00
|
|
|
// Is this tag closing the parent?
|
2015-03-25 14:38:49 +00:00
|
|
|
if(parentTag != null && tagName.endsWith(parentTag.substring(1)))
|
|
|
|
|
return root;
|
2015-03-26 23:01:38 +00:00
|
|
|
log.severe("Closing non-opened tag: {{" + tagName + "}}");
|
|
|
|
|
root.add(new TemplateStaticString("{{"+tagName+"}}"));
|
|
|
|
|
break;
|
|
|
|
|
case '!': // Comment
|
2015-03-25 14:38:49 +00:00
|
|
|
break;
|
2015-03-24 21:45:23 +00:00
|
|
|
default:
|
2015-03-26 23:01:38 +00:00
|
|
|
root.add(new TemplateDataAttribute(tagName));
|
2015-03-24 21:45:23 +00:00
|
|
|
}
|
2015-03-23 23:09:56 +00:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
data.append(c);
|
2015-03-24 13:56:53 +00:00
|
|
|
break;
|
2015-03-23 23:09:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
2015-03-25 14:43:14 +00:00
|
|
|
if(tagOpen) // Incomplete tag, insert it as normal text
|
|
|
|
|
data.insert(0, "{{");
|
|
|
|
|
if(data.length() > 0) // Still some text left, add to node
|
2015-03-26 23:01:38 +00:00
|
|
|
root.add(new TemplateStaticString(data.toString()));
|
2015-03-25 14:38:49 +00:00
|
|
|
|
|
|
|
|
// If we get to this point means that this node is incorrectly close
|
|
|
|
|
// or this is the end of the file, so we convert it to a normal node
|
|
|
|
|
if(parentTag != null) {
|
|
|
|
|
root = new TemplateNode(root);
|
|
|
|
|
String tagName = "{{"+parentTag+"}}";
|
2015-03-26 23:01:38 +00:00
|
|
|
log.severe("Missing closure of tag: " + tagName);
|
|
|
|
|
root.addFirst(new TemplateStaticString(tagName));
|
2015-03-25 14:38:49 +00:00
|
|
|
}
|
|
|
|
|
return root;
|
2015-03-23 23:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**************************** Template Helper Classes *************************************/
|
|
|
|
|
|
|
|
|
|
protected interface TemplateEntity {
|
|
|
|
|
public void compile(StringBuilder str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected class TemplateNode implements TemplateEntity {
|
|
|
|
|
private List<TemplateEntity> entities;
|
|
|
|
|
|
|
|
|
|
public TemplateNode(){
|
|
|
|
|
this.entities = new ArrayList<TemplateEntity>();
|
|
|
|
|
}
|
2015-03-25 14:38:49 +00:00
|
|
|
public TemplateNode(TemplateNode node){
|
|
|
|
|
this.entities = node.entities;
|
|
|
|
|
}
|
2015-03-23 23:09:56 +00:00
|
|
|
|
2015-03-25 14:38:49 +00:00
|
|
|
public void addFirst(TemplateEntity s){
|
|
|
|
|
entities.add(0, s);
|
|
|
|
|
}
|
|
|
|
|
public void add(TemplateEntity s){
|
2015-03-23 23:09:56 +00:00
|
|
|
entities.add(s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void compile(StringBuilder str) {
|
|
|
|
|
for(TemplateEntity sec : entities)
|
|
|
|
|
sec.compile(str);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-24 21:45:23 +00:00
|
|
|
protected class TemplateCondition extends TemplateNode {
|
2015-03-27 19:03:32 +00:00
|
|
|
private TemplateDataAttribute attrib;
|
2015-03-24 21:45:23 +00:00
|
|
|
|
|
|
|
|
public TemplateCondition(String key){
|
2015-03-27 19:03:32 +00:00
|
|
|
this.attrib = new TemplateDataAttribute(key);
|
2015-03-24 21:45:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void compile(StringBuilder str) {
|
2015-03-27 19:03:32 +00:00
|
|
|
Object obj = attrib.getObject();
|
|
|
|
|
if(obj != null) {
|
2015-04-03 22:49:45 +00:00
|
|
|
Object prevObj = get(".");
|
|
|
|
|
set(".", obj);
|
|
|
|
|
|
2015-03-27 19:03:32 +00:00
|
|
|
if(obj instanceof Boolean){
|
|
|
|
|
if ((Boolean) obj)
|
|
|
|
|
super.compile(str);
|
|
|
|
|
}
|
2015-04-03 22:49:45 +00:00
|
|
|
else if(obj instanceof Integer){
|
2015-04-09 21:14:25 +00:00
|
|
|
if ((Integer) obj != 0)
|
2015-04-03 22:49:45 +00:00
|
|
|
super.compile(str);
|
|
|
|
|
}
|
2015-03-27 19:03:32 +00:00
|
|
|
else if(obj instanceof Iterable){
|
|
|
|
|
for(Object o : (Iterable)obj){ // Iterate through the whole list
|
|
|
|
|
set(".", o);
|
|
|
|
|
super.compile(str);
|
|
|
|
|
}
|
2015-04-03 22:49:45 +00:00
|
|
|
}
|
|
|
|
|
else if (obj.getClass().isArray()) {
|
|
|
|
|
int length = Array.getLength(obj);
|
|
|
|
|
for (int i = 0; i < length; i ++) {
|
|
|
|
|
set(".", Array.get(obj, i));
|
|
|
|
|
super.compile(str);
|
|
|
|
|
}
|
2015-03-27 19:03:32 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
super.compile(str);
|
2015-04-03 22:49:45 +00:00
|
|
|
|
|
|
|
|
// Reset map to parent object
|
|
|
|
|
if(prevObj != null)
|
2015-04-09 21:14:25 +00:00
|
|
|
set(".", prevObj);
|
2015-04-03 22:49:45 +00:00
|
|
|
else
|
|
|
|
|
remove(".");
|
2015-03-27 19:03:32 +00:00
|
|
|
}
|
2015-03-24 21:45:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-26 23:01:38 +00:00
|
|
|
protected class TemplateNegativeCondition extends TemplateNode {
|
2015-03-27 19:03:32 +00:00
|
|
|
private TemplateDataAttribute attrib;
|
2015-03-26 23:01:38 +00:00
|
|
|
|
|
|
|
|
public TemplateNegativeCondition(String key){
|
2015-03-27 19:03:32 +00:00
|
|
|
this.attrib = new TemplateDataAttribute(key);
|
2015-03-26 23:01:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void compile(StringBuilder str) {
|
2015-03-27 19:03:32 +00:00
|
|
|
Object obj = attrib.getObject();
|
|
|
|
|
if(obj == null)
|
2015-03-26 23:01:38 +00:00
|
|
|
super.compile(str);
|
2015-03-27 19:03:32 +00:00
|
|
|
else {
|
|
|
|
|
if(obj instanceof Boolean) {
|
|
|
|
|
if ( ! (Boolean) obj)
|
|
|
|
|
super.compile(str);
|
|
|
|
|
}
|
2015-04-03 22:49:45 +00:00
|
|
|
else if(obj instanceof Integer){
|
|
|
|
|
if ((Integer) obj == 0)
|
|
|
|
|
super.compile(str);
|
|
|
|
|
}
|
2015-03-27 19:03:32 +00:00
|
|
|
else if(obj instanceof Collection) {
|
|
|
|
|
if (((Collection) obj).isEmpty())
|
|
|
|
|
super.compile(str);
|
|
|
|
|
}
|
2015-04-03 22:49:45 +00:00
|
|
|
else if(obj.getClass().isArray()) {
|
|
|
|
|
if (((Object[]) obj).length <= 0)
|
|
|
|
|
super.compile(str);
|
|
|
|
|
}
|
2015-03-27 19:03:32 +00:00
|
|
|
}
|
2015-03-26 23:01:38 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected class TemplateStaticString implements TemplateEntity {
|
2015-03-23 23:09:56 +00:00
|
|
|
private String text;
|
|
|
|
|
|
2015-03-26 23:01:38 +00:00
|
|
|
public TemplateStaticString(String text){
|
2015-03-23 23:09:56 +00:00
|
|
|
this.text = text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void compile(StringBuilder str) {
|
|
|
|
|
str.append(text);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-26 23:01:38 +00:00
|
|
|
protected class TemplateDataAttribute implements TemplateEntity {
|
2015-03-27 19:03:32 +00:00
|
|
|
private String tag;
|
2015-04-09 21:14:25 +00:00
|
|
|
private String[] keys;
|
2015-03-23 23:09:56 +00:00
|
|
|
|
2015-03-27 19:03:32 +00:00
|
|
|
public TemplateDataAttribute(String tag){
|
|
|
|
|
this.tag = tag;
|
2015-04-09 21:14:25 +00:00
|
|
|
this.keys = tag.trim().split("\\.");
|
|
|
|
|
if(this.keys.length == 0)
|
|
|
|
|
this.keys = new String[]{"."};
|
|
|
|
|
if(this.keys[0].isEmpty()) // if tag starts with "."
|
|
|
|
|
this.keys[0] = ".";
|
2015-03-23 23:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
2015-03-27 19:03:32 +00:00
|
|
|
|
|
|
|
|
public Object getObject(){
|
|
|
|
|
if (data.containsKey(tag))
|
|
|
|
|
return data.get(tag);
|
2015-04-09 21:14:25 +00:00
|
|
|
else if (data.containsKey(keys[0]) && data.get(keys[0]) != null) {
|
|
|
|
|
Object obj = data.get(keys[0]);
|
|
|
|
|
for(int i=1; i<keys.length; ++i){
|
|
|
|
|
obj = getFieldValue(obj, keys[i]);
|
|
|
|
|
if(obj == null)
|
|
|
|
|
return null;
|
2015-03-25 23:24:15 +00:00
|
|
|
}
|
2015-04-09 21:14:25 +00:00
|
|
|
return obj;
|
2015-03-27 19:03:32 +00:00
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
protected Object getFieldValue(Object obj, String attrib){
|
|
|
|
|
try {
|
2015-04-03 22:49:45 +00:00
|
|
|
if(obj.getClass().isArray() && "length".equals(attrib))
|
|
|
|
|
return Array.getLength(obj);
|
2015-04-09 21:14:25 +00:00
|
|
|
else if(obj instanceof Collection && "length".equals(attrib))
|
|
|
|
|
return ((Collection) obj).size();
|
|
|
|
|
else {
|
|
|
|
|
for (Field field : obj.getClass().getDeclaredFields()) {
|
|
|
|
|
if (field.getName().equals(attrib)) {
|
|
|
|
|
field.setAccessible(true);
|
|
|
|
|
return field.get(obj);
|
|
|
|
|
}
|
2015-03-27 19:03:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}catch (IllegalAccessException e){
|
|
|
|
|
log.log(Level.WARNING, null, e);
|
2015-03-25 23:24:15 +00:00
|
|
|
}
|
2015-03-27 19:03:32 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void compile(StringBuilder str) {
|
|
|
|
|
Object obj = getObject();
|
|
|
|
|
if(obj != null)
|
|
|
|
|
str.append(obj.toString());
|
2015-03-23 23:09:56 +00:00
|
|
|
else
|
2015-03-27 19:03:32 +00:00
|
|
|
str.append("{{").append(tag).append("}}");
|
2015-03-23 23:09:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|