没啥用的前言
说着再做UWP就剁手,我还是开了一个新坑🤣
这次是B岛的UWP端
论坛客户端的一个老大难问题是内容的呈现。论坛一般以网页端为主,网页做好,论坛活跃起来后之后才会开发客户端/有开发者愿意做第三方的客户端。因此,API绝大多数情况是为网页端为一等公民的。此外,各个UI框架展示内容的格式也各有不同。以上两个原因导致HTML被选做富文本展示的通用语言。
对于客户端来说,HTML的呈现就成了问题。可以嵌入浏览器来渲染HTML,但存在两个难以解决的问题
- 与应用原生部分交互困难,
- 可能有性能问题。
因此,客户端的做法一般是绕过WebView,将HTML直接渲染为原生控件。那么问题来了,怎么做呢?Android的TextView可以渲染部分HTML,但UWP里就没有相应的API了。//@微软,出来挨打
先来看看手上有什么工具。首先是原生的XAML控件。Windows SDK 1903与.Net starndard 2.0兼容。又有一大堆.NET Standard 2.0的类库可以用了。
API返回的结果是
尝试搜索
搜索一下,找到了WinRT-RichTextBlock.Html2Xaml,它能把HTML渲染到RichTextBlock
上,但是很遗憾,它不支持UWP。继续搜索,找到HTML2XAML的一个支持UWP的fork,使用后发现有不支持的标签。查看他的代码,似乎用到了xslt
。面对HTML已经够头疼了,还是别引入另一个标记语言了。出师不利。
继续搜索,找到了一个MarkdownTextBlock的库。我以前做另一个论坛的客户端时用过它。当时是先想办法把HTML转成Markdown,再用它来渲染。但是在处理多级嵌套引用(<quote>
)的时候会出错。况且时隔这么久我已经看不懂当年写的代码了🤣
通过这两次搜索我们得到了以下信息:
RichTextBlock
很可能能够作为我们渲染的容器- HTML标签和使用的原生控件有关
- 结构化的输出处理起来更方便,如果能把HTML转化为DOM树,靠dfs就可以实现转换。
第一步,先要把HTML结构化。
结构化HTML
要把某种语言结构化,Parser是不二选择。而HTML的Parser因为经常面对残缺的HTML,通常支持将残缺的片段补齐。AngleSharp就是一个基于.NET Standard的HTML parser。
第一步搞定。有了结构,接下来就顺手多了。
遍历DOM树
上面把HTML转成Markdown的源函数,里面的一大堆分支看的云里雾里。加上奇奇怪怪的边界情况后更是让人头疼。有没有一种代码的组织方法能让我针对一个标签写一个函数?答案是:Visitor pattern. 百科上写的详细的多,我就只举一个例子。假设有<p>
,<img>
,<a>
标签需要解析。定义INode
作为所有DOM元素的接口,IVisitor
是要对元素访问的接口。INode
的实现者通过visitor.Visit(this)
把控制流返还给Visitor
。只需在visitor
上实现对各个类的Visit
方法,就达成目的。
RichTextBlock
接下来考虑如何呈现。根据文档,RichTextBlock
可以包含多个Block
,一个Block
又可以包含若干Inline
。与HTML标签刚好对应!InlineUIContainer
自己是Inline
,但Child
属性可以塞下任何UIElement
。如果塞进去另一个RichTextBlock
就实现了对引用<quote>
呈现。这样可以实现任意级引用<quote>
的呈现。具体实现上,提供一个Stack<Block>
供使用。转化为Inline
的元素每次添加到栈顶的Block
中。遇到转化为Block
的元素则压栈。最后把Block
按照先后顺序加入RichTextBlock
即可。
下面是效果,具体代码请参考HTML Parser和RichTextBlockRenderer
搞定了一个困扰多年的难题,可喜可贺(^o^)ノ