Exemple #1
0
    def test_merge_contexts_base(self):
        self.assertEqual(
            JsonObj(**{'@context': JsonObj(**{'@base': 'file://relloc'})}),
            merge_contexts(base='file://relloc'))
        self.assertEqual(
            loads(f'{{"@context": {{"@base": "{META_BASE_URI}"}}}}'),
            merge_contexts(base=META_BASE_URI))
        self.assertEqual(
            loads("""
{"@context": [
      "https://w3id.org/biolink/biolinkml/context.jsonld",
      {
         "ex": "http://example.org/test/",
         "ex2": "http://example.org/test2/"
      },
      {
         "ex": "http://example.org/test3/",
         "ex2": {
            "@id": "http://example.org/test4/"
         }
      },
      {
         "@base": "https://w3id.org/biolink/biolinkml/"
      }
   ]
}"""),
            merge_contexts([METAMODEL_CONTEXT_URI, json_1, json_2],
                           base=META_BASE_URI))
Exemple #2
0
def xform_dbgap_dimension(inp: jsonasobj.JsonObj) -> None:
        inp['@type'] = BIOCADDIE + "Dimension"
        inp['@id'] = DBGAP + inp.id
        inp.identifierInfo = [{'identifier': DBGAP + inp.id,
                                  'identifierScheme': 'dbgap'}]
        if 'type' in inp and inp.type in dimension_type_map:
            inp.dimensionType = dimension_type_map[inp.type]
            del inp['type']
Exemple #3
0
 def visit_schema(self, inline: bool=False, **kwargs) -> None:
     self.inline = inline
     self.schemaobj = JsonObj(title=self.schema.name,
                              type="object",
                              properties=JsonObj(),
                              definitions=JsonObj())
     self.schemaobj['$schema'] = "http://json-schema.org/draft-04/schema#"
     self.schemaobj['$id'] = self.schema.id
Exemple #4
0
 def visit_slot(self, slot_name: str, slot: SlotDefinition) -> None:
     # Don't emit redefined slots unless we are inlining
     if slot_name == slot.name or self.inline:
         defn = JsonObj(type="array", items=self.type_or_ref(slot.range)) if slot.multivalued \
                else self.type_or_ref(slot.range)
         if slot.description:
             defn.description = slot.description
         self.schemaobj.definitions[underscore(slot.name)] = defn
Exemple #5
0
 def visit_class(self, cls: ClassDefinition) -> bool:
     if cls.abstract:
         return False
     self.clsobj = JsonObj(title=camelcase(cls.name),
                           type='object',
                           properties=JsonObj(),
                           description=be(cls.description))
     return True
Exemple #6
0
def as_json(schema: YAMLRoot, context: Optional[Union[JsonObj, dict]] = None) -> JsonObj:
    rval = JsonObj(**schema.__dict__)
    rval['type'] = schema.__class__.__name__
    if context:
        if isinstance(context, JsonObj):
            context = context.__dict__
        for k, v in context.items():
            rval[k] = JsonObj(**v) if isinstance(v, dict) else v
    return rval
Exemple #7
0
    def __init__(self, context: JSGContext, **kwargs):
        """ Generic constructor

        :param context: Context for TYPE and IGNORE variables
        :param kwargs: Initial values - object specific
        """
        JsonObj.__init__(self)
        self._context = context
        if self._class_name not in context.TYPE_EXCEPTIONS and context.TYPE:
            self[context.TYPE] = self._class_name
        for k, v in kwargs.items():
            setattr(self, k, kwargs[k])
Exemple #8
0
 def visit_schema(self, inline: bool = False, **kwargs) -> None:
     self.inline = inline
     self.schemaobj = JsonObj(title=self.schema.name,
                              type="object",
                              properties={},
                              definitions=JsonObj())
     for p, c in self.entryProperties.items():
         self.schemaobj['properties'][p] = {
             'type': "array",
             'items': {'$ref': f"#/definitions/{camelcase(c)}"}}
     self.schemaobj['$schema'] = "http://json-schema.org/draft-07/schema#"
     self.schemaobj['$id'] = self.schema.id
Exemple #9
0
 def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> None:
     if self.inline:
         # If inline we have to include redefined slots
         prop = JsonObj(type=JsonObj())
         prop.type['$ref'] = self.jref(underscore(slot.name))
     elif slot.multivalued:
         prop = JsonObj(type="array", items=self.type_or_ref(slot.range))
     else:
         prop = JsonObj(type="string")
     if slot.description:
         prop.description = slot.description
     if slot.required:
         prop.required = True
     self.clsobj.properties[underscore(aliased_slot_name)] = prop
Exemple #10
0
def generate_pubtator(entry: JsonObj) -> None:
    """
    Generate a puptator link if this has a PMC id
    :param entry: metadata entry
    """
    if hasattr(entry, 'pmcid'):
        entry.pubtator = entry.pmcid
Exemple #11
0
    def parent_classes(self) -> str:
        """ Return the schema subclass entries"""
        rval = ''
        for e in self._obj._get('snapshot', JsonObj())._get('element', []):
            if e._get('path', '') == self._obj._get('name', None):
                for m in e._get('mapping', []):
                    if m.identity == 'w5':
                        rval += schema_subclass(FHIR_SDO[m.map], 'w5:', m.map)
        for e in self._obj._get('mapping', []):
            if e.identity == 'w5' and not url_leaf(e.uri) == 'w5':
                rval += schema_subclass(e.uri, 'w5:', url_leaf(e.uri))

        base_url = self._obj._get('baseDefinition', None)
        base_ns = 'fhir:'
        if not base_url:
            base_url = self._obj._get('base', None)
        if not base_url and not rval:
            base_url = 'http://schema.org/Thing'
            base_ns = ''

        base_type = self._obj._get('baseType', None)
        if not base_type:
            base_type = self._obj._get('constrainedType', None)
        if base_url:
            rval += schema_subclass(
                base_url, base_ns,
                base_type if base_type else url_leaf(base_url))
        return rval
 def gen_reference(rd: JsonObj) -> List[JsonObj]:
     """
     Return the object of a fhir:link based on the reference in d
     :param rd: object containing the reference
     :return: link and optional type element
     """
     # TODO: find the official regular expression for the type node.  For the moment we make the (incorrect)
     #       assumption that the type is everything that preceeds the first slash
     if "://" not in rd.reference and not rd.reference.startswith('/'):
         if hasattr(rd, 'type'):
             typ = rd.type
         else:
             typ = rd.reference.split('/', 1)[0]
         link = '../' + rd.reference
     else:
         link = rd.reference
         typ = getattr(rd, 'type', None)
     #   "subject": [{"@id": "../Patient/foo1",
     #             "@type": "fhir:Patient" },
     #             {"reference": "Patient/f001",
     #             "display": "P. van de Heuvel"}]
     ref = JsonObj(**{"@id": link})
     if typ:
         ref['@type'] = "fhir:" + typ
     return [ref, rd]
    def list_processor(k: str, lst: List) -> Any:
        """
        Process a list, adding interior identifiers if its members are objects and then replicating it so that we
        end up with a SET and a LIST

        :param k: List key (for error reporting)
        :param lst: List to be processed
        """
        bnode_generator = BNodeGenerator()

        def list_element(e: Any) -> Any:
            """
            Add a list index to list element "e"

            :param e: Element in a list
            :return: adjusted object
            """
            if isinstance(e, JsonObj):
                dict_processor(e, resource_type, True)
                if not hasattr(e, '@id'):
                    e['@id'] = ('#' + e.id) if hasattr(e, 'id') else bnode_generator.next_node()
            elif isinstance(e, list):
                print(f"Problem: {k} has a list in a list", file=sys.stderr)
            else:
                e = to_value(e)
            return e

        adjusted_list = [list_element(le) for le in lst]
        return [adjusted_list,
                {"fhir:ordered":
                     [JsonObj(**{"@id": e['@id']}) if isinstance(e, JsonObj) else e for e in adjusted_list]}]
def gen_reference(ref: str, refobject: JsonObj, server: Optional[str], id_map: Optional[Dict[str, str]])\
        -> Optional[JsonObj]:
    """
    Return the object of a fhir:link based on the reference in refObject

    :param ref: reference
    :param refobject: object containing the reference
    :param server: If supplied,
    :param id_map: Map of bundle reference names to URI/type tuples
    :return: link and optional type element
    """
    if "://" not in ref and not ref.startswith('/'):  # Relative path
        link = ('' if server else '../') + ref        # If server is supplied, ref is local, else relative to parent
    else:
        link = ref
    if link:
        if hasattr(refobject, TYPE_KEY):
            typ = refobject[TYPE_KEY]
        else:
            # TODO: we need to decide whether to use R5 or R4 regular expressions here
            m = R5_FHIR_URI_RE.match(ref)
            typ = m[4] if m else None

        rval = JsonObj()

        if link in id_map:
            rval['@id'] = id_map[link]
        else:
            rval['@id'] = link
            if typ:
                rval['@type'] = "fhir:" + typ
        return rval
    return None
Exemple #15
0
    def end_schema(self,
                   base: Optional[str] = None,
                   output: Optional[str] = None,
                   **_) -> None:
        comments = f'''Auto generated from {self.schema.source_file} by {self.generatorname} version: {self.generatorversion}
Generation date: {self.schema.generation_date}
Schema: {self.schema.name}

id: {self.schema.id}
description: {be(self.schema.description)}
license: {be(self.schema.license)}
'''
        context = JsonObj()
        context["_comments"] = comments
        context_content = {"_comments": None, "type": "@type"}
        if base:
            if '://' not in base:
                self.context_body['@base'] = os.path.relpath(
                    base, os.path.dirname(self.schema.source_file))
            else:
                self.context_body['@base'] = base
        for prefix in sorted(self.emit_prefixes):
            if self.namespaces[prefix] != self.context_body['@vocab']:
                context_content[prefix] = self.namespaces[prefix]
        for k, v in self.context_body.items():
            context_content[k] = v
        for k, v in self.slot_class_maps.items():
            context_content[k] = v
        context['@context'] = context_content
        if output:
            with open(output, 'w') as outf:
                outf.write(as_json(context))
        else:
            print(as_json(context))
Exemple #16
0
 def end_schema(self,
                base: Optional[str] = None,
                output: Optional[str] = None,
                **_) -> None:
     context = JsonObj()
     if self.emit_metadata:
         comments = f'''Auto generated from {self.schema.source_file} by {self.generatorname} version: {self.generatorversion}'''
         if self.schema.generation_date:
             comments += f'''
 Generation date: {self.schema.generation_date}
 Schema: {self.schema.name}
 '''
         comments += f'''
 id: {self.schema.id}
 description: {be(self.schema.description)}
 license: {be(self.schema.license)}
 '''
         context["_comments"] = comments
     context_content = {}
     if base:
         if '://' not in base:
             self.context_body['@base'] = os.path.relpath(
                 base, os.path.dirname(self.schema.source_file))
         else:
             self.context_body['@base'] = base
     for prefix in sorted(self.emit_prefixes):
         url = str(self.namespaces[prefix])
         if ':' == url[-1] or '/' == url[-1] or '?' == url[-1] or '#' == url[
                 -1] or '[' == url[-1] or ']' == url[-1] or '@' == url[-1]:
             context_content[prefix] = self.namespaces[prefix]
         else:
             prefix_obj = JsonObj()
             prefix_obj["@id"] = self.namespaces[prefix]
             prefix_obj["@prefix"] = True
             context_content[prefix] = prefix_obj
     for k, v in self.context_body.items():
         context_content[k] = v
     for k, v in self.slot_class_maps.items():
         context_content[k] = v
     context['@context'] = context_content
     if output:
         with open(output, 'w') as outf:
             outf.write(as_json(context))
     else:
         print(as_json(context))
Exemple #17
0
def merge_contexts(contexts: CONTEXTS_PARAM_TYPE = None,
                   base: Optional[Any] = None,
                   root_directory: Optional[str] = None) -> JsonObj:
    """ Take a list of JSON-LD contexts, which can be one of:
        * the name of a JSON-LD file
        * the URI of a JSON-lD file
        * JSON-LD text
        * A JsonObj object that contains JSON-LD
        * A dictionary that contains JSON-LD

    And turn it into an object that can be tacked onto the end of any JSON object for conversion into RDF

    The base is added back in because @base is ignored in imported and nested contexts -- it must be at the
    root in the object itself.

    :param contexts: Ordered list of contexts to add
    :param base: base to add in (optional)
    :return: aggregated context
    """
    def prune_context_node(ctxt: Union[str, JsonObj]) -> Union[str, JsonObj]:
        return ctxt['@context'] if isinstance(
            ctxt, JsonObj) and '@context' in ctxt else ctxt

    def to_file_uri(fname: str) -> str:
        return 'file://' + fname

    context_list = []
    for context in [] if contexts is None else [contexts] if not isinstance(
            contexts, (list, tuple, set)) else contexts:
        if isinstance(context, str):
            # One of filename, URL or json text
            if context.strip().startswith("{"):
                context = loads(context)
            elif '://' not in context:
                context = to_file_uri(context)
        elif not isinstance(context, (JsonObj, str)):
            context = JsonObj(**context)  # dict
        context_list.append(prune_context_node(context))
    if base:
        context_list.append(JsonObj(**{'@base': str(base)}))
    return None if not context_list else \
        JsonObj(**{"@context": context_list[0] if len(context_list) == 1 else context_list})
Exemple #18
0
        def check_types(s: SchemaDefinition) -> None:
            output = os.path.join(outputdir, 'schema4.json')
            if not os.path.exists(output):
                with open(output, 'w') as f:
                    f.write(as_json(JsonObj(**{k: as_dict(loads(as_json(v))) for k, v in s.types.items()})))
                    self.fail(f"File {output} created - rerun test")

            with open(output) as f:
                expected = as_dict(load(f))
            self.assertEqual(expected, {k: as_dict(loads(as_json(v))) for k, v in s.types.items()})
            s.types = None
Exemple #19
0
    def type_last(self, obj: JsonObj) -> JsonObj:
        """ Move the type identifiers to the end of the object for print purposes """
        def _tl_list(v: List) -> List:
            return [
                self.type_last(e) if isinstance(e, JsonObj) else
                _tl_list(e) if isinstance(e, list) else e for e in v
                if e is not None
            ]

        rval = JsonObj()
        for k in as_dict(obj).keys():
            v = obj[k]
            if v is not None and k not in ('type', '_context'):
                rval[k] = _tl_list(v) if isinstance(
                    v, list) else self.type_last(v) if isinstance(
                        v, JsonObj) else v

        if 'type' in obj and obj.type:
            rval.type = obj.type
        return rval
def map_parameters(struct) -> Optional[JsonObj]:
    if struct.resourceType == "Parameters":
        rval = JsonObj()
        for p in struct.parameter:
            if hasattr(p, 'valueString'):
                rval[p.name] = p.valueString
            elif hasattr(p, 'part'):
                rval[p.name] = p.part
            else:
                pass
        return rval
    return None
Exemple #21
0
def xform_dbgap_dataset(inp: jsonasobj.JsonObj, file_name: str) -> jsonasobj.JsonObj:
    """ Transform the json image of a dbgap dataset into a bioCaddie compatible image
    :param inp: json image of dbgap dataset
    :param file_name: name of dbgap file -- needed because the purpose and context is encoded in the name itself
    :return:
    """
    if 'Subject_Phenotypes' in file_name:
        inp.data_table.context = 'fhir:Observation'
    elif 'Sample_Attributes' in file_name:
        inp.data_table.context = 'fhir:Specimen'

    inp.data_table['@id'] = DBGAP + inp.data_table.study_id
    inp.data_table.identifierInfo = [{'identifier': DBGAP + inp.data_table.study_id,
                                      'identifierScheme': 'dbgap'}]
    inp.data_table.date_info = [{'date': inp.data_table.date_created,
                                 'dateType': 'dct:created'}]
    inp.data_table['@type'] = BIOCADDIE + "Dataset"
    [xform_dbgap_dimension(v) for v in inp.data_table.variable]
    inp.data_table.hasPartDimension = [DBGAP + v.id for v in inp.data_table.variable]

    return inp
Exemple #22
0
def json_to_rdf(json_in: jsonasobj.JsonObj, schema_uri: str) -> Graph:
    """ Use the jsonld processor to convert the json into an RDF graph
    :param json_in:
    :param schema_uri: the base URI of the schema file
    :return: RDF graph
    """
    json_in['@context'] = schema_uri + ('/' if schema_uri[-1] not in ['/', '#'] else '') + 'context.json'
    normalized = jsonld.normalize(json_in._as_json_obj(), {'format': 'application/nquads', 'algorithm': 'URDNA2015'})
    g = Graph()
    g.parse(data=prefixes, format="turtle")
    g.parse(data=normalized, format="turtle")
    return g
Exemple #23
0
def as_json_object(element: YAMLRoot, contexts: CONTEXTS_PARAM_TYPE = None) -> JsonObj:
    """
    Return the representation of element as a JsonObj object
    :param element: element to return
    :param contexts: context(s) to include in the output
    :return: JsonObj representation of element
    """
    rval = JsonObj(**element.__dict__)
    rval['type'] = element.__class__.__name__
    context_element = merge_contexts(contexts)
    if context_element:
        rval['@context'] = context_element['@context']
    return rval
    def end_schema(self) -> None:
        comments = f'''Auto generated from {self.schema.source_file} by {self.generatorname} version: {self.generatorversion}
Generation date: {self.schema.generation_date}
Schema: {self.schema.name}

id: {self.schema.id}
description: {be(self.schema.description)}
license: {be(self.schema.license)}
'''
        context = JsonObj(comments=comments)
        for k, v in self.slot_class_maps.items():
            self.prefixmap[k] = v
        context['@context'] = self.prefixmap
        print(as_json(context))
    def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str,
                         slot: SlotDefinition) -> None:
        if slot.range in self.schema.classes and slot.inlined:
            rng = f"#/definitions/{camelcase(slot.range)}"
        elif slot.range in self.schema.types:
            rng = self.schema.types[slot.range].base
        else:
            # note we assume string for non-lined complex objects
            rng = "string"

        # translate to json-schema builtins
        if rng == 'int':
            rng = 'integer'
        elif rng == 'Bool':
            rng = 'boolean'
        elif rng == 'str':
            rng = 'string'
        elif rng == 'float' or rng == 'double':
            rng = 'number'
        elif not rng.startswith('#'):
            # URIorCURIE, etc
            rng = 'string'

        if slot.inlined:
            # If inline we have to include redefined slots
            ref = JsonObj()
            ref['$ref'] = rng
            if slot.multivalued:
                prop = JsonObj(type="array", items=ref)
            else:
                prop = ref
        else:
            if slot.multivalued:
                prop = JsonObj(type="array", items={'type': rng})
            else:
                prop = JsonObj(type=rng)
        if slot.description:
            prop.description = slot.description
        if slot.required:
            self.clsobj.required.append(underscore(aliased_slot_name))

        self.clsobj.properties[underscore(aliased_slot_name)] = prop
        if self.topCls is not None and camelcase(self.topCls) == camelcase(cls.name) or \
                self.topCls is None and cls.tree_root:
            self.schemaobj.properties[underscore(aliased_slot_name)] = prop
Exemple #26
0
    def test_element_slots(self):
        """ Test all element slots and their inheritence """
        schema = SchemaLoader(env.input_path('resolver3.yaml')).resolve()
        x = {
            k: v
            for k, v in as_dict(schema.slots['s1']).items()
            if v is not None and v != []
        }
        outfile = env.expected_path('resolver3.json')
        if not os.path.exists(outfile):
            with open(outfile, 'w') as f:
                f.write(as_json(JsonObj(**x)))
        with open(outfile) as f:
            expected = as_dict(load(f))

        self.assertEqual(expected, x)
        def check_types(s: SchemaDefinition) -> None:
            output = env.expected_path('schema4.json')
            if not os.path.exists(output):
                with open(output, 'w') as f:
                    f.write(
                        as_json(
                            JsonObj(
                                **{
                                    k: as_dict(loads(as_json(v)))
                                    for k, v in s.types.items()
                                })))

            with open(output) as f:
                expected = as_dict(load(f))
            self.assertEqual(
                expected,
                {k: as_dict(loads(as_json(v)))
                 for k, v in s.types.items()})
            s.types = None
 def gen_reference(do: JsonObj) -> JsonObj:
     """
     Return the object of a fhir:link based on the reference in d
     :param do: object containing the reference
     :return: link and optional type element
     """
     # TODO: find the official regular expression for the type node.  For the moment we make the (incorrect)
     #       assumption that the type is everything that preceeds the first slash
     if "://" not in do.reference and not do.reference.startswith('/'):
         if hasattr(do, 'type'):
             typ = do.type
         else:
             typ = do.reference.split('/', 1)[0]
         link = '../' + do.reference
     else:
         link = do.reference
         typ = getattr(do, 'type', None)
     rval = JsonObj(**{"@id": link})
     if typ:
         rval['@type'] = "fhir:" + typ
     return rval
def add_date_type(date_type: URIRef, container: JsonObj) -> None:
    """
    Add the appropriate type to container
    :param date_type: FHIR date/time type
    :param container: Target container
    """
    date_str = from_value(container)
    dt = None
    if date_type in (FHIR.date, FHIR.dateTime):
        if gYear_re.match(date_str):
            dt = XSD.gYear
        elif gYearMonth_re.match(date_str):
            dt = XSD.gYearMonth
        elif date_re.match(date_str):
            dt = XSD.date
        elif date_type == FHIR.dateTime and dateTime_re.match(date_str):
            dt = XSD.dateTime
    if dt:
        typed_obj = JsonObj()
        typed_obj['@value'] = date_str
        typed_obj['@type'] = str(dt)
        container['value'] = typed_obj
Exemple #30
0
    def end_schema(self,
                   base: Optional[str] = None,
                   output: Optional[str] = None,
                   **_) -> None:

        context = JsonObj()
        if base:
            if '://' not in base:
                self.context_body['@base'] = os.path.relpath(
                    base, os.path.dirname(self.schema.source_file))
            else:
                self.context_body['@base'] = base
        for prefix in sorted(self.emit_prefixes):
            context[prefix] = self.namespaces[prefix]
        for k, v in self.context_body.items():
            context[k] = v
        for k, v in self.slot_class_maps.items():
            context[k] = v
        if output:
            with open(output, 'w') as outf:
                outf.write(as_json(context))
        else:
            print(as_json(context))
    def dict_processor(d: JsonObj, resource_type: str, resource_type_set: Set[str], full_url: Optional[str] = None) \
            -> None:
        """
        Process the elements in dictionary d:
        1) Ignore keys that begin with "@" - this is already JSON information
        2) Add index numbers to lists
        3) Recursively process values of type object
        4) Convert any elements that are not one of "nodeRole", "index", "id" and "div" to objects
        5) Merge

        :param d: dictionary to be processed
        :param resource_type: unedited resource type
        :param resource_type_set: set of all resource types in the model
        :param full_url: official ID of the resource from the containing fullURL
        :return: List of resourceTypes referenced by the resource
        """
        def gen_reference(do: JsonObj) -> JsonObj:
            """
            Return the object of a fhir:link based on the reference in d
            :param do: object containing the reference
            :return: link and optional type element
            """
            # TODO: find the official regular expression for the type node.  For the moment we make the (incorrect)
            #       assumption that the type is everything that preceeds the first slash
            if "://" not in do.reference and not do.reference.startswith('/'):
                if hasattr(do, 'type'):
                    typ = do.type
                else:
                    typ = do.reference.split('/', 1)[0]
                link = '../' + do.reference
            else:
                link = do.reference
                typ = getattr(do, 'type', None)
            rval = JsonObj(**{"@id": link})
            if typ:
                rval['@type'] = "fhir:" + typ
            return rval

        # Normalize all the elements in d.
        #  We realize the keys as a list up front to prevent messing with our own updates

        # full_url is set if we're an embedded resource that has a full_url
        inner_type = getattr(d, 'resourceType', None)
        if inner_type:
            resource_type = inner_type
            if full_url:
                d['@id'] = full_url
        # If this has a resource type, it overrides what was passed in
        for k in list(as_dict(d).keys()):
            v = d[k]
            if k.startswith('@'):  # Ignore JSON-LD components
                pass
            elif isinstance(v, JsonObj):  # Inner object -- process recursively
                dict_processor(v, resource_type, resource_type_set,
                               getattr(d, 'fullUrl', None))
            elif isinstance(v, list):  # Add ordering to the list
                d[k] = list_processor(k, v, resource_type_set)
            elif k == "id":  # Internal ids are relative to the document
                if not full_url:
                    d['@id'] = \
                        ('#' if not inner_type and not v.startswith('#') else (resource_type + '/')) + v
                d[k] = to_value(v)
            elif k == "reference":  # Link to another document
                if not hasattr(d, 'link'):
                    d["fhir:link"] = gen_reference(d)
                d[k] = to_value(v)
            elif k == "resourceType" and not (v.startswith('fhir:')):
                resource_type_set.add(v)
                d[k] = 'fhir:' + v
            elif k not in ["nodeRole", "index",
                           "div"]:  # Convert most other nodes to value entries
                d[k] = to_value(v)
            if k == 'coding':
                [add_type_arc(n) for n in v]

        # Merge any extensions (keys that start with '_') into the base
        for k in [k for k in as_dict(d).keys() if k.startswith('_')]:
            base_k = k[1:]
            if not hasattr(d, base_k) or not isinstance(d[base_k], JsonObj):
                d[base_k] = JsonObj()
            else:
                for kp, vp in as_dict(d[k]).items():
                    if kp in d[base_k]:
                        print(
                            f"Extension element {kp} is already in the base for {k}"
                        )
                    else:
                        d[base_k][kp] = vp
            del (d[k])
 def to_value(v: Any) -> JsonObj:
     return JsonObj(**{VALUE_TAG: v})
def to_r4(o: JsonObj, server: Optional[str], add_context: bool) -> JsonObj:
    """
    Convert the FHIR Resource in "o" into the R4 value notation

    :param o: FHIR resource
    :param server: Server root - if absent, use the file location
    :param add_context: True means add @context
    :return: reference to "o" with changes applied.  Warning: object is NOT copied before change
    """
    def to_value(v: Any) -> JsonObj:
        return JsonObj(**{VALUE_TAG: v})

    def parse_resource_type(rt: str) -> str:
        """ Parse rt returning just the resource type """
        return rt.rsplit(':')[1] if ':' in rt else rt

    def from_value(v: Any) -> Any:
        return v[VALUE_TAG] if isinstance(
            v, JsonObj) and VALUE_TAG in as_dict(v) else v

    def add_type_arc(n: JsonObj) -> None:
        if hasattr(n, 'system') and hasattr(n, 'code'):
            # Note: This is another reason we hate the value work
            system = from_value(n.system)
            code = urllib.parse.quote(from_value(n.code), safe='')
            system_root = system[:-1] if system[-1] in '/#' else system
            if system_root in CODE_SYSTEM_MAP:
                base = CODE_SYSTEM_MAP[system_root] + ':'
            else:
                base = system + ('' if system[-1] in '/#' else '/')
            # TODO: Escape code
            n['@type'] = base + code

    def dict_processor(d: JsonObj, resource_type: str, resource_type_set: Set[str], full_url: Optional[str] = None) \
            -> None:
        """
        Process the elements in dictionary d:
        1) Ignore keys that begin with "@" - this is already JSON information
        2) Add index numbers to lists
        3) Recursively process values of type object
        4) Convert any elements that are not one of "nodeRole", "index", "id" and "div" to objects
        5) Merge

        :param d: dictionary to be processed
        :param resource_type: unedited resource type
        :param resource_type_set: set of all resource types in the model
        :param full_url: official ID of the resource from the containing fullURL
        :return: List of resourceTypes referenced by the resource
        """
        def gen_reference(do: JsonObj) -> JsonObj:
            """
            Return the object of a fhir:link based on the reference in d
            :param do: object containing the reference
            :return: link and optional type element
            """
            # TODO: find the official regular expression for the type node.  For the moment we make the (incorrect)
            #       assumption that the type is everything that preceeds the first slash
            if "://" not in do.reference and not do.reference.startswith('/'):
                if hasattr(do, 'type'):
                    typ = do.type
                else:
                    typ = do.reference.split('/', 1)[0]
                link = '../' + do.reference
            else:
                link = do.reference
                typ = getattr(do, 'type', None)
            rval = JsonObj(**{"@id": link})
            if typ:
                rval['@type'] = "fhir:" + typ
            return rval

        # Normalize all the elements in d.
        #  We realize the keys as a list up front to prevent messing with our own updates

        # full_url is set if we're an embedded resource that has a full_url
        inner_type = getattr(d, 'resourceType', None)
        if inner_type:
            resource_type = inner_type
            if full_url:
                d['@id'] = full_url
        # If this has a resource type, it overrides what was passed in
        for k in list(as_dict(d).keys()):
            v = d[k]
            if k.startswith('@'):  # Ignore JSON-LD components
                pass
            elif isinstance(v, JsonObj):  # Inner object -- process recursively
                dict_processor(v, resource_type, resource_type_set,
                               getattr(d, 'fullUrl', None))
            elif isinstance(v, list):  # Add ordering to the list
                d[k] = list_processor(k, v, resource_type_set)
            elif k == "id":  # Internal ids are relative to the document
                if not full_url:
                    d['@id'] = \
                        ('#' if not inner_type and not v.startswith('#') else (resource_type + '/')) + v
                d[k] = to_value(v)
            elif k == "reference":  # Link to another document
                if not hasattr(d, 'link'):
                    d["fhir:link"] = gen_reference(d)
                d[k] = to_value(v)
            elif k == "resourceType" and not (v.startswith('fhir:')):
                resource_type_set.add(v)
                d[k] = 'fhir:' + v
            elif k not in ["nodeRole", "index",
                           "div"]:  # Convert most other nodes to value entries
                d[k] = to_value(v)
            if k == 'coding':
                [add_type_arc(n) for n in v]

        # Merge any extensions (keys that start with '_') into the base
        for k in [k for k in as_dict(d).keys() if k.startswith('_')]:
            base_k = k[1:]
            if not hasattr(d, base_k) or not isinstance(d[base_k], JsonObj):
                d[base_k] = JsonObj()
            else:
                for kp, vp in as_dict(d[k]).items():
                    if kp in d[base_k]:
                        print(
                            f"Extension element {kp} is already in the base for {k}"
                        )
                    else:
                        d[base_k][kp] = vp
            del (d[k])

    def list_processor(k: str, lo: List, rts: Set[str]) -> List[Any]:
        """
        Process the elements in the supplied list adding indices and converting the interior nodes

        :param k: List key (for error reporting)
        :param lo: List to be processed
        :param rts: Set of referenced resource types.  May be updated
        :return: Ordered list of entries
        """
        def list_element(e: Any, pos: int) -> Any:
            """
            Add a list index to list element "e"

            :param e: Element in a list
            :param pos: position of element
            :return: adjusted object
            """
            if isinstance(e, JsonObj):
                dict_processor(e, resource_type, rts,
                               getattr(e, 'fullUrl', None))
                if getattr(e, 'index', None) is not None:
                    print(f'Problem: "{k}" element {pos} already has an index')
                else:
                    e.index = pos  # Add positioning
            elif isinstance(e, list):
                print(f"Problem: {k} has a list in a list", file=sys.stderr)
            else:
                e = to_value(e)
                e.index = pos
            return e

        return [list_element(le, p) for p, le in enumerate(lo)]

    resource_type = parse_resource_type(o.resourceType)
    resource_type_set = {resource_type}
    dict_processor(o, resource_type, resource_type_set)

    # Add nodeRole
    o['nodeRole'] = "fhir:treeRoot"

    # Add the "ontology header"
    hdr = JsonObj()
    hdr["@id"] = o['@id'] + ".ttl"
    hdr["owl:versionIRI"] = hdr["@id"]
    hdr["owl:imports"] = "fhir:fhir.ttl"
    hdr["@type"] = 'owl:Ontology'
    # TODO: replace this with included once we get the bug fixed.
    o = JsonObj(**{"@graph": [deepcopy(o), hdr]})
    # o["@included"] = hdr

    # Fill out the rest of the context
    if add_context:
        o['@context'] = [
            f"{CONTEXT_SERVER}{rt.lower()}.context.jsonld"
            for rt in sorted(resource_type_set)
        ]
        o['@context'].append(f"{CONTEXT_SERVER}root.context.jsonld")
        local_context = JsonObj()
        local_context["nodeRole"] = JsonObj(**{
            "@type": "@id",
            "@id": "fhir:nodeRole"
        })
        if server:
            local_context["@base"] = server
        local_context['owl:imports'] = JsonObj(**{"@type": "@id"})
        local_context['owl:versionIRI'] = JsonObj(**{"@type": "@id"})
        o['@context'].append(local_context)
    return o