22// See https://github.com/rdfjs/representation-task-force/blob/master/interface-spec.md
33
44import namespaces from './IRIs' ;
5+ import { isDefaultGraph } from './N3Util' ;
56const { rdf, xsd } = namespaces ;
67
78let DEFAULTGRAPH ;
89let _blankNodeCounter = 0 ;
910
11+ const escapedLiteral = / ^ " ( .* " .* ) (? = " [ ^ " ] * $ ) / ;
12+ const quadId = / ^ < < ( " (?: " " | [ ^ " ] ) * " [ ^ ] * | [ ^ ] + ) ( " (?: " " | [ ^ " ] ) * " [ ^ ] * | [ ^ ] + ) ( " (?: " " | [ ^ " ] ) * " [ ^ ] * | [ ^ ] + ) ? ( " (?: " " | [ ^ " ] ) * " [ ^ ] * | [ ^ ] + ) ? > > $ / ;
13+
1014// ## DataFactory singleton
1115const DataFactory = {
1216 namedNode,
@@ -186,8 +190,10 @@ export function termFromId(id, factory) {
186190
187191 // Identify the term type based on the first character
188192 switch ( id [ 0 ] ) {
189- case '_' : return factory . blankNode ( id . substr ( 2 ) ) ;
190- case '?' : return factory . variable ( id . substr ( 1 ) ) ;
193+ case '?' :
194+ return factory . variable ( id . substr ( 1 ) ) ;
195+ case '_' :
196+ return factory . blankNode ( id . substr ( 2 ) ) ;
191197 case '"' :
192198 // Shortcut for internal literals
193199 if ( factory === DataFactory )
@@ -200,15 +206,24 @@ export function termFromId(id, factory) {
200206 return factory . literal ( id . substr ( 1 , endPos - 1 ) ,
201207 id [ endPos + 1 ] === '@' ? id . substr ( endPos + 2 )
202208 : factory . namedNode ( id . substr ( endPos + 3 ) ) ) ;
203- default : return factory . namedNode ( id ) ;
209+ case '<' :
210+ const components = quadId . exec ( id ) ;
211+ return factory . quad (
212+ termFromId ( unescapeQuotes ( components [ 1 ] ) , factory ) ,
213+ termFromId ( unescapeQuotes ( components [ 2 ] ) , factory ) ,
214+ termFromId ( unescapeQuotes ( components [ 3 ] ) , factory ) ,
215+ components [ 4 ] && termFromId ( unescapeQuotes ( components [ 4 ] ) , factory )
216+ ) ;
217+ default :
218+ return factory . namedNode ( id ) ;
204219 }
205220}
206221
207222// ### Constructs an internal string ID from the given term or ID string
208223export function termToId ( term ) {
209224 if ( typeof term === 'string' )
210225 return term ;
211- if ( term instanceof Term )
226+ if ( term instanceof Term && term . termType !== 'Quad' )
212227 return term . id ;
213228 if ( ! term )
214229 return DEFAULTGRAPH . id ;
@@ -222,20 +237,38 @@ export function termToId(term) {
222237 case 'Literal' : return '"' + term . value + '"' +
223238 ( term . language ? '@' + term . language :
224239 ( term . datatype && term . datatype . value !== xsd . string ? '^^' + term . datatype . value : '' ) ) ;
240+ case 'Quad' :
241+ // To identify RDF* quad components, we escape quotes by doubling them.
242+ // This avoids the overhead of backslash parsing of Turtle-like syntaxes.
243+ return `<<${
244+ escapeQuotes ( termToId ( term . subject ) )
245+ } ${
246+ escapeQuotes ( termToId ( term . predicate ) )
247+ } ${
248+ escapeQuotes ( termToId ( term . object ) )
249+ } ${
250+ ( isDefaultGraph ( term . graph ) ) ? '' : ` ${ termToId ( term . graph ) } `
251+ } >>`;
225252 default : throw new Error ( 'Unexpected termType: ' + term . termType ) ;
226253 }
227254}
228255
229256
230257// ## Quad constructor
231- export class Quad {
258+ export class Quad extends Term {
232259 constructor ( subject , predicate , object , graph ) {
260+ super ( '' ) ;
233261 this . subject = subject ;
234262 this . predicate = predicate ;
235263 this . object = object ;
236264 this . graph = graph || DEFAULTGRAPH ;
237265 }
238266
267+ // ### The term type of this term
268+ get termType ( ) {
269+ return 'Quad' ;
270+ }
271+
239272 // ### Returns a plain object representation of this quad
240273 toJSON ( ) {
241274 return {
@@ -256,6 +289,15 @@ export class Quad {
256289}
257290export { Quad as Triple } ;
258291
292+ // ### Escapes the quotes within the given literal
293+ export function escapeQuotes ( id ) {
294+ return id . replace ( escapedLiteral , ( _ , quoted ) => `"${ quoted . replace ( / " / g, '""' ) } ` ) ;
295+ }
296+
297+ // ### Unescapes the quotes within the given literal
298+ export function unescapeQuotes ( id ) {
299+ return id . replace ( escapedLiteral , ( _ , quoted ) => `"${ quoted . replace ( / " " / g, '"' ) } ` ) ;
300+ }
259301
260302// ### Creates an IRI
261303function namedNode ( iri ) {
0 commit comments