diff --git a/android/app/build.gradle b/android/app/build.gradle index 077647e2e..9116411b4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -56,6 +56,9 @@ android { // minSdkVersion constraints: // - Flutter & other plugins: 16 // - google_maps_flutter v2.1.1: 20 + // - to build XML documents from XMP data, `metadata-extractor` and `PixyMeta` rely on `DocumentBuilder`, + // which implementation `DocumentBuilderImpl` is provided by the OS and is not customizable on Android, + // but the implementation on API <19 is not robust enough and fails to build XMP documents minSdkVersion 19 targetSdkVersion 31 versionCode flutterVersionCode.toInteger() diff --git a/lib/ref/xmp.dart b/lib/ref/xmp.dart index 161d3e418..69de5c470 100644 --- a/lib/ref/xmp.dart +++ b/lib/ref/xmp.dart @@ -13,6 +13,7 @@ class XMP { 'crs': 'Camera Raw Settings', 'dc': 'Dublin Core', 'drone-dji': 'DJI Drone', + 'dwc': 'Darwin Core', 'exif': 'Exif', 'exifEX': 'Exif Ex', 'GettyImagesGIFT': 'Getty Images', diff --git a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart index 37f2b33bf..317ec7e5d 100644 --- a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart +++ b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart @@ -6,6 +6,7 @@ import 'package:aves/widgets/common/identity/highlight_title.dart'; import 'package:aves/widgets/viewer/info/common.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/crs.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/darktable.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_ns/dwc.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/exif.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/google.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_ns/iptc.dart'; @@ -38,6 +39,8 @@ class XmpNamespace extends Equatable { return XmpCrsNamespace(rawProps); case XmpDarktableNamespace.ns: return XmpDarktableNamespace(rawProps); + case XmpDwcNamespace.ns: + return XmpDwcNamespace(rawProps); case XmpExifNamespace.ns: return XmpExifNamespace(rawProps); case XmpGAudioNamespace.ns: diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/dwc.dart b/lib/widgets/viewer/info/metadata/xmp_ns/dwc.dart new file mode 100644 index 000000000..81b543f33 --- /dev/null +++ b/lib/widgets/viewer/info/metadata/xmp_ns/dwc.dart @@ -0,0 +1,92 @@ +import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; +import 'package:aves/widgets/viewer/info/metadata/xmp_structs.dart'; +import 'package:flutter/widgets.dart'; + +class XmpDwcNamespace extends XmpNamespace { + static const ns = 'dwc'; + + static final dcTermsLocationPattern = RegExp(ns + r':dctermsLocation/(.*)'); + static final eventPattern = RegExp(ns + r':Event/(.*)'); + static final geologicalContextPattern = RegExp(ns + r':GeologicalContext/(.*)'); + static final identificationPattern = RegExp(ns + r':Identification/(.*)'); + static final measurementOrFactPattern = RegExp(ns + r':MeasurementOrFact/(.*)'); + static final occurrencePattern = RegExp(ns + r':Occurrence/(.*)'); + static final recordPattern = RegExp(ns + r':Record/(.*)'); + static final resourceRelationshipPattern = RegExp(ns + r':ResourceRelationship/(.*)'); + static final taxonPattern = RegExp(ns + r':Taxon/(.*)'); + + final dcTermsLocation = {}; + final event = {}; + final identification = {}; + final geologicalContext = {}; + final measurementOrFact = {}; + final occurrence = {}; + final record = {}; + final resourceRelationship = {}; + final taxon = {}; + + XmpDwcNamespace(Map rawProps) : super(ns, rawProps); + + @override + bool extractData(XmpProp prop) { + var hasStructs = extractStruct(prop, dcTermsLocationPattern, dcTermsLocation); + hasStructs |= extractStruct(prop, eventPattern, event); + hasStructs |= extractStruct(prop, geologicalContextPattern, geologicalContext); + hasStructs |= extractStruct(prop, measurementOrFactPattern, measurementOrFact); + hasStructs |= extractStruct(prop, identificationPattern, identification); + hasStructs |= extractStruct(prop, occurrencePattern, occurrence); + hasStructs |= extractStruct(prop, recordPattern, record); + hasStructs |= extractStruct(prop, resourceRelationshipPattern, resourceRelationship); + hasStructs |= extractStruct(prop, taxonPattern, taxon); + return hasStructs; + } + + @override + List buildFromExtractedData() => [ + if (dcTermsLocation.isNotEmpty) + XmpStructCard( + title: 'DC Terms Location', + struct: dcTermsLocation, + ), + if (event.isNotEmpty) + XmpStructCard( + title: 'Event', + struct: event, + ), + if (geologicalContext.isNotEmpty) + XmpStructCard( + title: 'Geological Context', + struct: geologicalContext, + ), + if (identification.isNotEmpty) + XmpStructCard( + title: 'Identification', + struct: identification, + ), + if (measurementOrFact.isNotEmpty) + XmpStructCard( + title: 'Measurement Or Fact', + struct: measurementOrFact, + ), + if (occurrence.isNotEmpty) + XmpStructCard( + title: 'Occurrence', + struct: occurrence, + ), + if (record.isNotEmpty) + XmpStructCard( + title: 'Record', + struct: record, + ), + if (resourceRelationship.isNotEmpty) + XmpStructCard( + title: 'Resource Relationship', + struct: resourceRelationship, + ), + if (taxon.isNotEmpty) + XmpStructCard( + title: 'Taxon', + struct: taxon, + ), + ]; +}