Commit cb0b238b authored by rotn's avatar rotn
Browse files

Refactored template creation and configuration

extended parsing to enable arbitrary strings as attribute values 
added backward attribute

git-svn-id: https://svn.code.sf.net/p/snippetory/code/trunk@45 154fa62a-ae8f-480d-91fd-dc75d68c4636
parent 94172c3a
......@@ -228,6 +228,6 @@ public enum Encodings implements Encoding {
}
public Template parse(CharSequence data) {
return context().data(data).parse();
return context().parse(data);
}
}
/*****************************************************************************
* Copyright (c) 2011 B. Ebertz *
* All rights reserved. This program and the accompanying materials *
* are made available under the terms of the Eclipes Public License v1.0 *
* which accompanies this distribution, and is available at *
* http://www.eclipse.org/legal/epl-v10.html *
*****************************************************************************/
package org.jproggy.snippetory;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.util.Locale;
import org.jproggy.snippetory.TemplateContext.ToString;
import org.jproggy.snippetory.spi.Encoding;
import org.jproggy.snippetory.spi.Syntax;
import org.jproggy.snippetory.spi.SyntaxID;
/**
* Whenever you work with Snippetory things start here. The Repo(sitory) provides access to different
* sources of template code. May it be the simple String within your code, a file or a stream got from
* an url. Repo will help you create the TemplateContext, and after configuration, the TemplateContext will provide
* the template.
* Whenever you work with Snippetory things start here. The Repo(sitory)
* provides access to different sources of template code. May it be the simple
* String within your code, a file or a stream got from an url. Repo will help
* you create the TemplateContext, and after configuration, the TemplateContext
* will provide the template.
* <p>
* For Strings there are even short cuts to directly parse the template.
* </p>
* @author B. Ebertz
*
* @author B. Ebertz
*/
public class Repo {
/**
* The really short short cut for the simple jobs. This helps
* to scale from a very low level, where any character hurts. At least for
* playing around it's very handy.
* The really short short cut for the simple jobs. This helps to scale from
* a very low level, where any character hurts. At least for playing around
* it's very handy.
*/
public static Template parse(CharSequence data) {
return new TemplateContext().data(data).parse();
return new TemplateContext(data).parse();
}
public static Template parse(CharSequence data, Locale l) {
return new TemplateContext().data(data).locale(l).parse();
return new TemplateContext(data).locale(l).parse();
}
/**
* The really short short cut for the simple jobs. This helps to scale from
* a very low level, where any character hurts. At least for playing around
* it's very handy.
*/
public static TemplateContext read(CharSequence data) {
return new TemplateContext().data(data);
return new TemplateContext(data);
}
/**
* The data for the TemplateContext is searched on class path
*/
public static TemplateContext readResource(String name) {
return new TemplateContext().readResource(name);
return new TemplateContext(ToString.resource(name, null));
}
public static TemplateContext readResource(String name, ClassLoader test) {
return new TemplateContext().readResource(name, test);
return new TemplateContext(ToString.resource(name, test));
}
public static TemplateContext readFile(String fileName) {
return new TemplateContext().readFile(fileName);
return new TemplateContext(ToString.file(fileName));
}
public static TemplateContext readFile(File fileName) {
return new TemplateContext().readFile(fileName);
return new TemplateContext(ToString.file(fileName));
}
/**
*
* @param in
* @param in
*/
public static TemplateContext readStream(InputStream in) {
return new TemplateContext().readStream(in);
return new TemplateContext(ToString.stream(in));
}
public static TemplateContext readReader(Reader in) {
return new TemplateContext().readReader(in);
public static TemplateContext readReader(Reader in) {
return new TemplateContext(ToString.reader(in));
}
public static class TemplateContext extends org.jproggy.snippetory.TemplateContext {
private final CharSequence data;
private TemplateContext(CharSequence data) {
this.data = data;
}
@Override
public TemplateContext locale(Locale locale) {
return (TemplateContext)super.locale(locale);
}
@Override
public TemplateContext attrib(String name,
String value) {
return (TemplateContext)super.attrib(name, value);
}
@Override
public TemplateContext encoding(Encoding encoding) {
return (TemplateContext)super.encoding(encoding);
}
@Override
public TemplateContext encoding(String encoding) {
return (TemplateContext)super.encoding(encoding);
}
@Override
public TemplateContext syntax(Syntax syntax) {
return (TemplateContext)super.syntax(syntax);
}
@Override
public TemplateContext syntax(SyntaxID syntax) {
return (TemplateContext)super.syntax(syntax);
}
public Template parse() {
return parse(data);
}
}
}
......@@ -40,6 +40,6 @@ public enum Syntaxes implements SyntaxID {
}
public Template parse(CharSequence data) {
return context().data(data).parse();
return context().parse(data);
}
}
\ No newline at end of file
......@@ -7,7 +7,9 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
......@@ -17,46 +19,57 @@ import org.jproggy.snippetory.spi.Encoding;
import org.jproggy.snippetory.spi.Syntax;
import org.jproggy.snippetory.spi.SyntaxID;
/**
* The TemplateContext represents the configuration how templates are parsed. It
* provides a fluent interface for inline creation as well as a bean interface
* for convenient injection. Using the injection variant it's also easy to use a
* subclass and use a different parsing mechanism or place an additional layer
* of interceptors.
*
* @author B. Ebertz
*/
public class TemplateContext {
private Locale locale = Locale.getDefault();
private Syntax syntax = Syntax.REGISTRY.getDefault();
private CharSequence data;
private Map<String, String> baseAttribs = new HashMap<String, String>();
public TemplateContext() {
this.baseAttribs.put("date", "");
this.baseAttribs.put("number", "");
}
/**
* Omit initialization here to ensure TemplateContext to be cheap in default behavior
* and generate the cost of a map only when required.
*/
private Map<String, String> baseAttribs;
private static final Map<String, String> DEFAULT_ATTRIBUTES;
public TemplateContext data(CharSequence data) {
setData(data);
return this;
}
public CharSequence getData() {
return data;
}
public void setData(CharSequence data) {
this.data = data;
static {
Map<String,String> map = new HashMap<String, String>(2);
map.put("date", "");
map.put("number", "");
DEFAULT_ATTRIBUTES = Collections.unmodifiableMap(map);
}
public TemplateContext syntax(SyntaxID syntax) {
return syntax(Syntax.REGISTRY.byName(syntax.getName()));
}
public TemplateContext syntax(Syntax syntax) {
if (syntax == null) throw new NullPointerException();
this.syntax = syntax;
setSyntax(syntax);
return this;
}
public Syntax getSyntax() {
return syntax;
}
public void setSyntax(Syntax syntax) {
if (syntax == null)
throw new NullPointerException();
this.syntax = syntax;
}
public TemplateContext encoding(String encoding) {
return attrib("enc", encoding);
}
public TemplateContext encoding(Encoding encoding) {
return encoding(encoding.getName());
}
......@@ -65,86 +78,102 @@ public class TemplateContext {
this.locale = locale;
return this;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public TemplateContext attrib(String name, String value) {
if (baseAttribs == null) {
// if not initialized copy defaults
baseAttribs = new LinkedHashMap<String, String>(DEFAULT_ATTRIBUTES);
}
this.baseAttribs.put(name, value);
return this;
}
/**
* Returns the attributes intended to be set on the root node of a template created
* by one of the parse methods. This makes especially sense for inherited attributes.
* Expect the returned map to be unmodifiable. I.e. copy via copy constructor and
* set newly if you want to extend it.
* @return
*/
public Map<String, String> getBaseAttribs() {
if (baseAttribs == null) return DEFAULT_ATTRIBUTES;
return baseAttribs;
}
public void setBaseAttribs(Map<String, String> baseAttribs) {
this.baseAttribs = baseAttribs;
}
public Template parse() {
return new TemplateBuilder().parse(this);
public Template parse(CharSequence data) {
return TemplateBuilder.parse(this, data);
}
/**
* The data for the TemplateContext is searched on class path
*/
public TemplateContext readResource(String name) {
public Template parseResource(String name) {
return parseResource(name, null);
}
public Template parseResource(String name, ClassLoader test) {
if (name == null) {
throw new NullPointerException();
}
return readResource(name, null);
return parse(ToString.resource(name, test));
}
public TemplateContext readResource(String name, ClassLoader test) {
if (name == null) {
public Template parseFile(String fileName) {
if (fileName == null) {
throw new NullPointerException();
}
return new TemplateContext().data(ToString.resource(name, test));
return parse(ToString.file(fileName));
}
public TemplateContext readFile(String fileName) {
public Template parseFile(File fileName) {
if (fileName == null) {
throw new NullPointerException();
}
return new TemplateContext().data(ToString.file(fileName));
return parse(ToString.file(fileName));
}
public TemplateContext readFile(File fileName) {
return new TemplateContext().data(ToString.file(fileName));
}
/**
*
* @param in
* @param in
*/
public TemplateContext readStream(InputStream in) {
return new TemplateContext().data(ToString.stream(in));
public Template parseStream(InputStream in) {
return parse(ToString.stream(in));
}
public TemplateContext readReader(Reader in) {
return new TemplateContext().data(ToString.reader(in));
public Template parseReader(Reader in) {
return parse(ToString.reader(in));
}
private static class ToString {
static class ToString {
public static String resource(String name, ClassLoader test) {
if (test != null) {
InputStream in = test.getResourceAsStream(name);
if (in == null) return resource(name, null);
if (in == null)
return resource(name, null);
return stream(in);
}
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
loader = Repo.class.getClassLoader();
if (loader == null) {
loader = ToString.class.getClassLoader();
}
if (loader == null) {
if (loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
return stream(loader.getResourceAsStream(name));
}
public static String file(String fileName) {
try {
return stream(new FileInputStream(fileName));
......@@ -152,7 +181,7 @@ public class TemplateContext {
throw new SnippetoryException(e);
}
}
public static String file(File in) {
if (in == null) {
return null;
......@@ -163,7 +192,7 @@ public class TemplateContext {
throw new SnippetoryException(e);
}
}
public static String stream(InputStream in) {
if (in == null) {
return null;
......@@ -175,7 +204,7 @@ public class TemplateContext {
}
}
public static String reader(Reader in) {
public static String reader(Reader in) {
if (in == null) {
return null;
}
......@@ -186,7 +215,8 @@ public class TemplateContext {
while ((c = in.read(buffer)) == buffer.length) {
s.write(buffer);
}
if (c > 0) s.write(buffer, 0, c);
if (c > 0)
s.write(buffer, 0, c);
return s.toString();
} catch (IOException e) {
throw new SnippetoryException(e);
......
......@@ -43,6 +43,7 @@ class Attributes {
REGISTRY.register("delimiter", Types.DELIMITER);
REGISTRY.register("prefix", Types.PREFIX);
REGISTRY.register("suffix", Types.SUFFIX);
REGISTRY.register("backward", Types.BACKWARD);
FormatRegistry.INSTANCE.register("stretch", new StretchFormat.Factory());
FormatRegistry.INSTANCE.register("shorten", new ShortenFormat.Factory());
FormatRegistry.INSTANCE.register("number", new NumFormat.Factory());
......@@ -57,6 +58,6 @@ class Attributes {
}
}
enum Types {
FORMAT, DEFAULT, ENCODING, DELIMITER, PREFIX, SUFFIX
FORMAT, DEFAULT, ENCODING, DELIMITER, PREFIX, SUFFIX, BACKWARD
}
}
......@@ -8,6 +8,17 @@ import org.jproggy.snippetory.spi.Syntax;
public abstract class RegExSyntax implements Syntax {
protected static final String LINE_END =
"[ \t]*(?:\\n|\\r|\\r\\n|\\u0085|\\u2028|\\u2029)";
protected static final String LINE_START = "^[ \\t]*";
protected final static String ESCAPES = "\\\\\\\\|\\\\'|\\\\\"|\\n|\\r|\\b|\\t|\\f";
protected static final String NAME = "[\\p{Alnum}\\#\\._-]+";
protected static final String ATTRIBUTES =
"(?:[ \t]+" + NAME + "=(?:\\'(?:" + ESCAPES + "|[^\\\\'])*\\'|\\\"(?:" + ESCAPES +
"|[^\\\\\"])*\\\"))*";
protected static final String CONTENT =
"(" + NAME + ")|[ \\t]+(" + NAME + ")=(?:\\'((?:" + ESCAPES +
"|[^\\'])*)\\'|\\\"((?:" + ESCAPES + "|[^\"])*)\\\")";
@Override
public abstract RegexParser parse(CharSequence data) ;
......@@ -23,11 +34,10 @@ public abstract class RegExSyntax implements Syntax {
private final Map<Pattern, TokenType> patterns;
private final Matcher matcher;
private final CharSequence data;
private final Pattern vari;
private Boolean found;
private int pos = 0;
public RegexParser(CharSequence data, Map<Pattern, TokenType> patterns, String chars) {
public RegexParser(CharSequence data, Map<Pattern, TokenType> patterns) {
this.patterns = patterns;
String compoundPattern = "";
for (Pattern p : patterns.keySet()) {
......@@ -36,8 +46,6 @@ public abstract class RegExSyntax implements Syntax {
}
matcher = Pattern.compile(compoundPattern, Pattern.MULTILINE).matcher(data);
this.data = data;
vari = Pattern.compile("([\\p{Alnum}._-]+)|(?: ([\\p{Alnum}_]+)=(?:'([\\\"" +
chars + "]*)'|\\\"([\\'" + chars + "]*)\\\"))");
}
@Override
......@@ -88,6 +96,7 @@ public abstract class RegExSyntax implements Syntax {
return t;
}
private static final Pattern vari = Pattern.compile(CONTENT);
protected Token createToken(String varDef, TokenType type) {
Matcher m = vari.matcher(varDef);
m.find();
......@@ -103,10 +112,55 @@ public abstract class RegExSyntax implements Syntax {
}
String value = m.group(3);
if (value == null) value = m.group(4);
value = decode(value, token);
token.getAttributes().put(m.group(2), value);
}
return token;
}
private String decode(String val, Token t) {
StringBuilder result = new StringBuilder();
boolean bsFound = false;
for (int i = 0; i < val.length(); i++) {
if (bsFound) {
switch (val.charAt(i)) {
case '\\':
result.append('\\');
break;
case 'n':
result.append('\n');
break;
case 'r':
result.append('\r');
break;
case 't':
result.append('\t');
break;
case 'b':
result.append('\b');
break;
case 'f':
result.append('\f');
break;
case '\'':
result.append('\'');
break;
case '"':
result.append('"');
break;
default:
throw new ParseError("Unkown escaped character. " + val.charAt(i), t);
}
bsFound = false;
} else {
if (val.charAt(i) == '\\') {
bsFound = true;
} else result.append(val.charAt(i));
}
}
return result.toString();
}
public String getContent() {
for (int i = 1; i <= matcher.groupCount(); i++) {
......
......@@ -5,23 +5,33 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jproggy.snippetory.Template;
import org.jproggy.snippetory.TemplateContext;
import org.jproggy.snippetory.spi.Syntax;
/**
* Builds a template tree from the token stream provided by the tokenizer.
*
* @author B. Ebertz
*/
public class TemplateBuilder {
private Syntax tempSyntax;
private Syntax.Tokenizer _parser;
private TemplateContext _ctx;
private final TemplateContext _ctx;
public Template parse(TemplateContext ctx) {
private TemplateBuilder(TemplateContext ctx, CharSequence data) {
_ctx = ctx;
tempSyntax = ctx.getSyntax();
_parser = getSyntax().parse(ctx.getData());
_parser = getSyntax().parse(data);
}
public static Template parse(TemplateContext ctx, CharSequence data) {
TemplateBuilder builder = new TemplateBuilder(ctx, data);
Location root = new Location(null, null, ctx.getBaseAttribs(), "", ctx.getLocale());
Template template = parse(root);
Template template = builder.parse(root);
root.setTemplate(template);
return template;
}
......@@ -35,26 +45,14 @@ public class TemplateBuilder {
try {
switch (t.getType()) {
case BlockStart: {
if (children.containsKey(t.getName())) {
throw new ParseError("duplicate child template " +
t.getName(), t);
}
Location var = new Location(parent, t.getName(),
t.getAttributes(), "", getLocale());
parts.add(var);
Syntax.Tokenizer old = null;