New UWP Community Toolkit 6.0 - Markdown

      網友投稿 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 - 代碼塊渲染接口

      New UWP Community Toolkit 6.0 - Markdown

      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?MarkdownRendered;public?event?EventHandler?LinkClicked;public?event?EventHandler?ImageClicked;public?event?EventHandler?ImageResolving;public?event?EventHandler?CodeBlockResolving;

      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?blockElements,?IRenderContext?context)?{...}protected?override?void?RenderParagraph(ParagraphBlock?element,?IRenderContext?context) {????var?paragraph?=?new?Paragraph ????{ ????????Margin?=?ParagraphMargin ????};????var?childContext?=?new?InlineRenderContext(paragraph.Inlines,?context) ????{ ????????Parent?=?paragraph ????}; ????RenderInlineChildren(element.Inlines,?childContext);????var?textBlock?=?CreateOrReuseRichTextBlock(context); ????textBlock.Blocks.Add(paragraph); }protected?override?void?RenderHeader(HeaderBlock?element,?IRenderContext?context)?{...}protected?override?void?RenderListElement(ListBlock?element,?IRenderContext?context)?{...}protected?override?void?RenderHorizontalRule(IRenderContext?context)?{...}protected?override?void?RenderQuote(QuoteBlock?element,?IRenderContext?context)?{...}protected?override?void?RenderCode(CodeBlock?element,?IRenderContext?context)?{...}protected?override?void?RenderTable(TableBlock?element,?IRenderContext?context)?{...}

      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?_references;public?IList?Blocks?{?get;?set;?}public?void?Parse(string?markdownText) {????int?actualEnd; ????Blocks?=?Parse(markdownText,?0,?markdownText.Length,?quoteDepth:?0,?actualEnd:?out?actualEnd);????//?Remove?any?references?from?the?list?of?blocks,?and?add?them?to?a?dictionary. ????for?(int?i?=?Blocks.Count?-?1;?i?>=?0;?i--) ????{????????if?(Blocks[i].Type?==?MarkdownBlockType.LinkReference) ????????{????????????var?reference?=?(LinkReferenceBlock)Blocks[i];????????????if?(_references?==?null) ????????????{ ????????????????_references?=?new?Dictionary(StringComparer.OrdinalIgnoreCase); ????????????}????????????if?(!_references.ContainsKey(reference.Id)) ????????????{ ????????????????_references.Add(reference.Id,?reference); ????????????} ????????????Blocks.RemoveAt(i); ????????} ????} }internal?static?List?Parse(string?markdown,?int?start,?int?end,?int?quoteDepth,?out?int?actualEnd)? {????//?We?need?to?parse?out?the?list?of?blocks.????//?Some?blocks?need?to?start?on?a?new?paragraph?(code,?lists?and?tables)?while?other????//?blocks?can?start?on?any?line?(headers,?horizontal?rules?and?quotes).????//?Text?that?is?outside?of?any?other?block?becomes?a?paragraph. ????var?blocks?=?new?List();????int?startOfLine?=?start;????bool?lineStartsNewParagraph?=?true;????var?paragraphText?=?new?StringBuilder();????//?These?are?needed?to?parse?underline-style?header?blocks. ????int?previousStartOfLine?=?start;????int?previousEndOfLine?=?start;????//?Go?line?by?line. ????while?(startOfLine?

      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?blockElements,?IRenderContext?context) {????foreach?(MarkdownBlock?element?in?blockElements) ????{ ????????RenderBlock(element,?context); ????} }protected?void?RenderBlock(MarkdownBlock?element,?IRenderContext?context) { ????{????????switch?(element.Type) ????????{????????????case?MarkdownBlockType.Paragraph: ????????????????RenderParagraph((ParagraphBlock)element,?context);????????????????break;????????????//?case?other?Block?types????????????... ????????} ????} }protected?void?RenderInline(MarkdownInline?element,?IRenderContext?context) {????switch?(element.Type) ????{????????case?MarkdownInlineType.TextRun: ????????????RenderTextRun((TextRunInline)element,?context);????????????break;????????//?case?other?Inline?types????????... ????} }

      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小時內刪除侵權內容。

      上一篇:基于javaweb(springboot+mybatis)網站建設服務管理系統設計和實現以及文檔報告設計
      下一篇:如何將wps文本從傳統轉換為簡化
      相關文章
      亚洲黄色免费观看| 亚洲欧美国产欧美色欲| 亚洲一区二区三区高清视频| 18亚洲男同志videos网站| 亚洲线精品一区二区三区影音先锋 | 国产亚洲日韩一区二区三区| 亚洲偷自拍拍综合网| 亚洲高清最新av网站| 国产成人亚洲精品无码AV大片| 337P日本欧洲亚洲大胆艺术图| 久久水蜜桃亚洲AV无码精品| 亚洲精品9999久久久久无码| 亚洲高清国产拍精品熟女| 国产精品亚洲а∨天堂2021| 在线精品自拍亚洲第一区| 亚洲AV网站在线观看| 久久久久一级精品亚洲国产成人综合AV区 | 亚洲视频中文字幕| 亚洲精品中文字幕无乱码| 亚洲人成777在线播放| 亚洲男人天堂2022| 亚洲国产成人久久精品软件 | 亚洲a在线视频视频| 亚洲激情中文字幕| 亚洲明星合成图综合区在线| 国产成人精品日本亚洲直接| 亚洲精品无码你懂的| 亚洲国产精品专区在线观看 | 伊人久久精品亚洲午夜| 亚洲va久久久噜噜噜久久狠狠| 亚洲福利在线观看| 亚洲国产精品线观看不卡| 亚洲日韩一区二区三区| 国产亚洲精品2021自在线| 国产成人亚洲影院在线观看| 亚洲αv在线精品糸列| 亚洲理论在线观看| 亚洲色大18成人网站WWW在线播放| 在线观看亚洲专区| 国产AV无码专区亚洲AV男同| 亚洲精品熟女国产|