使用並解析 OPML 格式的訂閱列表來轉移自己的 RSS 訂閱(解析篇)
OPML 全稱是 Outline Processor Markup Language ,即 大綱處理標記語言 。目前流行於收集部落格的 RSS 源,便於使用者轉移自己的訂閱專案。
本文將介紹這個古老的格式,並提供一個 .NET 上的簡易解析器。
本文是兩個部分的第二篇,前者是理解 OPML 格式,此篇是解析此格式:
OPML 格式
在解析之前,最好先理解此格式的的元素組成和元素屬性,所以如果你沒有閱讀概念篇,請先前往閱讀。
建立適用於 RSS 的簡易 OPML 模型
我們先為模型建立基類 OpmlModel
。
為了方便在客戶端應用中使用,可以使其繼承自 INotifyPropertyChanged
。
namespace Walterlv.Rssman.Models { public abstract class OpmlModel : NotificationObject { public void Deserialize(XElement element) { OnDeserializing(element); } protected abstract void OnDeserializing(XElement element); } }
namespace Walterlv.Rssman.Models { public abstract class NotificationObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; [NotifyPropertyChangedInvocator] protected void SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null) { if (Equals(field, value)) return; field = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } [NotifyPropertyChangedInvocator] protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
拿出我們關心的 outline
的屬性來解析,於是有:
namespace Walterlv.Rssman.Models { [DebuggerDisplay("RssOutline {Text,nq}, {XmlUrl,nq}, Count={Children.Count,nq}")] public sealed class RssOutline : OpmlModel { private string _text; private OutlineType _type; private string _xmlUrl; private string _htmlUrl; public string Text { get => _text; set => SetValue(ref _text, value); } public OutlineType Type { get => _type; set => SetValue(ref _type, value); } public string XmlUrl { get => _xmlUrl; set => SetValue(ref _xmlUrl, value); } public string HtmlUrl { get => _htmlUrl; set => SetValue(ref _htmlUrl, value); } public bool HasChildren => Children.Any(); public ObservableCollection<RssOutline> Children { get; } = new ObservableCollection<RssOutline>(); protected override void OnDeserializing(XElement element) { // 等待編寫解析程式碼。 } } }
還有表示 OPML 文件的模型:
namespace Walterlv.Rssman.Models { [DebuggerDisplay("RssOpml {Title,nq}, Count={Children.Count,nq}")] public sealed class RssOpml : OpmlModel { private string _title; public string Title { get => _title; set => SetValue(ref _title, value); } public ObservableCollection<RssOutline> Children { get; } = new ObservableCollection<RssOutline>(); protected override void OnDeserializing(XElement element) { // 等待編寫解析程式碼。 } } }
從 OPML 文件中解析出模型
在以上的模型程式碼中,我為基類留有 OnDeserializing
方法以供反序列化。
為了儘可能簡化此部落格的程式碼,引數我直接使用了 XElement
型別,以便在方法中使用 XPath 語法來解析。(當然,如果你是做庫或者進行大型可維護專案的開發,這裡就需要一些抽象了。)
現在,我們寫一個新的靜態型別 Opml
來解析 OPML 文件:
namespace Walterlv.Rssman.Services { public static class Opml { public static async Task<RssOpml> ParseAsync(Stream stream) { var document = await XDocument.LoadAsync(stream, LoadOptions.None, CancellationToken.None); var root = document.XPathSelectElement("opml"); var opml = new RssOpml(); opml.Deserialize(root); return opml; } } }
於是,再補全模型 RssOpml
和 RssOutline
的反序列化部分:
// RssOpml.cs protected override void OnDeserializing(XElement element) { var title = element.XPathSelectElement("head/title"); Title = title?.Value; var outlines = element.XPathSelectElements("body/outline"); Children.Clear(); foreach (var value in outlines) { var outline = new RssOutline(); outline.Deserialize(value); Children.Add(outline); } }
// RssOutline.cs protected override void OnDeserializing(XElement element) { var text = element.Attribute("text"); Text = text?.Value; var type = element.Attribute("type"); if (type != null && Enum.TryParse(type.Value, out OutlineType outlineType)) { Type = outlineType; } var xmlUrl = element.Attribute("xmlUrl"); XmlUrl = xmlUrl?.Value; var htmlUrl = element.Attribute("htmlUrl"); HtmlUrl = htmlUrl?.Value; var outlines = element.XPathSelectElements("outline"); Children.Clear(); foreach (var value in outlines) { var outline = new RssOutline(); outline.Deserialize(value); Children.Add(outline); } }
注意,以上兩個方法請分別填充到 RssOpml.cs
和 RssOutline.cs
的 OnDeserializing
方法中。
這裡,所有的 XML 解析均使用的是 XPath 語法,關於 XPath 語法,可以閱讀 XML 的 XPath 語法 - walterlv ,關於如何使用 XPath 在 .NET 中讀寫 XML 檔案,可以閱讀 .NET 使用 XPath 來讀寫 XML 檔案 - walterlv 。
使用此 OPML 模型
當你把這些類都準備好,那麼你就可以使用簡單的幾句話來完成 OPML 文件的解析了。
在 UWP 應用中,可以通過 StorageFile
來開啟一個檔案流:
var folder = Package.Current.InstalledLocation; using (var stream = await folder.OpenStreamForReadAsync("sample-opml.xml")) { var opml = await Opml.ParseAsync(stream); // 使用此 OPML 文件 }
在 .NET Framework 傳統應用中,可以使用 File.Read
來開啟一個檔案流。
由於我們本文中建立的模型均實現了 INotifyPropertyChanged
介面,所以你甚至可以直接將 Opml.ParseAsync
的返回結果應用於繫結。
本文會經常更新,請閱讀原文: https://walterlv.com/post/deserialize-opml-using-dotnet.html ,以避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗。
本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名 呂毅 (包含連結:https://walterlv.com ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。如有任何疑問,請 與我聯絡 ([email protected]) 。