Skip to content

Commit

Permalink
XMP : Anchor namespace declarations to custom nodes + XmlArray unit t…
Browse files Browse the repository at this point in the history
…ests
  • Loading branch information
Zeugma440 committed Feb 1, 2025
1 parent 692268a commit 08c9b64
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 35 deletions.
218 changes: 218 additions & 0 deletions ATL.unit-test/Misc/XmlArrayTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
using ATL.AudioData;
using ATL.AudioData.IO;
using System.IO;

namespace ATL.test
{
[TestClass]
public class XmlArrayTest
{
private static string getFrom(string name)
{
var dataPath = TestUtils.GetResourceLocationRoot() + "_Xml" + Path.DirectorySeparatorChar + name;
using (var source = new FileStream(dataPath, FileMode.Open))
{
var reader = new StreamReader(source);
return reader.ReadToEnd().ReplaceLineEndings("").Replace("\t", "");
}
}

[TestMethod]
public void XmlArray_writeBasic()
{
var xmlArray = new XmlArray(
"root",
"test",
_ => false,
_ => false
);

TagHolder holder = new TagHolder();
var data = new Dictionary<string, string>();
data["test.one"] = "aaa";
data["test.two"] = "bbb";
holder.AdditionalFields = data;

var memStream = new MemoryStream();
xmlArray.ToStream(memStream, holder);
var reader = new StreamReader(memStream);
memStream.Position = 0;
string result = reader.ReadToEnd();

Assert.IsTrue(result.Length > 0);

var expected = getFrom("basic.xml");
Assert.AreEqual(expected, result);
}

[TestMethod]
public void XmlArray_writeAttributes()
{
var xmlArray = new XmlArray(
"root",
"test",
_ => false,
_ => false
);
xmlArray.setStructuralAttributes(new HashSet<string> { "hey", "PIP" });

TagHolder holder = new TagHolder();
var data = new Dictionary<string, string>();
data["test.one.hey"] = "ho";
data["test.one"] = "aaa";
data["test.two.pip"] = "boy";
data["test.two"] = "bbb";
holder.AdditionalFields = data;

var memStream = new MemoryStream();
xmlArray.ToStream(memStream, holder);
var reader = new StreamReader(memStream);
memStream.Position = 0;
string result = reader.ReadToEnd();

Assert.IsTrue(result.Length > 0);

var expected = getFrom("attributes.xml");
Assert.AreEqual(expected, result);
}

[TestMethod]
public void XmlArray_writeCollections()
{
var xmlArray = new XmlArray(
"root",
"test",
e => e.EndsWith("LIST", StringComparison.OrdinalIgnoreCase),
_ => false
);

TagHolder holder = new TagHolder();
var data = new Dictionary<string, string>();
data["test.one"] = "aaa";
data["test.two"] = "bbb";
data["test.theList.elt[0].value"] = "11";
data["test.theList.elt[1].value"] = "22";
data["test.theList.elt[2].value"] = "33";
holder.AdditionalFields = data;

var memStream = new MemoryStream();
xmlArray.ToStream(memStream, holder);
var reader = new StreamReader(memStream);
memStream.Position = 0;
string result = reader.ReadToEnd();

Assert.IsTrue(result.Length > 0);

var expected = getFrom("collection.xml");
Assert.AreEqual(expected, result);
}

[TestMethod]
public void XmlArray_writeRootNs()
{
IDictionary<string, string> DEFAULT_NAMESPACES = new Dictionary<string, string> { { "pap", "test:ns:meta/" } };
var xmlArray = new XmlArray(
"root",
"test",
_ => false,
_ => false
);
// No namespace anchors (=> default anchor goes to root)
xmlArray.setDefaultNamespaces(DEFAULT_NAMESPACES);

TagHolder holder = new TagHolder();
var data = new Dictionary<string, string>();
data["test.pap:one"] = "aaa";
data["test.pap:two"] = "bbb";
holder.AdditionalFields = data;

var memStream = new MemoryStream();
xmlArray.ToStream(memStream, holder);
var reader = new StreamReader(memStream);
memStream.Position = 0;
string result = reader.ReadToEnd();

Assert.IsTrue(result.Length > 0);

var expected = getFrom("rootNs.xml");
Assert.AreEqual(expected, result);


// All namespaces explicitly anchored to root
IDictionary<string, ISet<string>> NAMESPACE_ANCHORS = new Dictionary<string, ISet<string>> { { "root", new HashSet<string> { "" } } };
xmlArray.setNamespaceAnchors(NAMESPACE_ANCHORS);

memStream = new MemoryStream();
xmlArray.ToStream(memStream, holder);
reader = new StreamReader(memStream);
memStream.Position = 0;
result = reader.ReadToEnd();

Assert.IsTrue(result.Length > 0);

Assert.AreEqual(expected, result);


// Specific namespace explicitly anchored to root
IDictionary<string, ISet<string>> NAMESPACE_ANCHORS2 = new Dictionary<string, ISet<string>> { { "root", new HashSet<string> { "pap" } } };
xmlArray.setNamespaceAnchors(NAMESPACE_ANCHORS2);

memStream = new MemoryStream();
xmlArray.ToStream(memStream, holder);
reader = new StreamReader(memStream);
memStream.Position = 0;
result = reader.ReadToEnd();

Assert.IsTrue(result.Length > 0);

Assert.AreEqual(expected, result);
}

[TestMethod]
public void XmlArray_writeAnchoredNs()
{
IDictionary<string, string> DEFAULT_NAMESPACES = new Dictionary<string, string> { { "pap", "test:ns:meta/" } };
// Anchor one specific ns to one specific node
IDictionary<string, ISet<string>> NAMESPACE_ANCHORS = new Dictionary<string, ISet<string>> { { "container", new HashSet<string> { "pap" } } };
var xmlArray = new XmlArray(
"root",
"test",
_ => false,
_ => false
);
xmlArray.setDefaultNamespaces(DEFAULT_NAMESPACES);
xmlArray.setNamespaceAnchors(NAMESPACE_ANCHORS);

TagHolder holder = new TagHolder();
var data = new Dictionary<string, string>();
data["test.container.pap:one"] = "aaa";
data["test.container.pap:two"] = "bbb";
holder.AdditionalFields = data;

var memStream = new MemoryStream();
xmlArray.ToStream(memStream, holder);
var reader = new StreamReader(memStream);
memStream.Position = 0;
string result = reader.ReadToEnd();

Assert.IsTrue(result.Length > 0);

var expected = getFrom("anchoredNs.xml");
Assert.AreEqual(expected, result);

// Anchor all ns'es to one specific node
IDictionary<string, ISet<string>> NAMESPACE_ANCHORS2 = new Dictionary<string, ISet<string>> { { "container", new HashSet<string> { "" } } };
xmlArray.setNamespaceAnchors(NAMESPACE_ANCHORS2);

memStream = new MemoryStream();
xmlArray.ToStream(memStream, holder);
reader = new StreamReader(memStream);
memStream.Position = 0;
result = reader.ReadToEnd();

Assert.IsTrue(result.Length > 0);

Assert.AreEqual(expected, result);
}
}
}
7 changes: 7 additions & 0 deletions ATL.unit-test/Resources/_Xml/anchoredNs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<container xmlns:pap="test:ns:meta/">
<pap:one>aaa</pap:one>
<pap:two>bbb</pap:two>
</container>
</root>
5 changes: 5 additions & 0 deletions ATL.unit-test/Resources/_Xml/attributes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<one hey="ho">aaa</one>
<two pip="boy">bbb</two>
</root>
5 changes: 5 additions & 0 deletions ATL.unit-test/Resources/_Xml/basic.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<one>aaa</one>
<two>bbb</two>
</root>
10 changes: 10 additions & 0 deletions ATL.unit-test/Resources/_Xml/collection.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<one>aaa</one>
<two>bbb</two>
<theList>
<elt><value>11</value></elt>
<elt><value>22</value></elt>
<elt><value>33</value></elt>
</theList>
</root>
5 changes: 5 additions & 0 deletions ATL.unit-test/Resources/_Xml/rootNs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:pap="test:ns:meta/">
<pap:one>aaa</pap:one>
<pap:two>bbb</pap:two>
</root>
14 changes: 7 additions & 7 deletions ATL/AudioData/IO/Helpers/IXmlTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,24 @@ public static bool IsDataEligible(MetaDataHolder meta)
return WavHelper.IsDataEligible(meta, "ixml.");
}

public static int ToStream(BinaryWriter w, bool isLittleEndian, MetaDataHolder meta)
public static int ToStream(Stream w, bool isLittleEndian, MetaDataHolder meta)
{
w.Write(Utils.Latin1Encoding.GetBytes(CHUNK_IXML));

long sizePos = w.BaseStream.Position;
w.Write(0); // Placeholder for chunk size that will be rewritten at the end of the method
long sizePos = w.Position;
w.Write(StreamUtils.EncodeInt32(0)); // Placeholder for chunk size that will be rewritten at the end of the method

XmlArray xmlArray = createXmlArray();
int result = xmlArray.ToStream(w, meta);

long finalPos = w.BaseStream.Position;
long finalPos = w.Position;

// Add the extra padding byte if needed
long paddingSize = (finalPos - sizePos) % 2;
if (paddingSize > 0) w.BaseStream.WriteByte(0);
if (paddingSize > 0) w.WriteByte(0);

w.BaseStream.Seek(sizePos, SeekOrigin.Begin);
if (isLittleEndian) w.Write((int)(finalPos - sizePos - 4));
w.Seek(sizePos, SeekOrigin.Begin);
if (isLittleEndian) w.Write(StreamUtils.EncodeInt32((int)(finalPos - sizePos - 4)));
else w.Write(StreamUtils.EncodeBEInt32((int)(finalPos - sizePos - 4)));

return result;
Expand Down
22 changes: 15 additions & 7 deletions ATL/AudioData/IO/Helpers/XmpTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ internal static class XmpTag
{"tscHS", "http://www.techsmith.com/xmp/tscHS/"},
};

// Namespace anchors
private static readonly IDictionary<string, ISet<string>> NAMESPACE_ANCHORS = new Dictionary<string, ISet<string>>
{
{ "x:xmpmeta", new HashSet<string>{ "x" } },
{ "rdf:RDF", new HashSet<string> { "" } }
};


private static XmlArray createXmlArray()
{
Expand All @@ -72,6 +79,7 @@ private static XmlArray createXmlArray()
);
result.setStructuralAttributes(ATTRIBUTES);
result.setDefaultNamespaces(DEFAULT_NAMESPACES);
result.setNamespaceAnchors(NAMESPACE_ANCHORS);
return result;
}

Expand All @@ -86,25 +94,25 @@ public static bool IsDataEligible(MetaDataHolder meta)
return WavHelper.IsDataEligible(meta, "xmp.");
}

public static int ToStream(BinaryWriter w, MetaDataHolder meta, bool isLittleEndian = false, bool wavEmbed = false)
public static int ToStream(Stream w, MetaDataHolder meta, bool isLittleEndian = false, bool wavEmbed = false)
{
if (wavEmbed) w.Write(Utils.Latin1Encoding.GetBytes(CHUNK_XMP));

long sizePos = w.BaseStream.Position;
long sizePos = w.Position;
// Placeholder for chunk size that will be rewritten at the end of the method
if (wavEmbed) w.Write(0);
if (wavEmbed) w.Write(StreamUtils.EncodeInt32(0));

XmlArray xmlArray = createXmlArray();
int result = xmlArray.ToStream(w, meta);

if (wavEmbed) // Add the extra padding byte if needed
{
long finalPos = w.BaseStream.Position;
long finalPos = w.Position;
long paddingSize = (finalPos - sizePos) % 2;
if (paddingSize > 0) w.BaseStream.WriteByte(0);
if (paddingSize > 0) w.WriteByte(0);

w.BaseStream.Seek(sizePos, SeekOrigin.Begin);
if (isLittleEndian) w.Write((int)(finalPos - sizePos - 4));
w.Seek(sizePos, SeekOrigin.Begin);
if (isLittleEndian) w.Write(StreamUtils.EncodeInt32((int)(finalPos - sizePos - 4)));
else w.Write(StreamUtils.EncodeBEInt32((int)(finalPos - sizePos - 4)));
}

Expand Down
3 changes: 1 addition & 2 deletions ATL/AudioData/IO/MP4.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2675,8 +2675,7 @@ private static int writeUuidFrame(TagData tag, string key, BinaryWriter w)
if (keyNominal.Equals(XmpTag.UUID_XMP, StringComparison.OrdinalIgnoreCase))
{
using var mem = new MemoryStream();
using var memW = new BinaryWriter(mem);
XmpTag.ToStream(memW, new TagHolder(tag));
XmpTag.ToStream(mem, new TagHolder(tag));
data = mem.ToArray();
}
else
Expand Down
4 changes: 2 additions & 2 deletions ATL/AudioData/IO/WAV.cs
Original file line number Diff line number Diff line change
Expand Up @@ -594,8 +594,8 @@ private int write(BinaryWriter w, MetaDataHolder tag, string zone)
}
else if (zone.Equals(CHUNK_DISP + ".0") && DispTag.IsDataEligible(tag)) result += DispTag.ToStream(w, isLittleEndian, tag); // Process the 1st position as a whole
else if (zone.Equals(CHUNK_BEXT) && BextTag.IsDataEligible(tag)) result += BextTag.ToStream(w, isLittleEndian, tag);
else if (zone.Equals(CHUNK_IXML) && IXmlTag.IsDataEligible(tag)) result += IXmlTag.ToStream(w, isLittleEndian, tag);
else if (zone.Equals(CHUNK_XMP) && XmpTag.IsDataEligible(tag)) result += XmpTag.ToStream(w, tag, isLittleEndian, true);
else if (zone.Equals(CHUNK_IXML) && IXmlTag.IsDataEligible(tag)) result += IXmlTag.ToStream(w.BaseStream, isLittleEndian, tag);
else if (zone.Equals(CHUNK_XMP) && XmpTag.IsDataEligible(tag)) result += XmpTag.ToStream(w.BaseStream, tag, isLittleEndian, true);
else if (zone.Equals(CHUNK_CART) && CartTag.IsDataEligible(tag)) result += CartTag.ToStream(w, isLittleEndian, tag);

break;
Expand Down
Loading

0 comments on commit 08c9b64

Please sign in to comment.