11"""
22This module handles the import of XML data into the DSP platform.
33"""
4+ import base64
45import json
56import os
7+ import re
8+ import uuid
69from datetime import datetime
710from pathlib import Path
811from typing import Optional , Union
@@ -229,6 +232,8 @@ class XMLResource:
229232 """Represents a resource in the XML used for data import"""
230233
231234 _id : str
235+ _iri : Optional [str ]
236+ _ark : Optional [str ]
232237 _label : str
233238 _restype : str
234239 _permissions : Optional [str ]
@@ -242,8 +247,13 @@ def __init__(self, node: etree.Element, default_ontology: Optional[str] = None)
242247 Args:
243248 node: The DOM node to be processed representing a resource (which is a child of the knora element)
244249 default_ontology: The default ontology (given in the attribute default-ontology of the knora element)
250+
251+ Returns:
252+ None
245253 """
246254 self ._id = node .attrib ['id' ]
255+ self ._iri = node .attrib .get ('iri' )
256+ self ._ark = node .attrib .get ('ark' )
247257 self ._label = node .attrib ['label' ]
248258 # get the resource type which is in format namespace:resourcetype, p.ex. rosetta:Image
249259 tmp_res_type = node .attrib ['restype' ].split (':' )
@@ -255,11 +265,7 @@ def __init__(self, node: etree.Element, default_ontology: Optional[str] = None)
255265 self ._restype = default_ontology + ':' + tmp_res_type [1 ]
256266 else :
257267 self ._restype = 'knora-admin:' + tmp_res_type [0 ]
258- permissions_tmp = node .attrib .get ("permissions" )
259- if permissions_tmp :
260- self ._permissions = node .attrib ['permissions' ]
261- else :
262- self ._permissions = None
268+ self ._permissions = node .attrib .get ("permissions" )
263269 self ._bitstream = None
264270 self ._properties = []
265271 for subnode in node :
@@ -277,6 +283,16 @@ def id(self) -> str:
277283 """The unique id of the resource"""
278284 return self ._id
279285
286+ @property
287+ def iri (self ) -> Optional [str ]:
288+ """The custom IRI of the resource"""
289+ return self ._iri
290+
291+ @property
292+ def ark (self ) -> Optional [str ]:
293+ """The custom ARK of the resource"""
294+ return self ._ark
295+
280296 @property
281297 def label (self ) -> str :
282298 """The label of the resource"""
@@ -288,7 +304,7 @@ def restype(self) -> str:
288304 return self ._restype
289305
290306 @property
291- def permissions (self ) -> str :
307+ def permissions (self ) -> Optional [ str ] :
292308 """The reference to the permissions set for this resource"""
293309 return self ._permissions
294310
@@ -323,8 +339,7 @@ def get_resptrs(self) -> list[str]:
323339 resptrs .extend (value .resrefs )
324340 return resptrs
325341
326- def get_propvals (self , resiri_lookup : dict [str , str ], permissions_lookup : dict [str , Permissions ]) -> dict [
327- str , Permissions ]:
342+ def get_propvals (self , resiri_lookup : dict [str , str ], permissions_lookup : dict [str , Permissions ]) -> dict [str , Permissions ]:
328343 """
329344 Get a dictionary of the property names and their values belonging to a resource
330345
@@ -371,8 +386,7 @@ def get_propvals(self, resiri_lookup: dict[str, str], permissions_lookup: dict[s
371386 prop_data [prop .name ] = vals if len (vals ) > 1 else vals [0 ]
372387 return prop_data
373388
374- def get_bitstream (self , internal_file_name_bitstream : str , permissions_lookup : dict [str , Permissions ]) -> Optional [
375- dict [str , Union [str , Permissions ]]]:
389+ def get_bitstream (self , internal_file_name_bitstream : str , permissions_lookup : dict [str , Permissions ]) -> Optional [dict [str , Union [str , Permissions ]]]:
376390 """
377391 Get the bitstream object belonging to the resource
378392
@@ -574,6 +588,46 @@ def validate_xml_against_schema(input_file: str, schema_file: str) -> bool:
574588 return is_valid
575589
576590
591+ def convert_ark_v0_to_resource_iri (ark : str ) -> str :
592+ """
593+ Converts an ARK URL from salsah.org (ARK version 0) of the form ark:/72163/080c-779b9990a0c3f-6e to a DSP resource
594+ IRI of the form http://rdfh.ch/080C/Ef9heHjPWDS7dMR_gGax2Q
595+
596+ This method is needed for the migration of projects from salsah.org to DSP. Resources need to be created with an
597+ existing ARK, so the IRI needs to be extracted from that ARK in order for the ARK URL to be still valid after the
598+ migration.
599+
600+ Args:
601+ ark : an ARK version 0 of the form ark:/72163/080c-779b9990a0c3f-6e, '72163' being the Name Assigning Authority
602+ number, '080c' being the project shortcode, '779b9990a0c3f' being an ID derived from the object's Salsah ID and
603+ '6e' being check digits
604+
605+ Returns:
606+ Resource IRI (str) of the form http://rdfh.ch/080C/Ef9heHjPWDS7dMR_gGax2Q
607+ """
608+ # create the DaSCH namespace to create version 5 UUIDs
609+ generic_namespace_url = uuid .NAMESPACE_URL
610+ dasch_uuid_ns = uuid .uuid5 (generic_namespace_url , "https://dasch.swiss" ) # cace8b00-717e-50d5-bcb9-486f39d733a2
611+
612+ # get the salsah resource ID from the ARK and convert it to a UUID version 5 (base64 encoded)
613+ if ark .count ("-" ) != 2 :
614+ raise BaseError (f"while converting ARK '{ ark } '. The ARK seems to be invalid" )
615+ project_id , resource_id , _ = ark .split ("-" )
616+ _ , project_id = project_id .rsplit ("/" , 1 )
617+ project_id = project_id .upper ()
618+ if not re .match ("^[0-9a-fA-F]{4}$" , project_id ):
619+ raise BaseError (f"while converting ARK '{ ark } '. Invalid project shortcode '{ project_id } '" )
620+ if not re .match ("^[0-9A-Za-z]+$" , resource_id ):
621+ raise BaseError (f"while converting ARK '{ ark } '. Invalid Salsah ID '{ resource_id } '" )
622+
623+ # make a UUID v5 from the namespace created above (which is a UUID itself) and the resource ID and encode it to base64
624+ dsp_uuid = base64 .urlsafe_b64encode (uuid .uuid5 (dasch_uuid_ns , resource_id ).bytes ).decode ("utf-8" )
625+ dsp_uuid = dsp_uuid [:- 2 ]
626+
627+ # use the new UUID to create the resource IRI
628+ return "http://rdfh.ch/" + project_id + "/" + dsp_uuid
629+
630+
577631def xml_upload (input_file : str , server : str , user : str , password : str , imgdir : str , sipi : str , verbose : bool ,
578632 validate_only : bool , incremental : bool ) -> None :
579633 """
@@ -670,6 +724,10 @@ def xml_upload(input_file: str, server: str, user: str, password: str, imgdir: s
670724 if verbose :
671725 resource .print ()
672726
727+ resource_iri = resource .iri
728+ if resource .ark :
729+ resource_iri = convert_ark_v0_to_resource_iri (resource .ark )
730+
673731 resource_bitstream = None
674732 if resource .bitstream :
675733 img = sipi .upload_bitstream (os .path .join (imgdir , resource .bitstream .value ))
@@ -682,6 +740,7 @@ def xml_upload(input_file: str, server: str, user: str, password: str, imgdir: s
682740 # create a resource instance (ResourceInstance) from the given resource in the XML (XMLResource)
683741 instance : ResourceInstance = res_classes [resource .restype ](con = con ,
684742 label = resource .label ,
743+ iri = resource_iri ,
685744 permissions = permissions_tmp ,
686745 bitstream = resource_bitstream ,
687746 values = resource .get_propvals (res_iri_lookup ,
0 commit comments