=encoding utf8
=head1 NAME
rdf/parser/common - Shared RDF parser helpers.
=head1 SYNOPSIS
from rdf/parser/common import RDFReader;
let reader := new RDFReader(source: "<http://example.com/s>");
let term := reader.read_iri();
=head1 DESCRIPTION
This module contains the shared reader used by the N-Triples, N-Quads,
Turtle, and RDF/XML parsers. It is primarily useful for parser
implementors. Public parser classes should normally be preferred by
application code.
=head1 EXPORTS
=head2 Classes
=over
=item C<RDFReader>
A cursor over a source string with namespace, base IRI, blank node, term,
literal, and N-Triples/N-Quads statement readers.
=over
=item C<< advance(Number count) >>
Moves the cursor and returns the reader.
=item C<< position() >>
Returns the current cursor offset.
=item C<< fresh_blank() >>
Returns a generated blank node term.
=item C<< set_base(String value) >>
Sets the base IRI used by C<resolve_iri> and returns the reader.
=item C<< read_iri() >>
Reads an IRI reference and resolves it against the reader base.
=item C<< read_absolute_iri() >>
Reads an IRI and throws unless it is absolute.
=item C<< resolve_iri(String iri) >>
Returns C<iri> resolved against the reader base.
=item C<< read_name() >>
Reads an ASCII name token used by the shared parsers.
=item C<< read_blank_label() >>
Reads a blank node label after C<_:>.
=item C<< read_prefixed_name() >>
Reads a Turtle/SPARQL prefixed name using the reader prefix map.
=item C<< read_prefixed_or_keyword() >>
Reads a prefixed name or the C<a> keyword as C<rdf:type>.
=item C<< read_blank() >>
Reads a blank node term.
=item C<< read_string_literal(Boolean long_allowed, Boolean single_allowed, String escape_mode) >>
Reads a string literal. The flags control triple-quoted and single-quoted
forms. C<escape_mode> selects the grammar-specific escape rules.
=item C<< read_langtag() >>
Reads a language tag.
=item C<< read_literal() >>
Reads a Turtle literal.
=item C<< read_nt_literal() >>
Reads an N-Triples/N-Quads literal.
=item C<< read_number_token() >>
Reads a numeric token and returns its lexical form and datatype.
=item C<< read_number_or_boolean() >>
Reads a Turtle numeric or boolean literal.
=item C<< read_term() >>, C<< read_subject() >>, C<< read_object() >>
Read Turtle-style terms in the named positions.
=item C<< read_nt_subject() >>, C<< read_nt_predicate() >>, C<< read_nt_object() >>, C<< read_nt_graph() >>
Read N-Triples/N-Quads terms in the named positions.
=item C<< read_nt_statement(Boolean graph_allowed) >>
Reads one N-Triples or N-Quads statement.
=item C<< read_nt_all(Boolean graph_allowed) >>
Reads all N-Triples or N-Quads statements from the source.
=item C<< read_statement(Boolean graph_allowed) >>, C<< read_all(Boolean graph_allowed) >>
Compatibility aliases for statement and source readers.
=back
=back
=head1 COPYRIGHT AND LICENCE
B<< rdf/parser/common >> is copyright Toby Inkster.
It is free software; you may redistribute it and/or modify it under
the terms of either the Artistic License 1.0 or the GNU General Public
License version 2.
=cut
from rdf/term import
RDF_NS,
RDFSyntaxError,
XSD_NS,
rdf_blank,
rdf_default_graph,
rdf_iri,
rdf_literal,
rdf_quad,
rdf_term_key;
from std/string import contains, ends_with, index, join, rindex, split,
sprint, substr;
function _parser_options ( PairList options ) {
let out := { into: null, base: "" };
for ( let pair in options.to_Array() ) {
if ( pair.key eq "into" ) {
out{into} := pair.value;
}
else if ( pair.key eq "base" ) {
out{base} := "" _ pair.value;
}
else {
die "rdf parser: unsupported option '" _ pair.key _ "'";
}
}
return out;
}
function _parser_result ( Array quads, PairList options ) {
let opts := _parser_options(options);
if ( not (opts{into} == null) ) {
opts{into}.add_quads(quads);
return opts{into};
}
return quads;
}
function _hex_value ( String ch ) {
return 0 + ch if ch ~ /^[0-9]$/;
return 10 + index( "abcdef", lc(ch) );
}
function _valid_codepoint ( Number n ) {
return false if n < 0;
return false if n > 1114111;
return false if n >= 55296 and n <= 57343;
return true;
}
function _hex_number ( String hex ) {
let n := 0;
let i := 0;
while ( i < length hex ) {
n := n * 16 + _hex_value(substr(hex, i, 1));
i++;
}
return n;
}
function _unicode_char ( String hex ) {
let n := _hex_number(hex);
die "Invalid Unicode codepoint" unless _valid_codepoint(n);
return sprint( "%c", n );
}
function _rdf_remove_dot_segments ( String path ) {
let absolute := substr( path, 0, 1 ) eq "/";
let trailing := ends_with( path, "/" ) or ends_with( path, "/." ) or
ends_with( path, "/.." ) or path eq "." or path eq "..";
let parts := [];
let i := 0;
for ( let segment in split( path, "/" ) ) {
if ( segment eq "" ) {
parts.push(segment) if i > 0 or absolute;
i++;
next;
}
if ( segment eq "." ) {
i++;
next;
}
if ( segment eq ".." ) {
let min_parts := absolute ? 1 : 0;
if ( parts.length() > min_parts ) {
parts.pop();
}
}
else {
parts.push(segment);
}
i++;
}
let out := join( "/", parts );
if ( trailing and not ends_with( out, "/" ) ) {
out _= "/";
}
return out eq "" and absolute ? "/" : out;
}
function _rdf_parse_iri_parts ( String iri ) {
let m := iri ~ /^(([A-Za-z][A-Za-z0-9+.-]*):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#([\s\S]*))?$/;
return {
scheme: m[2],
has_authority: m[3] ne "",
authority: m[4],
path: m[5],
has_query: m[6] ne "",
query: m[7],
has_fragment: m[8] ne "",
fragment: m[9],
};
}
function _rdf_build_iri_parts ( parts ) {
let out := "";
out _= parts{scheme} _ ":" if parts{scheme} ne "";
out _= "//" _ parts{authority} if parts{has_authority};
out _= parts{path};
out _= "?" _ parts{query} if parts{has_query};
out _= "#" _ parts{fragment} if parts{has_fragment};
return out;
}
function _rdf_merge_iri_path ( base, String ref_path ) {
if ( base{has_authority} and base{path} eq "" ) {
return "/" _ ref_path;
}
let slash := rindex( base{path}, "/" );
return ref_path if slash < 0;
return substr( base{path}, 0, slash + 1 ) _ ref_path;
}
function _rdf_resolve_iri ( String base, String ref ) {
return ref if base eq "";
let r := _rdf_parse_iri_parts(ref);
return _rdf_build_iri_parts({
scheme: r{scheme},
has_authority: r{has_authority},
authority: r{authority},
path: _rdf_remove_dot_segments(r{path}),
has_query: r{has_query},
query: r{query},
has_fragment: r{has_fragment},
fragment: r{fragment},
}) if r{scheme} ne "";
let b := _rdf_parse_iri_parts(base);
let target := {
scheme: b{scheme},
has_authority: false,
authority: "",
path: "",
has_query: false,
query: "",
has_fragment: r{has_fragment},
fragment: r{fragment},
};
if ( r{has_authority} ) {
target{has_authority} := true;
target{authority} := r{authority};
target{path} := _rdf_remove_dot_segments(r{path});
target{has_query} := r{has_query};
target{query} := r{query};
return _rdf_build_iri_parts(target);
}
target{has_authority} := b{has_authority};
target{authority} := b{authority};
if ( r{path} eq "" ) {
target{path} := b{path};
target{has_query} := r{has_query} ? true : b{has_query};
target{query} := r{has_query} ? r{query} : b{query};
}
else {
target{path} := substr( r{path}, 0, 1 ) eq "/"
? _rdf_remove_dot_segments(r{path})
: _rdf_remove_dot_segments(_rdf_merge_iri_path( b, r{path} ));
target{has_query} := r{has_query};
target{query} := r{query};
}
return _rdf_build_iri_parts(target);
}
function _is_ws ( value ) {
return not (value == null) and value ~ /\s/;
}
function _is_hex ( value ) {
return not (value == null) and value ~ /^[0-9A-Fa-f]$/;
}
function _is_name_base ( value ) {
return false if value == null;
return true if value ~ /^[A-Za-z]$/;
return true if value ~ /^[^\x00-\x7F]$/;
return false;
}
function _is_name_start ( value ) {
return _is_name_base(value) or value eq "_";
}
function _is_name_continue ( value ) {
return false if value == null;
return true if _is_name_start(value);
return true if value ~ /^[0-9-]$/;
return false;
}
function _is_blank_continue ( value ) {
return _is_name_continue(value) or value eq ".";
}
function _is_pn_local_extra ( value ) {
return false;
}
function _is_token_delimiter ( value ) {
return true if value == null;
return true if _is_ws(value);
return contains( "[]();,", value );
}
class RDFReader {
let String source := "";
let String source_name := "<string>";
let Number pos := 0;
let Dict prefixes with get := {};
let String base with get := "";
let Number blank_counter := 0;
let Boolean validate_decoded_iri := false;
method advance ( Number count ) {
pos += count;
return self;
}
method position () {
return pos;
}
method set_position ( Number value ) {
pos := value;
return self;
}
method fresh_blank () {
blank_counter++;
return rdf_blank("genid" _ blank_counter);
}
method set_base ( String value ) {
base := value;
return self;
}
method _peek () {
return null if pos >= length source;
return substr( source, pos, 1 );
}
method _next () {
let ch := self._peek();
pos++;
return ch;
}
method _starts ( String text ) {
return substr( source, pos, length text ) eq text;
}
method _starts_ci ( String text ) {
return uc(substr( source, pos, length text )) eq uc(text);
}
method _error ( String message ) {
throw new RDFSyntaxError(
message: message _ " at " _ source_name _ ":" _ ( pos + 1 ),
);
}
method _skip_ws () {
while ( not (self._peek() == null) ) {
let ch := self._peek();
if ( ch ~ /\s/ ) {
self._next();
next;
}
if ( ch eq "#" ) {
while ( not (self._peek() == null) and self._peek() ne "\n" ) {
self._next();
}
next;
}
last;
}
}
method _expect ( String text ) {
self._skip_ws();
self._error("Expected " _ text) unless self._starts(text);
pos += length text;
}
method _read_until ( String close ) {
let out := "";
while ( not (self._peek() == null) ) {
let ch := self._next();
return out if ch eq close;
if ( ch eq "\\" ) {
let esc := self._next();
self._error("Unterminated escape") if esc == null;
out _= _unicode_char("0008") if esc eq "b";
out _= _unicode_char("000C") if esc eq "f";
out _= "\t" if esc eq "t";
out _= "\n" if esc eq "n";
out _= "\r" if esc eq "r";
out _= "\"" if esc eq "\"";
out _= "\\" if esc eq "\\";
if ( esc eq "u" ) {
let hex := substr(source, pos, 4);
self._error("Invalid Unicode escape") unless hex ~ /^[0-9A-Fa-f]{4}$/;
out _= _unicode_char(hex);
pos += 4;
}
else if ( esc eq "U" ) {
let hex := substr(source, pos, 8);
self._error("Invalid Unicode escape") unless hex ~ /^[0-9A-Fa-f]{8}$/;
out _= _unicode_char(hex);
pos += 8;
}
else if ( not( esc eq "b" or esc eq "f" or esc eq "t" or
esc eq "n" or esc eq "r" or
esc eq "\"" or esc eq "\\" ) ) {
self._error("Invalid escape \\" _ esc);
}
next;
}
out _= ch;
}
self._error("Unterminated quoted value");
}
method _read_unicode_escape ( Number count ) {
let hex := substr(source, pos, count);
self._error("Invalid Unicode escape")
unless hex ~ /^[0-9A-Fa-f]+$/ and length hex == count;
self._error("Invalid Unicode codepoint")
unless _valid_codepoint(_hex_number(hex));
pos += count;
return _unicode_char(hex);
}
method _read_iri_body () {
let iri := "";
while ( not (self._peek() == null) ) {
let ch := self._next();
return iri if ch eq ">";
if ( ch eq "\\" ) {
let esc := self._next();
if ( esc eq "u" ) {
let decoded := self._read_unicode_escape(4);
self._error("Invalid character in IRI")
if validate_decoded_iri and
decoded ~ /[\x00-\x20<>"{}|^`]/;
iri _= decoded;
}
else if ( esc eq "U" ) {
let decoded := self._read_unicode_escape(8);
self._error("Invalid character in IRI")
if validate_decoded_iri and
decoded ~ /[\x00-\x20<>"{}|^`]/;
iri _= decoded;
}
else {
self._error("Invalid IRI escape \\" _ esc);
}
next;
}
self._error("Invalid character in IRI")
if ch ~ /[\x00-\x20<>"{}|^`]/;
iri _= ch;
}
self._error("Unterminated IRI");
}
method read_iri () {
self._skip_ws();
self._error("Expected IRI") unless self._peek() eq "<";
self._next();
let iri := self._read_iri_body();
return rdf_iri(self.resolve_iri(iri));
}
method read_absolute_iri () {
let term := self.read_iri();
self._error("Expected absolute IRI")
unless term.get_value() ~ /^[A-Za-z][A-Za-z0-9+.-]*:/;
return term;
}
method resolve_iri ( String iri ) {
return _rdf_resolve_iri( base, iri );
}
method read_name () {
self._skip_ws();
let out := "";
while ( not (self._peek() == null) ) {
let ch := self._peek();
if ( ch eq "." ) {
let after := substr( source, pos + 1, 1 );
last unless after ~ /[A-Za-z0-9_\-:]/;
}
last unless ch ~ /[A-Za-z0-9_\-.:]/;
out _= self._next();
}
self._error("Expected name") if out eq "";
return out;
}
method read_blank_label () {
self._skip_ws();
self._expect("_:");
let out := "";
let first := self._peek();
self._error("Expected blank node label")
unless _is_name_start(first) or first ~ /^[0-9]$/;
out _= self._next();
while ( _is_blank_continue(self._peek()) ) {
let ch := self._next();
if ( ch eq "." and not _is_blank_continue(self._peek()) ) {
pos--;
last;
}
out _= ch;
}
return out;
}
method _read_pn_escape () {
let esc := self._next();
self._error("Unterminated prefixed-name escape") if esc == null;
self._error("Invalid prefixed-name escape \\" _ esc)
unless contains( "_~.-!$&'()*+,;=/?#@%", esc );
return esc;
}
method _read_percent_escape () {
self._expect("%");
let a := self._next();
let b := self._next();
self._error("Invalid percent escape") unless _is_hex(a) and _is_hex(b);
return "%" _ a _ b;
}
method _read_prefix_part () {
let out := "";
return out unless _is_name_base(self._peek());
out _= self._next();
while ( true ) {
let ch := self._peek();
if ( ch eq "." ) {
let after := substr( source, pos + 1, 1 );
last unless _is_name_continue(after);
out _= self._next();
next;
}
last unless _is_name_continue(ch);
out _= self._next();
}
return out;
}
method _read_local_part () {
let out := "";
let saw := false;
while ( true ) {
let ch := self._peek();
self._error("Expected prefixed name")
if not saw and ( ch eq "-" or ch eq "." );
if ( ch eq "\\" ) {
self._next();
out _= self._read_pn_escape();
saw := true;
next;
}
if ( ch eq "%" ) {
out _= self._read_percent_escape();
saw := true;
next;
}
if ( ch eq "." ) {
let after := substr( source, pos + 1, 1 );
last unless _is_name_continue(after) or after eq ":" or
_is_pn_local_extra(after) or after eq "%" or after eq "\\" or
after eq ".";
out _= self._next();
saw := true;
next;
}
if ( ch eq ":" or _is_name_continue(ch) or
_is_pn_local_extra(ch) ) {
out _= self._next();
saw := true;
next;
}
last;
}
return out;
}
method read_prefixed_name () {
self._skip_ws();
let prefix := self._read_prefix_part();
self._error("Expected prefixed name") unless self._peek() eq ":";
self._next();
let local := self._read_local_part();
self._error("Unknown prefix " _ prefix) unless prefixes.exists(prefix);
return rdf_iri(prefixes.get(prefix) _ local);
}
method read_prefixed_or_keyword () {
self._skip_ws();
if ( self._peek() eq "a" and
_is_token_delimiter(substr( source, pos + 1, 1 )) ) {
pos++;
return rdf_iri(RDF_NS _ "type");
}
return self.read_prefixed_name();
}
method read_blank () {
return rdf_blank(self.read_blank_label());
}
method read_string_literal ( Boolean long_allowed,
Boolean single_allowed, String escape_mode ) {
self._skip_ws();
let quote := self._peek();
self._error("Expected literal")
unless quote eq "\"" or ( single_allowed and quote eq "'" );
let close := quote;
let long := false;
if ( long_allowed and
substr( source, pos, 3 ) eq quote _ quote _ quote ) {
long := true;
close := quote _ quote _ quote;
pos += 3;
}
else {
self._next();
}
let out := "";
while ( not (self._peek() == null) ) {
if ( long and self._starts(close) ) {
pos += 3;
return out;
}
let ch := self._next();
return out if not long and ch eq quote;
self._error("Line break in short string")
if not long and ( ch eq "\n" or ch eq "\r" );
if ( ch eq "\\" ) {
let esc := self._next();
self._error("Unterminated escape") if esc == null;
out _= _unicode_char("0008") if esc eq "b";
out _= _unicode_char("000C") if esc eq "f";
out _= "\t" if esc eq "t";
out _= "\n" if esc eq "n";
out _= "\r" if esc eq "r";
out _= "\"" if esc eq "\"";
out _= "'" if esc eq "'";
out _= "\\" if esc eq "\\";
if ( esc eq "u" ) {
out _= self._read_unicode_escape(4);
}
else if ( esc eq "U" ) {
out _= self._read_unicode_escape(8);
}
else if ( not( esc eq "b" or esc eq "f" or
esc eq "t" or esc eq "n" or esc eq "r" or
esc eq "\"" or esc eq "\\" or
( escape_mode eq "turtle" and esc eq "'" ) ) ) {
self._error("Invalid escape \\" _ esc);
}
next;
}
out _= ch;
}
self._error("Unterminated quoted value");
}
method read_langtag () {
self._skip_ws();
self._expect("@");
let out := "";
self._error("Expected language tag") unless self._peek() ~ /^[A-Za-z]$/;
while ( not (self._peek() == null) and self._peek() ~ /^[A-Za-z]$/ ) {
out _= self._next();
}
while ( self._peek() eq "-" ) {
out _= self._next();
self._error("Expected language tag subtag")
unless self._peek() ~ /^[A-Za-z0-9]$/;
while ( not (self._peek() == null) and
self._peek() ~ /^[A-Za-z0-9]$/ ) {
out _= self._next();
}
}
return out;
}
method read_literal () {
let value := self.read_string_literal( true, true, "turtle" );
self._skip_ws();
let lang := "";
let datatype := rdf_iri(XSD_NS _ "string");
if ( self._peek() eq "@" ) {
lang := self.read_langtag();
datatype := rdf_iri(RDF_NS _ "langString");
}
else if ( self._starts("^^") ) {
pos += 2;
datatype := self.read_term();
}
return rdf_literal( value, lang, datatype );
}
method read_nt_literal () {
let value := self.read_string_literal( false, false, "ntriples" );
self._skip_ws();
let lang := "";
let datatype := rdf_iri(XSD_NS _ "string");
if ( self._peek() eq "@" ) {
lang := self.read_langtag();
datatype := rdf_iri(RDF_NS _ "langString");
}
else if ( self._starts("^^") ) {
pos += 2;
datatype := self.read_absolute_iri();
}
return rdf_literal( value, lang, datatype );
}
method read_number_token () {
self._skip_ws();
let start := pos;
let ch := self._peek();
if ( ch eq "+" or ch eq "-" ) {
self._next();
}
while ( not (self._peek() == null) and
contains( "0123456789.eE+-", self._peek() ) ) {
self._next();
}
let token := substr( source, start, pos - start );
if ( ends_with( token, "." ) and not( token ~ /[eE]\.$/ ) ) {
token := substr( token, 0, length token - 1 );
pos--;
}
return token;
}
method read_number_or_boolean () {
self._skip_ws();
let ch := self._peek();
if ( ch eq "t" and substr( source, pos, 4 ) eq "true" and
_is_token_delimiter(substr( source, pos + 4, 1 )) ) {
pos += 4;
return rdf_literal( "true", "", rdf_iri(XSD_NS _ "boolean") );
}
if ( ch eq "f" and substr( source, pos, 5 ) eq "false" and
_is_token_delimiter(substr( source, pos + 5, 1 )) ) {
pos += 5;
return rdf_literal( "false", "", rdf_iri(XSD_NS _ "boolean") );
}
let token := self.read_number_token();
return rdf_literal( "true", "", rdf_iri(XSD_NS _ "boolean") )
if token eq "true";
return rdf_literal( "false", "", rdf_iri(XSD_NS _ "boolean") )
if token eq "false";
if ( token ~ /^[+-]?[0-9]+$/ ) {
return rdf_literal( token, "", rdf_iri(XSD_NS _ "integer") );
}
if ( token ~ /^[+-]?[0-9]*\.[0-9]+$/ ) {
return rdf_literal( token, "", rdf_iri(XSD_NS _ "decimal") );
}
if ( token ~ /^[+-]?[0-9]+[eE][+-]?[0-9]+$/ or
token ~ /^[+-]?[0-9]*\.[0-9]+[eE][+-]?[0-9]+$/ or
token ~ /^[+-]?[0-9]+\.[0-9]*[eE][+-]?[0-9]+$/ ) {
return rdf_literal( token, "", rdf_iri(XSD_NS _ "double") );
}
self._error("Expected numeric or boolean literal");
}
method read_term () {
self._skip_ws();
let ch := self._peek();
return self.read_iri() if ch eq "<";
return self.read_blank() if self._starts("_:");
return self.read_literal() if ch eq "\"" or ch eq "'";
if ( ch eq "+" or ch eq "-" or ch eq "." or ch ~ /[0-9]/ ) {
return self.read_number_or_boolean();
}
if ( self._starts("true") or self._starts("false") ) {
return self.read_number_or_boolean();
}
return self.read_prefixed_name();
}
method read_subject () {
self._skip_ws();
return self.read_iri() if self._peek() eq "<";
return self.read_blank() if self._starts("_:");
return self.read_prefixed_name();
}
method read_object () {
return self.read_term();
}
method read_nt_subject () {
self._skip_ws();
return self.read_absolute_iri() if self._peek() eq "<";
return self.read_blank() if self._starts("_:");
self._error("Expected N-Triples subject");
}
method read_nt_predicate () {
self._skip_ws();
return self.read_absolute_iri() if self._peek() eq "<";
self._error("Expected N-Triples predicate IRI");
}
method read_nt_object () {
self._skip_ws();
return self.read_absolute_iri() if self._peek() eq "<";
return self.read_blank() if self._starts("_:");
return self.read_nt_literal() if self._peek() eq "\"";
self._error("Expected N-Triples object");
}
method read_nt_graph () {
self._skip_ws();
return self.read_absolute_iri() if self._peek() eq "<";
return self.read_blank() if self._starts("_:");
self._error("Expected N-Quads graph label");
}
method read_nt_statement ( Boolean graph_allowed ) {
self._skip_ws();
return null if self._peek() == null;
let s := self.read_nt_subject();
let p := self.read_nt_predicate();
let o := self.read_nt_object();
let g := rdf_default_graph();
self._skip_ws();
if ( graph_allowed and self._peek() ne "." ) {
g := self.read_nt_graph();
}
self._expect(".");
return rdf_quad( s, p, o, g );
}
method read_nt_all ( Boolean graph_allowed ) {
let out := [];
while ( true ) {
self._skip_ws();
last if self._peek() == null;
out.push(self.read_nt_statement(graph_allowed));
}
return out;
}
method read_statement ( Boolean graph_allowed ) {
self._skip_ws();
return null if self._peek() == null;
let s := self.read_subject();
let p := self.read_term();
let o := self.read_object();
let g := rdf_default_graph();
self._skip_ws();
if ( graph_allowed and self._peek() ne "." ) {
g := self.read_subject();
}
self._expect(".");
return rdf_quad( s, p, o, g );
}
method read_all ( Boolean graph_allowed ) {
let out := [];
while ( true ) {
self._skip_ws();
last if self._peek() == null;
out.push(self.read_statement(graph_allowed));
}
return out;
}
}
modules/rdf/parser/common.zzm
rdf-0.0.3 source code
Package
- Name
- rdf
- Version
- 0.0.3
- Uploaded
- 2026-06-12 23:55:02
- Repository
- https://github.com/tobyink/zuzu-rdf
- Dependencies
-
-
std/data/xml>= 0 -
std/data/xml/escape>= 0 -
std/data/json>= 0 -
std/db>= 0 -
std/digest/sha>= 0 -
std/getopt>= 0 -
std/internals>= 0 -
std/io>= 0 -
std/math>= 0 -
std/proc>= 0 -
std/string>= 0 -
std/time>= 0 -
std/uuid>= 0
-
- Metadata
- zuzu-distribution.json
- Archive
- Download .tar.gz