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 });
}