Skip to content

Commit

Permalink
Merge pull request #10352 from barronpm/xml-parsing-cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
Bond-009 committed Oct 10, 2023
2 parents 733ee12 + 1ce49b4 commit c451571
Show file tree
Hide file tree
Showing 8 changed files with 547 additions and 1,245 deletions.
193 changes: 193 additions & 0 deletions MediaBrowser.Controller/Extensions/XmlReaderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
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.
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();
}
}
}

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

0 comments on commit c451571

Please sign in to comment.