1+ import 'package:collection/collection.dart' ;
12import 'package:curie/curie.dart' ;
23
34import '../additional_expected_response.dart' ;
@@ -24,6 +25,12 @@ import '../thing_description.dart';
2425import '../validation/validation_exception.dart' ;
2526import '../version_info.dart' ;
2627
28+ const _validTdContextValues = [
29+ 'https://www.w3.org/2019/wot/td/v1' ,
30+ 'https://www.w3.org/2022/wot/td/v1.1' ,
31+ 'http://www.w3.org/ns/td'
32+ ];
33+
2734/// Extension for parsing fields of JSON objects.
2835extension ParseField on Map <String , dynamic > {
2936 dynamic _processFieldName (String name, Set <String >? parsedFields) {
@@ -583,4 +590,80 @@ extension ParseField on Map<String, dynamic> {
583590
584591 return value;
585592 }
593+
594+ /// Parses the JSON-LD @context of a TD and returns a [List] of
595+ /// [ContextEntry] s.
596+ List <ContextEntry > parseContext (
597+ PrefixMapping prefixMapping,
598+ Set <String >? parsedFields, {
599+ bool firstEntry = true ,
600+ }) {
601+ final fieldValue = parseField ('@context' , parsedFields);
602+
603+ return _parseContext (fieldValue, prefixMapping);
604+ }
605+ }
606+
607+ /// Parses a [List] of `@context` entries from a given [json] value.
608+ ///
609+ /// `@context` extensions are added to the provided [prefixMapping] .
610+ /// If a given entry is the [firstEntry] , it will be set in the
611+ /// [prefixMapping] accordingly.
612+ List <ContextEntry > _parseContext (
613+ dynamic json,
614+ PrefixMapping prefixMapping, {
615+ bool firstEntry = true ,
616+ }) {
617+ switch (json) {
618+ case final String jsonString:
619+ {
620+ if (firstEntry && _validTdContextValues.contains (jsonString)) {
621+ prefixMapping.defaultPrefixValue = jsonString;
622+ }
623+ return [(key: null , value: jsonString)];
624+ }
625+ case final List <dynamic > contextList:
626+ {
627+ final List <ContextEntry > result = [];
628+ contextList
629+ .mapIndexed (
630+ (index, contextEntry) => _parseContext (
631+ contextEntry,
632+ prefixMapping,
633+ firstEntry: index == 0 ,
634+ ),
635+ )
636+ .forEach (result.addAll);
637+ return result;
638+ }
639+ case final Map <String , dynamic > contextList:
640+ {
641+ return contextList.entries.map ((entry) {
642+ final key = entry.key;
643+ final value = entry.value;
644+
645+ if (value is ! String ) {
646+ throw ContextValidationException (value.runtimeType);
647+ }
648+
649+ if (! key.startsWith ('@' ) && Uri .tryParse (value) != null ) {
650+ prefixMapping.addPrefix (key, value);
651+ }
652+ return (key: key, value: value);
653+ }).toList ();
654+ }
655+ }
656+
657+ throw ContextValidationException (json.runtimeType);
658+ }
659+
660+ /// Custom [ValidationException] that is thrown for an invalid [ContextEntry] .
661+ class ContextValidationException extends ValidationException {
662+ /// Creates a new [ContextValidationException] indicating the invalid
663+ /// [runtimeType] .
664+ ContextValidationException (Type runtimeType)
665+ : super (
666+ 'Excepted either a String or a Map<String, String> '
667+ 'as @context entry, got $runtimeType instead.' ,
668+ );
586669}
0 commit comments