rdftemplate

Library for generating XML documents from RDF data using templates
git clone https://code.djc.id.au/git/rdftemplate/
commit 3fc1b5040826e317263e0b2dd260a74e3d1c8109
parent 32c9a81a8af415aa0b9af868293034d5cc7d3f52
Author: Dan Callaghan <djc@djc.id.au>
Date:   Sun,  1 Nov 2009 15:45:18 +1000

datetime data type which converts to joda DateTime instances; adaptation for formatting dates/datetimes

Diffstat:
Msrc/main/antlr3/au/com/miskinhill/rdftemplate/selector/Selector.g | 11++++++++++-
Asrc/main/java/au/com/miskinhill/rdftemplate/datatype/DateTimeDataType.java | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main/java/au/com/miskinhill/rdftemplate/selector/DefaultAdaptationResolver.java | 1+
Asrc/main/java/au/com/miskinhill/rdftemplate/selector/FormattedDateTimeAdaptation.java | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main/java/au/com/miskinhill/rdftemplate/selector/LiteralValueAdaptation.java | 2+-
Msrc/test/java/au/com/miskinhill/rdftemplate/selector/AdaptationMatcher.java | 6++++++
Msrc/test/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationUnitTest.java | 17+++++++++++------
Msrc/test/java/au/com/miskinhill/rdftemplate/selector/SelectorParserUnitTest.java | 7+++++++
Msrc/test/resources/au/com/miskinhill/rdftemplate/test-data.xml | 32+++++++++++++++++++++++++++++++-
9 files changed, 218 insertions(+), 9 deletions(-)
diff --git a/src/main/antlr3/au/com/miskinhill/rdftemplate/selector/Selector.g b/src/main/antlr3/au/com/miskinhill/rdftemplate/selector/Selector.g
@@ -65,7 +65,7 @@ selector returns [Selector<?> result]
                     throw new InvalidSelectorSyntaxException("No adaptation named " + $adaptationName.text);
             }
         ( '('
-          startIndex=INTEGER {
+          ( startIndex=INTEGER {
                                 try {
                                     adaptation = adaptationClass.getConstructor(Integer.class)
                                             .newInstance(Integer.parseInt($startIndex.text));
@@ -73,6 +73,15 @@ selector returns [Selector<?> result]
                                     throw new InvalidSelectorSyntaxException(e);
                                 }
                              }
+          | sq=SINGLE_QUOTED {
+                                try {
+                                    adaptation = adaptationClass.getConstructor(String.class)
+                                            .newInstance($sq.text);
+                                } catch (Exception e) {
+                                    throw new InvalidSelectorSyntaxException(e);
+                                }
+                             }
+          )
           ')'
         | {
                try {
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/datatype/DateTimeDataType.java b/src/main/java/au/com/miskinhill/rdftemplate/datatype/DateTimeDataType.java
@@ -0,0 +1,98 @@
+package au.com.miskinhill.rdftemplate.datatype;
+
+import com.hp.hpl.jena.datatypes.DatatypeFormatException;
+import com.hp.hpl.jena.datatypes.RDFDatatype;
+import com.hp.hpl.jena.datatypes.TypeMapper;
+import com.hp.hpl.jena.graph.impl.LiteralLabel;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DateTimeDataType implements RDFDatatype {
+    
+    public static final String URI = "http://www.w3.org/TR/xmlschema-2/#datetime";
+    
+    @SuppressWarnings("unused")
+    private static DateTimeDataType instance;
+    public static void registerStaticInstance() {
+        instance = new DateTimeDataType();
+    }
+    
+    private final DateTimeFormatter parser = ISODateTimeFormat.dateTimeNoMillis();
+
+    public DateTimeDataType() {
+        TypeMapper.getInstance().registerDatatype(this);
+    }
+
+    @Override
+    public String getURI() {
+        return URI;
+    }
+    
+    @Override
+    public Class<DateTime> getJavaClass() {
+        return DateTime.class;
+    }
+
+    @Override
+    public String unparse(Object value) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Object cannonicalise(Object value) {
+        return value;
+    }
+
+    @Override
+    public Object extendedTypeDefinition() {
+        return null;
+    }
+
+    @Override
+    public int getHashCode(LiteralLabel lit) {
+        return lit.getValue().hashCode();
+    }
+
+    @Override
+    public boolean isEqual(LiteralLabel left, LiteralLabel right) {
+        return left.getValue().equals(right.getValue());
+    }
+    
+    @Override
+    public DateTime parse(String lexicalForm) throws DatatypeFormatException {
+        try {
+            return parser.parseDateTime(lexicalForm);
+        } catch (IllegalArgumentException e) {
+            throw new DatatypeFormatException(lexicalForm, this, "Parser barfed");
+        }
+    }
+
+    @Override
+    public boolean isValid(String lexicalForm) {
+        try {
+            parse(lexicalForm);
+            return true;
+        } catch (DatatypeFormatException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isValidLiteral(LiteralLabel lit) {
+        return lit.getDatatypeURI().equals(URI) && isValid(lit.getLexicalForm());
+    }
+
+    @Override
+    public boolean isValidValue(Object valueForm) {
+        return (valueForm instanceof DateTime);
+    }
+
+    @Override
+    public RDFDatatype normalizeSubType(Object value, RDFDatatype dt) {
+        return dt;
+    }
+
+}
+\ No newline at end of file
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/DefaultAdaptationResolver.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/DefaultAdaptationResolver.java
@@ -11,6 +11,7 @@ public class DefaultAdaptationResolver implements AdaptationResolver {
         ADAPTATIONS.put("uri-slice", UriSliceAdaptation.class);
         ADAPTATIONS.put("lv", LiteralValueAdaptation.class);
         ADAPTATIONS.put("comparable-lv", ComparableLiteralValueAdaptation.class);
+        ADAPTATIONS.put("formatted-dt", FormattedDateTimeAdaptation.class);
     }
 
     @Override
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/FormattedDateTimeAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/FormattedDateTimeAdaptation.java
@@ -0,0 +1,52 @@
+package au.com.miskinhill.rdftemplate.selector;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+import com.hp.hpl.jena.rdf.model.Literal;
+import com.hp.hpl.jena.rdf.model.RDFNode;
+import org.joda.time.ReadableInstant;
+import org.joda.time.ReadablePartial;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+public class FormattedDateTimeAdaptation implements Adaptation<String> {
+    
+    private final String pattern;
+    
+    public FormattedDateTimeAdaptation(String pattern) {
+        this.pattern = pattern;
+    }
+
+    public String getPattern() {
+        return pattern;
+    }
+    
+    @Override
+    public Class<String> getDestinationType() {
+        return String.class;
+    }
+
+    @Override
+    public String adapt(RDFNode node) {
+        if (!node.isLiteral()) {
+            throw new SelectorEvaluationException("Attempted to apply #formatted-dt to non-literal node " + node);
+        }
+        DateTimeFormatter formatter = DateTimeFormat.forPattern(pattern);
+        Object lv = ((Literal) node).getValue();
+        if (lv instanceof ReadableInstant) {
+            ReadableInstant instant = (ReadableInstant) lv;
+            return formatter.print(instant);
+        } else if (lv instanceof ReadablePartial) {
+            ReadablePartial instant = (ReadablePartial) lv;
+            return formatter.print(instant);
+        } else {
+            throw new SelectorEvaluationException("Attempted to apply #formatted-dt to non-datetime literal " + lv);
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this).append("pattern", pattern).toString();
+    }
+
+}
diff --git a/src/main/java/au/com/miskinhill/rdftemplate/selector/LiteralValueAdaptation.java b/src/main/java/au/com/miskinhill/rdftemplate/selector/LiteralValueAdaptation.java
@@ -13,7 +13,7 @@ public class LiteralValueAdaptation implements Adaptation<Object> {
     @Override
     public Object adapt(RDFNode node) {
         if (!node.isLiteral()) {
-            throw new SelectorEvaluationException("Attempted to apply #comparable-lv to non-literal node " + node);
+            throw new SelectorEvaluationException("Attempted to apply #lv to non-literal node " + node);
         }
         return ((Literal) node).getValue();
     }
diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/AdaptationMatcher.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/AdaptationMatcher.java
@@ -26,5 +26,11 @@ public class AdaptationMatcher<T extends Adaptation<?>> extends BeanPropertyMatc
     public static AdaptationMatcher<ComparableLiteralValueAdaptation> comparableLVAdaptation() {
         return new AdaptationMatcher<ComparableLiteralValueAdaptation>(ComparableLiteralValueAdaptation.class);
     }
+    
+    public static AdaptationMatcher<FormattedDateTimeAdaptation> formattedDTAdaptation(String pattern) {
+        AdaptationMatcher<FormattedDateTimeAdaptation> m = new AdaptationMatcher<FormattedDateTimeAdaptation>(FormattedDateTimeAdaptation.class);
+        m.addRequiredProperty("pattern", equalTo(pattern));
+        return m;
+    }
 
 }
diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorEvaluationUnitTest.java
@@ -1,10 +1,5 @@
 package au.com.miskinhill.rdftemplate.selector;
 
-import static au.com.miskinhill.rdftemplate.selector.AdaptationMatcher.comparableLVAdaptation;
-import static au.com.miskinhill.rdftemplate.selector.SelectorComparatorMatcher.selectorComparator;
-import static au.com.miskinhill.rdftemplate.selector.SelectorMatcher.selector;
-import static au.com.miskinhill.rdftemplate.selector.TraversalMatcher.traversal;
-
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.assertThat;
 import static org.junit.matchers.JUnitMatchers.hasItems;
@@ -22,16 +17,18 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 
 import au.com.miskinhill.rdftemplate.datatype.DateDataType;
+import au.com.miskinhill.rdftemplate.datatype.DateTimeDataType;
 
 public class SelectorEvaluationUnitTest {
     
     private Model m;
-    private Resource journal, issue, article, citedArticle, author, anotherAuthor, book, review, anotherReview, obituary, en, ru;
+    private Resource journal, issue, article, citedArticle, author, anotherAuthor, book, review, anotherReview, obituary, en, ru, forum;
     private SelectorFactory selectorFactory;
     
     @BeforeClass
     public static void ensureDatatypesRegistered() {
         DateDataType.registerStaticInstance();
+        DateTimeDataType.registerStaticInstance();
     }
     
     @Before
@@ -51,6 +48,7 @@ public class SelectorEvaluationUnitTest {
         obituary = m.createResource("http://miskinhill.com.au/journals/test/1:1/in-memoriam-john-doe");
         en = m.createResource("http://www.lingvoj.org/lang/en");
         ru = m.createResource("http://www.lingvoj.org/lang/ru");
+        forum = m.createResource("http://miskinhill.com.au/");
         selectorFactory = new AntlrSelectorFactory();
     }
     
@@ -177,4 +175,11 @@ public class SelectorEvaluationUnitTest {
         assertThat(results.get(2), equalTo((RDFNode) review));
     }
     
+    @Test
+    public void shouldEvaluateFormattedDTAdaptation() throws Exception {
+        String result = selectorFactory.get("!sioc:has_container/dc:created#formatted-dt('d MMMM yyyy')")
+                .withResultType(String.class).singleResult(forum);
+        assertThat(result, equalTo("15 June 2009"));
+    }
+    
 }
diff --git a/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorParserUnitTest.java b/src/test/java/au/com/miskinhill/rdftemplate/selector/SelectorParserUnitTest.java
@@ -166,6 +166,13 @@ public class SelectorParserUnitTest {
                     selectorComparator(selector(traversal("mhs", "startPage")).withAdaptation(comparableLVAdaptation())))));
     }
     
+    @Test
+    public void shouldRecogniseFormattedDTAdaptation() throws Exception {
+        Selector<?> selector = factory.get("dc:created#formatted-dt('d MMMM yyyy')");
+        assertThat(selector, selector(traversal("dc", "created"))
+                .withAdaptation(formattedDTAdaptation("d MMMM yyyy")));
+    }
+    
     @Test(expected = InvalidSelectorSyntaxException.class)
     public void shouldThrowForInvalidSyntax() throws Exception {
         factory.get("dc:creator]["); // this is a parser error
diff --git a/src/test/resources/au/com/miskinhill/rdftemplate/test-data.xml b/src/test/resources/au/com/miskinhill/rdftemplate/test-data.xml
@@ -5,7 +5,37 @@
          xmlns:contact="http://www.w3.org/2000/10/swap/pim/contact#"
          xmlns:geonames="http://www.geonames.org/ontology#"
          xmlns:lingvoj="http://www.lingvoj.org/ontology#"
-         xmlns:mhs="http://miskinhill.com.au/rdfschema/1.0/">
+         xmlns:mhs="http://miskinhill.com.au/rdfschema/1.0/"
+         xmlns:sioc="http://rdfs.org/sioc/ns#"
+         xmlns:awol="http://bblfish.net/work/atom-owl/2006-06-06/#">
+
+    <sioc:Forum rdf:about="http://miskinhill.com.au/">
+        <dc:title xml:lang="en">News</dc:title>
+    </sioc:Forum>
+    <sioc:Post rdf:about="http://miskinhill.com.au/news/data-feeds">
+        <dc:created rdf:datatype="http://www.w3.org/TR/xmlschema-2/#datetime">2009-06-15T18:21:32+10:00</dc:created>
+        <dc:title xml:lang="en">New data feeds available</dc:title>
+        <sioc:content>
+            <awol:Content>
+                <awol:body rdf:parseType="Literal">
+                    <div xmlns="http://www.w3.org/1999/xhtml">
+            <p>To coincide with the publication of our second issue, the <a 
+            href="/journals/asees/22:1-2/">2008 volume</a> of <a 
+            href="/journals/asees/"><em>Australian Slavonic and East European 
+            Studies</em></a>, we are making available two new data feeds: an <a 
+            href="/feeds/issues">Atom feed of all journal issues</a> published 
+            on this site, and the <a href="/feeds/world">complete RDF 
+            dataset</a> underlying the site.</p>
+            <p>We hope this helps our users and aggregators to discover new 
+            content as it is published.</p>
+                    </div>
+                </awol:body>
+                <awol:type>application/xhtml+xml</awol:type>
+            </awol:Content>
+        </sioc:content>
+        <sioc:has_container rdf:resource="http://miskinhill.com.au/" />
+    </sioc:Post>
+
   <mhs:Journal rdf:about="http://miskinhill.com.au/journals/test/">
     <dc:title xml:lang="en">Test Journal of Good Stuff</dc:title>
     <dc:publisher rdf:nodeID="pub"/>