@@ -7,7 +7,9 @@ const { rdf, xsd } = namespaces;
77
88let DEFAULTGRAPH ;
99let _blankNodeCounter = 0 ;
10- let quotedMatchingRegex = / ^ " ( .* " .* ) (? = " [ ^ " ] * $ ) / ;
10+
11+ const escapedLiteral = / ^ " ( .* " .* ) (? = " [ ^ " ] * $ ) / ;
12+ const quadId = / ^ < < ( " (?: " " | [ ^ " ] ) * " [ ^ ] * | [ ^ ] + ) ( " (?: " " | [ ^ " ] ) * " [ ^ ] * | [ ^ ] + ) ( " (?: " " | [ ^ " ] ) * " [ ^ ] * | [ ^ ] + ) ? ( " (?: " " | [ ^ " ] ) * " [ ^ ] * | [ ^ ] + ) ? > > $ / ;
1113
1214// ## DataFactory singleton
1315const DataFactory = {
@@ -188,8 +190,10 @@ export function termFromId(id, factory) {
188190
189191 // Identify the term type based on the first character
190192 switch ( id [ 0 ] ) {
191- case '_' : return factory . blankNode ( id . substr ( 2 ) ) ;
192- 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 ) ) ;
193197 case '"' :
194198 // Shortcut for internal literals
195199 if ( factory === DataFactory )
@@ -203,16 +207,15 @@ export function termFromId(id, factory) {
203207 id [ endPos + 1 ] === '@' ? id . substr ( endPos + 2 )
204208 : factory . namedNode ( id . substr ( endPos + 3 ) ) ) ;
205209 case '<' :
206- // Parse quad
207- let result = id . match ( / ^ < < ( " (?: " " | [ ^ " ] ) * " [ ^ ] * | [ ^ ] + ) ( " (?: " " | [ ^ " ] ) * " [ ^ ] * | [ ^ ] + ) ( " (?: " " | [ ^ " ] ) * " [ ^ ] * | [ ^ ] + ) ? ( " (?: " " | [ ^ " ] ) * " [ ^ ] * | [ ^ ] + ) ? > > $ / ) ;
208- let quad = [ ] ;
209- for ( let i = 1 ; i < result . length ; i ++ ) {
210- if ( typeof result [ i ] !== 'undefined' )
211- quad . push ( termFromId ( unescape ( result [ i ] ) , factory ) ) ;
212- }
213-
214- return factory . quad ( ...quad ) ;
215- default : return factory . namedNode ( id ) ;
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 ) ;
216219 }
217220}
218221
@@ -235,12 +238,14 @@ export function termToId(term) {
235238 ( term . language ? '@' + term . language :
236239 ( term . datatype && term . datatype . value !== xsd . string ? '^^' + term . datatype . value : '' ) ) ;
237240 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.
238243 return `<<${
239- escape ( termToId ( term . subject ) )
244+ escapeQuotes ( termToId ( term . subject ) )
240245 } ${
241- escape ( termToId ( term . predicate ) )
246+ escapeQuotes ( termToId ( term . predicate ) )
242247 } ${
243- escape ( termToId ( term . object ) )
248+ escapeQuotes ( termToId ( term . object ) )
244249 } ${
245250 ( isDefaultGraph ( term . graph ) ) ? '' : ` ${ termToId ( term . graph ) } `
246251 } >>`;
@@ -285,13 +290,13 @@ export class Quad extends Term {
285290export { Quad as Triple } ;
286291
287292// ### Escapes the quotes within the given literal
288- export function escape ( id ) {
289- return id . replace ( quotedMatchingRegex , ( _ , quoted ) => `"${ quoted . replace ( / " / g, '""' ) } ` ) ;
293+ export function escapeQuotes ( id ) {
294+ return id . replace ( escapedLiteral , ( _ , quoted ) => `"${ quoted . replace ( / " / g, '""' ) } ` ) ;
290295}
291296
292297// ### Unescapes the quotes within the given literal
293- export function unescape ( id ) {
294- return id . replace ( quotedMatchingRegex , ( _ , quoted ) => `"${ quoted . replace ( / " " / g, '"' ) } ` ) ;
298+ export function unescapeQuotes ( id ) {
299+ return id . replace ( escapedLiteral , ( _ , quoted ) => `"${ quoted . replace ( / " " / g, '"' ) } ` ) ;
295300}
296301
297302// ### Creates an IRI
0 commit comments