package rdfimport ()// XSD datatypes used by Turtle's literal shorthands.const ( xsdBoolean = "http://www.w3.org/2001/XMLSchema#boolean" xsdInteger = "http://www.w3.org/2001/XMLSchema#integer" xsdDecimal = "http://www.w3.org/2001/XMLSchema#decimal" xsdDouble = "http://www.w3.org/2001/XMLSchema#double")// maxParseDepth bounds nesting in the recursive-descent parsers — Turtle// blank-node property lists (`[ … ]`) and collections (`( … )`), and RDF/XML// node elements — so adversarial deeply nested input fails with an error// instead of overflowing the goroutine stack, which is a fatal, unrecoverable// runtime crash (recover cannot catch it). No real document nests this deep.const maxParseDepth = 1 << 13// ParseTurtle parses a Turtle document into a Graph. It supports the constructs// real RDF uses: @prefix/@base and SPARQL-style PREFIX/BASE, prefixed names and// IRIs, the `a` keyword, predicate-object lists (`;`) and object lists (`,`),// blank-node labels, `[ … ]` blank-node property lists, `( … )` collections, and// string (including triple-quoted), language-tagged, datatyped, numeric and// boolean literals. Local names are read as the common identifier subset.func ( []byte) (*Graph, error) { := &Graph{Triples: make([]Triple, 0, len()/32)} := &turtleParser{s: string(),emit: .Add,prefixes: map[string]string{},iriCache: map[string]map[string]string{},strs: &arena{}, }for { , := .stmt()if {return , nil }if ! {return , errTurtle() } }}// stmt parses one directive or triple statement from the current buffer, emitting// its triples. done is true at end of input; ok is false on a malformed statement.// It is shared by the whole-document parser and the streaming decoder (which feeds// one statement at a time).func ( *turtleParser) () (, bool) { .ws()if .pos >= len(.s) {returntrue, true }if .s[.pos] == '@' || .peekKeyword("prefix") || .peekKeyword("base") {return .directive(), false }return .triples(), false}type turtleError struct{ pos, line, col int }// Error reports the failure with its position. For a whole-document parse the// line and column locate the byte in the source; when streaming, p.s is a single// statement, so they are relative to the start of that statement.func ( *turtleError) () string {returnfmt.Sprintf("rdf: malformed Turtle at line %d, column %d (byte offset %d)", .line, .col, .pos)}func errTurtle( *turtleParser) error { , := lineCol(.s, .pos)return &turtleError{pos: .pos, line: , col: }}// lineCol returns the 1-based line and column of byte offset pos in s.func lineCol( string, int) (, int) { , = 1, 1if > len() { = len() }for := range {if [] == '\n' { ++ = 1 } else { ++ } }return , }type turtleParser struct { s string pos int emit func(s, pr, o Term) // sink: Graph.Add (whole document) or channel (streaming) prefixes map[string]string iriCache map[string]map[string]string// prefix -> local -> interned full IRI strs *arena// backs expanded IRIs when whole-document; nil when streaming base string blanks int depth int// current `[`/`(` nesting, bounded by maxParseDepth}func ( *turtleParser) () Term { .blanks++returnNewBlank("t" + strconv.Itoa(.blanks))}// ws skips whitespace and # comments.func ( *turtleParser) () {for .pos < len(.s) {switch .s[.pos] {case' ', '\t', '\r', '\n': .pos++case'#':for .pos < len(.s) && .s[.pos] != '\n' { .pos++ }default:return } }}func ( *turtleParser) () byte {if .pos < len(.s) {return .s[.pos] }return0}// peekKeyword reports whether a case-insensitive keyword (SPARQL PREFIX/BASE)// begins at the cursor, followed by a non-name character.func ( *turtleParser) ( string) bool {if .pos+len() > len(.s) {returnfalse }if !strings.EqualFold(.s[.pos:.pos+len()], ) {returnfalse } := .pos + len()return >= len(.s) || isWS(.s[])}func isWS( byte) bool { return == ' ' || == '\t' || == '\r' || == '\n' }// directive parses @prefix/@base or SPARQL PREFIX/BASE.func ( *turtleParser) () bool { := .s[.pos] != '@'if ! { .pos++ // '@' } := .readBareWord() .ws()switchstrings.ToLower() {case"prefix": := .readPrefixLabel() .ws() , := .iriRef()if ! {returnfalse } .prefixes[] = case"base": , := .iriRef()if ! {returnfalse } .base = default:returnfalse }if ! { .ws()if .peek() != '.' {returnfalse } .pos++ }returntrue}// triples parses a subject and its predicate-object list, terminated by '.'.// A blankNodePropertyList subject ("[ ... ]") carries its predicate-object pairs// inside the brackets, so per the Turtle grammar the trailing predicate-object// list is optional there ("[ a :Work ] ." is a complete statement); every other// subject requires one.func ( *turtleParser) () bool { .ws() := .peek() == '[' , := .subject()if ! {returnfalse } .ws()if && .peek() == '.' { .pos++returntrue }if !.predicateObjectList() {returnfalse } .ws()if .peek() != '.' {returnfalse } .pos++returntrue}// predicateObjectList parses `verb objectList (';' verb objectList)*`.func ( *turtleParser) ( Term) bool {for { .ws() , := .verb()if ! {returnfalse }if !.objectList(, ) {returnfalse } .ws()if .peek() != ';' {returntrue }for .peek() == ';' { // allow repeated/empty ';' .pos++ .ws() }// A trailing ';' before '.' or ']' ends the list.if := .peek(); == '.' || == ']' || == 0 {returntrue } }}func ( *turtleParser) (, Term) bool {for { , := .object()if ! {returnfalse } .emit(, , ) .ws()if .peek() != ',' {returntrue } .pos++ }}func ( *turtleParser) () (Term, bool) {if .peek() == 'a' { := .pos + 1if >= len(.s) || isWS(.s[]) || .s[] == '<' { .pos++returnNewIRI(TypeIRI), true } }return .iriOrPName()}func ( *turtleParser) () (Term, bool) { .ws()switch .peek() {case'_':return .blankLabel()case'[':return .blankPropertyList()case'(':return .collection()default:return .iriOrPName() }}func ( *turtleParser) () (Term, bool) { .ws()switch := .peek(); {case == '<':return .iriOrPName()case == '"', == '\'':return .literal()case == '_':return .blankLabel()case == '[':return .blankPropertyList()case == '(':return .collection()case == 't' && .matchWord("true"):returnNewLiteral("true", "", xsdBoolean), truecase == 'f' && .matchWord("false"):returnNewLiteral("false", "", xsdBoolean), truecase ( >= '0' && <= '9') || == '+' || == '-' || ( == '.' && .digitNext()):return .number()default:return .iriOrPName() }}// digitNext reports whether a digit follows the cursor (to tell a leading-dot// number from a statement terminator).
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.