using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Xml; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Extensions; /// /// Provides extension methods for to parse 's. /// public static class XmlReaderExtensions { /// /// Reads a trimmed string from the current node. /// /// The . /// The trimmed content. public static string ReadNormalizedString(this XmlReader reader) { ArgumentNullException.ThrowIfNull(reader); return reader.ReadElementContentAsString().Trim(); } /// /// Reads an int from the current node. /// /// The . /// The parsed int. /// A value indicating whether the parsing succeeded. public static bool TryReadInt(this XmlReader reader, out int value) { ArgumentNullException.ThrowIfNull(reader); return int.TryParse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture, out value); } /// /// Parses a from the current node. /// /// The . /// The parsed . /// A value indicating whether the parsing succeeded. public static bool TryReadDateTime(this XmlReader reader, out DateTime value) { ArgumentNullException.ThrowIfNull(reader); return DateTime.TryParse( reader.ReadElementContentAsString(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out value); } /// /// Parses a from the current node. /// /// The . /// The date format string. /// The parsed . /// A value indicating whether the parsing succeeded. public static bool TryReadDateTimeExact(this XmlReader reader, string formatString, out DateTime value) { ArgumentNullException.ThrowIfNull(reader); ArgumentNullException.ThrowIfNull(formatString); return DateTime.TryParseExact( reader.ReadElementContentAsString(), formatString, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out value); } /// /// Parses a from the xml node. /// /// The . /// A , or null if none is found. public static PersonInfo? GetPersonFromXmlNode(this XmlReader reader) { ArgumentNullException.ThrowIfNull(reader); if (reader.IsEmptyElement) { reader.Read(); return null; } var name = string.Empty; var type = PersonKind.Actor; // If type is not specified assume actor var role = string.Empty; int? sortOrder = null; string? imageUrl = null; using var subtree = reader.ReadSubtree(); subtree.MoveToContent(); subtree.Read(); while (subtree is { EOF: false, ReadState: ReadState.Interactive }) { if (subtree.NodeType != XmlNodeType.Element) { subtree.Read(); continue; } switch (subtree.Name) { case "name": case "Name": name = subtree.ReadNormalizedString(); break; case "role": case "Role": role = subtree.ReadNormalizedString(); break; case "type": case "Type": Enum.TryParse(subtree.ReadElementContentAsString(), true, out type); break; case "order": case "sortorder": case "SortOrder": if (subtree.TryReadInt(out var sortOrderVal)) { sortOrder = sortOrderVal; } break; case "thumb": imageUrl = subtree.ReadNormalizedString(); break; default: subtree.Skip(); break; } } if (string.IsNullOrWhiteSpace(name)) { return null; } return new PersonInfo { Name = name, Role = role, Type = type, SortOrder = sortOrder, ImageUrl = imageUrl }; } /// /// Used to split names of comma or pipe delimited genres and people. /// /// The . /// IEnumerable{System.String}. public static IEnumerable GetStringArray(this XmlReader reader) { ArgumentNullException.ThrowIfNull(reader); var value = reader.ReadElementContentAsString(); // Only split by comma if there is no pipe in the string // We have to be careful to not split names like Matthew, Jr. var separator = !value.Contains('|', StringComparison.Ordinal) && !value.Contains(';', StringComparison.Ordinal) ? new[] { ',' } : new[] { '|', ';' }; foreach (var part in value.Trim().Trim(separator).Split(separator)) { if (!string.IsNullOrWhiteSpace(part)) { yield return part.Trim(); } } } /// /// Parses a array from the xml node. /// /// The . /// The . /// The . public static IEnumerable GetPersonArray(this XmlReader reader, PersonKind personKind) => reader.GetStringArray() .Select(part => new PersonInfo { Name = part, Type = personKind }); }