NanoXLSX.Core 3.2.1
Loading...
Searching...
No Matches
XmlElement.cs
1/*
2 * NanoXLSX is a small .NET library to generate and read XLSX (Microsoft Excel 2007 or newer) files in an easy and native way
3 * Copyright Raphael Stoeckli © 2026
4 * This library is licensed under the MIT License.
5 * You find a copy of the license in project folder or on: http://opensource.org/licenses/MIT
6 */
7
8using System.Collections.Generic;
9using System.Linq;
10using System.Xml;
11
12namespace NanoXLSX.Utils.Xml
13{
14 // TODO Set to internal in next major release
18 public class XmlElement
19 {
20 private readonly bool hasPrefix;
21 private bool hasNameSpaces;
22 private bool hasDefaultNameSpace;
23 private bool hasAttributes;
24 private bool hasInnerValue;
25 private bool hasChildren;
26 private string innerValue;
27 private string defaultXmlNsUri;
28
32 public string Prefix { get; set; }
36 public string Name { get; private set; }
40 public List<XmlElement> Children { get; private set; }
44 public HashSet<XmlAttribute> Attributes { get; private set; }
48 public Dictionary<string, string> PrefixNameSpaceMap { get; private set; }
49
53 public string InnerValue
54 {
55 get => innerValue;
56 set
57 {
58 if (string.IsNullOrEmpty(value))
59 {
60 innerValue = null;
61 hasInnerValue = false;
62 }
63 else
64 {
65 innerValue = value;
66 hasInnerValue = true;
67 }
68 }
69 }
70
76 internal XmlElement(string name, string prefix)
77 {
78 this.Name = name;
79 this.Prefix = prefix;
80 this.hasPrefix = !string.IsNullOrEmpty(prefix);
81 }
82
90 internal void AddNameSpaceAttribute(string prefix, string rootNameSpace, string uri)
91 {
92 if (string.IsNullOrEmpty(prefix) || string.IsNullOrEmpty(uri))
93 {
94 return;
95 }
96 if (PrefixNameSpaceMap == null)
97 {
98 PrefixNameSpaceMap = new Dictionary<string, string>();
99 }
100 if (!PrefixNameSpaceMap.ContainsKey(prefix))
101 {
102 PrefixNameSpaceMap.Add(prefix, uri);
103 }
104 hasNameSpaces = true;
105 AddAttribute(prefix, uri, rootNameSpace);
106 }
107
112 internal void AddDefaultXmlNameSpace(string defaultXmlNsUri)
113 {
114 this.defaultXmlNsUri = defaultXmlNsUri;
115 hasDefaultNameSpace = true;
116 }
117
124 internal void AddAttribute(string name, string value, string prefix = "")
125 {
126 if (!hasAttributes)
127 {
128 Attributes = new HashSet<XmlAttribute>();
129 hasAttributes = true;
130 }
131 Attributes.Add(XmlAttribute.CreateAttribute(name, value, prefix));
132 }
133
138 internal void AddAttribute(XmlAttribute? nullableAttribute)
139 {
140 if (!nullableAttribute.HasValue)
141 {
142 return;
143 }
144 if (!hasAttributes)
145 {
146 Attributes = new HashSet<XmlAttribute>();
147 hasAttributes = true;
148 }
149 Attributes.Add(nullableAttribute.Value);
150 }
151
156 internal void AddAttributes(IEnumerable<XmlAttribute> attributes)
157 {
158 if (attributes == null || !attributes.Any())
159 {
160 return;
161 }
162 if (!hasAttributes)
163 {
164 Attributes = new HashSet<XmlAttribute>();
165 hasAttributes = true;
166 }
167 foreach (XmlAttribute attribute in attributes)
168 {
169 Attributes.Add(attribute);
170 }
171 }
172
182 internal XmlElement AddChildElementWithAttribute(string name, string attributeName, string attributeValue, string namePrefix = "", string attributePrefix = "")
183 {
184 XmlElement childElement = CreateElementWithAttribute(name, attributeName, attributeValue, namePrefix, attributePrefix);
185 AddChildElement(childElement);
186 return childElement;
187 }
188
196 internal XmlElement AddChildElementWithValue(string name, string innerValue, string prefix = "")
197 {
198 if (string.IsNullOrEmpty(innerValue))
199 {
200 return null; // Omit empty nodes
201 }
202 XmlElement childElement = CreateElement(name, prefix);
203 childElement.InnerValue = innerValue;
204 AddChildElement(childElement);
205 return childElement;
206 }
207
214 internal XmlElement AddChildElement(string name, string prefix = "")
215 {
216 XmlElement childElement = CreateElement(name, prefix);
217 AddChildElement(childElement);
218 return childElement;
219 }
220
225 internal void AddChildElement(XmlElement xmlElement)
226 {
227 if (xmlElement == null)
228 {
229 return;
230 }
231 if (!hasChildren)
232 {
233 Children = new List<XmlElement>();
234 hasChildren = true;
235 }
236 Children.Add(xmlElement);
237 }
238
243 internal void AddChildElements(IEnumerable<XmlElement> xmlElements)
244 {
245 if (xmlElements == null || !xmlElements.Any())
246 {
247 return;
248 }
249 if (!hasChildren)
250 {
251 Children = new List<XmlElement>();
252 hasChildren = true;
253 }
254 Children.AddRange(xmlElements);
255 }
256
263 internal void AddChildElementBefore(XmlElement xmlElement, params string[] ancestors)
264 {
265 if (xmlElement == null)
266 {
267 return;
268 }
269 if (hasChildren && ancestors != null)
270 {
271 for (int ancestorIndex = 0; ancestorIndex < ancestors.Length; ancestorIndex++)
272 {
273 for (int childIndex = 0; childIndex < Children.Count; childIndex++)
274 {
275 if (Children[childIndex].Name == ancestors[ancestorIndex])
276 {
277 Children.Insert(childIndex, xmlElement);
278 return;
279 }
280 }
281 }
282 }
283 throw new NanoXLSX.Exceptions.IOException("None of the specified ancestor elements were found");
284 }
285
292 internal void AddChildElementAfter(XmlElement xmlElement, params string[] successors)
293 {
294 if (xmlElement == null)
295 {
296 return;
297 }
298 if (hasChildren && successors != null)
299 {
300 for (int successorIndex = 0; successorIndex < successors.Length; successorIndex++)
301 {
302 for (int childIndex = Children.Count - 1; childIndex >= 0; childIndex--)
303 {
304 if (Children[childIndex].Name == successors[successorIndex])
305 {
306 Children.Insert(childIndex + 1, xmlElement);
307 return;
308 }
309 }
310 }
311 }
312 throw new NanoXLSX.Exceptions.IOException("None of the specified successor elements were found");
313 }
314
319 public XmlDocument TransformToDocument()
320 {
321 XmlDocument doc = new XmlDocument
322 {
323 XmlResolver = null
324 };
325 XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
326 if (hasNameSpaces)
327 {
328 foreach (KeyValuePair<string, string> nameSpace in PrefixNameSpaceMap)
329 {
330 if (nameSpace.Key == "xmlns")
331 {
332 continue;
333 }
334 nsManager.AddNamespace(nameSpace.Key, nameSpace.Value);
335 }
336 }
337 // Create the root element from this instance recursively.
338 System.Xml.XmlElement rootElement = null;
339 if (hasDefaultNameSpace)
340 {
341 rootElement = XmlElement.CreateXmlElement(doc, this, nsManager, defaultXmlNsUri);
342 }
343 else
344 {
345 rootElement = XmlElement.CreateXmlElement(doc, this, nsManager);
346 }
347 doc.AppendChild(rootElement);
348 return doc;
349 }
350
356 internal void WriteTo(XmlWriter writer, string defaultNs = null)
357 {
358 string elementDefaultNs = hasDefaultNameSpace ? defaultXmlNsUri : defaultNs;
359
360 if (hasPrefix)
361 {
362 string nsUri = null;
363 if (PrefixNameSpaceMap != null && PrefixNameSpaceMap.TryGetValue(Prefix, out string prefixNs))
364 {
365 nsUri = prefixNs;
366 }
367 writer.WriteStartElement(Prefix, Name, nsUri);
368 }
369 else
370 {
371 writer.WriteStartElement(Name, elementDefaultNs);
372 }
373
374 if (hasNameSpaces && PrefixNameSpaceMap != null)
375 {
376 foreach (KeyValuePair<string, string> ns in PrefixNameSpaceMap)
377 {
378 if (ns.Key == "xmlns")
379 {
380 continue;
381 }
382 writer.WriteAttributeString("xmlns", ns.Key, null, ns.Value);
383 }
384 }
385
386 if (hasAttributes)
387 {
388 foreach (XmlAttribute attr in Attributes)
389 {
390 if (attr.HasPrefix)
391 {
392 if (attr.Prefix == "xmlns")
393 {
394 continue;
395 }
396 string attrNsUri = null;
397 if (PrefixNameSpaceMap != null && PrefixNameSpaceMap.TryGetValue(attr.Prefix, out string mapped))
398 {
399 attrNsUri = mapped;
400 }
401 writer.WriteAttributeString(attr.Prefix, attr.Name, attrNsUri, attr.Value);
402 }
403 else
404 {
405 int colonIndex = attr.Name.IndexOf(':');
406 if (colonIndex > 0)
407 {
408 // Qualified attribute name encoded as a single string (e.g. "mc:Ignorable").
409 // XmlWriter requires the prefix and local name to be supplied separately.
410 string implicitPrefix = attr.Name.Substring(0, colonIndex);
411 string localName = attr.Name.Substring(colonIndex + 1);
412 string nsUri = null;
413 if (PrefixNameSpaceMap != null && PrefixNameSpaceMap.TryGetValue(implicitPrefix, out string mappedNs))
414 {
415 nsUri = mappedNs;
416 }
417 writer.WriteAttributeString(implicitPrefix, localName, nsUri, attr.Value);
418 }
419 else
420 {
421 writer.WriteAttributeString(attr.Name, attr.Value);
422 }
423 }
424 }
425 }
426
427 if (hasInnerValue)
428 {
429 writer.WriteString(innerValue);
430 }
431
432 if (hasChildren)
433 {
434 foreach (XmlElement child in Children)
435 {
436 child.WriteTo(writer, elementDefaultNs);
437 }
438 }
439
440 writer.WriteEndElement();
441 }
442
448 public IEnumerable<XmlElement> FindChildElementsByName(string name)
449 {
450 if (!hasChildren)
451 {
452 return Enumerable.Empty<XmlElement>();
453 }
454 List<XmlElement> result = new List<XmlElement>();
455 foreach (XmlElement child in Children)
456 {
457 if (child.Name == name)
458 {
459 result.Add(child);
460 }
461 result.AddRange(child.FindChildElementsByName(name));
462 }
463 return result;
464 }
465
472 public IEnumerable<XmlElement> FindChildElementsByNameAndAttribute(string elementName, string attributeName)
473 {
474 return FindChildElementsByNameAndAttribute(elementName, attributeName, null, false);
475 }
476
484 public IEnumerable<XmlElement> FindChildElementsByNameAndAttribute(string elementName, string attributeName, string attributeValue)
485 {
486 return FindChildElementsByNameAndAttribute(elementName, attributeName, attributeValue, true);
487 }
488
497 private IEnumerable<XmlElement> FindChildElementsByNameAndAttribute(string elementName, string attributeName, string attributeValue, bool useValue)
498 {
499 if (!hasChildren)
500 {
501 return Enumerable.Empty<XmlElement>();
502 }
503 List<XmlElement> result = new List<XmlElement>();
504 foreach (XmlElement child in Children)
505 {
506 if (child.Name == elementName && child.hasAttributes)
507 {
508 XmlAttribute? attribute = XmlAttribute.FindAttribute(attributeName, child.Attributes);
509 if (attribute != null)
510 {
511 if (!useValue || (useValue && attribute.Value.Value == attributeValue))
512 {
513 result.Add(child);
514 }
515 }
516 }
517 result.AddRange(child.FindChildElementsByNameAndAttribute(elementName, attributeName, attributeValue, useValue));
518 }
519 return result;
520 }
521
528 internal static XmlElement CreateElement(string name, string prefix = "")
529 {
530 return new XmlElement(name, prefix);
531 }
532
542 internal static XmlElement CreateElementWithAttribute(string name, string attributeName, string attributeValue, string namePrefix = "", string attributePrefix = "")
543 {
544 XmlElement element = new XmlElement(name, namePrefix)
545 {
546 Attributes = new HashSet<XmlAttribute>()
547 };
548 element.Attributes.Add(XmlAttribute.CreateAttribute(attributeName, attributeValue, attributePrefix));
549 element.hasAttributes = true;
550 return element;
551 }
552
561 private static System.Xml.XmlElement CreateXmlElement(XmlDocument doc, XmlElement customElement, XmlNamespaceManager nsManager, string defaultXmlNsUri = null)
562 {
563 System.Xml.XmlElement xmlElem;
564 if (customElement.hasPrefix)
565 {
566 xmlElem = doc.CreateElement(customElement.Prefix, customElement.Name, nsManager.LookupNamespace(customElement.Prefix));
567 }
568 else
569 {
570 xmlElem = doc.CreateElement(customElement.Name, defaultXmlNsUri);
571 }
572
573 // Add attributes
574 if (customElement.hasAttributes)
575 {
576 foreach (var attr in customElement.Attributes)
577 {
578 if (attr.HasPrefix)
579 {
580 System.Xml.XmlAttribute xmlAttr = doc.CreateAttribute(attr.Prefix, attr.Name, nsManager.LookupNamespace(attr.Prefix));
581 xmlAttr.Value = attr.Value;
582 xmlElem.Attributes.Append(xmlAttr);
583 }
584 else
585 {
586 xmlElem.SetAttribute(attr.Name, attr.Value);
587 }
588 }
589 }
590
591 // Set inner text if available
592 if (customElement.hasInnerValue)
593 {
594 xmlElem.InnerText = customElement.InnerValue;
595 }
596
597 // Process children recursively.
598 if (customElement.hasChildren)
599 {
600 foreach (var child in customElement.Children)
601 {
602 System.Xml.XmlElement childXmlElem = XmlElement.CreateXmlElement(doc, child, nsManager, defaultXmlNsUri);
603 xmlElem.AppendChild(childXmlElem);
604 }
605 }
606 return xmlElem;
607 }
608 }
609}
Class representing an internally used XML element / node.
Definition XmlElement.cs:19
string Name
Name of the element (without prefix).
Definition XmlElement.cs:36
HashSet< XmlAttribute > Attributes
List of attributes of this element. If none, the list is null.
Definition XmlElement.cs:44
string Prefix
Prefix of the element. If not defined, the prefix will be an empty string.
Definition XmlElement.cs:32
XmlDocument TransformToDocument()
Transforms this custom XmlElement (and its children) into a standard XmlDocument.
IEnumerable< XmlElement > FindChildElementsByName(string name)
Method to find XML child elements, based of its name. Name space and hierarchy is not considered as e...
Dictionary< string, string > PrefixNameSpaceMap
Map of prefixes and corresponding name space URIs of this element.
Definition XmlElement.cs:48
IEnumerable< XmlElement > FindChildElementsByNameAndAttribute(string elementName, string attributeName, string attributeValue)
Method to find XML child elements, based of its name, an attribute name and value....
string InnerValue
Gets or set the inner value of the element.
Definition XmlElement.cs:54
List< XmlElement > Children
List of child elements. If none, the list is null.
Definition XmlElement.cs:40
IEnumerable< XmlElement > FindChildElementsByNameAndAttribute(string elementName, string attributeName)
Method to find XML child elements, based of its name, an attribute name. Name space and hierarchy is ...