Skip to content

Commit 6bb7ad4

Browse files
Fix some dodgy null handling
1 parent 0ace9c6 commit 6bb7ad4

4 files changed

Lines changed: 159 additions & 100 deletions

File tree

src/XmlConstants.cs

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/XmlConstants/Ableton.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Xml.Linq;
2+
3+
namespace LiveTagger.XmlConstants;
4+
5+
public static class Ableton
6+
{
7+
public static readonly XNamespace Namespace = "https://ns.ableton.com/xmp/fs-resources/1.0/";
8+
9+
public static readonly XName Items = Namespace + "items";
10+
public static readonly XName FilePath = Namespace + "filePath";
11+
public static readonly XName Keywords = Namespace + "keywords";
12+
}

src/XmlConstants/Rdf.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Xml.Linq;
2+
3+
namespace LiveTagger.XmlConstants;
4+
5+
public static class Rdf
6+
{
7+
public static readonly XNamespace Namespace = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
8+
9+
public static readonly XName RdfWrapper = Namespace + "RDF";
10+
public static readonly XName Description = Namespace + "Description";
11+
public static readonly XName Bag = Namespace + "Bag";
12+
public static readonly XName Li = Namespace + "li";
13+
public static readonly XName ParseType = Namespace + "parseType";
14+
}

src/Xmp.cs

Lines changed: 133 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ public class Xmp
1010
/// </summary>
1111
private static readonly string s_xmlTemplate = """
1212
<?xml version="1.0" encoding="utf-8"?>
13-
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.6.0">
13+
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.6.0">
1414
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
1515
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:ablFR="https://ns.ableton.com/xmp/fs-resources/1.0/" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
16-
<dc:format>application/vnd.ableton.folder</dc:format>
17-
<ablFR:resource>folder</ablFR:resource>
18-
<ablFR:items>
19-
<rdf:Bag></rdf:Bag>
20-
</ablFR:items>
16+
<dc:format>application/vnd.ableton.folder</dc:format>
17+
<ablFR:resource>folder</ablFR:resource>
18+
<ablFR:items>
19+
<rdf:Bag></rdf:Bag>
20+
</ablFR:items>
2121
</rdf:Description>
2222
</rdf:RDF>
23-
</x:xmpmeta>
23+
</x:xmpmeta>
2424
""";
2525

2626
/// <summary>
@@ -33,13 +33,24 @@ public class Xmp
3333
/// </summary>
3434
public XElement Xml { get; private set; }
3535

36+
/// <summary>
37+
/// A pointer to the main content of the document.
38+
/// </summary>
39+
private XElement _itemsBag;
40+
3641
/// <summary>
3742
/// Creates a new XMP document.
3843
/// </summary>
3944
/// <param name="xml">The XML to populate the document with.</param>
4045
private Xmp(XElement xml)
4146
{
4247
Xml = xml;
48+
49+
_itemsBag = Xml.Element(Rdf.RdfWrapper)?
50+
.Element(Rdf.Description)?
51+
.Element(Ableton.Items)?
52+
.Element(Rdf.Bag)
53+
?? throw new InvalidOperationException("Malformed XMP document");
4354
}
4455

4556
/// <summary>
@@ -79,6 +90,55 @@ public void Save(string path)
7990
IsDirty = false;
8091
}
8192

93+
/// <summary>
94+
/// Tag a file.
95+
/// </summary>
96+
/// <param name="file">The file to tag.</param>
97+
/// <param name="tags">The tags to apply.</param>
98+
public void AddTags(string file, List<string> tags)
99+
{
100+
var tagsAdded = new List<string>();
101+
102+
var existingItem = GetFileItem(file);
103+
104+
if (existingItem != null)
105+
{
106+
// Add keyword to existing items
107+
var keywordBag = existingItem.Element(Ableton.Keywords).Element(Rdf.Bag);
108+
109+
foreach (string tag in tags)
110+
{
111+
if (!keywordBag.Elements(Rdf.Li).Any(e => e.Value == tag))
112+
{
113+
keywordBag.Add(new XElement(Rdf.Li, tag));
114+
tagsAdded.Add(tag);
115+
IsDirty = true;
116+
}
117+
}
118+
}
119+
else
120+
{
121+
// Create new item
122+
var newItem = new XElement(Rdf.Li, new XAttribute(Rdf.ParseType, "Resource"),
123+
new XElement(Ableton.FilePath, file),
124+
new XElement(Ableton.Keywords,
125+
new XElement(Rdf.Bag,
126+
tags.Select(tag => new XElement(Rdf.Li, tag))
127+
)
128+
)
129+
);
130+
131+
_itemsBag.Add(newItem);
132+
tagsAdded = tags;
133+
IsDirty = true;
134+
}
135+
136+
if (tagsAdded.Count > 0)
137+
{
138+
Console.WriteLine($"Added tags to {file}: {string.Join(", ", tagsAdded)}");
139+
}
140+
}
141+
82142

83143
/// <summary>
84144
/// Tag a list of files.
@@ -87,49 +147,50 @@ public void Save(string path)
87147
/// <param name="tags">The tags to apply.</param>
88148
public void AddTags(List<string> files, List<string> tags)
89149
{
90-
var itemsBag = Xml.Descendants(Ableton.Items).First().Element(Rdf.Bag);
91-
92150
foreach (string file in files)
93151
{
94-
var tagsAdded = new List<string>();
152+
AddTags(file, tags);
153+
}
154+
}
95155

96-
var existingItem = itemsBag.Elements(Rdf.Li).FirstOrDefault(e => e.Element(Ableton.FilePath).Value == file);
156+
/// <summary>
157+
/// Remove a specific set of tags from a file.
158+
/// </summary>
159+
/// <param name="file">The file to untag.</param>
160+
/// <param name="tags">The tags to remove.</param>
161+
public void RemoveTags(string file, List<string> tags)
162+
{
163+
var item = GetFileItem(file);
97164

98-
if (existingItem != null)
99-
{
100-
// Add keyword to existing items
101-
var keywordBag = existingItem.Element(Ableton.Keywords).Element(Rdf.Bag);
165+
if (item != null)
166+
{
167+
var keywordBag = item.Element(Ableton.Keywords).Element(Rdf.Bag);
168+
169+
var tagsRemoved = new List<string>();
170+
var elementsToRemove = new List<XElement>();
102171

103-
foreach (string tag in tags)
172+
foreach (XElement keyword in keywordBag.Elements(Rdf.Li))
173+
{
174+
if (tags.Contains(keyword.Value))
104175
{
105-
if (!keywordBag.Elements(Rdf.Li).Any(e => e.Value == tag))
106-
{
107-
keywordBag.Add(new XElement(Rdf.Li, tag));
108-
tagsAdded.Add(tag);
109-
IsDirty = true;
110-
}
176+
tagsRemoved.Add(keyword.Value);
177+
elementsToRemove.Add(keyword);
111178
}
112179
}
113-
else
180+
181+
if (elementsToRemove.Count > 0)
114182
{
115-
// Create new item
116-
var newItem = new XElement(Rdf.Li, new XAttribute(Rdf.ParseType, "Resource"),
117-
new XElement(Ableton.FilePath, file),
118-
new XElement(Ableton.Keywords,
119-
new XElement(Rdf.Bag,
120-
tags.Select(tag => new XElement(Rdf.Li, tag))
121-
)
122-
)
123-
);
183+
if (elementsToRemove.Count == keywordBag.Elements(Rdf.Li).Count())
184+
{
185+
item.Remove();
186+
}
187+
else
188+
{
189+
elementsToRemove.Remove();
190+
}
124191

125-
itemsBag.Add(newItem);
126-
tagsAdded = tags;
127192
IsDirty = true;
128-
}
129-
130-
if (tagsAdded.Count > 0)
131-
{
132-
Console.WriteLine($"Added tags to {file}: {string.Join(", ", tagsAdded)}");
193+
Console.WriteLine($"Removed tags from {file}: {string.Join(", ", tagsRemoved)}");
133194
}
134195
}
135196
}
@@ -141,43 +202,25 @@ public void AddTags(List<string> files, List<string> tags)
141202
/// <param name="tags">The tags to remove.</param>
142203
public void RemoveTags(List<string> files, List<string> tags)
143204
{
144-
var itemsBag = Xml.Descendants(Ableton.Items).First().Element(Rdf.Bag);
145-
146205
foreach (string file in files)
147206
{
148-
var item = itemsBag.Elements(Rdf.Li).FirstOrDefault(e => e.Element(Ableton.FilePath).Value == file);
149-
150-
if (item != null)
151-
{
152-
var keywordBag = item.Element(Ableton.Keywords).Element(Rdf.Bag);
153-
154-
var tagsRemoved = new List<string>();
155-
var elementsToRemove = new List<XElement>();
156-
157-
foreach (XElement keyword in keywordBag.Elements(Rdf.Li))
158-
{
159-
if (tags.Contains(keyword.Value))
160-
{
161-
tagsRemoved.Add(keyword.Value);
162-
elementsToRemove.Add(keyword);
163-
}
164-
}
207+
RemoveTags(file, tags);
208+
}
209+
}
165210

166-
if (elementsToRemove.Count > 0)
167-
{
168-
if (elementsToRemove.Count == keywordBag.Elements(Rdf.Li).Count())
169-
{
170-
item.Remove();
171-
}
172-
else
173-
{
174-
elementsToRemove.Remove();
175-
}
211+
/// <summary>
212+
/// Remove all tags from a file.
213+
/// </summary>
214+
/// <param name="file">The file to untag.</param>
215+
public void RemoveTags(string file)
216+
{
217+
var item = GetFileItem(file);
176218

177-
IsDirty = true;
178-
Console.WriteLine($"Removed tags from {file}: {string.Join(", ", tagsRemoved)}");
179-
}
180-
}
219+
if (item != null)
220+
{
221+
item.Remove();
222+
IsDirty = true;
223+
Console.WriteLine($"Removed all tags from {file}");
181224
}
182225
}
183226

@@ -187,18 +230,29 @@ public void RemoveTags(List<string> files, List<string> tags)
187230
/// <param name="files">The files to untag.</param>
188231
public void RemoveTags(List<string> files)
189232
{
190-
var itemsBag = Xml.Descendants(Ableton.Items).First().Element(Rdf.Bag);
191-
192233
foreach (string file in files)
193234
{
194-
var item = itemsBag.Elements(Rdf.Li).FirstOrDefault(e => e.Element(Ableton.FilePath).Value == file);
235+
RemoveTags(file);
236+
}
237+
}
195238

196-
if (item != null)
239+
/// <summary>
240+
/// Gets the bag item for the given filename.
241+
/// </summary>
242+
/// <param name="file"></param>
243+
/// <returns></returns>
244+
private XElement? GetFileItem(string file)
245+
{
246+
foreach (var item in _itemsBag.Elements(Rdf.Li))
247+
{
248+
var filePath = item.Element(Ableton.FilePath);
249+
250+
if (filePath != null && filePath.Value == file)
197251
{
198-
item.Remove();
199-
IsDirty = true;
200-
Console.WriteLine($"Removed all tags from {file}");
252+
return item;
201253
}
202254
}
255+
256+
return null;
203257
}
204258
}

0 commit comments

Comments
 (0)