rdftemplate

Library for generating XML documents from RDF data using templates
git clone https://code.djc.id.au/git/rdftemplate/
commit 29902dea0fa92d278277ad18ce69e906ab08d59d
parent 3942ee75f0308d5df2da41aff3bdb4cec48d6d94
Author: Dan Callaghan <djc@djc.id.au>
Date:   Sun, 29 Nov 2009 16:38:35 +1000

more verbose exceptions

Diffstat:
Asrc/main/java/au/com/miskinhill/rdftemplate/ContentAction.java | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/java/au/com/miskinhill/rdftemplate/ForAction.java | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/main/java/au/com/miskinhill/rdftemplate/IfAction.java | 46++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/java/au/com/miskinhill/rdftemplate/JoinAction.java | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/java/au/com/miskinhill/rdftemplate/TemplateAction.java | 5+++++
Asrc/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolationException.java | 23+++++++++++++++++++++++
Msrc/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolator.java | 188++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/main/java/au/com/miskinhill/rdftemplate/TemplateSyntaxException.java | 15+++++++++++----
Msrc/main/java/au/com/miskinhill/rdftemplate/selector/SelectorComparator.java | 16++++++++++++++--
Msrc/main/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationException.java | 6+++++-
10 files changed, 367 insertions(+), 91 deletions(-)
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/ContentAction.java b/src/main/java/au/com/miskinhill/rdftemplate/ContentAction.java
@@ -0,0 +1,67 @@
+package au.com.miskinhill.rdftemplate;
+
+import java.util.Set;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.StartElement;
+
+import com.hp.hpl.jena.rdf.model.Literal;
+import com.hp.hpl.jena.rdf.model.RDFNode;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+import au.com.miskinhill.rdftemplate.selector.Selector;
+
+public class ContentAction extends TemplateAction {
+    
+    public static final String ACTION_NAME = "content";
+    public static final QName ACTION_QNAME = new QName(TemplateInterpolator.NS, ACTION_NAME);
+    private static final QName XML_LANG_QNAME = new QName(XMLConstants.XML_NS_URI, "lang", XMLConstants.XML_NS_PREFIX);
+    private static final String XHTML_NS_URI = "http://www.w3.org/1999/xhtml";
+    
+    private final StartElement start;
+    private final Selector<?> selector;
+    
+    public ContentAction(StartElement start, Selector<?> selector) {
+        this.start = start;
+        this.selector = selector;
+    }
+    
+    public void evaluate(TemplateInterpolator interpolator, RDFNode node, XMLEventDestination writer, XMLEventFactory eventFactory)
+            throws XMLStreamException {
+        Object replacement = selector.singleResult(node);
+        StartElement start = interpolator.interpolateAttributes(this.start, node);
+        Set<Attribute> attributes = interpolator.cloneAttributesWithout(start, ACTION_QNAME);
+        if (replacement instanceof Literal) {
+            Literal literal = (Literal) replacement;
+            if (!StringUtils.isEmpty(literal.getLanguage())) {
+                attributes.add(eventFactory.createAttribute(XML_LANG_QNAME, ((Literal) replacement).getLanguage()));
+                if (start.getName().getNamespaceURI().equals(XHTML_NS_URI)) {
+                    String xhtmlPrefixInContext = start.getNamespaceContext().getPrefix(XHTML_NS_URI);
+                    QName xhtmlLangQNameForContext; // ugh
+                    if (xhtmlPrefixInContext.isEmpty())
+                        xhtmlLangQNameForContext = new QName("lang");
+                    else
+                        xhtmlLangQNameForContext = new QName(XHTML_NS_URI, "lang", xhtmlPrefixInContext);
+                    attributes.add(eventFactory.createAttribute(xhtmlLangQNameForContext, literal.getLanguage()));
+                }
+            }
+        }
+        writer.add(eventFactory.createStartElement(start.getName(), attributes.iterator(), start.getNamespaces()));
+        interpolator.writeTreeForContent(writer, replacement);
+        writer.add(eventFactory.createEndElement(start.getName(), start.getNamespaces()));
+    }
+    
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this)
+                .append("start", start)
+                .append("selector", selector)
+                .toString();
+    }
+
+}
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/ForAction.java b/src/main/java/au/com/miskinhill/rdftemplate/ForAction.java
@@ -0,0 +1,41 @@
+package au.com.miskinhill.rdftemplate;
+
+import java.util.List;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.XMLEvent;
+
+import com.hp.hpl.jena.rdf.model.RDFNode;
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+import au.com.miskinhill.rdftemplate.selector.Selector;
+
+public class ForAction extends TemplateAction {
+    
+    public static final String ACTION_NAME = "for";
+    public static final QName ACTION_QNAME = new QName(TemplateInterpolator.NS, ACTION_NAME);
+    
+    private final List<XMLEvent> tree;
+    private final Selector<RDFNode> selector;
+    
+    public ForAction(List<XMLEvent> tree, Selector<RDFNode> selector) {
+        this.tree = tree;
+        this.selector = selector;
+    }
+    
+    public void evaluate(TemplateInterpolator interpolator, RDFNode node, XMLEventDestination writer)
+            throws XMLStreamException {
+        for (RDFNode eachNode: selector.result(node)) {
+            interpolator.interpolate(tree.iterator(), eachNode, writer);
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this)
+                .append("selector", selector)
+                .toString();
+    }
+
+}
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/IfAction.java b/src/main/java/au/com/miskinhill/rdftemplate/IfAction.java
@@ -0,0 +1,46 @@
+package au.com.miskinhill.rdftemplate;
+
+import java.util.List;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.XMLEvent;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+import com.hp.hpl.jena.rdf.model.RDFNode;
+
+import au.com.miskinhill.rdftemplate.selector.Selector;
+
+public class IfAction extends TemplateAction {
+    
+    public static final String ACTION_NAME = "if";
+    public static final QName ACTION_QNAME = new QName(TemplateInterpolator.NS, ACTION_NAME);
+    
+    private final List<XMLEvent> tree;
+    private final Selector<?> condition;
+    private final boolean negate;
+    
+    public IfAction(List<XMLEvent> tree, Selector<?> condition, boolean negate) {
+        this.tree = tree;
+        this.condition = condition;
+        this.negate = negate;
+    }
+    
+    public void evaluate(TemplateInterpolator interpolator, RDFNode node, XMLEventDestination writer)
+            throws XMLStreamException {
+        List<?> selectorResult = condition.result(node);
+        if (negate ? selectorResult.isEmpty() : !selectorResult.isEmpty()) {
+            interpolator.interpolate(tree.iterator(), node, writer);
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this)
+                .append("condition", condition)
+                .append("negate", negate)
+                .toString();
+    }
+
+}
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/JoinAction.java b/src/main/java/au/com/miskinhill/rdftemplate/JoinAction.java
@@ -0,0 +1,51 @@
+package au.com.miskinhill.rdftemplate;
+
+import java.util.List;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.XMLEvent;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+import com.hp.hpl.jena.rdf.model.RDFNode;
+
+import au.com.miskinhill.rdftemplate.selector.Selector;
+
+public class JoinAction extends TemplateAction {
+    
+    public static final String ACTION_NAME = "join";
+    public static final QName ACTION_QNAME = new QName(TemplateInterpolator.NS, ACTION_NAME);
+    
+    private final List<XMLEvent> tree;
+    private final Selector<RDFNode> selector;
+    private final String separator;
+    
+    public JoinAction(List<XMLEvent> tree, Selector<RDFNode> selector, String separator) {
+        this.tree = tree;
+        this.selector = selector;
+        this.separator = separator;
+    }
+    
+    public void evaluate(TemplateInterpolator interpolator, RDFNode node, XMLEventDestination writer, XMLEventFactory eventFactory)
+            throws XMLStreamException {
+        boolean first = true;
+        for (RDFNode eachNode: selector.result(node)) {
+            if (!first) {
+                writer.add(eventFactory.createCharacters(separator));
+            }
+            interpolator.interpolate(tree.iterator(), eachNode, writer);
+            first = false;
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this)
+                .append("selector", selector)
+                .append("separator", separator)
+                .toString();
+    }
+
+}
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/TemplateAction.java b/src/main/java/au/com/miskinhill/rdftemplate/TemplateAction.java
@@ -0,0 +1,5 @@
+package au.com.miskinhill.rdftemplate;
+
+public abstract class TemplateAction {
+
+}
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolationException.java b/src/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolationException.java
@@ -0,0 +1,23 @@
+package au.com.miskinhill.rdftemplate;
+
+import javax.xml.stream.Location;
+
+import com.hp.hpl.jena.rdf.model.RDFNode;
+
+public class TemplateInterpolationException extends RuntimeException {
+    
+    private static final long serialVersionUID = -1472104970210074672L;
+
+    public TemplateInterpolationException(Location location, TemplateAction action, RDFNode node, Throwable cause) {
+        super("Exception evaluating action [" + action + "] " +
+                "at location [" + location.getLineNumber() + "," + location.getColumnNumber() + "] " +
+                "with context node " + node, cause);
+    }
+    
+    public TemplateInterpolationException(Location location, String selectorExpression, RDFNode node, Throwable cause) {
+        super("Exception evaluating selector expression [" + selectorExpression + "] " +
+                "at location [" + location.getLineNumber() + "," + location.getColumnNumber() + "] " +
+                "with context node " + node, cause);
+    }
+
+}
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolator.java b/src/main/java/au/com/miskinhill/rdftemplate/TemplateInterpolator.java
@@ -14,7 +14,6 @@ import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import javax.xml.XMLConstants;
 import javax.xml.namespace.QName;
 import javax.xml.stream.XMLEventFactory;
 import javax.xml.stream.XMLEventReader;
@@ -32,24 +31,14 @@ import javax.xml.stream.events.XMLEvent;
 
 import com.hp.hpl.jena.rdf.model.Literal;
 import com.hp.hpl.jena.rdf.model.RDFNode;
-import org.apache.commons.lang.StringUtils;
 
+import au.com.miskinhill.rdftemplate.selector.InvalidSelectorSyntaxException;
 import au.com.miskinhill.rdftemplate.selector.Selector;
 import au.com.miskinhill.rdftemplate.selector.SelectorFactory;
 
 public class TemplateInterpolator {
     
     public static final String NS = "http://code.miskinhill.com.au/rdftemplate/";
-    public static final String CONTENT_ACTION = "content";
-    private static final QName CONTENT_ACTION_QNAME = new QName(NS, CONTENT_ACTION);
-    public static final String FOR_ACTION = "for";
-    private static final QName FOR_ACTION_QNAME = new QName(NS, FOR_ACTION);
-    public static final String IF_ACTION = "if";
-    private static final QName IF_ACTION_QNAME = new QName(NS, IF_ACTION);
-    public static final String JOIN_ACTION = "join";
-    private static final QName JOIN_ACTION_QNAME = new QName(NS, JOIN_ACTION);
-    private static final QName XML_LANG_QNAME = new QName(XMLConstants.XML_NS_URI, "lang", XMLConstants.XML_NS_PREFIX);
-    private static final String XHTML_NS_URI = "http://www.w3.org/1999/xhtml";
     
     private final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
     private final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
@@ -105,108 +94,124 @@ public class TemplateInterpolator {
             switch (event.getEventType()) {
                 case XMLStreamConstants.START_ELEMENT: {
                     StartElement start = (StartElement) event;
-                    if (start.getName().equals(IF_ACTION_QNAME)) {
+                    if (start.getName().equals(IfAction.ACTION_QNAME)) {
                         Attribute testAttribute = start.getAttributeByName(new QName("test"));
                         Attribute notAttribute = start.getAttributeByName(new QName("not"));
                         String condition;
                         boolean negate = false;
                         if (testAttribute != null && notAttribute != null)
-                            throw new TemplateSyntaxException("test and not attribute on rdf:if are mutually exclusive");
+                            throw new TemplateSyntaxException(start.getLocation(), "test and not attribute on rdf:if are mutually exclusive");
                         else if (testAttribute != null)
                             condition = testAttribute.getValue();
                         else if (notAttribute != null) {
                             condition = notAttribute.getValue();
                             negate = true;
                         } else
-                            throw new TemplateSyntaxException("rdf:if must have a test attribute or a not attribute");
-                        List<?> selectorResult = selectorFactory.get(condition).result(node);
-                        if (negate ? !selectorResult.isEmpty() : selectorResult.isEmpty()) {
-                            consumeTree(start, reader);
-                            break;
-                        } else {
-                            List<XMLEvent> events = consumeTree(start, reader);
-                            // discard the enclosing rdf:if element
-                            events.remove(events.size() - 1);
-                            events.remove(0);
-                            interpolate(events.iterator(), node, writer);
+                            throw new TemplateSyntaxException(start.getLocation(), "rdf:if must have a test attribute or a not attribute");
+                        Selector<?> conditionSelector;
+                        try {
+                            conditionSelector = selectorFactory.get(condition);
+                        } catch (InvalidSelectorSyntaxException e) {
+                            throw new TemplateSyntaxException(start.getLocation(), e);
+                        }
+                        List<XMLEvent> tree = consumeTree(start, reader);
+                        // discard enclosing rdf:if
+                        tree.remove(tree.size() - 1);
+                        tree.remove(0);
+                        IfAction action = new IfAction(tree, conditionSelector, negate);
+                        try {
+                            action.evaluate(this, node, writer);
+                        } catch (Exception e) {
+                            throw new TemplateInterpolationException(start.getLocation(), action, node, e);
                         }
-                    } else if (start.getName().equals(JOIN_ACTION_QNAME)) {
+                    } else if (start.getName().equals(JoinAction.ACTION_QNAME)) {
                         Attribute eachAttribute = start.getAttributeByName(new QName("each"));
                         if (eachAttribute == null)
-                            throw new TemplateSyntaxException("rdf:join must have an each attribute");
+                            throw new TemplateSyntaxException(start.getLocation(), "rdf:join must have an each attribute");
                         String separator = "";
                         Attribute separatorAttribute = start.getAttributeByName(new QName("separator"));
                         if (separatorAttribute != null)
                             separator = separatorAttribute.getValue();
-                        Selector<RDFNode> selector = selectorFactory.get(eachAttribute.getValue()).withResultType(RDFNode.class);
+                        Selector<RDFNode> selector;
+                        try {
+                            selector = selectorFactory.get(eachAttribute.getValue()).withResultType(RDFNode.class);
+                        } catch (InvalidSelectorSyntaxException e) {
+                            throw new TemplateSyntaxException(start.getLocation(), e);
+                        }
                         List<XMLEvent> events = consumeTree(start, reader);
                         // discard enclosing rdf:join
                         events.remove(events.size() - 1);
                         events.remove(0);
-                        boolean first = true;
-                        for (RDFNode eachNode: selector.result(node)) {
-                            if (!first) {
-                                writer.add(eventFactory.createCharacters(separator));
-                            }
-                            interpolate(events.iterator(), eachNode, writer);
-                            first = false;
+                        JoinAction action = new JoinAction(events, selector, separator);
+                        try {
+                            action.evaluate(this, node, writer, eventFactory);
+                        } catch (Exception e) {
+                            throw new TemplateInterpolationException(start.getLocation(), action, node, e);
                         }
-                    } else if (start.getName().equals(FOR_ACTION_QNAME)) {
+                    } else if (start.getName().equals(ForAction.ACTION_QNAME)) {
                         Attribute eachAttribute = start.getAttributeByName(new QName("each"));
                         if (eachAttribute == null)
-                            throw new TemplateSyntaxException("rdf:for must have an each attribute");
+                            throw new TemplateSyntaxException(start.getLocation(), "rdf:for must have an each attribute");
+                        Selector<RDFNode> selector;
+                        try {
+                            selector = selectorFactory.get(eachAttribute.getValue()).withResultType(RDFNode.class);
+                        } catch (InvalidSelectorSyntaxException e) {
+                            throw new TemplateSyntaxException(start.getLocation(), e);
+                        }
                         List<XMLEvent> events = consumeTree(start, reader);
                         // discard enclosing rdf:for
                         events.remove(events.size() - 1);
                         events.remove(0);
-                        Selector<RDFNode> selector = selectorFactory.get(eachAttribute.getValue()).withResultType(RDFNode.class);
-                        for (RDFNode subNode : selector.result(node)) {
-                            interpolate(events.iterator(), subNode, writer);
+                        ForAction action = new ForAction(events, selector);
+                        try {
+                            action.evaluate(this, node, writer);
+                        } catch (Exception e) {
+                            throw new TemplateInterpolationException(start.getLocation(), action, node, e);
                         }
                     } else {
-                        Attribute ifAttribute = start.getAttributeByName(IF_ACTION_QNAME);
-                        Attribute contentAttribute = start.getAttributeByName(CONTENT_ACTION_QNAME);
-                        Attribute forAttribute = start.getAttributeByName(FOR_ACTION_QNAME);
+                        Attribute ifAttribute = start.getAttributeByName(IfAction.ACTION_QNAME);
+                        Attribute contentAttribute = start.getAttributeByName(ContentAction.ACTION_QNAME);
+                        Attribute forAttribute = start.getAttributeByName(ForAction.ACTION_QNAME);
                         if (ifAttribute != null) {
-                            Selector<?> selector = selectorFactory.get(ifAttribute.getValue());
-                            if (selector.result(node).isEmpty()) {
-                                consumeTree(start, reader);
-                                break;
+                            Selector<?> selector;
+                            try {
+                                selector = selectorFactory.get(ifAttribute.getValue());
+                            } catch (InvalidSelectorSyntaxException e) {
+                                throw new TemplateSyntaxException(ifAttribute.getLocation(), e);
                             }
-                            start = cloneStart(start, cloneAttributesWithout(start, IF_ACTION_QNAME), cloneNamespacesWithoutRdf(start));
-                        }
-                        if (contentAttribute != null && forAttribute != null) {
-                            throw new TemplateSyntaxException("rdf:for and rdf:content cannot both be present on an element");
+                            start = cloneStart(start, cloneAttributesWithout(start, IfAction.ACTION_QNAME), cloneNamespacesWithoutRdf(start));
+                            IfAction action = new IfAction(consumeTree(start, reader), selector, false);
+                            action.evaluate(this, node, writer);
+                        } else if (contentAttribute != null && forAttribute != null) {
+                            throw new TemplateSyntaxException(start.getLocation(), "rdf:for and rdf:content cannot both be present on an element");
                         } else if (contentAttribute != null) {
-                            consumeTree(start, reader);
-                            Selector<?> selector = selectorFactory.get(contentAttribute.getValue());
-                            Object replacement = selector.singleResult(node);
-                            start = interpolateAttributes(start, node);
-                            Set<Attribute> attributes = cloneAttributesWithout(start, CONTENT_ACTION_QNAME);
-                            if (replacement instanceof Literal) {
-                                Literal literal = (Literal) replacement;
-                                if (!StringUtils.isEmpty(literal.getLanguage())) {
-                                    attributes.add(eventFactory.createAttribute(XML_LANG_QNAME, ((Literal) replacement).getLanguage()));
-                                    if (start.getName().getNamespaceURI().equals(XHTML_NS_URI)) {
-                                        String xhtmlPrefixInContext = start.getNamespaceContext().getPrefix(XHTML_NS_URI);
-                                        QName xhtmlLangQNameForContext; // ugh
-                                        if (xhtmlPrefixInContext.isEmpty())
-                                            xhtmlLangQNameForContext = new QName("lang");
-                                        else
-                                            xhtmlLangQNameForContext = new QName(XHTML_NS_URI, "lang", xhtmlPrefixInContext);
-                                        attributes.add(eventFactory.createAttribute(xhtmlLangQNameForContext, literal.getLanguage()));
-                                    }
-                                }
+                            consumeTree(start, reader); // discard
+                            Selector<?> selector;
+                            try {
+                                selector = selectorFactory.get(contentAttribute.getValue());
+                            } catch (InvalidSelectorSyntaxException e) {
+                                throw new TemplateSyntaxException(contentAttribute.getLocation(), e);
+                            }
+                            ContentAction action = new ContentAction(start, selector);
+                            try {
+                                action.evaluate(this, node, writer, eventFactory);
+                            } catch (Exception e) {
+                                throw new TemplateInterpolationException(contentAttribute.getLocation(), action, node, e);
                             }
-                            writer.add(eventFactory.createStartElement(start.getName(), attributes.iterator(), start.getNamespaces()));
-                            writeTreeForContent(writer, replacement);
-                            writer.add(eventFactory.createEndElement(start.getName(), start.getNamespaces()));
                         } else if (forAttribute != null) {
-                            start = cloneStart(start, cloneAttributesWithout(start, FOR_ACTION_QNAME), cloneNamespacesWithoutRdf(start));
+                            Selector<RDFNode> selector;
+                            try {
+                                selector = selectorFactory.get(forAttribute.getValue()).withResultType(RDFNode.class);
+                            } catch (InvalidSelectorSyntaxException e) {
+                                throw new TemplateSyntaxException(forAttribute.getLocation(), e);
+                            }
+                            start = cloneStart(start, cloneAttributesWithout(start, ForAction.ACTION_QNAME), cloneNamespacesWithoutRdf(start));
                             List<XMLEvent> tree = consumeTree(start, reader);
-                            Selector<RDFNode> selector = selectorFactory.get(forAttribute.getValue()).withResultType(RDFNode.class);
-                            for (RDFNode subNode : selector.result(node)) {
-                                interpolate(tree.iterator(), subNode, writer);
+                            ForAction action = new ForAction(tree, selector);
+                            try {
+                                action.evaluate(this, node, writer);
+                            } catch (Exception e) {
+                                throw new TemplateInterpolationException(forAttribute.getLocation(), action, node, e);
                             }
                         } else {
                             start = interpolateAttributes(start, node);
@@ -257,13 +262,18 @@ public class TemplateInterpolator {
     }
     
     @SuppressWarnings("unchecked")
-    private StartElement interpolateAttributes(StartElement start, RDFNode node) {
+    protected StartElement interpolateAttributes(StartElement start, RDFNode node) {
         Set<Attribute> replacementAttributes = new LinkedHashSet<Attribute>();
         for (Iterator<Attribute> it = start.getAttributes(); it.hasNext(); ) {
             Attribute attribute = it.next();
             String replacementValue = attribute.getValue();
-            if (!attribute.getName().getNamespaceURI().equals(NS)) // skip rdf: attributes
-                replacementValue = interpolateString(attribute.getValue(), node); 
+            if (!attribute.getName().getNamespaceURI().equals(NS)) { // skip rdf: attributes
+                try {
+                    replacementValue = interpolateString(attribute.getValue(), node);
+                } catch (Exception e) {
+                    throw new TemplateInterpolationException(attribute.getLocation(), attribute.getValue(), node, e);
+                }
+            }
             replacementAttributes.add(eventFactory.createAttribute(attribute.getName(),
                     replacementValue));
         }
@@ -333,13 +343,23 @@ public class TemplateInterpolator {
             writer.add(eventFactory.createCharacters(template.substring(lastAppendedPos, matcher.start())));
             lastAppendedPos = matcher.end();
             String expression = matcher.group(1);
-            Object replacement = selectorFactory.get(expression).singleResult(node);
-            writeTreeForContent(writer, replacement);
+            Selector<?> selector;
+            try {
+                selector = selectorFactory.get(expression);
+            } catch (InvalidSelectorSyntaxException e) {
+                throw new TemplateSyntaxException(characters.getLocation(), e);
+            }
+            try {
+                Object replacement = selector.singleResult(node);
+                writeTreeForContent(writer, replacement);
+            } catch (Exception e) {
+                throw new TemplateInterpolationException(characters.getLocation(), expression, node, e);
+            }
         }
         writer.add(eventFactory.createCharacters(template.substring(lastAppendedPos)));
     }
     
-    private void writeTreeForContent(XMLEventDestination writer, Object replacement)
+    protected void writeTreeForContent(XMLEventDestination writer, Object replacement)
             throws XMLStreamException {
         if (replacement instanceof RDFNode) {
             RDFNode replacementNode = (RDFNode) replacement;
@@ -363,7 +383,7 @@ public class TemplateInterpolator {
     }
 
     @SuppressWarnings("unchecked")
-    private Set<Attribute> cloneAttributesWithout(StartElement start, QName omit) {
+    protected Set<Attribute> cloneAttributesWithout(StartElement start, QName omit) {
         // clone attributes, but without rdf:content
         Set<Attribute> attributes = new LinkedHashSet<Attribute>();
         for (Iterator<Attribute> it = start.getAttributes(); it.hasNext(); ) {
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/TemplateSyntaxException.java b/src/main/java/au/com/miskinhill/rdftemplate/TemplateSyntaxException.java
@@ -1,15 +1,22 @@
 package au.com.miskinhill.rdftemplate;
 
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLStreamException;
+
 public class TemplateSyntaxException extends RuntimeException {
 
     private static final long serialVersionUID = 6518982504570154030L;
     
-    public TemplateSyntaxException(String message) {
-        super(message);
+    public TemplateSyntaxException(Location location, String message) {
+        super("[location " + location.getLineNumber() + "," + location.getColumnNumber() + "] " + message);
+    }
+    
+    public TemplateSyntaxException(Location location, Throwable cause) {
+        super("[location " + location.getLineNumber() + "," + location.getColumnNumber() + "]", cause);
     }
     
-    public TemplateSyntaxException(Throwable cause) {
-        super(cause);
+    public TemplateSyntaxException(XMLStreamException e) {
+        super(e);
     }
     
 }
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorComparator.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorComparator.java
@@ -37,8 +37,20 @@ public class SelectorComparator<T extends Comparable<T>> implements Comparator<R
     
     @Override
     public int compare(RDFNode left, RDFNode right) {
-        T leftKey = selector.singleResult(left);
-        T rightKey = selector.singleResult(right);
+        T leftKey;
+        try {
+            leftKey = selector.singleResult(left);
+        } catch (SelectorEvaluationException e) {
+            throw new SelectorEvaluationException("Exception evaluating selector [" + selector + "] " +
+            		"with context node [" + left + "] for comparison", e);
+        }
+        T rightKey;
+        try {
+            rightKey = selector.singleResult(right);
+        } catch (SelectorEvaluationException e) {
+            throw new SelectorEvaluationException("Exception evaluating selector [" + selector + "] " +
+            		"with context node [" + right + "] for comparison", e);
+        }
         int result = leftKey.compareTo(rightKey);
         return reversed ? -result : result;
     }
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationException.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationException.java
@@ -2,10 +2,14 @@ package au.com.miskinhill.rdftemplate.selector;
 
 public class SelectorEvaluationException extends RuntimeException {
 
-    private static final long serialVersionUID = -398277800899471325L;
+    private static final long serialVersionUID = -398277800899471326L;
     
     public SelectorEvaluationException(String message) {
         super(message);
     }
+    
+    public SelectorEvaluationException(String message, Throwable cause) {
+        super(message, cause);
+    }
 
 }