ABAP Netweaver, Hybris Commerce和SAP 云平臺的登錄認證
674
2025-03-31
概述
前面 New UWP Community Toolkit 文章中,我們對 V6.0 版本的重要更新做了簡單回顧,其中簡單介紹了?MarkdownTextBlock 和?MarkdownDocument,本篇我們結合代碼詳細講解一下 Markdown 相關功能。
Markdown?是一種非常常用的標記語言,對于編寫文檔或者文章排版等有很大幫助:Markdown 維基百科。關于 Markdown 語法,大家可以去網絡查詢,很容易上手,一次書寫,到各個平臺都能有一樣的操作體驗,非常的簡便實用。而 UWP Community Toolkit 對 Markdown 的解析和渲染提供了完整的支持,即使復雜的 Markdown 文本,也可以在低配置的硬件上獲得流暢的體驗。UWP Community Toolkit 完成 Markdown 整個功能的兩個重要組成部分就是:MarkdownTextBlock 和 MarkdownDocument。
MarkdownDocument 提供了對 markdown 的解析操作,傳遞給?MarkdownTextBlock,負責 markdown 解析后內容的渲染操作,然后顯示在界面。
MarkdownTextBlock
Source:?https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock
Doc:?https://docs.microsoft.com/zh-cn/windows/uwpcommunitytoolkit/controls/markdowntextblock
Namespace:?Microsoft.Toolkit.Uwp.UI.Controls;??Nuget:?Microsoft.Toolkit.Uwp.UI.Controls
MarkdownDocument
Source:?https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Parsers/Markdown
Doc:?https://docs.microsoft.com/zh-cn/windows/uwpcommunitytoolkit/parsers/markdownparser
Namespace:?Microsoft.Toolkit.Parsers.Markdown;??Nuget:?Microsoft.Toolkit.Parsers
代碼分析
MarkdownTextBlock
MarkdownTextBlock 項目起源自一個開源項目 - Universal Markdown:?https://github.com/QuinnDamerell/UniversalMarkdown
Universal Markdown 是由?Quinn Damerell 和 Paul Bartrum 創建的開發項目,用于一個 reddit UWP 應用 Baconit。旨在創建一種通用的 markdown 渲染控件,可以方便高效的使用。這個項目支持完整的 markdown 標記,性能表現也非常理想。
我們來看一下?MarkdownTextBlock 的項目結構:
Render 文件夾 - Markdown 實際渲染代碼
***EventArgs.cs - Markdown 事件參數,比如超鏈接點擊時的鏈接地址參數
MarkdownTextBlock.Dimensions.cs -?MarkdownTextBlock 部分類中負責設置各維度依賴屬性的類,包括字體、字號、背景色等的設置都由它負責
MarkdownTextBlock.Events.cs -?MarkdownTextBlock 部分類中負責事件處理的類,包括鏈接點擊、圖片顯示等時間的觸發都由它負責
MarkdownTextBlock.Methods.cs -?MarkdownTextBlock 部分類中負責具體方法執行的類,包括鏈接點擊、圖片顯示等方法的處理執行都由它負責
MarkdownTextBlock.Properties.cs -?MarkdownTextBlock 部分類中負責設置和獲取各種屬性的類
MarkdownTextBlock.cs -?MarkdownTextBlock 部分類,負責類初始化、主題變化響應等
MarkdownTextBlock.xaml -?MarkdownTextBlock 類的 XAML 代碼,負責 UI 編寫和各種依賴屬性初始化
其中 Render 文件夾的項目結構:
ICodeBlockResolver.cs - 代碼塊渲染接口
IImageResolver.cs - 圖片渲染接口
ILinkRegister.cs - 鏈接注冊接口
InlineRenderContext - TextBlock 中的 Inline 集合渲染上下文
MarkdownRenderer.Blocks.cs -?MarkdownRenderer 部分類中負責塊渲染的類,包括代碼、塊、段落、引用等的渲染由它負責
MarkdownRenderer.Dimensions.cs -?MarkdownRenderer 部分類中負責獲取和設置各個維度量值的類
MarkdownRenderer.Inlines.cs -?MarkdownRenderer 部分類中負責所有 Inline 渲染的類,包括常規、斜體、加粗、鏈接和圖片等
MarkdownRenderer.Properties.cs -?MarkdownRenderer 部分類中負責獲取和設置所有屬性的類
MarkdownRenderer.cs -?MarkdownRenderer 部分類負責初始化和渲染的類
MarkdownTable.cs - markdown 中表格控件渲染類
RenderContext.cs - markdown 渲染上下文
RenderContextIncorrectException.cs - 渲染上下文不正確的異常定義類
UIElementCollectionRenderContext - UI 元素結合渲染上下文
接下來我們分幾個重要部分來詳細分析一下源代碼,因為篇幅考慮,我們只摘錄關鍵的代碼片段:
1.?MarkdownTextBlock.Events.cs
可以看到,類為 MarkdownTextBlock 注冊了 MarkdownRendered、LinkClicked、ImageClicked、ImageResolving、CodeBlockResolving 這幾個事件,在渲染、點擊和需要顯示內容時使用;并相應兩種操作:Hyperlink_Click、NewImagelink_Tapped,分別是超鏈接點擊和圖片鏈接點按的操作處理,這也是 MarkdownTextBlock 僅有的兩種用戶主動觸發的事件。
private?void?Hyperlink_Click(Hyperlink?sender,?HyperlinkClickEventArgs?args) { ????LinkHandled((string)sender.GetValue(HyperlinkUrlProperty),?true); }private?void?NewImagelink_Tapped(object?sender,?Windows.UI.Xaml.Input.TappedRoutedEventArgs?e) { ????LinkHandled((string)(sender?as?Image).GetValue(HyperlinkUrlProperty),?false); }public?event?EventHandler
2.?MarkdownTextBlock.Methods.cs
我們截取了幾個重要的方法:
RenderMarkdown() - 使用 MarkdownDocument 類解析文本,然后使用上面所述 Render 文件夾中的 MarkdownRender 來渲染,添加到父容器中;
RegisterNewHyperLink(s,e) -? 注冊一個新的超鏈接,在點擊操作時觸發這個事件;超鏈接和圖片鏈接都會被注冊;
ICodeBlockResolver.ParseSyntax(a,b,c) - 解析代碼塊的語法,如果沒有復制,則根據系統主題和富文本控件的默認樣式初始化一個值
MarkdownDocument?markdown?=? ?????renderer?=?Activator.CreateInstance(renderertype,?markdown,?,?,?)?= ????MarkdownRendered?.Invoke(??RegisterNewHyperLink(Hyperlink?newHyperlink,? ????newHyperlink.Click?+=?ICodeBlockResolver.ParseSyntax(InlineCollection?inlineCollection,??text,??(language?!=??(CodeStyling?!=?=??theme?=?themeListener.CurrentTheme?==?ApplicationTheme.Dark???(RequestedTheme?!===
3.?MarkdownRenderer.Blocks.cs
我們省略了大部分方法的實現過程,主要讓大家看到都有哦哪些類型的渲染,而他們和 RenderParagraph 都比較相似;大致的實現過程就是讀取解析后的 element,讀取對應的 margin width thickness 等信息來初始化控件,然后把控件以配置的某個位置和尺寸添加到 TextBlock 中,渲染到 UI 中。
protected?override?void?RenderBlocks(IEnumerable
4.?MarkdownRenderer.Inlines.cs
我們同樣省略了大部分方法的實現過程,主要看都有哪些渲染的類型,包括表情、粗體、斜體、超鏈接、圖片、上標和代碼等;參照 Emoji 的實現過程,讀取 inline 中的 Emoji,設置文字信息和 Emoji 內容,然后添加到 inline 集合中。
protected?override?void?RenderEmoji(EmojiInline?element,?IRenderContext?context) {????var?localContext?=?context?as?InlineRenderContext; ????...????var?inlineCollection?=?localContext.InlineCollection;????var?emoji?=?new?Run ????{ ????????FontFamily?=?EmojiFontFamily????DefaultEmojiFont, ????????Text?=?element.Text ????}; ????inlineCollection.Add(emoji); }protected?override?void?RenderTextRun(TextRunInline?element,?IRenderContext?context)?{...}protected?override?void?RenderBoldRun(BoldTextInline?element,?IRenderContext?context)?{...}protected?override?void?RenderMarkdownLink(MarkdownLinkInline?element,?IRenderContext?context)?{...}protected?override?void?RenderHyperlink(HyperlinkInline?element,?IRenderContext?context)?{...}protected?override?async?void?RenderImage(ImageInline?element,?IRenderContext?context)?{...}protected?override?void?RenderItalicRun(ItalicTextInline?element,?IRenderContext?context)?{...}protected?override?void?RenderStrikethroughRun(StrikethroughTextInline?element,?IRenderContext?context)?{...}protected?override?void?RenderSuperscriptRun(SuperscriptTextInline?element,?IRenderContext?context)?{...}protected?override?void?RenderCodeRun(CodeInline?element,?IRenderContext?context)?{...}
5.?MarkdownRenderer.cs
我們來看,渲染器初始化時,傳入的是鏈接注冊、圖片顯示、代碼塊顯示和表情字體(默認為 Segoe UI Emoji);后面提供了創建文本、創建富文本的方法,以及修改某個范圍內的 runs,檢測是否上標、去掉上標等方法;
public?MarkdownRenderer(MarkdownDocument?document,?ILinkRegister?linkRegister,?IImageResolver?imageResolver,?ICodeBlockResolver?codeBlockResolver) :?base(document) { ????LinkRegister?=?linkRegister; ????ImageResolver?=?imageResolver; ????CodeBlockResolver?=?codeBlockResolver; ????DefaultEmojiFont?=?new?FontFamily("Segoe?UI?Emoji"); }protected?RichTextBlock?CreateOrReuseRichTextBlock(IRenderContext?context)?{...}protected?TextBlock?CreateTextBlock(RenderContext?context)?{...}protected?void?AlterChildRuns(Span?parentSpan,?Action?action)?{...}private?bool?AllTextIsSuperscript(IInlineContainer?container,?int?superscriptLevel?=?0)?{...}private?void?RemoveSuperscriptRuns(IInlineContainer?container,?bool?insertCaret)?{...}
調用示例:
看完源代碼的主要構成后,我們再簡單看一下 MarkdownTextBlock 的使用過程:
我們在其中添加了正常顯示文本、粗體和斜體,還添加了超鏈接文本,而在 LinkClicked 事件中處理超鏈接的跳轉。在復雜的源代碼之上,使用過程變得非常簡單,我們只需要準備好 markdown 文本,以及需要處理的點擊、點按等事件就可以了。
MarkdownDocument
MarkdownDocument 是 Markdown Parser 的主要組成部分,負責 markdown 文本的解析工作,把文本解析為?MarkdownDocument,而 Markdown Parser 還提供了?MarkdownRendererBase,作為渲染功能的基類,它也是 MarkdownTextBlock 的?MarkdownRenderer.cs 類的基類。
來看一下 Markdown Parser 的項目主要構成:
Blocks - 每個分類塊的解析類
Enums - 各個類型的枚舉類
Helpers - 一些通用的幫助類
Inlines - TextBlock 中 inline 解析類
Render - Markdown Parser 負責渲染的基類
MarkdownBlock.cs - Markdown 塊定義類, MarkdownDocument 的基類
MarkdownDocument.cs -?Markdown Parser 和 Render 的主要類
MarkdownElement.cs - 所有 Markdown 元素的基類
MarkdownInline.cs - markdown inline 元素的基類
接下來我們分幾個重要部分來詳細分析一下源代碼,因為篇幅考慮,我們只摘錄關鍵的代碼片段:
1. MarkdownDocument.cs
MarkdownDocument 負責 markdown parser 的主要功能,看到兩個變量:_references 存放鏈接和對應文本的列表,Blocks 存放文本,包含樣式;public 的 Parse 方法復雜解析和整理文本/鏈接文本;internal 的 Parse 方法負責實際的解析工作,按照 MarkdownBlock 的類型分別解析每種 Block,拆分每個特殊符號,根據 Block 的換行/縮進等屬性進行單獨的解析工作;LookUpReference 方法負責查找引用的 ID;
private?Dictionary 2. Render /?MarkdownRendererBase.cs 前面我們說到, MarkdownTextBlock 的 Render 功能繼承自 MarkdownRendererBase 類。這個類定義了每種不同類型的 Block 和 Inline 的渲染;我們看到兩個主要方法:RenderBlock 和 RenderInline,根據不同的類型,分別進行渲染。 我們在實現 Renderer 功能的時候,可以繼承?MarkdownRendererBase 類,像?MarkdownTextBlock 那樣,也可以根據自己的需求,做一些類型的定制化。 public?virtual?void?Render(IRenderContext?context) { ????RenderBlocks(Document.Blocks,?context); }protected?virtual?void?RenderBlocks(IEnumerable 3. Blocks /?CodeBlock.cs 上面的 MarkdownDocument 類中涉及到每種類型的 Parse 功能,而實際的 Parse 工作由每個 Block 和 Inline 完成,我們在 Block 中用 CodeBlock 做例子,可以看到 Parse 方法會把對應的 markdown 文本解析為 Renderer 可以識別的元素; internal?static?CodeBlock?Parse(string?markdown,?int?start,?int?maxEnd,?int?quoteDepth,?out?int?actualEnd) { ????StringBuilder?code?=?null; ????actualEnd?=?start;????bool?insideCodeBlock?=?false;????string?codeLanguage?=?string.Empty;????/* ????????Two?options?here: ????????Either?every?line?starts?with?a?tab?character?or?at?least?4?spaces ????????Or?the?code?block?starts?and?ends?with?```????*/ ????foreach?(var?lineInfo?in?Common.ParseLines(markdown,?start,?maxEnd,?quoteDepth)) ????{ ????????... ????} ????... } 調用示例: 一段簡單 markdown 字符串(This is?Markdown)的解析代碼和結果: This is 和 Markdown 被解析為兩個 Inline,Type = 'TextRun',其中 Markdown 的 顯示 Type = 'Bold',這個預期的一致,Markdown 顯示為加粗。 string?md?=?"This?is?**Markdown**"; MarkdownDocument?Document?=?new?MarkdownDocument(); Document.Parse(md);//?Takes?note?of?all?of?the?Top?Level?Headers.foreach?(var?element?in?document.Blocks) {????if?(element?is?HeaderBlock?header) ????{ ????????Console.WriteLine($"Header:?{header.ToString()}"); ????} } 總結 如果大家有興趣,或想開發 Markdown 相關的功能,可以對源代碼和調用做更深入的研究,歡迎大家多多交流,謝謝! 渲染 Markdown
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。