BaseItemXmlParser.cs 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading;
  8. using System.Xml;
  9. using MediaBrowser.Controller.Entities;
  10. using MediaBrowser.Controller.Providers;
  11. using MediaBrowser.Model.Entities;
  12. using Microsoft.Extensions.Logging;
  13. namespace MediaBrowser.LocalMetadata.Parsers
  14. {
  15. /// <summary>
  16. /// Provides a base class for parsing metadata xml
  17. /// </summary>
  18. /// <typeparam name="T"></typeparam>
  19. public class BaseItemXmlParser<T>
  20. where T : BaseItem
  21. {
  22. /// <summary>
  23. /// The logger
  24. /// </summary>
  25. protected ILogger<BaseItemXmlParser<T>> Logger { get; private set; }
  26. protected IProviderManager ProviderManager { get; private set; }
  27. private Dictionary<string, string> _validProviderIds;
  28. /// <summary>
  29. /// Initializes a new instance of the <see cref="BaseItemXmlParser{T}" /> class.
  30. /// </summary>
  31. /// <param name="logger">The logger.</param>
  32. public BaseItemXmlParser(ILogger<BaseItemXmlParser<T>> logger, IProviderManager providerManager)
  33. {
  34. Logger = logger;
  35. ProviderManager = providerManager;
  36. }
  37. /// <summary>
  38. /// Fetches metadata for an item from one xml file
  39. /// </summary>
  40. /// <param name="item">The item.</param>
  41. /// <param name="metadataFile">The metadata file.</param>
  42. /// <param name="cancellationToken">The cancellation token.</param>
  43. /// <exception cref="ArgumentNullException"></exception>
  44. public void Fetch(MetadataResult<T> item, string metadataFile, CancellationToken cancellationToken)
  45. {
  46. if (item == null)
  47. {
  48. throw new ArgumentNullException(nameof(item));
  49. }
  50. if (string.IsNullOrEmpty(metadataFile))
  51. {
  52. throw new ArgumentException("The metadata file was empty or null.", nameof(metadataFile));
  53. }
  54. var settings = new XmlReaderSettings()
  55. {
  56. ValidationType = ValidationType.None,
  57. CheckCharacters = false,
  58. IgnoreProcessingInstructions = true,
  59. IgnoreComments = true
  60. };
  61. _validProviderIds = _validProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  62. var idInfos = ProviderManager.GetExternalIdInfos(item.Item);
  63. foreach (var info in idInfos)
  64. {
  65. var id = info.Key + "Id";
  66. if (!_validProviderIds.ContainsKey(id))
  67. {
  68. _validProviderIds.Add(id, info.Key);
  69. }
  70. }
  71. //Additional Mappings
  72. _validProviderIds.Add("IMDB", "Imdb");
  73. //Fetch(item, metadataFile, settings, Encoding.GetEncoding("ISO-8859-1"), cancellationToken);
  74. Fetch(item, metadataFile, settings, Encoding.UTF8, cancellationToken);
  75. }
  76. /// <summary>
  77. /// Fetches the specified item.
  78. /// </summary>
  79. /// <param name="item">The item.</param>
  80. /// <param name="metadataFile">The metadata file.</param>
  81. /// <param name="settings">The settings.</param>
  82. /// <param name="encoding">The encoding.</param>
  83. /// <param name="cancellationToken">The cancellation token.</param>
  84. private void Fetch(MetadataResult<T> item, string metadataFile, XmlReaderSettings settings, Encoding encoding, CancellationToken cancellationToken)
  85. {
  86. item.ResetPeople();
  87. using (var fileStream = File.OpenRead(metadataFile))
  88. using (var streamReader = new StreamReader(fileStream, encoding))
  89. using (var reader = XmlReader.Create(streamReader, settings))
  90. {
  91. reader.MoveToContent();
  92. reader.Read();
  93. // Loop through each element
  94. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  95. {
  96. cancellationToken.ThrowIfCancellationRequested();
  97. if (reader.NodeType == XmlNodeType.Element)
  98. {
  99. FetchDataFromXmlNode(reader, item);
  100. }
  101. else
  102. {
  103. reader.Read();
  104. }
  105. }
  106. }
  107. }
  108. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  109. /// <summary>
  110. /// Fetches metadata from one Xml Element
  111. /// </summary>
  112. /// <param name="reader">The reader.</param>
  113. /// <param name="itemResult">The item result.</param>
  114. protected virtual void FetchDataFromXmlNode(XmlReader reader, MetadataResult<T> itemResult)
  115. {
  116. var item = itemResult.Item;
  117. switch (reader.Name)
  118. {
  119. // DateCreated
  120. case "Added":
  121. {
  122. var val = reader.ReadElementContentAsString();
  123. if (!string.IsNullOrWhiteSpace(val))
  124. {
  125. if (DateTime.TryParse(val, out var added))
  126. {
  127. item.DateCreated = added.ToUniversalTime();
  128. }
  129. else
  130. {
  131. Logger.LogWarning("Invalid Added value found: " + val);
  132. }
  133. }
  134. break;
  135. }
  136. case "OriginalTitle":
  137. {
  138. var val = reader.ReadElementContentAsString();
  139. if (!string.IsNullOrEmpty(val))
  140. {
  141. item.OriginalTitle = val;
  142. }
  143. break;
  144. }
  145. case "LocalTitle":
  146. item.Name = reader.ReadElementContentAsString();
  147. break;
  148. case "CriticRating":
  149. {
  150. var text = reader.ReadElementContentAsString();
  151. if (!string.IsNullOrEmpty(text))
  152. {
  153. if (float.TryParse(text, NumberStyles.Any, _usCulture, out var value))
  154. {
  155. item.CriticRating = value;
  156. }
  157. }
  158. break;
  159. }
  160. case "SortTitle":
  161. {
  162. var val = reader.ReadElementContentAsString();
  163. if (!string.IsNullOrWhiteSpace(val))
  164. {
  165. item.ForcedSortName = val;
  166. }
  167. break;
  168. }
  169. case "Overview":
  170. case "Description":
  171. {
  172. var val = reader.ReadElementContentAsString();
  173. if (!string.IsNullOrWhiteSpace(val))
  174. {
  175. item.Overview = val;
  176. }
  177. break;
  178. }
  179. case "Language":
  180. {
  181. var val = reader.ReadElementContentAsString();
  182. item.PreferredMetadataLanguage = val;
  183. break;
  184. }
  185. case "CountryCode":
  186. {
  187. var val = reader.ReadElementContentAsString();
  188. item.PreferredMetadataCountryCode = val;
  189. break;
  190. }
  191. case "PlaceOfBirth":
  192. {
  193. var val = reader.ReadElementContentAsString();
  194. if (!string.IsNullOrWhiteSpace(val))
  195. {
  196. var person = item as Person;
  197. if (person != null)
  198. {
  199. person.ProductionLocations = new string[] { val };
  200. }
  201. }
  202. break;
  203. }
  204. case "LockedFields":
  205. {
  206. var val = reader.ReadElementContentAsString();
  207. if (!string.IsNullOrWhiteSpace(val))
  208. {
  209. item.LockedFields = val.Split('|').Select(i =>
  210. {
  211. if (Enum.TryParse(i, true, out MetadataField field))
  212. {
  213. return (MetadataField?)field;
  214. }
  215. return null;
  216. }).Where(i => i.HasValue).Select(i => i.Value).ToArray();
  217. }
  218. break;
  219. }
  220. case "TagLines":
  221. {
  222. if (!reader.IsEmptyElement)
  223. {
  224. using (var subtree = reader.ReadSubtree())
  225. {
  226. FetchFromTaglinesNode(subtree, item);
  227. }
  228. }
  229. else
  230. {
  231. reader.Read();
  232. }
  233. break;
  234. }
  235. case "Countries":
  236. {
  237. if (!reader.IsEmptyElement)
  238. {
  239. using (var subtree = reader.ReadSubtree())
  240. {
  241. FetchFromCountriesNode(subtree, item);
  242. }
  243. }
  244. else
  245. {
  246. reader.Read();
  247. }
  248. break;
  249. }
  250. case "ContentRating":
  251. case "MPAARating":
  252. {
  253. var rating = reader.ReadElementContentAsString();
  254. if (!string.IsNullOrWhiteSpace(rating))
  255. {
  256. item.OfficialRating = rating;
  257. }
  258. break;
  259. }
  260. case "CustomRating":
  261. {
  262. var val = reader.ReadElementContentAsString();
  263. if (!string.IsNullOrWhiteSpace(val))
  264. {
  265. item.CustomRating = val;
  266. }
  267. break;
  268. }
  269. case "RunningTime":
  270. {
  271. var text = reader.ReadElementContentAsString();
  272. if (!string.IsNullOrWhiteSpace(text))
  273. {
  274. if (int.TryParse(text.Split(' ')[0], NumberStyles.Integer, _usCulture, out var runtime))
  275. {
  276. item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
  277. }
  278. }
  279. break;
  280. }
  281. case "AspectRatio":
  282. {
  283. var val = reader.ReadElementContentAsString();
  284. var hasAspectRatio = item as IHasAspectRatio;
  285. if (!string.IsNullOrWhiteSpace(val) && hasAspectRatio != null)
  286. {
  287. hasAspectRatio.AspectRatio = val;
  288. }
  289. break;
  290. }
  291. case "LockData":
  292. {
  293. var val = reader.ReadElementContentAsString();
  294. if (!string.IsNullOrWhiteSpace(val))
  295. {
  296. item.IsLocked = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  297. }
  298. break;
  299. }
  300. case "Network":
  301. {
  302. foreach (var name in SplitNames(reader.ReadElementContentAsString()))
  303. {
  304. if (string.IsNullOrWhiteSpace(name))
  305. {
  306. continue;
  307. }
  308. item.AddStudio(name);
  309. }
  310. break;
  311. }
  312. case "Director":
  313. {
  314. foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Director }))
  315. {
  316. if (string.IsNullOrWhiteSpace(p.Name))
  317. {
  318. continue;
  319. }
  320. itemResult.AddPerson(p);
  321. }
  322. break;
  323. }
  324. case "Writer":
  325. {
  326. foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Writer }))
  327. {
  328. if (string.IsNullOrWhiteSpace(p.Name))
  329. {
  330. continue;
  331. }
  332. itemResult.AddPerson(p);
  333. }
  334. break;
  335. }
  336. case "Actors":
  337. {
  338. var actors = reader.ReadInnerXml();
  339. if (actors.Contains("<"))
  340. {
  341. // This is one of the mis-named "Actors" full nodes created by MB2
  342. // Create a reader and pass it to the persons node processor
  343. FetchDataFromPersonsNode(XmlReader.Create(new StringReader("<Persons>" + actors + "</Persons>")), itemResult);
  344. }
  345. else
  346. {
  347. // Old-style piped string
  348. foreach (var p in SplitNames(actors).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Actor }))
  349. {
  350. if (string.IsNullOrWhiteSpace(p.Name))
  351. {
  352. continue;
  353. }
  354. itemResult.AddPerson(p);
  355. }
  356. }
  357. break;
  358. }
  359. case "GuestStars":
  360. {
  361. foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.GuestStar }))
  362. {
  363. if (string.IsNullOrWhiteSpace(p.Name))
  364. {
  365. continue;
  366. }
  367. itemResult.AddPerson(p);
  368. }
  369. break;
  370. }
  371. case "Trailer":
  372. {
  373. var val = reader.ReadElementContentAsString();
  374. if (!string.IsNullOrWhiteSpace(val))
  375. {
  376. item.AddTrailerUrl(val);
  377. }
  378. break;
  379. }
  380. case "DisplayOrder":
  381. {
  382. var val = reader.ReadElementContentAsString();
  383. var hasDisplayOrder = item as IHasDisplayOrder;
  384. if (hasDisplayOrder != null)
  385. {
  386. if (!string.IsNullOrWhiteSpace(val))
  387. {
  388. hasDisplayOrder.DisplayOrder = val;
  389. }
  390. }
  391. break;
  392. }
  393. case "Trailers":
  394. {
  395. if (!reader.IsEmptyElement)
  396. {
  397. using (var subtree = reader.ReadSubtree())
  398. {
  399. FetchDataFromTrailersNode(subtree, item);
  400. }
  401. }
  402. else
  403. {
  404. reader.Read();
  405. }
  406. break;
  407. }
  408. case "ProductionYear":
  409. {
  410. var val = reader.ReadElementContentAsString();
  411. if (!string.IsNullOrWhiteSpace(val))
  412. {
  413. if (int.TryParse(val, out var productionYear) && productionYear > 1850)
  414. {
  415. item.ProductionYear = productionYear;
  416. }
  417. }
  418. break;
  419. }
  420. case "Rating":
  421. case "IMDBrating":
  422. {
  423. var rating = reader.ReadElementContentAsString();
  424. if (!string.IsNullOrWhiteSpace(rating))
  425. {
  426. // All external meta is saving this as '.' for decimal I believe...but just to be sure
  427. if (float.TryParse(rating.Replace(',', '.'), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var val))
  428. {
  429. item.CommunityRating = val;
  430. }
  431. }
  432. break;
  433. }
  434. case "BirthDate":
  435. case "PremiereDate":
  436. case "FirstAired":
  437. {
  438. var firstAired = reader.ReadElementContentAsString();
  439. if (!string.IsNullOrWhiteSpace(firstAired))
  440. {
  441. if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var airDate) && airDate.Year > 1850)
  442. {
  443. item.PremiereDate = airDate.ToUniversalTime();
  444. item.ProductionYear = airDate.Year;
  445. }
  446. }
  447. break;
  448. }
  449. case "DeathDate":
  450. case "EndDate":
  451. {
  452. var firstAired = reader.ReadElementContentAsString();
  453. if (!string.IsNullOrWhiteSpace(firstAired))
  454. {
  455. if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var airDate) && airDate.Year > 1850)
  456. {
  457. item.EndDate = airDate.ToUniversalTime();
  458. }
  459. }
  460. break;
  461. }
  462. case "CollectionNumber":
  463. var tmdbCollection = reader.ReadElementContentAsString();
  464. if (!string.IsNullOrWhiteSpace(tmdbCollection))
  465. {
  466. item.SetProviderId(MetadataProvider.TmdbCollection, tmdbCollection);
  467. }
  468. break;
  469. case "Genres":
  470. {
  471. if (!reader.IsEmptyElement)
  472. {
  473. using (var subtree = reader.ReadSubtree())
  474. {
  475. FetchFromGenresNode(subtree, item);
  476. }
  477. }
  478. else
  479. {
  480. reader.Read();
  481. }
  482. break;
  483. }
  484. case "Tags":
  485. {
  486. if (!reader.IsEmptyElement)
  487. {
  488. using (var subtree = reader.ReadSubtree())
  489. {
  490. FetchFromTagsNode(subtree, item);
  491. }
  492. }
  493. else
  494. {
  495. reader.Read();
  496. }
  497. break;
  498. }
  499. case "Persons":
  500. {
  501. if (!reader.IsEmptyElement)
  502. {
  503. using (var subtree = reader.ReadSubtree())
  504. {
  505. FetchDataFromPersonsNode(subtree, itemResult);
  506. }
  507. }
  508. else
  509. {
  510. reader.Read();
  511. }
  512. break;
  513. }
  514. case "Studios":
  515. {
  516. if (!reader.IsEmptyElement)
  517. {
  518. using (var subtree = reader.ReadSubtree())
  519. {
  520. FetchFromStudiosNode(subtree, item);
  521. }
  522. }
  523. else
  524. {
  525. reader.Read();
  526. }
  527. break;
  528. }
  529. case "Shares":
  530. {
  531. if (!reader.IsEmptyElement)
  532. {
  533. using (var subtree = reader.ReadSubtree())
  534. {
  535. var hasShares = item as IHasShares;
  536. if (hasShares != null)
  537. {
  538. FetchFromSharesNode(subtree, hasShares);
  539. }
  540. }
  541. }
  542. else
  543. {
  544. reader.Read();
  545. }
  546. break;
  547. }
  548. case "Format3D":
  549. {
  550. var val = reader.ReadElementContentAsString();
  551. var video = item as Video;
  552. if (video != null)
  553. {
  554. if (string.Equals("HSBS", val, StringComparison.OrdinalIgnoreCase))
  555. {
  556. video.Video3DFormat = Video3DFormat.HalfSideBySide;
  557. }
  558. else if (string.Equals("HTAB", val, StringComparison.OrdinalIgnoreCase))
  559. {
  560. video.Video3DFormat = Video3DFormat.HalfTopAndBottom;
  561. }
  562. else if (string.Equals("FTAB", val, StringComparison.OrdinalIgnoreCase))
  563. {
  564. video.Video3DFormat = Video3DFormat.FullTopAndBottom;
  565. }
  566. else if (string.Equals("FSBS", val, StringComparison.OrdinalIgnoreCase))
  567. {
  568. video.Video3DFormat = Video3DFormat.FullSideBySide;
  569. }
  570. else if (string.Equals("MVC", val, StringComparison.OrdinalIgnoreCase))
  571. {
  572. video.Video3DFormat = Video3DFormat.MVC;
  573. }
  574. }
  575. break;
  576. }
  577. default:
  578. {
  579. string readerName = reader.Name;
  580. if (_validProviderIds.TryGetValue(readerName, out string providerIdValue))
  581. {
  582. var id = reader.ReadElementContentAsString();
  583. if (!string.IsNullOrWhiteSpace(id))
  584. {
  585. item.SetProviderId(providerIdValue, id);
  586. }
  587. }
  588. else
  589. {
  590. reader.Skip();
  591. }
  592. break;
  593. }
  594. }
  595. }
  596. private void FetchFromSharesNode(XmlReader reader, IHasShares item)
  597. {
  598. var list = new List<Share>();
  599. reader.MoveToContent();
  600. reader.Read();
  601. // Loop through each element
  602. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  603. {
  604. if (reader.NodeType == XmlNodeType.Element)
  605. {
  606. switch (reader.Name)
  607. {
  608. case "Share":
  609. {
  610. if (reader.IsEmptyElement)
  611. {
  612. reader.Read();
  613. continue;
  614. }
  615. using (var subReader = reader.ReadSubtree())
  616. {
  617. var child = GetShare(subReader);
  618. if (child != null)
  619. {
  620. list.Add(child);
  621. }
  622. }
  623. break;
  624. }
  625. default:
  626. {
  627. reader.Skip();
  628. break;
  629. }
  630. }
  631. }
  632. else
  633. {
  634. reader.Read();
  635. }
  636. }
  637. item.Shares = list.ToArray();
  638. }
  639. private Share GetShareFromNode(XmlReader reader)
  640. {
  641. var share = new Share();
  642. reader.MoveToContent();
  643. reader.Read();
  644. // Loop through each element
  645. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  646. {
  647. if (reader.NodeType == XmlNodeType.Element)
  648. {
  649. switch (reader.Name)
  650. {
  651. case "UserId":
  652. {
  653. share.UserId = reader.ReadElementContentAsString();
  654. break;
  655. }
  656. case "CanEdit":
  657. {
  658. share.CanEdit = string.Equals(reader.ReadElementContentAsString(), true.ToString(), StringComparison.OrdinalIgnoreCase);
  659. break;
  660. }
  661. default:
  662. reader.Skip();
  663. break;
  664. }
  665. }
  666. else
  667. {
  668. reader.Read();
  669. }
  670. }
  671. return share;
  672. }
  673. private void FetchFromCountriesNode(XmlReader reader, T item)
  674. {
  675. reader.MoveToContent();
  676. reader.Read();
  677. // Loop through each element
  678. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  679. {
  680. if (reader.NodeType == XmlNodeType.Element)
  681. {
  682. switch (reader.Name)
  683. {
  684. case "Country":
  685. {
  686. var val = reader.ReadElementContentAsString();
  687. if (!string.IsNullOrWhiteSpace(val))
  688. {
  689. }
  690. break;
  691. }
  692. default:
  693. reader.Skip();
  694. break;
  695. }
  696. }
  697. else
  698. {
  699. reader.Read();
  700. }
  701. }
  702. }
  703. /// <summary>
  704. /// Fetches from taglines node.
  705. /// </summary>
  706. /// <param name="reader">The reader.</param>
  707. /// <param name="item">The item.</param>
  708. private void FetchFromTaglinesNode(XmlReader reader, T item)
  709. {
  710. reader.MoveToContent();
  711. reader.Read();
  712. // Loop through each element
  713. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  714. {
  715. if (reader.NodeType == XmlNodeType.Element)
  716. {
  717. switch (reader.Name)
  718. {
  719. case "Tagline":
  720. {
  721. var val = reader.ReadElementContentAsString();
  722. if (!string.IsNullOrWhiteSpace(val))
  723. {
  724. item.Tagline = val;
  725. }
  726. break;
  727. }
  728. default:
  729. reader.Skip();
  730. break;
  731. }
  732. }
  733. else
  734. {
  735. reader.Read();
  736. }
  737. }
  738. }
  739. /// <summary>
  740. /// Fetches from genres node.
  741. /// </summary>
  742. /// <param name="reader">The reader.</param>
  743. /// <param name="item">The item.</param>
  744. private void FetchFromGenresNode(XmlReader reader, T item)
  745. {
  746. reader.MoveToContent();
  747. reader.Read();
  748. // Loop through each element
  749. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  750. {
  751. if (reader.NodeType == XmlNodeType.Element)
  752. {
  753. switch (reader.Name)
  754. {
  755. case "Genre":
  756. {
  757. var genre = reader.ReadElementContentAsString();
  758. if (!string.IsNullOrWhiteSpace(genre))
  759. {
  760. item.AddGenre(genre);
  761. }
  762. break;
  763. }
  764. default:
  765. reader.Skip();
  766. break;
  767. }
  768. }
  769. else
  770. {
  771. reader.Read();
  772. }
  773. }
  774. }
  775. private void FetchFromTagsNode(XmlReader reader, BaseItem item)
  776. {
  777. reader.MoveToContent();
  778. reader.Read();
  779. var tags = new List<string>();
  780. // Loop through each element
  781. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  782. {
  783. if (reader.NodeType == XmlNodeType.Element)
  784. {
  785. switch (reader.Name)
  786. {
  787. case "Tag":
  788. {
  789. var tag = reader.ReadElementContentAsString();
  790. if (!string.IsNullOrWhiteSpace(tag))
  791. {
  792. tags.Add(tag);
  793. }
  794. break;
  795. }
  796. default:
  797. reader.Skip();
  798. break;
  799. }
  800. }
  801. else
  802. {
  803. reader.Read();
  804. }
  805. }
  806. item.Tags = tags.Distinct(StringComparer.Ordinal).ToArray();
  807. }
  808. /// <summary>
  809. /// Fetches the data from persons node.
  810. /// </summary>
  811. /// <param name="reader">The reader.</param>
  812. /// <param name="item">The item.</param>
  813. private void FetchDataFromPersonsNode(XmlReader reader, MetadataResult<T> item)
  814. {
  815. reader.MoveToContent();
  816. reader.Read();
  817. // Loop through each element
  818. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  819. {
  820. if (reader.NodeType == XmlNodeType.Element)
  821. {
  822. switch (reader.Name)
  823. {
  824. case "Person":
  825. case "Actor":
  826. {
  827. if (reader.IsEmptyElement)
  828. {
  829. reader.Read();
  830. continue;
  831. }
  832. using (var subtree = reader.ReadSubtree())
  833. {
  834. foreach (var person in GetPersonsFromXmlNode(subtree))
  835. {
  836. if (string.IsNullOrWhiteSpace(person.Name))
  837. {
  838. continue;
  839. }
  840. item.AddPerson(person);
  841. }
  842. }
  843. break;
  844. }
  845. default:
  846. reader.Skip();
  847. break;
  848. }
  849. }
  850. else
  851. {
  852. reader.Read();
  853. }
  854. }
  855. }
  856. private void FetchDataFromTrailersNode(XmlReader reader, T item)
  857. {
  858. reader.MoveToContent();
  859. reader.Read();
  860. // Loop through each element
  861. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  862. {
  863. if (reader.NodeType == XmlNodeType.Element)
  864. {
  865. switch (reader.Name)
  866. {
  867. case "Trailer":
  868. {
  869. var val = reader.ReadElementContentAsString();
  870. if (!string.IsNullOrWhiteSpace(val))
  871. {
  872. item.AddTrailerUrl(val);
  873. }
  874. break;
  875. }
  876. default:
  877. reader.Skip();
  878. break;
  879. }
  880. }
  881. else
  882. {
  883. reader.Read();
  884. }
  885. }
  886. }
  887. /// <summary>
  888. /// Fetches from studios node.
  889. /// </summary>
  890. /// <param name="reader">The reader.</param>
  891. /// <param name="item">The item.</param>
  892. private void FetchFromStudiosNode(XmlReader reader, T item)
  893. {
  894. reader.MoveToContent();
  895. reader.Read();
  896. // Loop through each element
  897. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  898. {
  899. if (reader.NodeType == XmlNodeType.Element)
  900. {
  901. switch (reader.Name)
  902. {
  903. case "Studio":
  904. {
  905. var studio = reader.ReadElementContentAsString();
  906. if (!string.IsNullOrWhiteSpace(studio))
  907. {
  908. item.AddStudio(studio);
  909. }
  910. break;
  911. }
  912. default:
  913. reader.Skip();
  914. break;
  915. }
  916. }
  917. else
  918. {
  919. reader.Read();
  920. }
  921. }
  922. }
  923. /// <summary>
  924. /// Gets the persons from XML node.
  925. /// </summary>
  926. /// <param name="reader">The reader.</param>
  927. /// <returns>IEnumerable{PersonInfo}.</returns>
  928. private IEnumerable<PersonInfo> GetPersonsFromXmlNode(XmlReader reader)
  929. {
  930. var name = string.Empty;
  931. var type = PersonType.Actor; // If type is not specified assume actor
  932. var role = string.Empty;
  933. int? sortOrder = null;
  934. reader.MoveToContent();
  935. reader.Read();
  936. // Loop through each element
  937. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  938. {
  939. if (reader.NodeType == XmlNodeType.Element)
  940. {
  941. switch (reader.Name)
  942. {
  943. case "Name":
  944. name = reader.ReadElementContentAsString() ?? string.Empty;
  945. break;
  946. case "Type":
  947. {
  948. var val = reader.ReadElementContentAsString();
  949. if (!string.IsNullOrWhiteSpace(val))
  950. {
  951. type = val;
  952. }
  953. break;
  954. }
  955. case "Role":
  956. {
  957. var val = reader.ReadElementContentAsString();
  958. if (!string.IsNullOrWhiteSpace(val))
  959. {
  960. role = val;
  961. }
  962. break;
  963. }
  964. case "SortOrder":
  965. {
  966. var val = reader.ReadElementContentAsString();
  967. if (!string.IsNullOrWhiteSpace(val))
  968. {
  969. if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var intVal))
  970. {
  971. sortOrder = intVal;
  972. }
  973. }
  974. break;
  975. }
  976. default:
  977. reader.Skip();
  978. break;
  979. }
  980. }
  981. else
  982. {
  983. reader.Read();
  984. }
  985. }
  986. var personInfo = new PersonInfo
  987. {
  988. Name = name.Trim(),
  989. Role = role,
  990. Type = type,
  991. SortOrder = sortOrder
  992. };
  993. return new[] { personInfo };
  994. }
  995. protected LinkedChild GetLinkedChild(XmlReader reader)
  996. {
  997. var linkedItem = new LinkedChild
  998. {
  999. Type = LinkedChildType.Manual
  1000. };
  1001. reader.MoveToContent();
  1002. reader.Read();
  1003. // Loop through each element
  1004. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  1005. {
  1006. if (reader.NodeType == XmlNodeType.Element)
  1007. {
  1008. switch (reader.Name)
  1009. {
  1010. case "Path":
  1011. {
  1012. linkedItem.Path = reader.ReadElementContentAsString();
  1013. break;
  1014. }
  1015. case "ItemId":
  1016. {
  1017. linkedItem.LibraryItemId = reader.ReadElementContentAsString();
  1018. break;
  1019. }
  1020. default:
  1021. reader.Skip();
  1022. break;
  1023. }
  1024. }
  1025. else
  1026. {
  1027. reader.Read();
  1028. }
  1029. }
  1030. // This is valid
  1031. if (!string.IsNullOrWhiteSpace(linkedItem.Path) || !string.IsNullOrWhiteSpace(linkedItem.LibraryItemId))
  1032. {
  1033. return linkedItem;
  1034. }
  1035. return null;
  1036. }
  1037. protected Share GetShare(XmlReader reader)
  1038. {
  1039. var item = new Share();
  1040. reader.MoveToContent();
  1041. reader.Read();
  1042. // Loop through each element
  1043. while (!reader.EOF && reader.ReadState == ReadState.Interactive)
  1044. {
  1045. if (reader.NodeType == XmlNodeType.Element)
  1046. {
  1047. switch (reader.Name)
  1048. {
  1049. case "UserId":
  1050. {
  1051. item.UserId = reader.ReadElementContentAsString();
  1052. break;
  1053. }
  1054. case "CanEdit":
  1055. {
  1056. item.CanEdit = string.Equals(reader.ReadElementContentAsString(), "true", StringComparison.OrdinalIgnoreCase);
  1057. break;
  1058. }
  1059. default:
  1060. {
  1061. reader.Skip();
  1062. break;
  1063. }
  1064. }
  1065. }
  1066. else
  1067. {
  1068. reader.Read();
  1069. }
  1070. }
  1071. // This is valid
  1072. if (!string.IsNullOrWhiteSpace(item.UserId))
  1073. {
  1074. return item;
  1075. }
  1076. return null;
  1077. }
  1078. /// <summary>
  1079. /// Used to split names of comma or pipe delimeted genres and people
  1080. /// </summary>
  1081. /// <param name="value">The value.</param>
  1082. /// <returns>IEnumerable{System.String}.</returns>
  1083. private IEnumerable<string> SplitNames(string value)
  1084. {
  1085. value = value ?? string.Empty;
  1086. // Only split by comma if there is no pipe in the string
  1087. // We have to be careful to not split names like Matthew, Jr.
  1088. var separator = value.IndexOf('|') == -1 && value.IndexOf(';') == -1 ? new[] { ',' } : new[] { '|', ';' };
  1089. value = value.Trim().Trim(separator);
  1090. return string.IsNullOrWhiteSpace(value) ? Array.Empty<string>() : Split(value, separator, StringSplitOptions.RemoveEmptyEntries);
  1091. }
  1092. /// <summary>
  1093. /// Provides an additional overload for string.split
  1094. /// </summary>
  1095. /// <param name="val">The val.</param>
  1096. /// <param name="separators">The separators.</param>
  1097. /// <param name="options">The options.</param>
  1098. /// <returns>System.String[][].</returns>
  1099. private string[] Split(string val, char[] separators, StringSplitOptions options)
  1100. {
  1101. return val.Split(separators, options);
  1102. }
  1103. }
  1104. }