Browse Source

Initial TTL encoding iteration; make codec iterator opaque.

Stefano Cossu 1 year ago
parent
commit
c8fef9a7c5
13 changed files with 305 additions and 126 deletions
  1. 21 31
      include/codec.h
  2. 3 0
      include/core.h
  3. 3 3
      include/graph.h
  4. 1 1
      include/namespace.h
  5. 22 2
      include/term.h
  6. 16 0
      src/codec.c
  7. 26 15
      src/codec/codec_nt.c
  8. 139 54
      src/codec/codec_ttl.c
  9. 5 5
      src/graph.c
  10. 22 5
      src/term.c
  11. 5 5
      test/test_codec_nt.c
  12. 40 1
      test/test_codec_ttl.c
  13. 2 4
      test/test_namespace.c

+ 21 - 31
include/codec.h

@@ -17,28 +17,6 @@
 typedef struct codec_t LSUP_Codec;
 
 
-/** @brief Codec iterator type.
- *
- * This structure holds state data including input and output for encoding a
- * graph into RDF. Normally it should not be inspected or manipulated directly,
- * but rather passed to codec iteration functions for processing RDF.
- *
- * NOTE: This should be used as an opaque handle, however it is exposed here
- * for easier inclusion into each codec.
- */
-typedef struct codec_iter_t {
-    const LSUP_Codec *  codec;      // Codec that generated this iterator.
-    LSUP_Triple *       trp;        // RDF fragment being encoded.
-    LSUP_GraphIterator *gr_it;      // Graph iterator.
-    const LSUP_NSMap *  nsm;        // Namespace map.
-    size_t              cur;        // Internal cursor.
-    LSUP_rc             rc;         // Internal return code.
-    char *              rep,        // String representation of a RDF fragment.
-         *              str_s,      // Temporary string.
-         *              str_p,      // Temporary string.
-         *              str_o;      // Temporary string.
-} LSUP_CodecIterator;
-
 /// Parser state.
 typedef struct {
     LSUP_GraphIterator *    it;     ///< Iterator used to build the graph.
@@ -93,9 +71,10 @@ typedef LSUP_rc (*term_enc_fn_t)(
  * loop is finalized.
  *
  * @return A codec iterator handle to be passed to a #gr_codec_iter_fn_t
- * function and, eventually, to a #gr_codec_done_fn_t function.
+ * function and, eventually, to a #gr_codec_done_fn_t function. This
+ * structure is opaque and defined by each codec according to its own needs.
  */
-typedef LSUP_CodecIterator * (*gr_encode_init_fn_t)(const LSUP_Graph *gr);
+typedef void * (*gr_encode_init_fn_t)(const LSUP_Graph *gr);
 
 
 /** @brief Perform one encoding iteration.
@@ -108,16 +87,15 @@ typedef LSUP_CodecIterator * (*gr_encode_init_fn_t)(const LSUP_Graph *gr);
  * @param[in] it Iterator handle.
  *
  * @param[out] res Handle to be populated with a string obtained from encoding.
- *  The output data should be UTF-8 [TODO or UTF-16] encoded. This pointer
- *  must be initialized (even to NULL) and should be eventually freed manually
- *  at the end of the loop. It is reallocated at each iteration, so memory from
- *  a previous iteration may be overwritten with new data.
+ *  The output data should be UTF-8 encoded. This pointer must be initialized
+ *  (even to NULL) and should be eventually freed manually at the end of the
+ *  loop. It is reallocated at each iteration, so memory from a previous
+ *  iteration may be overwritten with new data.
  *
  * @return LSUP_OK if a new token was processed; LSUP_END if the end of the
  *  loop was reached.
  */
-typedef LSUP_rc (*gr_encode_iter_fn_t)(
-        LSUP_CodecIterator *it, unsigned char **res);
+typedef LSUP_rc (*gr_encode_iter_fn_t)(void *it, char **res);
 
 
 /** @brief Finalize an encoding operation.
@@ -128,7 +106,7 @@ typedef LSUP_rc (*gr_encode_iter_fn_t)(
  *
  * @param[in] it Iterator handle.
  */
-typedef void (*gr_encode_done_fn_t)(LSUP_CodecIterator *it);
+typedef void (*gr_encode_done_fn_t)(void *it);
 
 
 /** @brief Prototype for decoding a string into a LSUP_Term.
@@ -303,4 +281,16 @@ unescape_char (const char c)
 uint8_t *unescape_unicode (const uint8_t *esc_str, size_t size);
 
 
+/** @brief Format an informational header.
+ *
+ * The information includes software version and current date. It is terminated
+ * by a newline + NUL and prefixed with the string specified in `pfx`. It is
+ * NOT prefixed by any comment characters.
+ *
+ * @param[in] pfx Prefix to add to the string. It may be a comment starter,
+ *  such as `# `.
+ */
+char *fmt_header (char *pfx);
+
+
 #endif

+ 3 - 0
include/core.h

@@ -17,6 +17,9 @@
 #include "xxhash.h"
 
 
+#define LSUP_VERSION "1.0a1"
+
+
 // Logging and debugging.
 
 #ifdef DEBUG

+ 3 - 3
include/graph.h

@@ -339,7 +339,7 @@ LSUP_graph_iter_free (LSUP_GraphIterator *it);
  */
 LSUP_LinkMap *
 LSUP_graph_connections (
-        LSUP_Graph *gr, LSUP_Term *t, LSUP_LinkType type);
+        const LSUP_Graph *gr, LSUP_Term *t, LSUP_LinkType type);
 
 
 /** @brief Get a list of terms related to a term pair in a graph.
@@ -358,7 +358,7 @@ LSUP_graph_connections (
  */
 LSUP_TermSet *
 LSUP_graph_term_set (
-        LSUP_Graph *gr, LSUP_Term *t1, LSUP_TriplePos t1_pos,
+        const LSUP_Graph *gr, LSUP_Term *t1, LSUP_TriplePos t1_pos,
         LSUP_Term *t2, LSUP_TriplePos t2_pos);
 
 
@@ -369,7 +369,7 @@ LSUP_graph_term_set (
  * @param[in] pos Position in the triples of the terms to look for.
  */
 LSUP_TermSet *
-LSUP_graph_unique_terms (LSUP_Graph *gr, LSUP_TriplePos pos);
+LSUP_graph_unique_terms (const LSUP_Graph *gr, LSUP_TriplePos pos);
 
 
 /** @brief Add triples for a term and related connection list to a graph.

+ 1 - 1
include/namespace.h

@@ -142,7 +142,7 @@ LSUP_nsmap_denormalize_uri (
  *
  * @return 2-dimensional array of strings, with as many rows as namespace
  *  entries, and two columns: the first for the namespace prefix, the second
- *  for the namespace.
+ *  for the namespace. The last entry is NULL.
  */
 const char ***
 LSUP_nsmap_dump (const LSUP_NSMap *map);

+ 22 - 2
include/term.h

@@ -612,9 +612,11 @@ LSUP_link_map_add (
 /** @brief Create a new iterator to loop through a link map.
  *
  * @param[in] lmap Map handle to iterate.
+ *
+ * @param[in] ext External term to look for connections.
  */
 LSUP_LinkMapIterator *
-LSUP_link_map_iter_new (const LSUP_LinkMap *lmap);
+LSUP_link_map_iter_new (const LSUP_LinkMap *lmap, LSUP_Term *ext);
 
 
 /// Free a link map iterator.
@@ -622,6 +624,24 @@ void
 LSUP_link_map_iter_free (LSUP_LinkMapIterator *it);
 
 
+/** @brief Iterate through a link map.
+ *
+ * Each call to this function yields a linked term and the related term set.
+ *
+ * @param[in] it Link map iterator obtained with #LSUP_link_map_iter_new().
+ *
+ * @param[out] lt Linked term returned.
+ *
+ * @param[out] ts Term set returned.
+ *
+ * @return LSUP_OK if a result was yielded; LSUP_END if the end of the link map
+ *  has been reached.
+ */
+LSUP_rc
+LSUP_link_map_next (
+        LSUP_LinkMapIterator *it, LSUP_Term **lt, LSUP_TermSet **ts);
+
+
 /**@brief Iterate over a link map and generate triples.
  *
  * Calling this function repeatedly builds triples for all the linked terms and
@@ -642,6 +662,6 @@ LSUP_link_map_iter_free (LSUP_LinkMapIterator *it);
  */
 LSUP_rc
 LSUP_link_map_triples (
-        LSUP_LinkMapIterator *it, LSUP_Term *term, LSUP_Triple *spo);
+        LSUP_LinkMapIterator *it, LSUP_Triple *spo);
 
 #endif

+ 16 - 0
src/codec.c

@@ -112,6 +112,22 @@ escape_lit (const char *in, char **out_p)
 }
 
 
+char *
+fmt_header (char *pfx)
+{
+    char *body = "Generated by lsup_rdf v" LSUP_VERSION " on ";
+    time_t now = time (NULL);
+    char date[16];
+    strftime (date, sizeof (date), "%m/%d/%Y", gmtime (&now));
+
+    char *out = malloc (strlen (pfx) + strlen (body) + strlen (date) + 2);
+    if (UNLIKELY (!out)) return NULL;
+
+    sprintf (out, "%s%s%s\n", pfx, body, date);
+
+    return out;
+}
+
 /*
  * Extern inline functions.
  */

+ 26 - 15
src/codec/codec_nt.c

@@ -1,6 +1,21 @@
 #include "codec/codec_nt.h"
 
 
+/// NT codec iterator type.
+typedef struct {
+    const LSUP_Codec *  codec;      // Codec that generated this iterator.
+    LSUP_Triple *       trp;        // RDF fragment being encoded.
+    LSUP_GraphIterator *gr_it;      // Graph iterator.
+    const LSUP_NSMap *  nsm;        // Namespace map.
+    size_t              cur;        // Internal cursor.
+    LSUP_rc             rc;         // Internal return code.
+    char *              str_s,      // Temporary string.
+         *              str_p,      // Temporary string.
+         *              str_o;      // Temporary string.
+} LSUP_NTCodecIterator;
+
+
+
 /* * * Codec functions. * * */
 
 static LSUP_rc
@@ -62,7 +77,7 @@ term_to_nt (const LSUP_Term *term, const LSUP_NSMap *nsm, char **out_p)
                 return LSUP_ERROR;
             buf_len = strlen (escaped) + 3; // Room for "" and terminator
 
-            if (term->lang != 0) {
+            if (term->lang[0] != '\0') {
                 metadata = term->lang;
                 buf_len += strlen (metadata) + 1; // Room for @
             }
@@ -99,12 +114,13 @@ term_to_nt (const LSUP_Term *term, const LSUP_NSMap *nsm, char **out_p)
 }
 
 
-static LSUP_CodecIterator *
+static void *
 gr_to_nt_init (const LSUP_Graph *gr);
 
 
 static LSUP_rc
-gr_to_nt_iter (LSUP_CodecIterator *it, unsigned char **res) {
+gr_to_nt_iter (void *h, char **res) {
+    LSUP_NTCodecIterator *it = h;
     LSUP_rc rc = LSUP_graph_iter_next (it->gr_it, it->trp);
     if (rc != LSUP_OK) goto finally;
 
@@ -113,7 +129,7 @@ gr_to_nt_iter (LSUP_CodecIterator *it, unsigned char **res) {
     term_to_nt (it->trp->o, it->nsm, &it->str_o);
 
     // 3 term separators + dot + newline + terminal = 6
-    unsigned char *tmp = realloc (
+    char *tmp = realloc (
             *res, strlen (it->str_s) + strlen (it->str_p)
             + strlen (it->str_o) + 6);
     if (UNLIKELY (!tmp)) {
@@ -122,7 +138,7 @@ gr_to_nt_iter (LSUP_CodecIterator *it, unsigned char **res) {
         goto finally;
     }
 
-    sprintf ((char*)tmp, "%s %s %s .\n", it->str_s, it->str_p, it->str_o);
+    sprintf (tmp, "%s %s %s .\n", it->str_s, it->str_p, it->str_o);
     *res = tmp;
 
     it->cur++;
@@ -137,11 +153,11 @@ finally:
 
 
 static void
-gr_to_nt_done (LSUP_CodecIterator *it)
+gr_to_nt_done (void *h)
 {
+    LSUP_NTCodecIterator *it = h;
     LSUP_graph_iter_free (it->gr_it);
     LSUP_triple_free (it->trp);
-    free (it->rep);
     free (it->str_s);
     free (it->str_p);
     free (it->str_o);
@@ -167,21 +183,16 @@ const LSUP_Codec nt_codec = {
 
 /* * * Other static functions. * * */
 
-static LSUP_CodecIterator *
+static void *
 gr_to_nt_init (const LSUP_Graph *gr)
 {
-    LSUP_CodecIterator *it;
-    MALLOC_GUARD (it, NULL);
+    LSUP_NTCodecIterator *it;
+    CALLOC_GUARD (it, NULL);
 
     it->codec = &nt_codec;
     it->gr_it = LSUP_graph_lookup(gr, NULL, NULL, NULL, &it->cur);
     it->nsm = LSUP_graph_namespace (gr);
-    it->cur = 0;
     it->trp = TRP_DUMMY;
-    it->rep = NULL;
-    it->str_s = NULL;
-    it->str_p = NULL;
-    it->str_o = NULL;
 
     return it;
 }

+ 139 - 54
src/codec/codec_ttl.c

@@ -1,6 +1,22 @@
 #include "codec/codec_ttl.h"
 
 
+/** @brief NT codec iterator.
+ *
+ * This iterator yields one or more triples at a time, one group per subject,
+ * with the most compact form allowed by Turtle, e.g.
+ *
+ * :s :p1 :o1, :o2, o3; p2 o4, o5, <http://example.com/ext1> .
+ */
+typedef struct {
+    const LSUP_Codec *  codec;      ///< Codec that generated this iterator.
+    const LSUP_Graph    *gr;        ///< Graph being encoded.
+    LSUP_TermSet *      subjects;   ///< All subjects in the graph.
+    size_t              s_cur;      ///< Term set cursor.
+    LSUP_rc             rc;         ///< Internal return code.
+} LSUP_TTLCodecIterator;
+
+
 /* * * Codec functions. * * */
 
 static LSUP_rc
@@ -61,7 +77,7 @@ term_to_ttl (const LSUP_Term *term, const LSUP_NSMap *nsm, char **out_p)
                 return LSUP_ERROR;
             buf_len = strlen (escaped) + 3; // Room for "" and terminator
 
-            if (term->lang != 0) {
+            if (term->lang[0] != '\0') {
                 metadata = term->lang;
                 buf_len += strlen (metadata) + 1; // Room for @
             }
@@ -98,52 +114,143 @@ term_to_ttl (const LSUP_Term *term, const LSUP_NSMap *nsm, char **out_p)
 }
 
 
-static LSUP_CodecIterator *
-gr_to_ttl_init (const LSUP_Graph *gr);
+static void *
+gr_to_ttl_init (const LSUP_Graph *gr)
+{
+    LSUP_TTLCodecIterator *it;
+    MALLOC_GUARD (it, NULL);
+
+    it->codec = &ttl_codec;
+    it->gr = gr;
+    it->subjects = LSUP_graph_unique_terms (gr, TRP_POS_S);
+    // Sets the condition to build the prolog on 1st iteration.
+    it->rc = LSUP_NORESULT;
+
+    return it;
+}
 
 
+/// Build header and prolog.
 static LSUP_rc
-gr_to_ttl_iter (LSUP_CodecIterator *it, unsigned char **res) {
-    LSUP_rc rc = LSUP_graph_iter_next (it->gr_it, it->trp);
-    if (rc != LSUP_OK) goto finally;
-
-    term_to_ttl (it->trp->s, it->nsm, &it->str_s);
-    term_to_ttl (it->trp->p, it->nsm, &it->str_p);
-    term_to_ttl (it->trp->o, it->nsm, &it->str_o);
-
-    // 3 term separators + dot + newline + terminal = 6
-    unsigned char *tmp = realloc (
-            *res, strlen (it->str_s) + strlen (it->str_p)
-            + strlen (it->str_o) + 6);
+build_prolog (LSUP_TTLCodecIterator *it, char **res_p)
+{
+    char *res = fmt_header ("# ");
+
+    const char ***nsm = LSUP_nsmap_dump (LSUP_graph_namespace (it->gr));
+    const char *ns_tpl = "@prefix %s: <%s>\n";
+
+    // Prefix map.
+    for (size_t i = 0; nsm[i]; i++) {
+        const char **ns = nsm[i];
+        size_t old_len = strlen (res);
+        size_t ns_len = strlen (ns[0]) + strlen (ns[1]) + strlen (ns_tpl);
+        char *tmp = realloc (nsm, old_len + ns_len);
+        if (!UNLIKELY (!tmp)) return LSUP_MEM_ERR;
+
+        sprintf (res + old_len, ns_tpl, ns[0], ns[1]);
+    }
+
+    // Base.
+    char *base_uri_str;
+    LSUP_rc rc = LSUP_nsmap_denormalize_uri (
+            LSUP_graph_namespace (it->gr), LSUP_graph_uri (it->gr)->data,
+            &base_uri_str);
+    PRCCK (rc);
+    char *base_stmt_tpl = "\n@base <%s> .\n\n";
+    char *base_stmt = malloc (strlen (base_stmt_tpl) + strlen (base_uri_str));
+    if (!UNLIKELY (base_stmt)) return LSUP_MEM_ERR;
+    sprintf (base_stmt, base_stmt_tpl, base_uri_str);
+    res = realloc (res, strlen (res) + strlen (base_stmt));
+    if (!UNLIKELY (res)) return LSUP_MEM_ERR;
+    res = strcat (res, base_stmt);
+
+    *res_p = res;
+    it->rc = LSUP_OK;
+
+    return LSUP_OK;
+}
+
+
+/// Encode all the triples for a single subject.
+static LSUP_rc
+gr_to_ttl_iter (void *h, char **res_p) {
+    LSUP_TTLCodecIterator *it = h;
+
+    if (it->rc == LSUP_NORESULT) return build_prolog (it, res_p);
+
+    LSUP_Term *s = NULL;
+    char *res = NULL;  // Result string.
+    RCCK (LSUP_term_set_next (it->subjects, &it->s_cur, &s));
+
+    term_to_ttl (s, LSUP_graph_namespace (it->gr), &res);
+
+    LSUP_LinkMap *lmap = LSUP_graph_connections (
+            it->gr, s, LSUP_LINK_OUTBOUND);
+
+    LSUP_LinkMapIterator *lmit = LSUP_link_map_iter_new (lmap, s);
+    LSUP_Term *p = NULL;
+    char *p_str = NULL;
+    LSUP_TermSet *o_ts = NULL;
+    char *p_join = " ";
+    // Begin predicate loop.
+    while (LSUP_link_map_next (lmit, &p, &o_ts) != LSUP_END) {
+        // Add predicate representation.
+        RCCK (term_to_ttl (p, LSUP_graph_namespace (it->gr), &p_str));
+        char *tmp = realloc (
+                res, strlen (res) + strlen (p_str) + strlen (p_join) + 1);
+        if (UNLIKELY (!tmp)) {
+            it->rc = LSUP_MEM_ERR;
+            goto finally;
+        }
+        res = strcat (strcat (tmp, p_join), p_str);
+
+        free (p_str);
+        p_join = "; ";
+
+        // Add objects for predicate.
+        size_t i = 0;
+        LSUP_Term *o = NULL;
+        char *o_str = NULL;
+        char *o_join = " ";
+        while (LSUP_term_set_next (o_ts, &i, &o) != LSUP_END) {
+            it->rc = term_to_ttl (o, LSUP_graph_namespace (it->gr), &o_str);
+            RCCK (it->rc);
+            char *tmp = realloc (
+                    res, strlen (res) + strlen (o_str) + strlen (o_join) + 1);
+            if (UNLIKELY (!tmp)) {
+                it->rc = LSUP_MEM_ERR;
+                goto finally;
+            }
+            res = strcat (strcat (tmp, o_join), o_str);
+
+            free (o_str);
+            o_join = ", ";
+        }
+        PRCCK (it->rc);
+    }
+
+    char *s_sep = ".\n";
+    char *tmp = realloc (res, strlen (res) + strlen (s_sep) + 1);
     if (UNLIKELY (!tmp)) {
-        *res = NULL;
-        rc = LSUP_MEM_ERR;
+        res = NULL;
+        it->rc = LSUP_MEM_ERR;
         goto finally;
     }
 
-    sprintf ((char*)tmp, "%s %s %s .\n", it->str_s, it->str_p, it->str_o);
-    *res = tmp;
-
-    it->cur++;
+    *res_p = strcat (res, s_sep);
 
 finally:
-    LSUP_term_free (it->trp->s); it->trp->s = NULL;
-    LSUP_term_free (it->trp->p); it->trp->p = NULL;
-    LSUP_term_free (it->trp->o); it->trp->o = NULL;
+    LSUP_link_map_iter_free (lmit);
 
-    return rc;
+    return it->rc;
 }
 
 
 static void
-gr_to_ttl_done (LSUP_CodecIterator *it)
+gr_to_ttl_done (void *h)
 {
-    LSUP_graph_iter_free (it->gr_it);
-    LSUP_triple_free (it->trp);
-    free (it->rep);
-    free (it->str_s);
-    free (it->str_p);
-    free (it->str_o);
+    LSUP_TTLCodecIterator *it = h;
+    LSUP_term_set_free (it->subjects);
     free (it);
 }
 
@@ -162,25 +269,3 @@ const LSUP_Codec ttl_codec = {
     //.decode_term        = LSUP_ttl_parse_term,
     .decode_graph       = LSUP_ttl_parse_doc,
 };
-
-
-/* * * Other static functions. * * */
-
-static LSUP_CodecIterator *
-gr_to_ttl_init (const LSUP_Graph *gr)
-{
-    LSUP_CodecIterator *it;
-    MALLOC_GUARD (it, NULL);
-
-    it->codec = &ttl_codec;
-    it->gr_it = LSUP_graph_lookup(gr, NULL, NULL, NULL, &it->cur);
-    it->nsm = LSUP_graph_namespace (gr);
-    it->cur = 0;
-    it->trp = TRP_DUMMY;
-    it->rep = NULL;
-    it->str_s = NULL;
-    it->str_p = NULL;
-    it->str_o = NULL;
-
-    return it;
-}

+ 5 - 5
src/graph.c

@@ -506,7 +506,7 @@ LSUP_graph_abort (LSUP_Graph *gr)
 
 LSUP_LinkMap *
 LSUP_graph_connections (
-        LSUP_Graph *gr, LSUP_Term *t, LSUP_LinkType type)
+        const LSUP_Graph *gr, LSUP_Term *t, LSUP_LinkType type)
 {
     LSUP_Term
         *s = NULL,
@@ -563,7 +563,7 @@ LSUP_graph_connections (
 
 LSUP_TermSet *
 LSUP_graph_term_set (
-        LSUP_Graph *gr, LSUP_Term *t1, LSUP_TriplePos t1_pos,
+        const LSUP_Graph *gr, LSUP_Term *t1, LSUP_TriplePos t1_pos,
         LSUP_Term *t2, LSUP_TriplePos t2_pos)
 {
     if (t1_pos == t2_pos) {
@@ -597,7 +597,7 @@ LSUP_graph_term_set (
 
 
 LSUP_TermSet *
-LSUP_graph_unique_terms (LSUP_Graph *gr, LSUP_TriplePos pos)
+LSUP_graph_unique_terms (const LSUP_Graph *gr, LSUP_TriplePos pos)
 {
     // TODO We should use spo indices for stores that have them...
     LSUP_GraphIterator *it = LSUP_graph_lookup (gr, NULL, NULL, NULL, NULL);
@@ -624,9 +624,9 @@ LSUP_graph_add_link_map (
 {
     LSUP_Triple *spo = TRP_DUMMY;
     size_t ct = 0;
-    LSUP_LinkMapIterator *lmit = LSUP_link_map_iter_new (lmap);
+    LSUP_LinkMapIterator *lmit = LSUP_link_map_iter_new (lmap, t);
 
-    while (LSUP_link_map_triples (lmit, t, spo) != LSUP_END) {
+    while (LSUP_link_map_triples (lmit, spo) != LSUP_END) {
         LSUP_rc rc = LSUP_graph_add_iter (it, spo);
         if (rc >= 0) ct++;
         PRCCK (rc);

+ 22 - 5
src/term.c

@@ -47,6 +47,7 @@ struct link_map_iter {
     const LSUP_LinkMap *map;        ///< Link map to iterate.
     size_t              i;          ///< External loop cursor.
     size_t              j;          ///< Internal loop cursor.
+    LSUP_Term *         ext;        ///< External link to look for connections.
     Link *              link;       ///< Current link being retrieved.
 };
 
@@ -660,11 +661,12 @@ LSUP_link_map_add (
 
 
 LSUP_LinkMapIterator *
-LSUP_link_map_iter_new (const LSUP_LinkMap *lmap)
+LSUP_link_map_iter_new (const LSUP_LinkMap *lmap, LSUP_Term *ext)
 {
     LSUP_LinkMapIterator *it;
     CALLOC_GUARD (it, NULL);
     it->map = lmap;
+    it->ext = ext;
 
     return it;
 }
@@ -675,16 +677,31 @@ LSUP_link_map_iter_free (LSUP_LinkMapIterator *it)
 { free (it); }
 
 
+LSUP_rc
+LSUP_link_map_next (
+        LSUP_LinkMapIterator *it, LSUP_Term **lt, LSUP_TermSet **ts)
+{
+    if (!hashmap_iter (it->map->links, &it->i, (void **)&it->link))
+        return LSUP_END;
+
+    *lt = it->link->term->term;
+    *ts = it->link->tset;
+
+    return LSUP_OK;
+}
+
+
+// TODO dismantle if the only triple generator is for the graph.
 LSUP_rc
 LSUP_link_map_triples (
-        LSUP_LinkMapIterator *it, LSUP_Term *term, LSUP_Triple *spo)
+        LSUP_LinkMapIterator *it, LSUP_Triple *spo)
 {
     // Assign external (related) term.
     if (it->map->type == LSUP_LINK_INBOUND)
-        spo->o = term;
+        spo->o = it->ext;
     else if (it->map->type == LSUP_LINK_OUTBOUND)
-        spo->s = term;
-    else spo->p = term;
+        spo->s = it->ext;
+    else spo->p = it->ext;
 
     KeyedTerm *kt;
 

+ 5 - 5
test/test_codec_nt.c

@@ -96,9 +96,9 @@ char *end_nt_doc[7] = {
 };
 
 
-
 static LSUP_Triple trp[8];
 
+
 void
 init_triples (LSUP_Term **terms)
 {
@@ -171,12 +171,12 @@ test_encode_nt_graph()
     LSUP_graph_add (gr, trp, &ins);
 
     char *out = calloc (1, 1);
-    LSUP_CodecIterator *it = codec.encode_graph_init (gr);
+    void *it = codec.encode_graph_init (gr);
     ASSERT (it != NULL, "Error creating codec iterator!");
 
     char *tmp = NULL;
     LSUP_rc rc;
-    while ((rc = codec.encode_graph_iter (it, (unsigned char **)&tmp)) != LSUP_END) {
+    while ((rc = codec.encode_graph_iter (it, &tmp)) != LSUP_END) {
         ASSERT (rc >= 0, "Encoding step failed!");
         out = realloc (out, strlen(out) + strlen (tmp) + 1);
         out = strcat (out, tmp);
@@ -228,8 +228,8 @@ test_decode_nt_graph()
     // For debugging: dump the decoded graph into NT.
     char *out = calloc (1, 1);
     char *tmp = NULL;
-    LSUP_CodecIterator *it = codec.encode_graph_init (gr);
-    while (codec.encode_graph_iter (it, (unsigned char **)&tmp) != LSUP_END) {
+    void *it = codec.encode_graph_init (gr);
+    while (codec.encode_graph_iter (it, &tmp) != LSUP_END) {
         out = realloc (out, strlen(out) + strlen (tmp) + 1);
         out = strcat (out, tmp);
     }

+ 40 - 1
test/test_codec_ttl.c

@@ -7,6 +7,45 @@
 #define W3C_TEST_BASE "http://www.w3.org/2001/sw/DataAccess/df1/tests/"
 
 
+static LSUP_Triple trp[8];
+
+
+/// Encode a graph as TTL.
+int
+test_encode_ttl_graph()
+{
+    log_info ("Test encoding graph document.");
+    LSUP_Graph *gr = LSUP_graph_new (
+            LSUP_iriref_new (NULL, NULL), LSUP_STORE_HTABLE, NULL, NULL, 0);
+    if (!gr) return LSUP_MEM_ERR;
+
+    size_t ins;
+    LSUP_graph_add (gr, trp, &ins);
+
+    char *out = calloc (1, 1);
+    void *it = codec.encode_graph_init (gr);
+    ASSERT (it != NULL, "Error creating codec iterator!");
+
+    char *tmp = NULL;
+    LSUP_rc rc;
+    while ((rc = codec.encode_graph_iter (it, &tmp)) != LSUP_END) {
+        printf ("Serialized graph: %s\n", tmp);
+        ASSERT (rc >= 0, "Encoding step failed!");
+        out = realloc (out, strlen(out) + strlen (tmp) + 1);
+        out = strcat (out, tmp);
+    }
+    codec.encode_graph_done (it);
+    free (tmp);
+    LSUP_graph_free (gr);
+
+    for (int i = 0; i < 7; i++)
+        ASSERT (strstr (out, end_nt_doc[i]) != NULL, end_nt_doc[i]);
+
+    free (out);
+
+    return 0;
+}
+
 
 /// Positive test suite from W3C.
 int
@@ -79,7 +118,7 @@ int codec_ttl_tests()
 
     codec = ttl_codec;
 
-    //RUN (test_encode_ttl_graph);
+    RUN (test_encode_ttl_graph);
     RUN (test_decode_nt_graph);
     RUN (test_decode_nt_bad_graph);
     RUN (test_w3c_pos);

+ 2 - 4
test/test_namespace.c

@@ -17,9 +17,7 @@ test_namespace()
     EXPECT_STR_EQ (
             LSUP_nsmap_get_ns (nsm, "dcterms"), "http://purl.org/dc/terms/");
     // Prefixes longer than 7 chars are truncated.
-    ASSERT (
-            LSUP_nsmap_get_ns (nsm, "dctermsxxx") == NULL,
-            "Non-existent NS found!");
+    ASSERT (LSUP_nsmap_get_ns (nsm, "dctermsxx"), "http://purl.org/dc/terms/");
     ASSERT (LSUP_nsmap_get_ns (nsm, "none") == NULL, "Non-existent NS found!");
 
     // Lookup NS.
@@ -28,7 +26,7 @@ test_namespace()
     EXPECT_STR_EQ (
             LSUP_nsmap_get_pfx (nsm, "http://purl.org/dc/terms/"), "dcterms");
 
-    // Normallize and denormalize URIs.
+    // Normalize and denormalize URIs.
     char *fq_uri, *pfx_uri;
     fq_uri = "http://purl.org/dc/elements/1.1/title";