using System.Text.RegularExpressions; using System.Xml.Linq; using FinlyticCore.Dtos.News; namespace FinlyticNews.Adapters.Discovery; /// /// Specialized RSS discovery adapter for Finanznachrichten.de, capable of parsing custom fn:isin elements and standard RSS metadata. /// public partial class FinanznachrichtenRssDiscoveryAdapter : ArticleDiscoveryAdapter { private static readonly XNamespace FnNamespace = "http://www.finanznachrichten.de/service/rss"; /// public override string Name => "finanznachrichten_rss"; /// public override List ExtractUrls(string content, string sourceUrl) { if (string.IsNullOrWhiteSpace(content)) return []; var articles = new List(); if (!Uri.TryCreate(sourceUrl, UriKind.Absolute, out var baseUri)) return articles; try { var doc = XDocument.Parse(content); var channel = doc.Root?.Element("channel"); var channelTitle = channel?.Element("title")?.Value?.Trim(); var channelLanguage = channel?.Element("language")?.Value?.Trim(); var items = doc.Descendants("item"); foreach (var item in items) { var link = item.Element("link")?.Value?.Trim() ?? item.Element("guid")?.Value?.Trim(); if (string.IsNullOrWhiteSpace(link)) continue; // ISIN Extraktion var isins = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var isinEl in item.Elements(FnNamespace + "isin")) { if (string.IsNullOrWhiteSpace(isinEl.Value)) continue; var parts = isinEl.Value.Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (var part in parts) { var trimmed = part.Trim().ToUpperInvariant(); if (IsValidIsin(trimmed)) { isins.Add(trimmed); } } } var title = item.Element("title")?.Value?.Trim(); var description = item.Element("description")?.Value?.Trim(); DateTime? pubDate = null; var pubDateValue = item.Element("pubDate")?.Value; if (!string.IsNullOrWhiteSpace(pubDateValue) && DateTime.TryParse(pubDateValue, out var parsedDate)) { pubDate = parsedDate.ToUniversalTime(); } if (Uri.TryCreate(baseUri, link, out var absoluteUri)) { articles.Add(new DiscoveredArticle( Url: absoluteUri.ToString(), Isins: isins.Count > 0 ? isins.ToList() : null, Title: title, Summary: description, PublishedAt: pubDate, Language: channelLanguage, SourceName: channelTitle )); } } } catch { // XML Malformed / Parsing Errors ignorieren } // Deduplizierung nach URL return articles.GroupBy(a => a.Url, StringComparer.OrdinalIgnoreCase) .Select(g => g.First()) .ToList(); } private static bool IsValidIsin(string input) => input.Length == 12 && char.IsLetter(input[0]) && char.IsLetter(input[1]); }