XmlReaderExtensions.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Xml;
  6. using Jellyfin.Data.Enums;
  7. using MediaBrowser.Controller.Entities;
  8. using Microsoft.Extensions.Logging;
  9. namespace MediaBrowser.Controller.Extensions;
  10. /// <summary>
  11. /// Provides extension methods for <see cref="XmlReader"/> to parse <see cref="BaseItem"/>'s.
  12. /// </summary>
  13. public static class XmlReaderExtensions
  14. {
  15. /// <summary>
  16. /// Reads a trimmed string from the current node.
  17. /// </summary>
  18. /// <param name="reader">The <see cref="XmlReader"/>.</param>
  19. /// <returns>The trimmed content.</returns>
  20. public static string ReadNormalizedString(this XmlReader reader)
  21. {
  22. ArgumentNullException.ThrowIfNull(reader);
  23. return reader.ReadElementContentAsString().Trim();
  24. }
  25. /// <summary>
  26. /// Reads an int from the current node.
  27. /// </summary>
  28. /// <param name="reader">The <see cref="XmlReader"/>.</param>
  29. /// <param name="value">The parsed <c>int</c>.</param>
  30. /// <returns>A value indicating whether the parsing succeeded.</returns>
  31. public static bool TryReadInt(this XmlReader reader, out int value)
  32. {
  33. ArgumentNullException.ThrowIfNull(reader);
  34. return int.TryParse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture, out value);
  35. }
  36. /// <summary>
  37. /// Parses a <see cref="DateTime"/> from the current node.
  38. /// </summary>
  39. /// <param name="reader">The <see cref="XmlReader"/>.</param>
  40. /// <param name="logger">The <see cref="ILogger"/> to use on failure.</param>
  41. /// <param name="value">The parsed <see cref="DateTime"/>.</param>
  42. /// <returns>A value indicating whether the parsing succeeded.</returns>
  43. public static bool TryReadDateTime(this XmlReader reader, ILogger logger, out DateTime value)
  44. {
  45. ArgumentNullException.ThrowIfNull(reader);
  46. ArgumentNullException.ThrowIfNull(logger);
  47. var text = reader.ReadElementContentAsString();
  48. if (DateTime.TryParse(
  49. text,
  50. CultureInfo.InvariantCulture,
  51. DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
  52. out value))
  53. {
  54. return true;
  55. }
  56. logger.LogWarning("Invalid date: {Date}", text);
  57. return false;
  58. }
  59. /// <summary>
  60. /// Parses a <see cref="DateTime"/> from the current node.
  61. /// </summary>
  62. /// <param name="reader">The <see cref="XmlReader"/>.</param>
  63. /// <param name="formatString">The date format string.</param>
  64. /// <param name="value">The parsed <see cref="DateTime"/>.</param>
  65. /// <returns>A value indicating whether the parsing succeeded.</returns>
  66. public static bool TryReadDateTimeExact(this XmlReader reader, string formatString, out DateTime value)
  67. {
  68. ArgumentNullException.ThrowIfNull(reader);
  69. ArgumentNullException.ThrowIfNull(formatString);
  70. return DateTime.TryParseExact(
  71. reader.ReadElementContentAsString(),
  72. formatString,
  73. CultureInfo.InvariantCulture,
  74. DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
  75. out value);
  76. }
  77. /// <summary>
  78. /// Parses a <see cref="PersonInfo"/> from the xml node.
  79. /// </summary>
  80. /// <param name="reader">The <see cref="XmlReader"/>.</param>
  81. /// <returns>A <see cref="PersonInfo"/>, or <c>null</c> if none is found.</returns>
  82. public static PersonInfo? GetPersonFromXmlNode(this XmlReader reader)
  83. {
  84. ArgumentNullException.ThrowIfNull(reader);
  85. if (reader.IsEmptyElement)
  86. {
  87. reader.Read();
  88. return null;
  89. }
  90. var name = string.Empty;
  91. var type = PersonKind.Actor; // If type is not specified assume actor
  92. var role = string.Empty;
  93. int? sortOrder = null;
  94. string? imageUrl = null;
  95. using var subtree = reader.ReadSubtree();
  96. subtree.MoveToContent();
  97. subtree.Read();
  98. while (subtree is { EOF: false, ReadState: ReadState.Interactive })
  99. {
  100. if (subtree.NodeType != XmlNodeType.Element)
  101. {
  102. subtree.Read();
  103. continue;
  104. }
  105. switch (subtree.Name)
  106. {
  107. case "name":
  108. case "Name":
  109. name = subtree.ReadElementContentAsString();
  110. break;
  111. case "role":
  112. case "Role":
  113. var roleValue = subtree.ReadElementContentAsString();
  114. if (!string.IsNullOrWhiteSpace(roleValue))
  115. {
  116. role = roleValue;
  117. }
  118. break;
  119. case "type":
  120. case "Type":
  121. Enum.TryParse(subtree.ReadElementContentAsString(), true, out type);
  122. break;
  123. case "order":
  124. case "sortorder":
  125. case "SortOrder":
  126. if (int.TryParse(
  127. subtree.ReadElementContentAsString(),
  128. NumberStyles.Integer,
  129. CultureInfo.InvariantCulture,
  130. out var intVal))
  131. {
  132. sortOrder = intVal;
  133. }
  134. break;
  135. case "thumb":
  136. var thumb = subtree.ReadElementContentAsString();
  137. if (!string.IsNullOrWhiteSpace(thumb))
  138. {
  139. imageUrl = thumb;
  140. }
  141. break;
  142. default:
  143. subtree.Skip();
  144. break;
  145. }
  146. }
  147. if (string.IsNullOrWhiteSpace(name))
  148. {
  149. return null;
  150. }
  151. return new PersonInfo
  152. {
  153. Name = name.Trim(),
  154. Role = role,
  155. Type = type,
  156. SortOrder = sortOrder,
  157. ImageUrl = imageUrl
  158. };
  159. }
  160. /// <summary>
  161. /// Used to split names of comma or pipe delimited genres and people.
  162. /// </summary>
  163. /// <param name="reader">The <see cref="XmlReader"/>.</param>
  164. /// <returns>IEnumerable{System.String}.</returns>
  165. public static IEnumerable<string> GetStringArray(this XmlReader reader)
  166. {
  167. ArgumentNullException.ThrowIfNull(reader);
  168. var value = reader.ReadElementContentAsString();
  169. // Only split by comma if there is no pipe in the string
  170. // We have to be careful to not split names like Matthew, Jr.
  171. var separator = !value.Contains('|', StringComparison.Ordinal)
  172. && !value.Contains(';', StringComparison.Ordinal)
  173. ? new[] { ',' }
  174. : new[] { '|', ';' };
  175. foreach (var part in value.Trim().Trim(separator).Split(separator))
  176. {
  177. if (!string.IsNullOrWhiteSpace(part))
  178. {
  179. yield return part.Trim();
  180. }
  181. }
  182. }
  183. /// <summary>
  184. /// Parses a <see cref="PersonInfo"/> array from the xml node.
  185. /// </summary>
  186. /// <param name="reader">The <see cref="XmlReader"/>.</param>
  187. /// <param name="personKind">The <see cref="PersonKind"/>.</param>
  188. /// <returns>The <see cref="IEnumerable{PersonInfo}"/>.</returns>
  189. public static IEnumerable<PersonInfo> GetPersonArray(this XmlReader reader, PersonKind personKind)
  190. => reader.GetStringArray()
  191. .Select(part => new PersonInfo { Name = part, Type = personKind });
  192. }