19
19
from rdflib .namespace import RDF , RDFS , OWL
20
20
from rdflib .plugins .parsers .notation3 import BadSyntax
21
21
import xml .sax
22
- from typing import cast , Union , Tuple , Dict , Any , Callable , Iterable
22
+ from typing import Any , AnyStr , Callable , cast , Dict , List , Iterable , Tuple , TypeVar , Union
23
23
24
24
_logger = logging .getLogger ("salad" )
25
25
@@ -94,6 +94,7 @@ def __init__(self, ctx, schemagraph=None, foreign_properties=None,
94
94
self .cache = {}
95
95
96
96
self .url_fields = None # type: Set[str]
97
+ self .scoped_ref_fields = None # type: Dict[unicode, int]
97
98
self .vocab_fields = None # type: Set[str]
98
99
self .identifiers = None # type: Set[str]
99
100
self .identity_links = None # type: Set[str]
@@ -186,6 +187,7 @@ def add_context(self, newcontext, baseuri=""):
186
187
"Refreshing context that already has stuff in it" )
187
188
188
189
self .url_fields = set ()
190
+ self .scoped_ref_fields = {}
189
191
self .vocab_fields = set ()
190
192
self .identifiers = set ()
191
193
self .identity_links = set ()
@@ -206,6 +208,8 @@ def add_context(self, newcontext, baseuri=""):
206
208
self .identity_links .add (key )
207
209
elif isinstance (value , dict ) and value .get ("@type" ) == "@id" :
208
210
self .url_fields .add (key )
211
+ if "refScope" in value :
212
+ self .scoped_ref_fields [key ] = value ["refScope" ]
209
213
if value .get ("identity" , False ):
210
214
self .identity_links .add (key )
211
215
elif isinstance (value , dict ) and value .get ("@type" ) == "@vocab" :
@@ -235,8 +239,8 @@ def add_context(self, newcontext, baseuri=""):
235
239
_logger .debug ("vocab_fields is %s" , self .vocab_fields )
236
240
_logger .debug ("vocab is %s" , self .vocab )
237
241
238
- def resolve_ref (self , ref , base_url = None ):
239
- # type: (Union[Dict[str, Any], str, unicode], Union[str, unicode]) -> Tuple[Union[Dict[str, Any], str, unicode], Dict[str, Any]]
242
+ def resolve_ref (self , ref , base_url = None , checklinks = True ):
243
+ # type: (Union[Dict[str, Any], str, unicode], Union[str, unicode], bool ) -> Tuple[Union[Dict[str, Any], str, unicode], Dict[str, Any]]
240
244
base_url = base_url or 'file://%s/' % os .path .abspath ('.' )
241
245
242
246
obj = None # type: Dict[str, Any]
@@ -297,7 +301,7 @@ def resolve_ref(self, ref, base_url=None):
297
301
doc = self .fetch (doc_url )
298
302
299
303
# Recursively expand urls and resolve directives
300
- obj , metadata = self .resolve_all (doc if doc else obj , doc_url )
304
+ obj , metadata = self .resolve_all (doc if doc else obj , doc_url , checklinks = checklinks )
301
305
302
306
# Requested reference should be in the index now, otherwise it's a bad
303
307
# reference
@@ -318,8 +322,8 @@ def resolve_ref(self, ref, base_url=None):
318
322
except TypeError :
319
323
return obj , metadata
320
324
321
- def resolve_all (self , document , base_url , file_base = None ):
322
- # type: (Any, Union[str, unicode], Union[str, unicode]) -> Tuple[Any, Dict[str, Any]]
325
+ def resolve_all (self , document , base_url , file_base = None , checklinks = True ):
326
+ # type: (Any, Union[str, unicode], Union[str, unicode], bool ) -> Tuple[Any, Dict[str, Any]]
323
327
loader = self
324
328
metadata = {} # type: Dict[str, Any]
325
329
if file_base is None :
@@ -328,7 +332,7 @@ def resolve_all(self, document, base_url, file_base=None):
328
332
if isinstance (document , dict ):
329
333
# Handle $import and $include
330
334
if ('$import' in document or '$include' in document ):
331
- return self .resolve_ref (document , file_base )
335
+ return self .resolve_ref (document , base_url = file_base , checklinks = checklinks )
332
336
elif isinstance (document , list ):
333
337
pass
334
338
else :
@@ -364,21 +368,22 @@ def resolve_all(self, document, base_url, file_base=None):
364
368
if "$graph" in document :
365
369
metadata = _copy_dict_without_key (document , "$graph" )
366
370
document = document ["$graph" ]
367
- metadata , _ = loader .resolve_all (metadata , base_url , file_base )
371
+ metadata , _ = loader .resolve_all (metadata , base_url , file_base = file_base , checklinks = False )
368
372
369
373
if isinstance (document , dict ):
370
374
for idmapField in loader .idmap :
371
375
if (idmapField in document and isinstance (document [idmapField ], dict ) and
372
376
"$import" not in document [idmapField ] and
373
377
"$include" not in document [idmapField ]):
374
378
ls = []
375
- for k , v in document [idmapField ].items ():
379
+ for k in sorted (document [idmapField ].keys ()):
380
+ v = document [idmapField ][k ]
376
381
if not isinstance (v , dict ):
377
382
if idmapField in loader .mapPredicate :
378
383
v = {loader .mapPredicate [idmapField ]: v }
379
384
else :
380
385
raise validate .ValidationException (
381
- "mapSubject '%s' value '%s' is not a dict and does not have a mapPredicate" , k , v )
386
+ "mapSubject '%s' value '%s' is not a dict and does not have a mapPredicate" % ( k , v ) )
382
387
v [loader .idmap [idmapField ]] = k
383
388
ls .append (v )
384
389
document [idmapField ] = ls
@@ -412,6 +417,8 @@ def resolve_all(self, document, base_url, file_base=None):
412
417
del document [d ]
413
418
414
419
for d in loader .url_fields :
420
+ if d in self .scoped_ref_fields :
421
+ continue
415
422
if d in document :
416
423
if isinstance (document [d ], basestring ):
417
424
document [d ] = loader .expand_url (
@@ -427,7 +434,7 @@ def resolve_all(self, document, base_url, file_base=None):
427
434
try :
428
435
for key , val in document .items ():
429
436
document [key ], _ = loader .resolve_all (
430
- val , base_url , file_base )
437
+ val , base_url , file_base = file_base , checklinks = False )
431
438
except validate .ValidationException as v :
432
439
_logger .debug ("loader is %s" , id (loader ))
433
440
raise validate .ValidationException ("(%s) (%s) Validation error in field %s:\n %s" % (
@@ -439,7 +446,7 @@ def resolve_all(self, document, base_url, file_base=None):
439
446
while i < len (document ):
440
447
val = document [i ]
441
448
if isinstance (val , dict ) and "$import" in val :
442
- l , _ = loader .resolve_ref (val , file_base )
449
+ l , _ = loader .resolve_ref (val , base_url = file_base , checklinks = False )
443
450
if isinstance (l , list ):
444
451
del document [i ]
445
452
for item in aslist (l ):
@@ -450,7 +457,7 @@ def resolve_all(self, document, base_url, file_base=None):
450
457
i += 1
451
458
else :
452
459
document [i ], _ = loader .resolve_all (
453
- val , base_url , file_base )
460
+ val , base_url , file_base = file_base , checklinks = False )
454
461
i += 1
455
462
except validate .ValidationException as v :
456
463
raise validate .ValidationException ("(%s) (%s) Validation error in position %i:\n %s" % (
@@ -463,6 +470,9 @@ def resolve_all(self, document, base_url, file_base=None):
463
470
metadata [identifer ], base_url , scoped = True )
464
471
loader .idx [metadata [identifer ]] = document
465
472
473
+ if checklinks :
474
+ self .validate_links (document , "" )
475
+
466
476
return document , metadata
467
477
468
478
def fetch_text (self , url ):
@@ -522,49 +532,72 @@ def check_file(self, fn): # type: (Union[str, unicode]) -> bool
522
532
else :
523
533
return False
524
534
525
- def validate_link (self , field , link ):
526
- # type: (str, Union[str, unicode, List[str], Dict[str, Any]]) -> bool
535
+ FieldType = TypeVar ('FieldType' , unicode , List [str ], Dict [str , Any ])
536
+
537
+ def validate_link (self , field , link , docid ):
538
+ # type: (AnyStr, FieldType, AnyStr) -> FieldType
527
539
if field in self .nolinkcheck :
528
- return True
540
+ return link
529
541
if isinstance (link , (str , unicode )):
530
542
if field in self .vocab_fields :
531
543
if link not in self .vocab and link not in self .idx and link not in self .rvocab :
532
544
if not self .check_file (link ):
533
545
raise validate .ValidationException (
534
546
"Field `%s` contains undefined reference to `%s`" % (field , link ))
535
547
elif link not in self .idx and link not in self .rvocab :
536
- if not self .check_file (link ):
548
+ if field in self .scoped_ref_fields :
549
+ split = urlparse .urlsplit (docid )
550
+ sp = split .fragment .split ("/" )
551
+ n = self .scoped_ref_fields [field ]
552
+ while n > 0 and len (sp ) > 0 :
553
+ sp .pop ()
554
+ n -= 1
555
+ while True :
556
+ sp .append (str (link ))
557
+ url = urlparse .urlunsplit (
558
+ (split .scheme , split .netloc , split .path , split .query , "/" .join (sp )))
559
+ if url in self .idx :
560
+ return url
561
+ sp .pop ()
562
+ if len (sp ) == 0 :
563
+ break
564
+ sp .pop ()
565
+ raise validate .ValidationException (
566
+ "Field `%s` contains undefined reference to `%s`" % (field , link ))
567
+ elif not self .check_file (link ):
537
568
raise validate .ValidationException (
538
569
"Field `%s` contains undefined reference to `%s`" % (field , link ))
539
570
elif isinstance (link , list ):
540
571
errors = []
541
- for i in link :
572
+ for n , i in enumerate ( link ) :
542
573
try :
543
- self .validate_link (field , i )
574
+ link [ n ] = self .validate_link (field , i , docid )
544
575
except validate .ValidationException as v :
545
576
errors .append (v )
546
577
if errors :
547
578
raise validate .ValidationException (
548
579
"\n " .join ([str (e ) for e in errors ]))
549
580
elif isinstance (link , dict ):
550
- self .validate_links (link )
581
+ self .validate_links (link , docid )
551
582
else :
552
583
raise validate .ValidationException ("Link must be a str, unicode, "
553
584
"list, or a dict." )
554
- return True
585
+ return link
555
586
556
- def getid (self , d ): # type: (Any) -> Union[basestring, None ]
587
+ def getid (self , d ): # type: (Any) -> Union[str, unicode ]
557
588
if isinstance (d , dict ):
558
589
for i in self .identifiers :
559
590
if i in d :
560
- if isinstance (d [i ], basestring ):
591
+ if isinstance (d [i ], ( str , unicode ) ):
561
592
return d [i ]
562
593
return None
563
594
564
- def validate_links (self , document ): # type: (Any) -> None
595
+ DocumentType = TypeVar ('DocumentType' )
596
+
597
+ def validate_links (self , document , base_url ): # type: (DocumentType, Union[str, unicode]) -> DocumentType
565
598
docid = self .getid (document )
566
- if docid is None :
567
- docid = ""
599
+ if not docid :
600
+ docid = base_url
568
601
569
602
errors = []
570
603
iterator = None # type: Any
@@ -573,26 +606,26 @@ def validate_links(self, document): # type: (Any) -> None
573
606
elif isinstance (document , dict ):
574
607
try :
575
608
for d in self .url_fields :
576
- if d not in self . identity_links and d in document :
577
- self .validate_link (d , document [d ])
609
+ if d in document and d not in self . identity_links :
610
+ document [ d ] = self .validate_link (d , document [d ], docid )
578
611
except validate .ValidationException as v :
579
612
errors .append (v )
580
613
if hasattr (document , "iteritems" ):
581
614
iterator = document .iteritems ()
582
615
else :
583
616
iterator = document .items ()
584
617
else :
585
- return
618
+ return document
586
619
587
620
for key , val in iterator :
588
621
try :
589
- self .validate_links (val )
622
+ document [ key ] = self .validate_links (val , docid ) # type: ignore
590
623
except validate .ValidationException as v :
591
624
if key not in self .nolinkcheck :
592
- docid = self .getid (val )
593
- if docid :
625
+ docid2 = self .getid (val )
626
+ if docid2 :
594
627
errors .append (validate .ValidationException (
595
- "While checking object `%s`\n %s" % (docid , validate .indent (str (v )))))
628
+ "While checking object `%s`\n %s" % (docid2 , validate .indent (str (v )))))
596
629
else :
597
630
if isinstance (key , basestring ):
598
631
errors .append (validate .ValidationException (
@@ -607,7 +640,7 @@ def validate_links(self, document): # type: (Any) -> None
607
640
"\n " .join ([str (e ) for e in errors ]))
608
641
else :
609
642
raise errors [0 ]
610
- return
643
+ return document
611
644
612
645
613
646
def _copy_dict_without_key (from_dict , filtered_key ):
0 commit comments