Skip to content

Commit 495c24f

Browse files
authored
Merge pull request #11 from boticas/feature/add-technical-metadata
Add technical, source and rights metadata support
2 parents 144da94 + 736addb commit 495c24f

10 files changed

Lines changed: 623 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Fixed
11+
12+
- Improved handling of whitespace in metadata type checks.
13+
- Fixed `IPFile` constructor to use the current directory when no path is provided.
14+
15+
### Added
16+
17+
- Support for technical, source, and rights metadata in the SIP creation and METS packaging process.
18+
- Methods for adding and retrieving technical, source, and rights metadata at both the package and representation levels.
19+
- New constants and folder structure for technical, source, and rights metadata.
20+
21+
### Tests
22+
23+
- Added comprehensive unit tests for metadata creation, addition, and retrieval, including technical, source, and rights metadata.
24+
825
## [1.0.4] - 2025-12-04
926

1027
### Fixed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
using IP;
2+
using Mets;
3+
4+
namespace dotnet_eark_sip_tests
5+
{
6+
public class MetadataTests
7+
{
8+
[Fact]
9+
public void TestIPMetadataCreation()
10+
{
11+
// Test IPMetadata constructors and getters/setters
12+
var metadataFile = new IPFile("dummy.txt");
13+
14+
// Test empty constructor (used for serialization)
15+
var emptyMetadata = new IPMetadata();
16+
Assert.NotNull(emptyMetadata);
17+
18+
// Test with only file
19+
var metadataWithFile = new IPMetadata(metadataFile);
20+
Assert.NotNull(metadataWithFile);
21+
Assert.NotNull(metadataWithFile.GetID());
22+
Assert.Equal(metadataFile, metadataWithFile.GetMetadata());
23+
Assert.Equal(IMetadataMdtype.OTHER, metadataWithFile.GetMetadataType()._GetType());
24+
Assert.Equal(EnumUtils.GetXmlEnumName(MetadataStatus.CURRENT), metadataWithFile.GetMetadataStatus());
25+
26+
// Test with file and type
27+
var dcType = IMetadataMdtype.DC;
28+
var metadataWithFileAndType = new IPMetadata(metadataFile);
29+
metadataWithFileAndType.SetMetadataType(dcType);
30+
Assert.Equal(dcType, metadataWithFileAndType.GetMetadataType()._GetType());
31+
32+
// Test with ID, file and type
33+
var id = "test-id-123";
34+
var metadataWithIdFileAndType = new IPMetadata(metadataFile);
35+
metadataWithIdFileAndType.SetID(id);
36+
metadataWithIdFileAndType.SetMetadataType(dcType);
37+
Assert.Equal(id, metadataWithIdFileAndType.GetID());
38+
39+
// Test setters
40+
var metadata = new IPMetadata();
41+
metadata.SetID("custom-id");
42+
metadata.SetMetadata(metadataFile);
43+
metadata.SetMetadataType(IMetadataMdtype.PREMIS);
44+
metadata.SetMetadataStatus(MetadataStatus.SUPERSEDED);
45+
metadata.SetCreateDate(DateTime.Now);
46+
47+
Assert.Equal("custom-id", metadata.GetID());
48+
Assert.Equal(metadataFile, metadata.GetMetadata());
49+
Assert.Equal(IMetadataMdtype.PREMIS, metadata.GetMetadataType()._GetType());
50+
Assert.Equal(EnumUtils.GetXmlEnumName(MetadataStatus.SUPERSEDED), metadata.GetMetadataStatus());
51+
Assert.NotNull(metadata.ToString());
52+
}
53+
54+
[Fact]
55+
public void TestAddMetadataToIP()
56+
{
57+
// Create a simple SIP
58+
var sip = new EARKSIP("SIP_ID_1", IPContentType.GetMIXED(), IPContentInformationType.GetMIXED(), "2.1.0");
59+
60+
// Create metadata files
61+
var descriptiveFile = new IPFile("descriptive.txt");
62+
var technicalFile = new IPFile("technical.txt");
63+
var rightsFile = new IPFile("rights.txt");
64+
var sourceFile = new IPFile("source.txt");
65+
66+
// Add descriptive metadata
67+
var descriptive = new IPDescriptiveMetadata(descriptiveFile, new MetadataType(IMetadataMdtype.DC), null);
68+
sip.AddDescriptiveMetadata(descriptive);
69+
Assert.Single(sip.GetDescriptiveMetadata());
70+
Assert.Equal(descriptive, sip.GetDescriptiveMetadata().First());
71+
72+
// Add preservation metadata
73+
var preservation = new IPMetadata(descriptiveFile);
74+
preservation.SetMetadataType(IMetadataMdtype.PREMIS);
75+
sip.AddPreservationMetadata(preservation);
76+
Assert.Single(sip.GetPreservationMetadata());
77+
Assert.Equal(preservation, sip.GetPreservationMetadata().First());
78+
79+
// Add technical metadata
80+
var technical = new IPMetadata(technicalFile);
81+
technical.SetMetadataType(IMetadataMdtype.NISOIMG);
82+
sip.AddTechnicalMetadata(technical);
83+
Assert.Single(sip.GetTechnicalMetadata());
84+
Assert.Equal(technical, sip.GetTechnicalMetadata().First());
85+
86+
// Add source metadata
87+
var source = new IPMetadata(sourceFile);
88+
source.SetMetadataType(IMetadataMdtype.DC);
89+
sip.AddSourceMetadata(source);
90+
Assert.Single(sip.GetSourceMetadata());
91+
Assert.Equal(source, sip.GetSourceMetadata().First());
92+
93+
// Add rights metadata
94+
var rights = new IPMetadata(rightsFile);
95+
rights.SetMetadataType(IMetadataMdtype.DC);
96+
sip.AddRightsMetadata(rights);
97+
Assert.Single(sip.GetRightsMetadata());
98+
Assert.Equal(rights, sip.GetRightsMetadata().First());
99+
100+
// Add other metadata
101+
var other = new IPMetadata(descriptiveFile);
102+
other.SetMetadataType(IMetadataMdtype.OTHER);
103+
sip.AddOtherMetadata(other);
104+
Assert.Single(sip.GetOtherMetadata());
105+
Assert.Equal(other, sip.GetOtherMetadata().First());
106+
}
107+
108+
[Fact]
109+
public void TestAddMetadataToRepresentation()
110+
{
111+
// Create a simple SIP
112+
var sip = new EARKSIP("SIP_ID_1", IPContentType.GetMIXED(), IPContentInformationType.GetMIXED(), "2.1.0");
113+
114+
// Create a representation
115+
var representation = new IPRepresentation("representation-1");
116+
sip.AddRepresentation(representation);
117+
118+
// Create metadata files
119+
var descriptiveFile = new IPFile("descriptive.txt");
120+
var technicalFile = new IPFile("technical.txt");
121+
var rightsFile = new IPFile("rights.txt");
122+
var sourceFile = new IPFile("source.txt");
123+
124+
// Add descriptive metadata to representation
125+
var descriptive = new IPDescriptiveMetadata(descriptiveFile, new MetadataType(IMetadataMdtype.DC), null);
126+
sip.AddDescriptiveMetadataToRepresentation("representation-1", descriptive);
127+
Assert.Single(representation.DescriptiveMetadata);
128+
Assert.Equal(descriptive, representation.DescriptiveMetadata.First());
129+
130+
// Add preservation metadata to representation
131+
var preservation = new IPMetadata(descriptiveFile);
132+
preservation.SetMetadataType(IMetadataMdtype.PREMIS);
133+
sip.AddPreservationMetadataToRepresentation("representation-1", preservation);
134+
Assert.Single(representation.PreservationMetadata);
135+
Assert.Equal(preservation, representation.PreservationMetadata.First());
136+
137+
// Add technical metadata directly to representation
138+
var technical = new IPMetadata(technicalFile);
139+
technical.SetMetadataType(IMetadataMdtype.NISOIMG);
140+
representation.AddTechnicalMetadata(technical);
141+
Assert.Single(representation.TechnicalMetadata);
142+
Assert.Equal(technical, representation.TechnicalMetadata.First());
143+
144+
// Add source metadata directly to representation
145+
var source = new IPMetadata(sourceFile);
146+
source.SetMetadataType(IMetadataMdtype.DC);
147+
representation.AddSourceMetadata(source);
148+
Assert.Single(representation.SourceMetadata);
149+
Assert.Equal(source, representation.SourceMetadata.First());
150+
151+
// Add rights metadata to representation
152+
var rights = new IPMetadata(rightsFile);
153+
rights.SetMetadataType(IMetadataMdtype.DC);
154+
sip.AddRightsMetadataToRepresentation("representation-1", rights);
155+
Assert.Single(representation.RightsMetadata);
156+
Assert.Equal(rights, representation.RightsMetadata.First());
157+
158+
// Add other metadata to representation
159+
var other = new IPMetadata(descriptiveFile);
160+
other.SetMetadataType(IMetadataMdtype.OTHER);
161+
sip.AddOtherMetadataToRepresentation("representation-1", other);
162+
Assert.Single(representation.OtherMetadata);
163+
Assert.Equal(other, representation.OtherMetadata.First());
164+
}
165+
166+
[Fact]
167+
public void TestMetadataTypeEnum()
168+
{
169+
// Test MetadataType constructors and getters/setters
170+
var dcType = new MetadataType(IMetadataMdtype.DC);
171+
Assert.Equal(IMetadataMdtype.DC, dcType._GetType());
172+
Assert.Equal(string.Empty, dcType.GetOtherType());
173+
174+
// Test with string value that matches enum
175+
var modsType = new MetadataType("MODS");
176+
Assert.Equal(IMetadataMdtype.MODS, modsType._GetType());
177+
Assert.Equal(string.Empty, dcType.GetOtherType());
178+
179+
// Test with string value that doesn't match enum
180+
var customType = "CUSTOM_TYPE";
181+
var customMetadataType = new MetadataType(customType);
182+
Assert.Equal(IMetadataMdtype.OTHER, customMetadataType._GetType());
183+
Assert.Equal(customType, customMetadataType.GetOtherType());
184+
185+
// Test setter
186+
var newCustomType = "NEW_CUSTOM_TYPE";
187+
customMetadataType.SetOtherType(newCustomType);
188+
Assert.Equal(newCustomType, customMetadataType.GetOtherType());
189+
}
190+
}
191+
}

dotnet-eark-sip/src/Impl/EARK/EARKMETSCreator.cs

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ protected MdSecTypeMdRef AddMetadataToMETS(
445445

446446
MdSecTypeMdRef mdRef = CreateMdRef(metadata.GetID(), metadataPath);
447447
mdRef.Mdtype = mdType;
448-
if (!string.IsNullOrEmpty(mdOtherType))
448+
if (!string.IsNullOrWhiteSpace(mdOtherType))
449449
{
450450
mdRef.Othermdtype = mdOtherType;
451451
}
@@ -503,6 +503,108 @@ public MdSecTypeMdRef AddPreservationMetadataToMETS(MetsWrapper metsWrapper, IPM
503503
return mdRef;
504504
}
505505

506+
/// <summary>
507+
/// Adds technical metadata to the METS wrapper.
508+
/// </summary>
509+
/// <param name="metsWrapper">The METS wrapper to which the metadata will be added.</param>
510+
/// <param name="technicalMetadata">The technical metadata to add.</param>
511+
/// <param name="technicalMetadataPath">The file path of the technical metadata.</param>
512+
/// <returns>An MdSecTypeMdRef object representing the added metadata reference.</returns>
513+
public MdSecTypeMdRef AddTechnicalMetadataToMETS(MetsWrapper metsWrapper, IPMetadata technicalMetadata, string technicalMetadataPath)
514+
{
515+
MdSecType techMD = new()
516+
{
517+
Id = Utils.GenerateRandomAndPrefixedUUID(),
518+
Status = technicalMetadata.GetMetadataStatus().ToString(),
519+
};
520+
521+
MdSecTypeMdRef mdRef = CreateMdRef(technicalMetadata.GetID(), technicalMetadataPath);
522+
mdRef.Mdtype = technicalMetadata.GetMetadataType()._GetType();
523+
if (!string.IsNullOrWhiteSpace(technicalMetadata.GetMetadataType().GetOtherType()))
524+
{
525+
mdRef.Othermdtype = technicalMetadata.GetMetadataType().GetOtherType();
526+
}
527+
528+
// set mimetype, date creation, etc.
529+
METSUtils.SetFileBasicInformation(technicalMetadata.GetMetadata().GetPath(), mdRef);
530+
531+
// structural map info.
532+
metsWrapper.MetadataDiv?.Admid.Add(techMD.Id);
533+
534+
techMD.MdRef = mdRef;
535+
metsWrapper.Mets.AmdSec[0].TechMd.Add(techMD);
536+
537+
return mdRef;
538+
}
539+
540+
/// <summary>
541+
/// Adds source metadata to the METS wrapper.
542+
/// </summary>
543+
/// <param name="metsWrapper">The METS wrapper to which the metadata will be added.</param>
544+
/// <param name="sourceMetadata">The source metadata to add.</param>
545+
/// <param name="sourceMetadataPath">The file path of the source metadata.</param>
546+
/// <returns>An MdSecTypeMdRef object representing the added metadata reference.</returns>
547+
public MdSecTypeMdRef AddSourceMetadataToMETS(MetsWrapper metsWrapper, IPMetadata sourceMetadata, string sourceMetadataPath)
548+
{
549+
MdSecType sourceMD = new()
550+
{
551+
Id = Utils.GenerateRandomAndPrefixedUUID(),
552+
Status = sourceMetadata.GetMetadataStatus().ToString(),
553+
};
554+
555+
MdSecTypeMdRef mdRef = CreateMdRef(sourceMetadata.GetID(), sourceMetadataPath);
556+
mdRef.Mdtype = sourceMetadata.GetMetadataType()._GetType();
557+
if (!string.IsNullOrWhiteSpace(sourceMetadata.GetMetadataType().GetOtherType()))
558+
{
559+
mdRef.Othermdtype = sourceMetadata.GetMetadataType().GetOtherType();
560+
}
561+
562+
// set mimetype, date creation, etc.
563+
METSUtils.SetFileBasicInformation(sourceMetadata.GetMetadata().GetPath(), mdRef);
564+
565+
// structural map info.
566+
metsWrapper.MetadataDiv?.Admid.Add(sourceMD.Id);
567+
568+
sourceMD.MdRef = mdRef;
569+
metsWrapper.Mets.AmdSec[0].SourceMd.Add(sourceMD);
570+
571+
return mdRef;
572+
}
573+
574+
/// <summary>
575+
/// Adds rights metadata to the METS wrapper.
576+
/// </summary>
577+
/// <param name="metsWrapper">The METS wrapper to which the metadata will be added.</param>
578+
/// <param name="rightsMetadata">The rights metadata to add.</param>
579+
/// <param name="rightsMetadataPath">The file path of the rights metadata.</param>
580+
/// <returns>An MdSecTypeMdRef object representing the added metadata reference.</returns>
581+
public MdSecTypeMdRef AddRightsMetadataToMETS(MetsWrapper metsWrapper, IPMetadata rightsMetadata, string rightsMetadataPath)
582+
{
583+
MdSecType rightsMD = new()
584+
{
585+
Id = Utils.GenerateRandomAndPrefixedUUID(),
586+
Status = rightsMetadata.GetMetadataStatus().ToString(),
587+
};
588+
589+
MdSecTypeMdRef mdRef = CreateMdRef(rightsMetadata.GetID(), rightsMetadataPath);
590+
mdRef.Mdtype = rightsMetadata.GetMetadataType()._GetType();
591+
if (!string.IsNullOrWhiteSpace(rightsMetadata.GetMetadataType().GetOtherType()))
592+
{
593+
mdRef.Othermdtype = rightsMetadata.GetMetadataType().GetOtherType();
594+
}
595+
596+
// set mimetype, date creation, etc.
597+
METSUtils.SetFileBasicInformation(rightsMetadata.GetMetadata().GetPath(), mdRef);
598+
599+
// structural map info.
600+
metsWrapper.MetadataDiv?.Admid.Add(rightsMD.Id);
601+
602+
rightsMD.MdRef = mdRef;
603+
metsWrapper.Mets.AmdSec[0].RightsMd.Add(rightsMD);
604+
605+
return mdRef;
606+
}
607+
506608
/// <summary>
507609
/// Creates a metadata reference (MdRef) for a given ID and metadata path.
508610
/// </summary>

dotnet-eark-sip/src/Impl/EARK/EARKSIP.cs

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -154,31 +154,38 @@ public override string Build(IWriteStrategy writeStrategy, string? fileNameWitho
154154
earkUtils.AddDefaultSchemas(logger, GetSchemas(), buildDir, GetOverride());
155155

156156
bool isMetadataOther = GetOtherMetadata() != null && GetOtherMetadata().Count > 0;
157-
bool isMetadata = (GetDescriptiveMetadata() != null && GetDescriptiveMetadata().Count > 0) || (GetPreservationMetadata() != null && GetPreservationMetadata().Count > 0);
157+
bool isMetadata = (GetDescriptiveMetadata() != null && GetDescriptiveMetadata().Count > 0)
158+
|| (GetPreservationMetadata() != null && GetPreservationMetadata().Count > 0)
159+
|| (GetTechnicalMetadata() != null && GetTechnicalMetadata().Count > 0)
160+
|| (GetSourceMetadata() != null && GetSourceMetadata().Count > 0)
161+
|| (GetRightsMetadata() != null && GetRightsMetadata().Count > 0);
158162
bool isDocumentation = GetDocumentation() != null && GetDocumentation().Count > 0;
159163
bool isSchemas = GetSchemas() != null && GetSchemas().Count > 0;
160164
bool isRepresentations = GetRepresentations() != null && GetRepresentations().Count > 0;
161165

162166
MetsWrapper mainMETSWrapper = metsCreator.GenerateMets(
163-
string.Join(" ", GetIds()),
164-
GetDescription(),
165-
GetProfile(),
166-
true,
167-
GetAncestors(),
168-
null,
169-
GetHeader(),
170-
GetContentType(),
171-
GetContentInformationType(),
172-
isMetadata,
173-
isMetadataOther,
174-
isSchemas,
175-
isDocumentation,
176-
isRepresentations,
177-
false
167+
string.Join(" ", GetIds()),
168+
GetDescription(),
169+
GetProfile(),
170+
true,
171+
GetAncestors(),
172+
null,
173+
GetHeader(),
174+
GetContentType(),
175+
GetContentInformationType(),
176+
isMetadata,
177+
isMetadataOther,
178+
isSchemas,
179+
isDocumentation,
180+
isRepresentations,
181+
false
178182
);
179183

180184
earkUtils.AddDescriptiveMetadataToZipAndMETS(zipEntries, mainMETSWrapper, GetDescriptiveMetadata(), null);
181185
earkUtils.AddPreservationMetadataToZipAndMETS(zipEntries, mainMETSWrapper, GetPreservationMetadata(), null);
186+
earkUtils.AddTechnicalMetadataToZipAndMETS(zipEntries, mainMETSWrapper, GetTechnicalMetadata(), null);
187+
earkUtils.AddSourceMetadataToZipAndMETS(zipEntries, mainMETSWrapper, GetSourceMetadata(), null);
188+
earkUtils.AddRightsMetadataToZipAndMETS(zipEntries, mainMETSWrapper, GetRightsMetadata(), null);
182189
earkUtils.AddOtherMetadataToZipAndMETS(zipEntries, mainMETSWrapper, GetOtherMetadata(), null);
183190
earkUtils.AddRepresentationsToZipAndMETS(this, GetRepresentations(), zipEntries, mainMETSWrapper, buildDir, sipType);
184191
earkUtils.AddSchemasToZipAndMETS(zipEntries, mainMETSWrapper, GetSchemas(), null);

0 commit comments

Comments
 (0)