| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 | 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;/// <summary>/// Provides extension methods for <see cref="XmlReader"/> to parse <see cref="BaseItem"/>'s./// </summary>public static class XmlReaderExtensions{    /// <summary>    /// Reads a trimmed string from the current node.    /// </summary>    /// <param name="reader">The <see cref="XmlReader"/>.</param>    /// <returns>The trimmed content.</returns>    public static string ReadNormalizedString(this XmlReader reader)    {        ArgumentNullException.ThrowIfNull(reader);        return reader.ReadElementContentAsString().Trim();    }    /// <summary>    /// Reads an int from the current node.    /// </summary>    /// <param name="reader">The <see cref="XmlReader"/>.</param>    /// <param name="value">The parsed <c>int</c>.</param>    /// <returns>A value indicating whether the parsing succeeded.</returns>    public static bool TryReadInt(this XmlReader reader, out int value)    {        ArgumentNullException.ThrowIfNull(reader);        return int.TryParse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture, out value);    }    /// <summary>    /// Parses a <see cref="DateTime"/> from the current node.    /// </summary>    /// <param name="reader">The <see cref="XmlReader"/>.</param>    /// <param name="value">The parsed <see cref="DateTime"/>.</param>    /// <returns>A value indicating whether the parsing succeeded.</returns>    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);    }    /// <summary>    /// Parses a <see cref="DateTime"/> from the current node.    /// </summary>    /// <param name="reader">The <see cref="XmlReader"/>.</param>    /// <param name="formatString">The date format string.</param>    /// <param name="value">The parsed <see cref="DateTime"/>.</param>    /// <returns>A value indicating whether the parsing succeeded.</returns>    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);    }    /// <summary>    /// Parses a <see cref="PersonInfo"/> from the xml node.    /// </summary>    /// <param name="reader">The <see cref="XmlReader"/>.</param>    /// <returns>A <see cref="PersonInfo"/>, or <c>null</c> if none is found.</returns>    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        };    }    /// <summary>    /// Used to split names of comma or pipe delimited genres and people.    /// </summary>    /// <param name="reader">The <see cref="XmlReader"/>.</param>    /// <returns>IEnumerable{System.String}.</returns>    public static IEnumerable<string> 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.        ReadOnlySpan<char> separator = !value.Contains('|', StringComparison.Ordinal)            && !value.Contains(';', StringComparison.Ordinal)                ? stackalloc[] { ',' }                : stackalloc[] { '|', ';' };        foreach (var part in value.AsSpan().Trim().Trim(separator).ToString().Split(separator))        {            if (!string.IsNullOrWhiteSpace(part))            {                yield return part.Trim();            }        }    }    /// <summary>    /// Parses a <see cref="PersonInfo"/> array from the xml node.    /// </summary>    /// <param name="reader">The <see cref="XmlReader"/>.</param>    /// <param name="personKind">The <see cref="PersonKind"/>.</param>    /// <returns>The <see cref="IEnumerable{PersonInfo}"/>.</returns>    public static IEnumerable<PersonInfo> GetPersonArray(this XmlReader reader, PersonKind personKind)        => reader.GetStringArray()            .Select(part => new PersonInfo { Name = part, Type = personKind });}
 |