package rdfimport ()const xmlNS = "http://www.w3.org/XML/1998/namespace"// errTooDeep aborts an RDF/XML parse whose element nesting would otherwise// recurse deep enough to overflow the goroutine stack.var errTooDeep = errors.New("rdf: RDF/XML nesting too deep")// maxLiteralBytes caps the character data accumulated into a single RDF/XML// literal. Without it, a streamed dump carrying one multi-gigabyte literal would// buffer wholly in memory, contradicting the streaming decoder's constant-memory// guarantee. It matches the Turtle streaming statement cap; no real literal is// anywhere near this large.const maxLiteralBytes = 1 << 24// 16 MiB// errLiteralTooLarge aborts a parse whose single literal exceeds maxLiteralBytes.var errLiteralTooLarge = errors.New("rdf: RDF/XML literal too large")// ParseRDFXML parses an RDF/XML document into a Graph. It supports the striped// syntax real bibliographic RDF uses: typed node elements and rdf:Description,// rdf:about / rdf:nodeID / rdf:ID subjects, rdf:resource and rdf:nodeID object// references, nested node elements, literal property values with rdf:datatype and// xml:lang, property attributes, and rdf:parseType="Resource". It does not handle// RDF containers, reification, or rdf:parseType="Literal"/"Collection".func ( []byte) (*Graph, error) { := &Graph{} := &xmlParser{dec: xml.NewDecoder(bytes.NewReader()), emit: .Add}if := .run(); != nil {return , }return , nil}// run drives the token loop, parsing each top-level node element. It is shared by// the whole-document parser and the streaming decoder.func ( *xmlParser) () error {for { , := .dec.Token()if == io.EOF {returnnil }if != nil {return } , := .(xml.StartElement)if ! {continue }// The document root is rdf:RDF (its children are node elements) or, when a // single node is the root, the node element itself.if .Name.Space == NS && .Name.Local == "RDF" {if := .parseNodeChildren(); != nil {return }continue }if , := .parseNode(); != nil {return } }}type xmlParser struct { dec *xml.Decoder emit func(s, pr, o Term) // sink: Graph.Add for ParseRDFXML, channel for streaming blanks int depth int// current node-element nesting, bounded by maxParseDepth}func ( *xmlParser) () Term { .blanks++returnNewBlank("b" + strconv.Itoa(.blanks))}// docBlank wraps a document-supplied rdf:nodeID as a blank node, prefixing it so// it can never collide with a generated ("b"+n) label -- a document nodeID of// "b1" would otherwise merge with the first anonymous node.func docBlank( string) Term {returnNewBlank("u" + )}func iriOf( xml.Name) string { return .Space + .Local }func attr( xml.StartElement, , string) (string, bool) {for , := range .Attr {if .Name.Space == && .Name.Local == {return .Value, true } }return"", false}// parseNode parses a node element (already read as a StartElement), emits its// type and property-attribute triples, recurses into property children, and// returns the node's subject term. It consumes the matching EndElement.func ( *xmlParser) ( xml.StartElement) (Term, error) {if .depth >= maxParseDepth {returnTerm{}, errTooDeep } .depth++deferfunc() { .depth-- }() := .subjectOf()if !(.Name.Space == NS && .Name.Local == "Description") { .emit(, NewIRI(TypeIRI), NewIRI(iriOf(.Name))) }// Property attributes: rdf attributes other than the identity ones become // literal-valued statements (rdf:type becomes a type IRI).for , := range .Attr {ifisStructuralAttr() {continue }if .Name.Space == NS && .Name.Local == "type" { .emit(, NewIRI(TypeIRI), NewIRI(.Value))continue } .emit(, NewIRI(iriOf(.Name)), NewLiteral(.Value, "", "")) }if := .parseProperties(, ); != nil {return , }return , nil}// parseNodeChildren reads the child node elements of a container (rdf:RDF) until// its EndElement.func ( *xmlParser) ( xml.StartElement) error {for { , := .dec.Token()if != nil {return }switch t := .(type) {casexml.StartElement:if , := .parseNode(); != nil {return }casexml.EndElement:if .Name == .Name {returnnil } } }}// parseProperties reads the property-element children of a node until its// EndElement.func ( *xmlParser) ( xml.StartElement, Term) error {for { , := .dec.Token()if != nil {return }switch t := .(type) {casexml.StartElement:if := .parseProperty(, ); != nil {return }casexml.EndElement:if .Name == .Name {returnnil } } }}// parseProperty parses one property element and emits the (subject, predicate,// object) triple, consuming the property's content and EndElement.func ( *xmlParser) ( Term, xml.StartElement) error { := NewIRI(iriOf(.Name))if , := attr(, NS, "resource"); { .emit(, , NewIRI())return .skipTo(.Name) }if , := attr(, NS, "nodeID"); { .emit(, , docBlank())return .skipTo(.Name) }if , := attr(, NS, "parseType"); && == "Resource" { := .fresh() .emit(, , )return .parseProperties(, ) } , := attr(, NS, "datatype") , := attr(, xmlNS, "lang")// Look at the content: a nested node element, or literal text.varstrings.Builderfor { , := .dec.Token()if != nil {return }switch t := .(type) {casexml.StartElement: , := .parseNode()if != nil {return } .emit(, , )return .skipTo(.Name) // consume any trailing whitespace + EndElementcasexml.CharData:if .Len()+len() > maxLiteralBytes {returnerrLiteralTooLarge } .Write()casexml.EndElement:if .Name == .Name { .emit(, , NewLiteral(.String(), , ))returnnil } } }}// skipTo consumes tokens until the EndElement matching name.func ( *xmlParser) ( xml.Name) error { := 0for { , := .dec.Token()if != nil {return }switch t := .(type) {casexml.StartElement: ++casexml.EndElement:if == 0 && .Name == {returnnil } -- } }}// subjectOf returns a node element's subject from rdf:about / rdf:nodeID / rdf:ID,// or a fresh blank node.func ( *xmlParser) ( xml.StartElement) Term {if , := attr(, NS, "about"); {returnNewIRI() }if , := attr(, NS, "nodeID"); {returndocBlank() }if , := attr(, NS, "ID"); {returnNewIRI("#" + ) }return .fresh()}// isStructuralAttr reports whether an attribute carries node identity or XML// machinery rather than a property statement.func isStructuralAttr( xml.Attr) bool {if .Name.Space == "xmlns" || .Name.Local == "xmlns" || .Name.Space == xmlNS {returntrue }if .Name.Space == NS {switch .Name.Local {case"about", "nodeID", "ID", "resource", "datatype", "parseType":returntrue } }returnfalse}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.