dsp-tools 0.9.13__py3-none-any.whl → 18.3.0.post13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. dsp_tools/__init__.py +5 -0
  2. dsp_tools/cli/args.py +47 -0
  3. dsp_tools/cli/call_action.py +85 -0
  4. dsp_tools/cli/call_action_files_only.py +101 -0
  5. dsp_tools/cli/call_action_with_network.py +207 -0
  6. dsp_tools/cli/create_parsers.py +479 -0
  7. dsp_tools/cli/entry_point.py +322 -0
  8. dsp_tools/cli/utils.py +87 -0
  9. dsp_tools/clients/CLAUDE.md +420 -0
  10. dsp_tools/clients/authentication_client.py +14 -0
  11. dsp_tools/clients/authentication_client_live.py +66 -0
  12. dsp_tools/clients/connection.py +35 -0
  13. dsp_tools/clients/connection_live.py +233 -0
  14. dsp_tools/clients/fuseki_metrics.py +60 -0
  15. dsp_tools/clients/group_user_clients.py +35 -0
  16. dsp_tools/clients/group_user_clients_live.py +181 -0
  17. dsp_tools/clients/legal_info_client.py +23 -0
  18. dsp_tools/clients/legal_info_client_live.py +132 -0
  19. dsp_tools/clients/list_client.py +49 -0
  20. dsp_tools/clients/list_client_live.py +166 -0
  21. dsp_tools/clients/metadata_client.py +24 -0
  22. dsp_tools/clients/metadata_client_live.py +47 -0
  23. dsp_tools/clients/ontology_clients.py +49 -0
  24. dsp_tools/clients/ontology_create_client_live.py +166 -0
  25. dsp_tools/clients/ontology_get_client_live.py +80 -0
  26. dsp_tools/clients/permissions_client.py +68 -0
  27. dsp_tools/clients/project_client.py +16 -0
  28. dsp_tools/clients/project_client_live.py +66 -0
  29. dsp_tools/commands/create/communicate_problems.py +24 -0
  30. dsp_tools/commands/create/create.py +134 -0
  31. dsp_tools/commands/create/create_on_server/cardinalities.py +111 -0
  32. dsp_tools/commands/create/create_on_server/classes.py +99 -0
  33. dsp_tools/commands/create/create_on_server/complete_ontologies.py +116 -0
  34. dsp_tools/commands/create/create_on_server/default_permissions.py +134 -0
  35. dsp_tools/commands/create/create_on_server/group_users.py +165 -0
  36. dsp_tools/commands/create/create_on_server/lists.py +163 -0
  37. dsp_tools/commands/create/create_on_server/mappers.py +12 -0
  38. dsp_tools/commands/create/create_on_server/onto_utils.py +74 -0
  39. dsp_tools/commands/create/create_on_server/ontology.py +52 -0
  40. dsp_tools/commands/create/create_on_server/project.py +68 -0
  41. dsp_tools/commands/create/create_on_server/properties.py +119 -0
  42. dsp_tools/commands/create/exceptions.py +29 -0
  43. dsp_tools/commands/create/lists_only.py +66 -0
  44. dsp_tools/commands/create/models/create_problems.py +87 -0
  45. dsp_tools/commands/create/models/parsed_ontology.py +88 -0
  46. dsp_tools/commands/create/models/parsed_project.py +81 -0
  47. dsp_tools/commands/create/models/rdf_ontology.py +12 -0
  48. dsp_tools/commands/create/models/server_project_info.py +100 -0
  49. dsp_tools/commands/create/parsing/parse_lists.py +45 -0
  50. dsp_tools/commands/create/parsing/parse_ontology.py +243 -0
  51. dsp_tools/commands/create/parsing/parse_project.py +149 -0
  52. dsp_tools/commands/create/parsing/parsing_utils.py +40 -0
  53. dsp_tools/commands/create/project_validate.py +595 -0
  54. dsp_tools/commands/create/serialisation/ontology.py +119 -0
  55. dsp_tools/commands/create/serialisation/project.py +44 -0
  56. dsp_tools/commands/excel2json/CLAUDE.md +101 -0
  57. dsp_tools/commands/excel2json/json_header.py +321 -0
  58. dsp_tools/commands/excel2json/lists/__init__.py +0 -0
  59. dsp_tools/commands/excel2json/lists/compliance_checks.py +292 -0
  60. dsp_tools/commands/excel2json/lists/make_lists.py +247 -0
  61. dsp_tools/commands/excel2json/lists/models/__init__.py +0 -0
  62. dsp_tools/commands/excel2json/lists/models/deserialise.py +30 -0
  63. dsp_tools/commands/excel2json/lists/models/input_error.py +216 -0
  64. dsp_tools/commands/excel2json/lists/models/serialise.py +57 -0
  65. dsp_tools/commands/excel2json/lists/utils.py +81 -0
  66. dsp_tools/commands/excel2json/models/__init__.py +0 -0
  67. dsp_tools/commands/excel2json/models/input_error.py +416 -0
  68. dsp_tools/commands/excel2json/models/json_header.py +175 -0
  69. dsp_tools/commands/excel2json/models/list_node_name.py +16 -0
  70. dsp_tools/commands/excel2json/models/ontology.py +76 -0
  71. dsp_tools/commands/excel2json/old_lists.py +328 -0
  72. dsp_tools/commands/excel2json/project.py +280 -0
  73. dsp_tools/commands/excel2json/properties.py +370 -0
  74. dsp_tools/commands/excel2json/resources.py +336 -0
  75. dsp_tools/commands/excel2json/utils.py +352 -0
  76. dsp_tools/commands/excel2xml/__init__.py +7 -0
  77. dsp_tools/commands/excel2xml/excel2xml_cli.py +523 -0
  78. dsp_tools/commands/excel2xml/excel2xml_lib.py +1953 -0
  79. dsp_tools/commands/excel2xml/propertyelement.py +47 -0
  80. dsp_tools/commands/get/__init__.py +0 -0
  81. dsp_tools/commands/get/get.py +166 -0
  82. dsp_tools/commands/get/get_permissions.py +257 -0
  83. dsp_tools/commands/get/get_permissions_legacy.py +89 -0
  84. dsp_tools/commands/get/legacy_models/__init__.py +0 -0
  85. dsp_tools/commands/get/legacy_models/context.py +318 -0
  86. dsp_tools/commands/get/legacy_models/group.py +241 -0
  87. dsp_tools/commands/get/legacy_models/helpers.py +47 -0
  88. dsp_tools/commands/get/legacy_models/listnode.py +390 -0
  89. dsp_tools/commands/get/legacy_models/model.py +12 -0
  90. dsp_tools/commands/get/legacy_models/ontology.py +324 -0
  91. dsp_tools/commands/get/legacy_models/project.py +366 -0
  92. dsp_tools/commands/get/legacy_models/propertyclass.py +417 -0
  93. dsp_tools/commands/get/legacy_models/resourceclass.py +676 -0
  94. dsp_tools/commands/get/legacy_models/user.py +438 -0
  95. dsp_tools/commands/get/models/__init__.py +0 -0
  96. dsp_tools/commands/get/models/permissions_models.py +10 -0
  97. dsp_tools/commands/id2iri.py +258 -0
  98. dsp_tools/commands/ingest_xmlupload/__init__.py +0 -0
  99. dsp_tools/commands/ingest_xmlupload/bulk_ingest_client.py +178 -0
  100. dsp_tools/commands/ingest_xmlupload/create_resources/__init__.py +0 -0
  101. dsp_tools/commands/ingest_xmlupload/create_resources/apply_ingest_id.py +69 -0
  102. dsp_tools/commands/ingest_xmlupload/create_resources/upload_xml.py +166 -0
  103. dsp_tools/commands/ingest_xmlupload/create_resources/user_information.py +121 -0
  104. dsp_tools/commands/ingest_xmlupload/ingest_files/__init__.py +0 -0
  105. dsp_tools/commands/ingest_xmlupload/ingest_files/ingest_files.py +64 -0
  106. dsp_tools/commands/ingest_xmlupload/upload_files/__init__.py +0 -0
  107. dsp_tools/commands/ingest_xmlupload/upload_files/filechecker.py +20 -0
  108. dsp_tools/commands/ingest_xmlupload/upload_files/input_error.py +57 -0
  109. dsp_tools/commands/ingest_xmlupload/upload_files/upload_failures.py +66 -0
  110. dsp_tools/commands/ingest_xmlupload/upload_files/upload_files.py +67 -0
  111. dsp_tools/commands/resume_xmlupload/__init__.py +0 -0
  112. dsp_tools/commands/resume_xmlupload/resume_xmlupload.py +96 -0
  113. dsp_tools/commands/start_stack.py +428 -0
  114. dsp_tools/commands/update_legal/CLAUDE.md +344 -0
  115. dsp_tools/commands/update_legal/__init__.py +0 -0
  116. dsp_tools/commands/update_legal/core.py +182 -0
  117. dsp_tools/commands/update_legal/csv_operations.py +135 -0
  118. dsp_tools/commands/update_legal/models.py +87 -0
  119. dsp_tools/commands/update_legal/xml_operations.py +247 -0
  120. dsp_tools/commands/validate_data/CLAUDE.md +159 -0
  121. dsp_tools/commands/validate_data/__init__.py +0 -0
  122. dsp_tools/commands/validate_data/constants.py +59 -0
  123. dsp_tools/commands/validate_data/mappers.py +143 -0
  124. dsp_tools/commands/validate_data/models/__init__.py +0 -0
  125. dsp_tools/commands/validate_data/models/api_responses.py +45 -0
  126. dsp_tools/commands/validate_data/models/input_problems.py +119 -0
  127. dsp_tools/commands/validate_data/models/rdf_like_data.py +117 -0
  128. dsp_tools/commands/validate_data/models/validation.py +106 -0
  129. dsp_tools/commands/validate_data/prepare_data/__init__.py +0 -0
  130. dsp_tools/commands/validate_data/prepare_data/get_rdf_like_data.py +296 -0
  131. dsp_tools/commands/validate_data/prepare_data/make_data_graph.py +91 -0
  132. dsp_tools/commands/validate_data/prepare_data/prepare_data.py +184 -0
  133. dsp_tools/commands/validate_data/process_validation_report/__init__.py +0 -0
  134. dsp_tools/commands/validate_data/process_validation_report/get_user_validation_message.py +358 -0
  135. dsp_tools/commands/validate_data/process_validation_report/query_validation_result.py +507 -0
  136. dsp_tools/commands/validate_data/process_validation_report/reformat_validation_results.py +150 -0
  137. dsp_tools/commands/validate_data/shacl_cli_validator.py +70 -0
  138. dsp_tools/commands/validate_data/sparql/__init__.py +0 -0
  139. dsp_tools/commands/validate_data/sparql/cardinality_shacl.py +209 -0
  140. dsp_tools/commands/validate_data/sparql/construct_shacl.py +92 -0
  141. dsp_tools/commands/validate_data/sparql/legal_info_shacl.py +36 -0
  142. dsp_tools/commands/validate_data/sparql/value_shacl.py +357 -0
  143. dsp_tools/commands/validate_data/utils.py +59 -0
  144. dsp_tools/commands/validate_data/validate_data.py +283 -0
  145. dsp_tools/commands/validate_data/validation/__init__.py +0 -0
  146. dsp_tools/commands/validate_data/validation/check_duplicate_files.py +55 -0
  147. dsp_tools/commands/validate_data/validation/check_for_unknown_classes.py +67 -0
  148. dsp_tools/commands/validate_data/validation/get_validation_report.py +94 -0
  149. dsp_tools/commands/validate_data/validation/validate_ontology.py +107 -0
  150. dsp_tools/commands/xmlupload/CLAUDE.md +292 -0
  151. dsp_tools/commands/xmlupload/__init__.py +0 -0
  152. dsp_tools/commands/xmlupload/iri_resolver.py +21 -0
  153. dsp_tools/commands/xmlupload/make_rdf_graph/__init__.py +0 -0
  154. dsp_tools/commands/xmlupload/make_rdf_graph/constants.py +63 -0
  155. dsp_tools/commands/xmlupload/make_rdf_graph/jsonld_utils.py +44 -0
  156. dsp_tools/commands/xmlupload/make_rdf_graph/make_file_value.py +77 -0
  157. dsp_tools/commands/xmlupload/make_rdf_graph/make_resource_and_values.py +114 -0
  158. dsp_tools/commands/xmlupload/make_rdf_graph/make_values.py +262 -0
  159. dsp_tools/commands/xmlupload/models/__init__.py +0 -0
  160. dsp_tools/commands/xmlupload/models/bitstream_info.py +18 -0
  161. dsp_tools/commands/xmlupload/models/formatted_text_value.py +10 -0
  162. dsp_tools/commands/xmlupload/models/ingest.py +143 -0
  163. dsp_tools/commands/xmlupload/models/input_problems.py +58 -0
  164. dsp_tools/commands/xmlupload/models/lookup_models.py +21 -0
  165. dsp_tools/commands/xmlupload/models/permission.py +45 -0
  166. dsp_tools/commands/xmlupload/models/permissions_parsed.py +93 -0
  167. dsp_tools/commands/xmlupload/models/processed/__init__.py +0 -0
  168. dsp_tools/commands/xmlupload/models/processed/file_values.py +29 -0
  169. dsp_tools/commands/xmlupload/models/processed/res.py +27 -0
  170. dsp_tools/commands/xmlupload/models/processed/values.py +101 -0
  171. dsp_tools/commands/xmlupload/models/rdf_models.py +26 -0
  172. dsp_tools/commands/xmlupload/models/upload_clients.py +14 -0
  173. dsp_tools/commands/xmlupload/models/upload_state.py +20 -0
  174. dsp_tools/commands/xmlupload/prepare_xml_input/__init__.py +0 -0
  175. dsp_tools/commands/xmlupload/prepare_xml_input/ark2iri.py +55 -0
  176. dsp_tools/commands/xmlupload/prepare_xml_input/get_processed_resources.py +252 -0
  177. dsp_tools/commands/xmlupload/prepare_xml_input/iiif_uri_validator.py +50 -0
  178. dsp_tools/commands/xmlupload/prepare_xml_input/list_client.py +120 -0
  179. dsp_tools/commands/xmlupload/prepare_xml_input/prepare_xml_input.py +67 -0
  180. dsp_tools/commands/xmlupload/prepare_xml_input/read_validate_xml_file.py +58 -0
  181. dsp_tools/commands/xmlupload/prepare_xml_input/transform_input_values.py +118 -0
  182. dsp_tools/commands/xmlupload/resource_create_client.py +25 -0
  183. dsp_tools/commands/xmlupload/richtext_id2iri.py +37 -0
  184. dsp_tools/commands/xmlupload/stash/__init__.py +0 -0
  185. dsp_tools/commands/xmlupload/stash/analyse_circular_reference_graph.py +236 -0
  186. dsp_tools/commands/xmlupload/stash/create_info_for_graph.py +53 -0
  187. dsp_tools/commands/xmlupload/stash/graph_models.py +87 -0
  188. dsp_tools/commands/xmlupload/stash/stash_circular_references.py +68 -0
  189. dsp_tools/commands/xmlupload/stash/stash_models.py +109 -0
  190. dsp_tools/commands/xmlupload/stash/upload_stashed_resptr_props.py +106 -0
  191. dsp_tools/commands/xmlupload/stash/upload_stashed_xml_texts.py +196 -0
  192. dsp_tools/commands/xmlupload/upload_config.py +76 -0
  193. dsp_tools/commands/xmlupload/write_diagnostic_info.py +27 -0
  194. dsp_tools/commands/xmlupload/xmlupload.py +516 -0
  195. dsp_tools/config/__init__.py +0 -0
  196. dsp_tools/config/logger_config.py +69 -0
  197. dsp_tools/config/warnings_config.py +32 -0
  198. dsp_tools/error/__init__.py +0 -0
  199. dsp_tools/error/custom_warnings.py +39 -0
  200. dsp_tools/error/exceptions.py +204 -0
  201. dsp_tools/error/problems.py +10 -0
  202. dsp_tools/error/xmllib_errors.py +20 -0
  203. dsp_tools/error/xmllib_warnings.py +54 -0
  204. dsp_tools/error/xmllib_warnings_util.py +159 -0
  205. dsp_tools/error/xsd_validation_error_msg.py +19 -0
  206. dsp_tools/legacy_models/__init__.py +0 -0
  207. dsp_tools/legacy_models/datetimestamp.py +81 -0
  208. dsp_tools/legacy_models/langstring.py +253 -0
  209. dsp_tools/legacy_models/projectContext.py +49 -0
  210. dsp_tools/py.typed +0 -0
  211. dsp_tools/resources/schema/data.xsd +648 -0
  212. dsp_tools/resources/schema/lists-only.json +72 -0
  213. dsp_tools/resources/schema/project.json +1258 -0
  214. dsp_tools/resources/schema/properties-only.json +874 -0
  215. dsp_tools/resources/schema/resources-only.json +140 -0
  216. dsp_tools/resources/start-stack/docker-compose.override-host.j2 +11 -0
  217. dsp_tools/resources/start-stack/docker-compose.override.yml +11 -0
  218. dsp_tools/resources/start-stack/docker-compose.yml +88 -0
  219. dsp_tools/resources/start-stack/dsp-app-config.json +45 -0
  220. dsp_tools/resources/start-stack/dsp-app-config.override-host.j2 +26 -0
  221. dsp_tools/resources/validate_data/api-shapes-resource-cardinalities.ttl +191 -0
  222. dsp_tools/resources/validate_data/api-shapes.ttl +804 -0
  223. dsp_tools/resources/validate_data/shacl-cli-image.yml +4 -0
  224. dsp_tools/resources/validate_data/validate-ontology.ttl +99 -0
  225. dsp_tools/utils/__init__.py +0 -0
  226. dsp_tools/utils/ansi_colors.py +32 -0
  227. dsp_tools/utils/data_formats/__init__.py +0 -0
  228. dsp_tools/utils/data_formats/date_util.py +166 -0
  229. dsp_tools/utils/data_formats/iri_util.py +30 -0
  230. dsp_tools/utils/data_formats/shared.py +81 -0
  231. dsp_tools/utils/data_formats/uri_util.py +76 -0
  232. dsp_tools/utils/fuseki_bloating.py +63 -0
  233. dsp_tools/utils/json_parsing.py +22 -0
  234. dsp_tools/utils/rdf_constants.py +42 -0
  235. dsp_tools/utils/rdflib_utils.py +10 -0
  236. dsp_tools/utils/replace_id_with_iri.py +66 -0
  237. dsp_tools/utils/request_utils.py +238 -0
  238. dsp_tools/utils/xml_parsing/__init__.py +0 -0
  239. dsp_tools/utils/xml_parsing/get_lookups.py +32 -0
  240. dsp_tools/utils/xml_parsing/get_parsed_resources.py +325 -0
  241. dsp_tools/utils/xml_parsing/models/__init__.py +0 -0
  242. dsp_tools/utils/xml_parsing/models/parsed_resource.py +76 -0
  243. dsp_tools/utils/xml_parsing/parse_clean_validate_xml.py +137 -0
  244. dsp_tools/xmllib/CLAUDE.md +302 -0
  245. dsp_tools/xmllib/__init__.py +49 -0
  246. dsp_tools/xmllib/general_functions.py +877 -0
  247. dsp_tools/xmllib/internal/__init__.py +0 -0
  248. dsp_tools/xmllib/internal/checkers.py +162 -0
  249. dsp_tools/xmllib/internal/circumvent_circular_imports.py +36 -0
  250. dsp_tools/xmllib/internal/constants.py +46 -0
  251. dsp_tools/xmllib/internal/input_converters.py +155 -0
  252. dsp_tools/xmllib/internal/serialise_file_value.py +57 -0
  253. dsp_tools/xmllib/internal/serialise_resource.py +177 -0
  254. dsp_tools/xmllib/internal/serialise_values.py +152 -0
  255. dsp_tools/xmllib/internal/type_aliases.py +11 -0
  256. dsp_tools/xmllib/models/__init__.py +0 -0
  257. dsp_tools/xmllib/models/config_options.py +28 -0
  258. dsp_tools/xmllib/models/date_formats.py +48 -0
  259. dsp_tools/xmllib/models/dsp_base_resources.py +1542 -0
  260. dsp_tools/xmllib/models/internal/__init__.py +0 -0
  261. dsp_tools/xmllib/models/internal/file_values.py +172 -0
  262. dsp_tools/xmllib/models/internal/geometry.py +162 -0
  263. dsp_tools/xmllib/models/internal/migration_metadata.py +55 -0
  264. dsp_tools/xmllib/models/internal/serialise_permissions.py +66 -0
  265. dsp_tools/xmllib/models/internal/values.py +342 -0
  266. dsp_tools/xmllib/models/licenses/__init__.py +0 -0
  267. dsp_tools/xmllib/models/licenses/other.py +59 -0
  268. dsp_tools/xmllib/models/licenses/recommended.py +107 -0
  269. dsp_tools/xmllib/models/permissions.py +41 -0
  270. dsp_tools/xmllib/models/res.py +1782 -0
  271. dsp_tools/xmllib/models/root.py +348 -0
  272. dsp_tools/xmllib/value_checkers.py +434 -0
  273. dsp_tools/xmllib/value_converters.py +777 -0
  274. dsp_tools-18.3.0.post13.dist-info/METADATA +90 -0
  275. dsp_tools-18.3.0.post13.dist-info/RECORD +286 -0
  276. dsp_tools-18.3.0.post13.dist-info/WHEEL +4 -0
  277. dsp_tools-18.3.0.post13.dist-info/entry_points.txt +3 -0
  278. dsp_tools-0.9.13.dist-info/LICENSE +0 -674
  279. dsp_tools-0.9.13.dist-info/METADATA +0 -144
  280. dsp_tools-0.9.13.dist-info/RECORD +0 -71
  281. dsp_tools-0.9.13.dist-info/WHEEL +0 -5
  282. dsp_tools-0.9.13.dist-info/entry_points.txt +0 -3
  283. dsp_tools-0.9.13.dist-info/top_level.txt +0 -1
  284. dsplib/models/connection.py +0 -272
  285. dsplib/models/group.py +0 -296
  286. dsplib/models/helpers.py +0 -505
  287. dsplib/models/langstring.py +0 -277
  288. dsplib/models/listnode.py +0 -578
  289. dsplib/models/model.py +0 -20
  290. dsplib/models/ontology.py +0 -448
  291. dsplib/models/permission.py +0 -112
  292. dsplib/models/project.py +0 -547
  293. dsplib/models/propertyclass.py +0 -505
  294. dsplib/models/resource.py +0 -366
  295. dsplib/models/resourceclass.py +0 -810
  296. dsplib/models/sipi.py +0 -30
  297. dsplib/models/user.py +0 -731
  298. dsplib/models/value.py +0 -1000
  299. dsplib/utils/knora-data-schema.xsd +0 -454
  300. dsplib/utils/knora-schema-lists.json +0 -83
  301. dsplib/utils/knora-schema.json +0 -434
  302. dsplib/utils/onto_commons.py +0 -24
  303. dsplib/utils/onto_create_lists.py +0 -73
  304. dsplib/utils/onto_create_ontology.py +0 -442
  305. dsplib/utils/onto_get.py +0 -58
  306. dsplib/utils/onto_validate.py +0 -33
  307. dsplib/utils/xml_upload.py +0 -539
  308. dsplib/widgets/doublepassword.py +0 -80
  309. knora/MLS-import-libraries.py +0 -84
  310. knora/dsp_tools.py +0 -96
  311. knora/dsplib/models/connection.py +0 -272
  312. knora/dsplib/models/group.py +0 -296
  313. knora/dsplib/models/helpers.py +0 -506
  314. knora/dsplib/models/langstring.py +0 -277
  315. knora/dsplib/models/listnode.py +0 -578
  316. knora/dsplib/models/model.py +0 -20
  317. knora/dsplib/models/ontology.py +0 -448
  318. knora/dsplib/models/permission.py +0 -112
  319. knora/dsplib/models/project.py +0 -583
  320. knora/dsplib/models/propertyclass.py +0 -505
  321. knora/dsplib/models/resource.py +0 -416
  322. knora/dsplib/models/resourceclass.py +0 -811
  323. knora/dsplib/models/sipi.py +0 -35
  324. knora/dsplib/models/user.py +0 -731
  325. knora/dsplib/models/value.py +0 -1000
  326. knora/dsplib/utils/knora-data-schema.xsd +0 -464
  327. knora/dsplib/utils/knora-schema-lists.json +0 -83
  328. knora/dsplib/utils/knora-schema.json +0 -444
  329. knora/dsplib/utils/onto_commons.py +0 -24
  330. knora/dsplib/utils/onto_create_lists.py +0 -73
  331. knora/dsplib/utils/onto_create_ontology.py +0 -451
  332. knora/dsplib/utils/onto_get.py +0 -58
  333. knora/dsplib/utils/onto_validate.py +0 -33
  334. knora/dsplib/utils/xml_upload.py +0 -540
  335. knora/dsplib/widgets/doublepassword.py +0 -80
  336. knora/knora.py +0 -2108
  337. knora/test.py +0 -99
  338. knora/testit.py +0 -76
  339. knora/xml2knora.py +0 -633
  340. {dsplib → dsp_tools/cli}/__init__.py +0 -0
  341. {dsplib/models → dsp_tools/clients}/__init__.py +0 -0
  342. {dsplib/utils → dsp_tools/commands}/__init__.py +0 -0
  343. {dsplib/widgets → dsp_tools/commands/create}/__init__.py +0 -0
  344. {knora → dsp_tools/commands/create/create_on_server}/__init__.py +0 -0
  345. {knora/dsplib → dsp_tools/commands/create/models}/__init__.py +0 -0
  346. {knora/dsplib/models → dsp_tools/commands/create/parsing}/__init__.py +0 -0
  347. {knora/dsplib/utils → dsp_tools/commands/create/serialisation}/__init__.py +0 -0
  348. {knora/dsplib/widgets → dsp_tools/commands/excel2json}/__init__.py +0 -0
@@ -0,0 +1,1542 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Collection
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+ from dsp_tools.error.xmllib_warnings import MessageInfo
8
+ from dsp_tools.error.xmllib_warnings_util import emit_xmllib_input_warning
9
+ from dsp_tools.xmllib.internal.input_converters import check_and_fix_collection_input
10
+ from dsp_tools.xmllib.internal.input_converters import check_and_fix_is_non_empty_string
11
+ from dsp_tools.xmllib.models.config_options import NewlineReplacement
12
+ from dsp_tools.xmllib.models.internal.geometry import Circle
13
+ from dsp_tools.xmllib.models.internal.geometry import GeometryPoint
14
+ from dsp_tools.xmllib.models.internal.geometry import GeometryShape
15
+ from dsp_tools.xmllib.models.internal.geometry import Polygon
16
+ from dsp_tools.xmllib.models.internal.geometry import Rectangle
17
+ from dsp_tools.xmllib.models.internal.geometry import Vector
18
+ from dsp_tools.xmllib.models.internal.migration_metadata import MigrationMetadata
19
+ from dsp_tools.xmllib.models.internal.values import LinkValue
20
+ from dsp_tools.xmllib.models.internal.values import Richtext
21
+ from dsp_tools.xmllib.models.internal.values import SimpleText
22
+ from dsp_tools.xmllib.models.internal.values import Value
23
+ from dsp_tools.xmllib.models.permissions import Permissions
24
+ from dsp_tools.xmllib.value_checkers import is_decimal
25
+ from dsp_tools.xmllib.value_checkers import is_nonempty_value
26
+
27
+ LIST_SEPARATOR = "\n - "
28
+
29
+
30
+ @dataclass
31
+ class RegionResource:
32
+ res_id: str
33
+ label: str
34
+ values: list[Value]
35
+ geometry: GeometryShape | None
36
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS
37
+ migration_metadata: MigrationMetadata | None = None
38
+
39
+ @staticmethod
40
+ def create_new(
41
+ res_id: str,
42
+ label: str,
43
+ region_of: str,
44
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
45
+ ) -> RegionResource:
46
+ """
47
+ Creates a new region resource.
48
+ A region is a region of interest (ROI) in a StillImageRepresentation.
49
+
50
+ Exactly one geometry shape has to be added to the resource with one of the following methods:
51
+ `add_rectangle`, `add_polygon`, `add_circle` (see documentation below for more information).
52
+
53
+ [See XML documentation for details](https://docs.dasch.swiss/latest/DSP-TOOLS/file-formats/xml-data-file/#region)
54
+
55
+ Args:
56
+ res_id: ID of this region resource
57
+ label: label of this region resource
58
+ region_of: ID of the image resource that this region refers to (cardinality 1)
59
+ permissions: permissions of this region resource
60
+
61
+ Returns:
62
+ A region resource
63
+
64
+ Examples:
65
+ ```python
66
+ region = xmllib.RegionResource.create_new(
67
+ res_id="ID",
68
+ label="label",
69
+ region_of="image_id",
70
+ )
71
+ ```
72
+ """
73
+ _check_strings(string_to_check=res_id, res_id=res_id, field_name="Resource ID")
74
+ res_id = str(res_id)
75
+ lbl = check_and_fix_is_non_empty_string(value=label, res_id=res_id, value_field="label")
76
+ return RegionResource(
77
+ res_id=res_id,
78
+ label=lbl,
79
+ values=[
80
+ LinkValue.new(
81
+ value=region_of, prop_name="isRegionOf", resource_id=res_id, comment=None, permissions=permissions
82
+ )
83
+ ],
84
+ geometry=None,
85
+ permissions=permissions,
86
+ )
87
+
88
+ def add_rectangle(
89
+ self,
90
+ point1: tuple[float, float],
91
+ point2: tuple[float, float],
92
+ line_width: float = 2,
93
+ color: str = "#5b24bf",
94
+ active: bool = True,
95
+ ) -> RegionResource:
96
+ """
97
+ Add a rectangle shape to the region.
98
+
99
+ [For a visual example see the XML documentation](https://docs.dasch.swiss/latest/DSP-TOOLS/file-formats/xml-data-file/#geometry)
100
+
101
+ Args:
102
+ point1: first point of the rectangle represented as two numbers between 0 and 1 in the format (x, y)
103
+ point2: second point of the rectangle represented as two numbers between 0 and 1 in the format (x, y)
104
+ line_width: A number in pixels between 1 - 5
105
+ color: A hexadecimal color value which starts with a `#` followed by 3 or 6 numerals.
106
+ The default value was chosen as it is distinguishable for most color-blind people.
107
+ active: If set to `False`, the region is marked as 'deleted'
108
+
109
+ Returns:
110
+ Region with added rectangle
111
+
112
+ Examples:
113
+ ```python
114
+ region = region.add_rectangle(
115
+ point1=(0.1, 0.1),
116
+ point2=(0.2, 0.2),
117
+ )
118
+ ```
119
+
120
+ ```python
121
+ # with custom display values
122
+ region = region.add_rectangle(
123
+ point1=(0.1, 0.1),
124
+ point2=(0.2, 0.2),
125
+ line_width=3,
126
+ color="#32a873",
127
+ )
128
+ ```
129
+ """
130
+ self.geometry = Rectangle(
131
+ point1=GeometryPoint(point1[0], point1[1], self.res_id),
132
+ point2=GeometryPoint(point2[0], point2[1], self.res_id),
133
+ line_width=line_width,
134
+ color=color,
135
+ active=active,
136
+ resource_id=self.res_id,
137
+ )
138
+ return self
139
+
140
+ def add_polygon(
141
+ self,
142
+ points: list[tuple[float, float]],
143
+ line_width: float = 2,
144
+ color: str = "#5b24bf",
145
+ active: bool = True,
146
+ ) -> RegionResource:
147
+ """
148
+ Add a polygon shape to the region.
149
+ A polygon should have at least three points.
150
+ If you wish to create a rectangle, please use the designated `add_rectangle` method.
151
+
152
+ [For a visual example see the XML documentation](https://docs.dasch.swiss/latest/DSP-TOOLS/file-formats/xml-data-file/#geometry)
153
+
154
+ **Please note that this cannot currently be displayed in the dsp-app.**
155
+
156
+ Args:
157
+ points: list of tuples containing two numbers between 0 and 1 in the format (x, y)
158
+ line_width: A number in pixels between 1 - 5
159
+ color: A hexadecimal color value which starts with a `#` followed by 3 or 6 numerals.
160
+ The default value was chosen as it is distinguishable for most color-blind people.
161
+ active: If set to `False` the region is marked as 'deleted'
162
+
163
+ Returns:
164
+ Region with added polygon
165
+
166
+ Examples:
167
+ ```python
168
+ region = region.add_polygon(
169
+ points=[(0.1, 0.1), (0.2, 0.2), (0.3, 0.3)],
170
+ )
171
+ ```
172
+
173
+ ```python
174
+ # with custom display values
175
+ region = region.add_polygon(
176
+ points=[(0.1, 0.1), (0.2, 0.2), (0.3, 0.3)],
177
+ line_width=3,
178
+ color="#32a873",
179
+ )
180
+ ```
181
+ """
182
+ geom_points = [GeometryPoint(val[0], val[1], self.res_id) for val in points]
183
+ self.geometry = Polygon(
184
+ points=geom_points, line_width=line_width, color=color, active=active, resource_id=self.res_id
185
+ )
186
+ return self
187
+
188
+ def add_circle(
189
+ self,
190
+ center: tuple[float, float],
191
+ radius: tuple[float, float],
192
+ line_width: float = 2,
193
+ color: str = "#5b24bf",
194
+ active: bool = True,
195
+ ) -> RegionResource:
196
+ """
197
+ Add a circle shape to the region.
198
+
199
+ [For a visual example see the XML documentation](https://docs.dasch.swiss/latest/DSP-TOOLS/file-formats/xml-data-file/#geometry)
200
+
201
+ **Please note that this cannot currently be displayed in the dsp-app.**
202
+
203
+ Args:
204
+ center: center of the circle, represented as two numbers between 0 and 1 in the format (x, y)
205
+ radius: radius of the circle, represented as a 2-dimensional vector,
206
+ i.e. two numbers between 0 and 1 in the format (x, y)
207
+ line_width: A number in pixels between 1 - 5
208
+ color: A hexadecimal color value which starts with a `#` followed by 3 or 6 numerals.
209
+ The default value was chosen as it is distinguishable for most color-blind people.
210
+ active: If set to `False` the region is marked as 'deleted'
211
+
212
+ Returns:
213
+ Region with added circle
214
+
215
+ Examples:
216
+ ```python
217
+ region = region.add_circle(
218
+ center=(0.1, 0.1),
219
+ radius=(0.2, 0.2),
220
+ )
221
+ ```
222
+
223
+ ```python
224
+ # with custom display values
225
+ region = region.add_circle(
226
+ center=(0.1, 0.1),
227
+ radius=(0.2, 0.2),
228
+ line_width=3,
229
+ color="#32a873",
230
+ )
231
+ ```
232
+ """
233
+ self.geometry = Circle(
234
+ center=GeometryPoint(center[0], center[1], self.res_id),
235
+ radius=Vector(radius[0], radius[1], self.res_id),
236
+ line_width=line_width,
237
+ color=color,
238
+ active=active,
239
+ resource_id=self.res_id,
240
+ )
241
+ return self
242
+
243
+ def add_comment(
244
+ self,
245
+ text: str,
246
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
247
+ comment: str | None = None,
248
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
249
+ ) -> RegionResource:
250
+ """
251
+ Add a comment to the region
252
+
253
+ Args:
254
+ text: text of the comment
255
+ permissions: optional permissions of this value
256
+ comment: optional comment about this comment
257
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
258
+
259
+ Returns:
260
+ The original region, with the added comment
261
+
262
+ Examples:
263
+ ```python
264
+ region = region.add_comment("comment text")
265
+ ```
266
+
267
+ ```python
268
+ region = region.add_comment(text="comment text", comment="Comment about the comment.")
269
+ ```
270
+ """
271
+ self.values.append(
272
+ Richtext.new(
273
+ value=text,
274
+ prop_name="hasComment",
275
+ permissions=permissions,
276
+ comment=comment,
277
+ resource_id=self.res_id,
278
+ newline_replacement=newline_replacement,
279
+ )
280
+ )
281
+ return self
282
+
283
+ def add_comment_multiple(
284
+ self,
285
+ texts: Collection[str],
286
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
287
+ comment: str | None = None,
288
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
289
+ ) -> RegionResource:
290
+ """
291
+ Add several comments to the region
292
+
293
+ Args:
294
+ texts: list of texts
295
+ permissions: optional permissions of these values
296
+ comment: optional comment about these comments
297
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
298
+
299
+ Returns:
300
+ The original region, with the added comments
301
+
302
+ Examples:
303
+ ```python
304
+ region = region.add_comment_multiple(["comment 1", "comment 2"])
305
+ ```
306
+ """
307
+ vals = check_and_fix_collection_input(texts, "hasComment", self.res_id)
308
+ comnts = [
309
+ Richtext.new(
310
+ value=x,
311
+ prop_name="hasComment",
312
+ permissions=permissions,
313
+ comment=comment,
314
+ resource_id=self.res_id,
315
+ newline_replacement=newline_replacement,
316
+ )
317
+ for x in vals
318
+ ]
319
+ self.values.extend(comnts)
320
+ return self
321
+
322
+ def add_comment_optional(
323
+ self,
324
+ text: Any,
325
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
326
+ comment: str | None = None,
327
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
328
+ ) -> RegionResource:
329
+ """
330
+ If the value is not empty, add it as comment, otherwise return the region unchanged.
331
+
332
+ Args:
333
+ text: text of the comment (or empty value)
334
+ permissions: optional permissions of this value
335
+ comment: optional comment about this comment
336
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
337
+
338
+ Returns:
339
+ The original region, with the added comment
340
+
341
+ Examples:
342
+ ```python
343
+ region = region.add_comment_optional("comment text")
344
+ ```
345
+
346
+ ```python
347
+ region = region.add_comment_optional(None)
348
+ ```
349
+ """
350
+ if is_nonempty_value(text):
351
+ return self.add_comment(text, permissions, comment, newline_replacement)
352
+ return self
353
+
354
+
355
+ @dataclass
356
+ class LinkResource:
357
+ res_id: str
358
+ label: str
359
+ values: list[Value]
360
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS
361
+ migration_metadata: MigrationMetadata | None = None
362
+
363
+ @staticmethod
364
+ def create_new(
365
+ res_id: str,
366
+ label: str,
367
+ link_to: Collection[str],
368
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
369
+ ) -> LinkResource:
370
+ """
371
+ Creates a new link resource.
372
+ A link resource is like a container that groups together several other resources of different classes.
373
+
374
+ [See XML documentation for details](https://docs.dasch.swiss/latest/DSP-TOOLS/file-formats/xml-data-file/#link)
375
+
376
+ Args:
377
+ res_id: ID of this link resource
378
+ label: label of this link resource
379
+ link_to: IDs of the resources that should be linked together (cardinality 1-n)
380
+ permissions: permissions of this link resource
381
+
382
+ Returns:
383
+ A link resource
384
+
385
+ Examples:
386
+ ```python
387
+ link_resource = xmllib.LinkResource.create_new(
388
+ res_id="ID",
389
+ label="label",
390
+ link_to=["target_resource_id_1", "target_resource_id_2"],
391
+ )
392
+ ```
393
+ """
394
+ _check_strings(string_to_check=res_id, res_id=res_id, field_name="Resource ID")
395
+ res_id = str(res_id)
396
+ lbl = check_and_fix_is_non_empty_string(value=label, res_id=res_id, value_field="label")
397
+ links_to = check_and_fix_collection_input(link_to, "hasLinkTo", res_id)
398
+ link_vals: list[Value] = [
399
+ LinkValue.new(value=x, prop_name="hasLinkTo", resource_id=res_id, comment=None, permissions=permissions)
400
+ for x in links_to
401
+ ]
402
+ return LinkResource(
403
+ res_id=res_id,
404
+ label=lbl,
405
+ values=link_vals,
406
+ permissions=permissions,
407
+ )
408
+
409
+ def add_comment(
410
+ self,
411
+ text: str,
412
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
413
+ comment: str | None = None,
414
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
415
+ ) -> LinkResource:
416
+ """
417
+ Add a comment to the resource
418
+
419
+ Args:
420
+ text: text of the comment
421
+ permissions: optional permissions of this value
422
+ comment: optional comment about this comment
423
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
424
+
425
+ Returns:
426
+ The original resource, with the added comment
427
+
428
+ Examples:
429
+ ```python
430
+ link_resource = link_resource.add_comment("comment text")
431
+ ```
432
+
433
+ ```python
434
+ link_resource = link_resource.add_comment(text="comment text", comment="Comment about the comment.")
435
+ ```
436
+ """
437
+ self.values.append(
438
+ Richtext.new(
439
+ value=text,
440
+ prop_name="hasComment",
441
+ permissions=permissions,
442
+ comment=comment,
443
+ resource_id=self.res_id,
444
+ newline_replacement=newline_replacement,
445
+ )
446
+ )
447
+ return self
448
+
449
+ def add_comment_multiple(
450
+ self,
451
+ texts: Collection[str],
452
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
453
+ comment: str | None = None,
454
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
455
+ ) -> LinkResource:
456
+ """
457
+ Add several comments to the resource
458
+
459
+ Args:
460
+ texts: list of texts
461
+ permissions: optional permissions of these values
462
+ comment: optional comment about this comment
463
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
464
+
465
+ Returns:
466
+ The original resource, with the added comments
467
+
468
+ Examples:
469
+ ```python
470
+ link_resource = link_resource.add_comment_multiple(["comment 1", "comment 2"])
471
+ ```
472
+ """
473
+ vals = check_and_fix_collection_input(texts, "hasComment", self.res_id)
474
+ for v in vals:
475
+ self.add_comment(v, permissions, comment, newline_replacement)
476
+ return self
477
+
478
+ def add_comment_optional(
479
+ self,
480
+ text: Any,
481
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
482
+ comment: str | None = None,
483
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
484
+ ) -> LinkResource:
485
+ """
486
+ If the value is not empty, add it as comment, otherwise return the resource unchanged.
487
+
488
+ Args:
489
+ text: text of the comment (or empty value)
490
+ permissions: optional permissions of this value
491
+ comment: optional comment about this comment
492
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
493
+
494
+ Returns:
495
+ The original resource, with the added comment
496
+
497
+ Examples:
498
+ ```python
499
+ link_resource = link_resource.add_comment_optional("comment text")
500
+ ```
501
+
502
+ ```python
503
+ link_resource = link_resource.add_comment_optional(None)
504
+ ```
505
+ """
506
+ if is_nonempty_value(text):
507
+ return self.add_comment(text, permissions, comment, newline_replacement)
508
+ return self
509
+
510
+
511
+ @dataclass
512
+ class SegmentBounds:
513
+ segment_start: float | int | str
514
+ segment_end: float | int | str
515
+ permissions: Permissions
516
+ res_id: str
517
+
518
+ def __post_init__(self) -> None:
519
+ msg: list[str] = []
520
+ if not is_decimal(self.segment_start):
521
+ msg.append(f"Segment Start Value: {self.segment_start} | Type: {type(self.segment_start)}")
522
+ if not is_decimal(self.segment_end):
523
+ msg.append(f"Segment End Value: {self.segment_end} | Type: {type(self.segment_start)}")
524
+ if msg:
525
+ wrng = f"{LIST_SEPARATOR}{LIST_SEPARATOR.join(msg)}"
526
+ msg_info = MessageInfo(
527
+ f"Segment bounds must be a float or integer. The following places have an unexpected type: {wrng}",
528
+ self.res_id,
529
+ )
530
+ emit_xmllib_input_warning(msg_info)
531
+ self.segment_start = str(self.segment_start)
532
+ self.segment_end = str(self.segment_end)
533
+
534
+
535
+ @dataclass
536
+ class VideoSegmentResource:
537
+ res_id: str
538
+ label: str
539
+ segment_bounds: SegmentBounds
540
+ values: list[Value]
541
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS
542
+ migration_metadata: MigrationMetadata | None = None
543
+
544
+ @staticmethod
545
+ def create_new(
546
+ res_id: str,
547
+ label: str,
548
+ segment_of: str,
549
+ segment_start: float | int | str,
550
+ segment_end: float | int | str,
551
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
552
+ ) -> VideoSegmentResource:
553
+ """
554
+ Creates a new video segment resource, i.e. a time span of a MovingImageRepresentation.
555
+
556
+ [See XML documentation for details](https://docs.dasch.swiss/latest/DSP-TOOLS/file-formats/xml-data-file/#video-segment-and-audio-segment)
557
+
558
+ Args:
559
+ res_id: ID of this video segment resource
560
+ label: label of this video segment resource
561
+ segment_of: ID of the video resource that this segment refers to (cardinality 1)
562
+ segment_start: start of the segment in seconds (cardinality 1)
563
+ segment_end: end of the segment in seconds (cardinality 1)
564
+ permissions: permissions of this resource
565
+
566
+ Returns:
567
+ A video segment resource
568
+
569
+ Examples:
570
+ ```python
571
+ video_segment = xmllib.VideoSegmentResource.create_new(
572
+ res_id="ID",
573
+ label="label",
574
+ segment_of="video_resource_id",
575
+ segment_start=1.2,
576
+ segment_end=3.4,
577
+ )
578
+ ```
579
+ """
580
+ _check_strings(string_to_check=res_id, res_id=res_id, field_name="Resource ID")
581
+ res_id = str(res_id)
582
+ lbl = check_and_fix_is_non_empty_string(value=label, res_id=res_id, value_field="label")
583
+ segment_of_val = LinkValue.new(
584
+ value=segment_of, prop_name="isSegmentOf", permissions=permissions, comment=None, resource_id=res_id
585
+ )
586
+ return VideoSegmentResource(
587
+ res_id=res_id,
588
+ label=lbl,
589
+ values=[segment_of_val],
590
+ segment_bounds=SegmentBounds(segment_start, segment_end, permissions, res_id),
591
+ permissions=permissions,
592
+ )
593
+
594
+ def add_title(
595
+ self,
596
+ title: str,
597
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
598
+ comment: str | None = None,
599
+ ) -> VideoSegmentResource:
600
+ """
601
+ Add a title to the resource.
602
+
603
+ Args:
604
+ title: text
605
+ permissions: permissions of the value
606
+ comment: comments on the value
607
+
608
+ Returns:
609
+ The original resource, with the added title
610
+
611
+ Examples:
612
+ ```python
613
+ video_segment = video_segment.add_title("segment title")
614
+ ```
615
+ """
616
+ existing_titles = [x for x in self.values if x.prop_name == "hasTitle"]
617
+ if any(existing_titles):
618
+ _warn_value_exists(
619
+ old_value=existing_titles.pop(0).value, new_value=title, value_field="title", res_id=self.res_id
620
+ )
621
+ self.values.append(
622
+ SimpleText.new(
623
+ value=title,
624
+ prop_name="hasTitle",
625
+ permissions=permissions,
626
+ comment=comment,
627
+ resource_id=self.res_id,
628
+ )
629
+ )
630
+ return self
631
+
632
+ def add_title_optional(
633
+ self,
634
+ title: Any,
635
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
636
+ comment: str | None = None,
637
+ ) -> VideoSegmentResource:
638
+ """
639
+ If the value is not empty, add it as title, otherwise return the resource unchanged.
640
+
641
+ Args:
642
+ title: text or empty value
643
+ permissions: permissions of the value
644
+ comment: comments on the value
645
+
646
+ Returns:
647
+ The original resource, with the added title
648
+
649
+ Examples:
650
+ ```python
651
+ video_segment = video_segment.add_title("segment title")
652
+ ```
653
+
654
+ ```python
655
+ video_segment = video_segment.add_title(None)
656
+ ```
657
+ """
658
+ if is_nonempty_value(title):
659
+ self.add_title(title, permissions, comment)
660
+ return self
661
+
662
+ def add_comment(
663
+ self,
664
+ text: str,
665
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
666
+ comment: str | None = None,
667
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
668
+ ) -> VideoSegmentResource:
669
+ """
670
+ Add a comment to the resource
671
+
672
+ Args:
673
+ text: text
674
+ permissions: optional permissions of this value
675
+ comment: optional comment about this comment
676
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
677
+
678
+ Returns:
679
+ The original resource, with the added comment
680
+
681
+ Examples:
682
+ ```python
683
+ video_segment = video_segment.add_comment("comment text")
684
+ ```
685
+ """
686
+ self.values.append(
687
+ Richtext.new(
688
+ value=text,
689
+ prop_name="hasComment",
690
+ permissions=permissions,
691
+ comment=comment,
692
+ resource_id=self.res_id,
693
+ newline_replacement=newline_replacement,
694
+ )
695
+ )
696
+ return self
697
+
698
+ def add_comment_multiple(
699
+ self,
700
+ texts: Collection[str],
701
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
702
+ comment: str | None = None,
703
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
704
+ ) -> VideoSegmentResource:
705
+ """
706
+ Add several comments to the resource
707
+
708
+ Args:
709
+ texts: list of texts
710
+ permissions: optional permissions of these values
711
+ comment: optional comment about these comments
712
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
713
+
714
+ Returns:
715
+ The original resource, with the added comments
716
+
717
+ Examples:
718
+ ```python
719
+ video_segment = video_segment.add_comment_multiple(["comment 1", "comment 2"])
720
+ ```
721
+ """
722
+ vals = check_and_fix_collection_input(texts, "hasComment", self.res_id)
723
+ for v in vals:
724
+ self.add_comment(v, permissions, comment, newline_replacement)
725
+ return self
726
+
727
+ def add_comment_optional(
728
+ self,
729
+ text: Any,
730
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
731
+ comment: str | None = None,
732
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
733
+ ) -> VideoSegmentResource:
734
+ """
735
+ If the value is not empty, add it as comment, otherwise return the resource unchanged.
736
+
737
+ Args:
738
+ text: text of the comment (or empty value)
739
+ permissions: optional permissions of this value
740
+ comment: optional comment about this comment
741
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
742
+
743
+ Returns:
744
+ The original resource, with the added comment
745
+
746
+ Examples:
747
+ ```python
748
+ video_segment = video_segment.add_comment_optional("comment text")
749
+ ```
750
+
751
+ ```python
752
+ video_segment = video_segment.add_comment_optional(None)
753
+ ```
754
+ """
755
+ if is_nonempty_value(text):
756
+ self.add_comment(text, permissions, comment, newline_replacement)
757
+ return self
758
+
759
+ def add_description(
760
+ self,
761
+ description: str,
762
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
763
+ comment: str | None = None,
764
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
765
+ ) -> VideoSegmentResource:
766
+ """
767
+ Add a description to the resource
768
+
769
+ Args:
770
+ description: text
771
+ permissions: optional permissions of this value
772
+ comment: optional comment
773
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
774
+
775
+ Returns:
776
+ The original resource, with the added description
777
+
778
+ Examples:
779
+ ```python
780
+ video_segment = video_segment.add_description("description text")
781
+ ```
782
+ """
783
+ self.values.append(
784
+ Richtext.new(
785
+ value=description,
786
+ prop_name="hasDescription",
787
+ permissions=permissions,
788
+ comment=comment,
789
+ resource_id=self.res_id,
790
+ newline_replacement=newline_replacement,
791
+ )
792
+ )
793
+ return self
794
+
795
+ def add_description_multiple(
796
+ self,
797
+ descriptions: Collection[str],
798
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
799
+ comment: str | None = None,
800
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
801
+ ) -> VideoSegmentResource:
802
+ """
803
+ Add several descriptions to the resource
804
+
805
+ Args:
806
+ descriptions: list of texts
807
+ permissions: optional permissions of these value
808
+ comment: optional comment
809
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
810
+
811
+ Returns:
812
+ The original resource, with the added descriptions
813
+
814
+ Examples:
815
+ ```python
816
+ video_segment = video_segment.add_description_multiple(["description 1", "description 2"])
817
+ ```
818
+ """
819
+ vals = check_and_fix_collection_input(descriptions, "description", self.res_id)
820
+ for v in vals:
821
+ self.add_description(v, permissions, comment, newline_replacement)
822
+ return self
823
+
824
+ def add_description_optional(
825
+ self,
826
+ description: Any,
827
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
828
+ comment: str | None = None,
829
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
830
+ ) -> VideoSegmentResource:
831
+ """
832
+ If the value is not empty, add it as description, otherwise return the resource unchanged.
833
+
834
+ Args:
835
+ description: text or empty value
836
+ permissions: optional permissions of this value
837
+ comment: optional comment
838
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
839
+
840
+ Returns:
841
+ The original resource, with the added description
842
+
843
+ Examples:
844
+ ```python
845
+ video_segment = video_segment.add_description_optional("description text")
846
+ ```
847
+
848
+ ```python
849
+ video_segment = video_segment.add_description_optional(None)
850
+ ```
851
+ """
852
+ if is_nonempty_value(description):
853
+ self.add_description(description, permissions, comment, newline_replacement)
854
+ return self
855
+
856
+ def add_keyword(
857
+ self,
858
+ keyword: str,
859
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
860
+ comment: str | None = None,
861
+ ) -> VideoSegmentResource:
862
+ """
863
+ Add a keyword to the resource
864
+
865
+ Args:
866
+ keyword: text
867
+ permissions: optional permissions of this value
868
+ comment: optional comment
869
+
870
+ Returns:
871
+ The original resource, with the added keyword
872
+
873
+ Examples:
874
+ ```python
875
+ video_segment = video_segment.add_keyword("keyword")
876
+ ```
877
+ """
878
+ self.values.append(
879
+ SimpleText.new(
880
+ value=keyword,
881
+ prop_name="hasKeyword",
882
+ permissions=permissions,
883
+ comment=comment,
884
+ resource_id=self.res_id,
885
+ )
886
+ )
887
+ return self
888
+
889
+ def add_keyword_multiple(
890
+ self,
891
+ keywords: Collection[str],
892
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
893
+ comment: str | None = None,
894
+ ) -> VideoSegmentResource:
895
+ """
896
+ Add several keywords to the resource
897
+
898
+ Args:
899
+ keywords: list of texts
900
+ permissions: optional permissions of these values
901
+ comment: optional comment
902
+
903
+ Returns:
904
+ The original resource, with the added keywords
905
+
906
+ Examples:
907
+ ```python
908
+ video_segment = video_segment.add_keyword_multiple(["keyword 1", "keyword 2"])
909
+ ```
910
+ """
911
+ vals = check_and_fix_collection_input(keywords, "keywords", self.res_id)
912
+ for v in vals:
913
+ self.add_keyword(v, permissions, comment)
914
+ return self
915
+
916
+ def add_keyword_optional(
917
+ self,
918
+ keyword: Any,
919
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
920
+ comment: str | None = None,
921
+ ) -> VideoSegmentResource:
922
+ """
923
+ If the value is not empty, add it as keyword, otherwise return the resource unchanged.
924
+
925
+ Args:
926
+ keyword: text or empty value
927
+ permissions: optional permissions of this value
928
+ comment: optional comment
929
+
930
+ Returns:
931
+ The original resource, with the added keyword
932
+
933
+ Examples:
934
+ ```python
935
+ video_segment = video_segment.add_keyword_optional("keyword")
936
+ ```
937
+
938
+ ```python
939
+ video_segment = video_segment.add_keyword_optional(None)
940
+ ```
941
+ """
942
+ if is_nonempty_value(keyword):
943
+ self.add_keyword(keyword, permissions, comment)
944
+ return self
945
+
946
+ def add_relates_to(
947
+ self,
948
+ relates_to: str,
949
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
950
+ comment: str | None = None,
951
+ ) -> VideoSegmentResource:
952
+ """
953
+ Add a link to a related resource
954
+
955
+ Args:
956
+ relates_to: ID of the related resource
957
+ permissions: optional permissions of this value
958
+ comment: optional comment
959
+
960
+ Returns:
961
+ The original resource, with the added related resource
962
+
963
+ Examples:
964
+ ```python
965
+ video_segment = video_segment.add_relates_to("target_resource_id")
966
+ ```
967
+ """
968
+ self.values.append(
969
+ LinkValue.new(
970
+ value=relates_to,
971
+ prop_name="relatesTo",
972
+ permissions=permissions,
973
+ comment=comment,
974
+ resource_id=self.res_id,
975
+ )
976
+ )
977
+ return self
978
+
979
+ def add_relates_to_multiple(
980
+ self,
981
+ relates_to: Collection[str],
982
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
983
+ comment: str | None = None,
984
+ ) -> VideoSegmentResource:
985
+ """
986
+ Add several links to related resources
987
+
988
+ Args:
989
+ relates_to: list of IDs of the related resources
990
+ permissions: optional permissions of these values
991
+ comment: optional comment
992
+
993
+ Returns:
994
+ The original resource, with the added related resources
995
+
996
+ Examples:
997
+ ```python
998
+ video_segment = video_segment.add_relates_to_multiple(["target_resource_id_1", "target_resource_id_2"])
999
+ ```
1000
+ """
1001
+ vals = check_and_fix_collection_input(relates_to, "relatesTo", self.res_id)
1002
+ for v in vals:
1003
+ self.add_relates_to(v, permissions, comment)
1004
+ return self
1005
+
1006
+ def add_relates_to_optional(
1007
+ self,
1008
+ relates_to: Any,
1009
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1010
+ comment: str | None = None,
1011
+ ) -> VideoSegmentResource:
1012
+ """
1013
+ If the value is not empty, add it as related resource, otherwise return the resource unchanged.
1014
+
1015
+ Args:
1016
+ relates_to: ID or the related resource or empty value
1017
+ permissions: optional permissions of this value
1018
+ comment: optional comment
1019
+
1020
+ Returns:
1021
+ The original resource, with the added related resources
1022
+
1023
+ Examples:
1024
+ ```python
1025
+ video_segment = video_segment.add_relates_to_optional("target_resource_id")
1026
+ ```
1027
+
1028
+ ```python
1029
+ video_segment = video_segment.add_relates_to_optional(None)
1030
+ ```
1031
+ """
1032
+ if is_nonempty_value(relates_to):
1033
+ self.add_relates_to(relates_to, permissions, comment)
1034
+ return self
1035
+
1036
+
1037
+ @dataclass
1038
+ class AudioSegmentResource:
1039
+ res_id: str
1040
+ label: str
1041
+ segment_bounds: SegmentBounds
1042
+ values: list[Value]
1043
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS
1044
+ migration_metadata: MigrationMetadata | None = None
1045
+
1046
+ @staticmethod
1047
+ def create_new(
1048
+ res_id: str,
1049
+ label: str,
1050
+ segment_of: str,
1051
+ segment_start: float | int | str,
1052
+ segment_end: float | int | str,
1053
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1054
+ ) -> AudioSegmentResource:
1055
+ """
1056
+ Creates a new audio segment resource, i.e. a time span of an AudioRepresentation.
1057
+
1058
+ [See XML documentation for details](https://docs.dasch.swiss/latest/DSP-TOOLS/file-formats/xml-data-file/#video-segment-and-audio-segment)
1059
+
1060
+ Args:
1061
+ res_id: ID of this audio segment resource
1062
+ label: label of this audio segment resource
1063
+ segment_of: ID of the audio resource that this segment refers to (cardinality 1)
1064
+ segment_start: start of the segment in seconds (cardinality 1)
1065
+ segment_end: end of the segment in seconds (cardinality 1)
1066
+ permissions: permissions of this resource
1067
+
1068
+ Returns:
1069
+ An audio segment resource
1070
+ """
1071
+ _check_strings(string_to_check=res_id, res_id=res_id, field_name="Resource ID")
1072
+ res_id = str(res_id)
1073
+ lbl = check_and_fix_is_non_empty_string(value=label, res_id=res_id, value_field="label")
1074
+ segment_of_val = LinkValue.new(
1075
+ value=segment_of, prop_name="isSegmentOf", permissions=permissions, comment=None, resource_id=res_id
1076
+ )
1077
+ return AudioSegmentResource(
1078
+ res_id=res_id,
1079
+ label=lbl,
1080
+ segment_bounds=SegmentBounds(segment_start, segment_end, permissions, res_id),
1081
+ values=[segment_of_val],
1082
+ permissions=permissions,
1083
+ )
1084
+
1085
+ def add_title(
1086
+ self,
1087
+ title: str,
1088
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1089
+ comment: str | None = None,
1090
+ ) -> AudioSegmentResource:
1091
+ """
1092
+ Add a title to the resource.
1093
+
1094
+ Args:
1095
+ title: text
1096
+ permissions: permissions of the value
1097
+ comment: comments on the value
1098
+
1099
+ Returns:
1100
+ The original resource, with the added title
1101
+
1102
+ Examples:
1103
+ ```python
1104
+ audio_segment = audio_segment.add_title("segment title")
1105
+ ```
1106
+ """
1107
+ existing_title = [x for x in self.values if x.prop_name == "hasTitle"]
1108
+ if any(existing_title):
1109
+ _warn_value_exists(
1110
+ old_value=existing_title.pop(0).value, new_value=title, value_field="title", res_id=self.res_id
1111
+ )
1112
+ self.values.append(
1113
+ SimpleText.new(
1114
+ value=title,
1115
+ prop_name="hasTitle",
1116
+ permissions=permissions,
1117
+ comment=comment,
1118
+ resource_id=self.res_id,
1119
+ )
1120
+ )
1121
+ return self
1122
+
1123
+ def add_title_optional(
1124
+ self,
1125
+ title: Any,
1126
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1127
+ comment: str | None = None,
1128
+ ) -> AudioSegmentResource:
1129
+ """
1130
+ If the value is not empty, add it as title, otherwise return the resource unchanged.
1131
+
1132
+ Args:
1133
+ title: text or empty value
1134
+ permissions: permissions of the value
1135
+ comment: comments on the value
1136
+
1137
+ Returns:
1138
+ The original resource, with the added title
1139
+
1140
+ Examples:
1141
+ ```python
1142
+ audio_segment = audio_segment.add_title("segment title")
1143
+ ```
1144
+
1145
+ ```python
1146
+ audio_segment = audio_segment.add_title(None)
1147
+ ```
1148
+ """
1149
+ if is_nonempty_value(title):
1150
+ self.add_title(title, permissions, comment)
1151
+ return self
1152
+
1153
+ def add_comment(
1154
+ self,
1155
+ text: str,
1156
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1157
+ comment: str | None = None,
1158
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
1159
+ ) -> AudioSegmentResource:
1160
+ """
1161
+ Add a comment to the resource
1162
+
1163
+ Args:
1164
+ text: text of the comment
1165
+ permissions: optional permissions of this value
1166
+ comment: optional comment about this comment
1167
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
1168
+
1169
+ Returns:
1170
+ The original resource, with the added comment
1171
+
1172
+ Examples:
1173
+ ```python
1174
+ audio_segment = audio_segment.add_comment("comment text")
1175
+ ```
1176
+ """
1177
+ self.values.append(
1178
+ Richtext.new(
1179
+ value=text,
1180
+ prop_name="hasComment",
1181
+ permissions=permissions,
1182
+ comment=comment,
1183
+ resource_id=self.res_id,
1184
+ newline_replacement=newline_replacement,
1185
+ )
1186
+ )
1187
+ return self
1188
+
1189
+ def add_comment_multiple(
1190
+ self,
1191
+ texts: Collection[str],
1192
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1193
+ comment: str | None = None,
1194
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
1195
+ ) -> AudioSegmentResource:
1196
+ """
1197
+ Add several comments to the resource
1198
+
1199
+ Args:
1200
+ texts: list of texts
1201
+ permissions: optional permissions of these values
1202
+ comment: optional comment about these comments
1203
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
1204
+
1205
+ Returns:
1206
+ The original resource, with the added comments
1207
+
1208
+ Examples:
1209
+ ```python
1210
+ audio_segment = audio_segment.add_comment_multiple(["comment 1", "comment 2"])
1211
+ ```
1212
+ """
1213
+ vals = check_and_fix_collection_input(texts, "hasComment", self.res_id)
1214
+ for v in vals:
1215
+ self.add_comment(v, permissions, comment, newline_replacement)
1216
+ return self
1217
+
1218
+ def add_comment_optional(
1219
+ self,
1220
+ text: Any,
1221
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1222
+ comment: str | None = None,
1223
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
1224
+ ) -> AudioSegmentResource:
1225
+ """
1226
+ If the value is not empty, add it as comment, otherwise return the resource unchanged.
1227
+
1228
+ Args:
1229
+ text: text of the comment (or empty value)
1230
+ permissions: optional permissions of this value
1231
+ comment: optional comment about this comment
1232
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
1233
+
1234
+ Returns:
1235
+ The original resource, with the added comment
1236
+
1237
+ Examples:
1238
+ ```python
1239
+ audio_segment = audio_segment.add_comment_optional("comment text")
1240
+ ```
1241
+
1242
+ ```python
1243
+ audio_segment = audio_segment.add_comment_optional(None)
1244
+ ```
1245
+ """
1246
+ if is_nonempty_value(text):
1247
+ self.add_comment(text, permissions, comment, newline_replacement)
1248
+ return self
1249
+
1250
+ def add_description(
1251
+ self,
1252
+ description: str,
1253
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1254
+ comment: str | None = None,
1255
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
1256
+ ) -> AudioSegmentResource:
1257
+ """
1258
+ Add a description to the resource
1259
+
1260
+ Args:
1261
+ description: text
1262
+ permissions: optional permissions of this value
1263
+ comment: optional comment
1264
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
1265
+
1266
+ Returns:
1267
+ The original resource, with the added description
1268
+
1269
+ Examples:
1270
+ ```python
1271
+ audio_segment = audio_segment.add_description("description text")
1272
+ ```
1273
+ """
1274
+ self.values.append(
1275
+ Richtext.new(
1276
+ value=description,
1277
+ prop_name="hasDescription",
1278
+ permissions=permissions,
1279
+ comment=comment,
1280
+ resource_id=self.res_id,
1281
+ newline_replacement=newline_replacement,
1282
+ )
1283
+ )
1284
+ return self
1285
+
1286
+ def add_description_multiple(
1287
+ self,
1288
+ descriptions: Collection[str],
1289
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1290
+ comment: str | None = None,
1291
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
1292
+ ) -> AudioSegmentResource:
1293
+ """
1294
+ Add several descriptions to the resource
1295
+
1296
+ Args:
1297
+ descriptions: list of texts
1298
+ permissions: optional permissions of these values
1299
+ comment: optional comment
1300
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
1301
+
1302
+ Returns:
1303
+ The original resource, with the added descriptions
1304
+
1305
+ Examples:
1306
+ ```python
1307
+ audio_segment = audio_segment.add_description_multiple(["description 1", "description 2"])
1308
+ ```
1309
+ """
1310
+ vals = check_and_fix_collection_input(descriptions, "description", self.res_id)
1311
+ for v in vals:
1312
+ self.add_description(v, permissions, comment, newline_replacement)
1313
+ return self
1314
+
1315
+ def add_description_optional(
1316
+ self,
1317
+ description: Any,
1318
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1319
+ comment: str | None = None,
1320
+ newline_replacement: NewlineReplacement = NewlineReplacement.LINEBREAK,
1321
+ ) -> AudioSegmentResource:
1322
+ """
1323
+ If the value is not empty, add it as description, otherwise return the resource unchanged.
1324
+
1325
+ Args:
1326
+ description: text or empty value
1327
+ permissions: optional permissions of this value
1328
+ comment: optional comment
1329
+ newline_replacement: options how to deal with `\\n` inside the text value. Default: replace with `<br/>`
1330
+
1331
+ Returns:
1332
+ The original resource, with the added description
1333
+
1334
+ Examples:
1335
+ ```python
1336
+ audio_segment = audio_segment.add_description_optional("description text")
1337
+ ```
1338
+
1339
+ ```python
1340
+ audio_segment = audio_segment.add_description_optional(None)
1341
+ ```
1342
+ """
1343
+ if is_nonempty_value(description):
1344
+ self.add_description(description, permissions, comment, newline_replacement)
1345
+ return self
1346
+
1347
+ def add_keyword(
1348
+ self,
1349
+ keyword: str,
1350
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1351
+ comment: str | None = None,
1352
+ ) -> AudioSegmentResource:
1353
+ """
1354
+ Add a keyword to the resource
1355
+
1356
+ Args:
1357
+ keyword: text
1358
+ permissions: optional permissions of this value
1359
+ comment: optional comment
1360
+
1361
+ Returns:
1362
+ The original resource, with the added keyword
1363
+
1364
+ Examples:
1365
+ ```python
1366
+ audio_segment = audio_segment.add_keyword("keyword")
1367
+ ```
1368
+ """
1369
+ self.values.append(
1370
+ SimpleText.new(
1371
+ value=keyword,
1372
+ prop_name="hasKeyword",
1373
+ permissions=permissions,
1374
+ comment=comment,
1375
+ resource_id=self.res_id,
1376
+ )
1377
+ )
1378
+ return self
1379
+
1380
+ def add_keyword_multiple(
1381
+ self,
1382
+ keywords: Collection[str],
1383
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1384
+ comment: str | None = None,
1385
+ ) -> AudioSegmentResource:
1386
+ """
1387
+ Add several keywords to the resource
1388
+
1389
+ Args:
1390
+ keywords: list of texts
1391
+ permissions: optional permissions of these values
1392
+ comment: optional comment
1393
+
1394
+ Returns:
1395
+ The original resource, with the added keywords
1396
+
1397
+ Examples:
1398
+ ```python
1399
+ audio_segment = audio_segment.add_keyword_multiple(["keyword 1", "keyword 2"])
1400
+ ```
1401
+ """
1402
+ vals = check_and_fix_collection_input(keywords, "keywords", self.res_id)
1403
+ for v in vals:
1404
+ self.add_keyword(v, permissions, comment)
1405
+ return self
1406
+
1407
+ def add_keyword_optional(
1408
+ self,
1409
+ keyword: Any,
1410
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1411
+ comment: str | None = None,
1412
+ ) -> AudioSegmentResource:
1413
+ """
1414
+ If the value is not empty, add it as keyword, otherwise return the resource unchanged.
1415
+
1416
+ Args:
1417
+ keyword: text or empty value
1418
+ permissions: optional permissions of this value
1419
+ comment: optional comment
1420
+
1421
+ Returns:
1422
+ The original resource, with the added keyword
1423
+
1424
+ Examples:
1425
+ ```python
1426
+ audio_segment = audio_segment.add_keyword_optional("keyword")
1427
+ ```
1428
+
1429
+ ```python
1430
+ audio_segment = audio_segment.add_keyword_optional(None)
1431
+ ```
1432
+ """
1433
+ if is_nonempty_value(keyword):
1434
+ self.add_keyword(keyword, permissions, comment)
1435
+ return self
1436
+
1437
+ def add_relates_to(
1438
+ self,
1439
+ relates_to: str,
1440
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1441
+ comment: str | None = None,
1442
+ ) -> AudioSegmentResource:
1443
+ """
1444
+ Add a link to a related resource
1445
+
1446
+ Args:
1447
+ relates_to: ID of the related resource
1448
+ permissions: optional permissions of this value
1449
+ comment: optional comment
1450
+
1451
+ Returns:
1452
+ The original resource, with the added related resource
1453
+
1454
+ Examples:
1455
+ ```python
1456
+ audio_segment = audio_segment.add_relates_to("target_resource_id")
1457
+ ```
1458
+ """
1459
+ self.values.append(
1460
+ LinkValue.new(
1461
+ value=relates_to,
1462
+ prop_name="relatesTo",
1463
+ permissions=permissions,
1464
+ comment=comment,
1465
+ resource_id=self.res_id,
1466
+ )
1467
+ )
1468
+ return self
1469
+
1470
+ def add_relates_to_multiple(
1471
+ self,
1472
+ relates_to: Collection[str],
1473
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1474
+ comment: str | None = None,
1475
+ ) -> AudioSegmentResource:
1476
+ """
1477
+ Add several links to related resources
1478
+
1479
+ Args:
1480
+ relates_to: list of IDs of the related resources
1481
+ permissions: optional permissions of these values
1482
+ comment: optional comment
1483
+
1484
+ Returns:
1485
+ The original resource, with the added related resources
1486
+
1487
+ Examples:
1488
+ ```python
1489
+ audio_segment = audio_segment.add_relates_to_multiple(["target_resource_id_1", "target_resource_id_2"])
1490
+ ```
1491
+ """
1492
+ vals = check_and_fix_collection_input(relates_to, "relatesTo", self.res_id)
1493
+ for v in vals:
1494
+ self.add_relates_to(v, permissions, comment)
1495
+ return self
1496
+
1497
+ def add_relates_to_optional(
1498
+ self,
1499
+ relates_to: Any,
1500
+ permissions: Permissions = Permissions.PROJECT_SPECIFIC_PERMISSIONS,
1501
+ comment: str | None = None,
1502
+ ) -> AudioSegmentResource:
1503
+ """
1504
+ If the value is not empty, add it as related resource, otherwise return the resource unchanged.
1505
+
1506
+ Args:
1507
+ relates_to: ID of the related resource or empty value
1508
+ permissions: optional permissions of this value
1509
+ comment: optional comment
1510
+
1511
+ Returns:
1512
+ The original resource, with the added related resources
1513
+
1514
+ Examples:
1515
+ ```python
1516
+ audio_segment = audio_segment.add_relates_to_optional("target_resource_id")
1517
+ ```
1518
+
1519
+ ```python
1520
+ audio_segment = audio_segment.add_relates_to_optional(None)
1521
+ ```
1522
+ """
1523
+ if is_nonempty_value(relates_to):
1524
+ self.add_relates_to(relates_to, permissions, comment)
1525
+ return self
1526
+
1527
+
1528
+ def _check_strings(
1529
+ *, string_to_check: str, res_id: str, prop_name: str | None = None, field_name: str | None = None
1530
+ ) -> None:
1531
+ if not is_nonempty_value(string_to_check):
1532
+ msg_info = MessageInfo("The entered string is not valid.", res_id, prop_name, field_name)
1533
+ emit_xmllib_input_warning(msg_info)
1534
+
1535
+
1536
+ def _warn_value_exists(*, old_value: Any, new_value: Any, res_id: str | None, value_field: str | None = None) -> None:
1537
+ msg = (
1538
+ f"This resource already has a value in this location. "
1539
+ f"The old value '{old_value}' is being replace with '{new_value}'."
1540
+ )
1541
+ msg_info = MessageInfo(msg, res_id, field=value_field)
1542
+ emit_xmllib_input_warning(msg_info)