1

XmlDocument 解决 Clone、CloneNode、ImportNode 等节点克隆后的标签自闭合问题 - 路...

 7 months ago
source link: https://www.cnblogs.com/cyq1162/p/18003665
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

这两天在对 Taurus.Mvc 做 html 加载性能优化时,发现存在这个问题。

具体优化的是 CYQ.Data 组件的 XHtmlAction 相关类。

问题过程:

之前 XmlDocument 调用 LoadXml(xml)之后,缓存对象,再次使用时,都是重新LoadXml:

            XmlDocument newDoc = new XmlDocument();
            try
            {
                newDoc.LoadXml(缓存xDoc.InnerXml);
            }
            catch (Exception err)
            {
                Log.Write(err, LogType.Error);
                newDoc.InnerXml = xDoc.InnerXml;
            }
            return newDoc;

上面的代码,多年来一直工作很好,直到最近,在做优化,发现还原节点,可以通过 Clone 方式,而且性能提升不少:

 //克隆速度快。
return 缓存xDoc.Clone() as XmlDocument;

后面发现用 CloneNode 方式,还能更优一点:

 //克隆速度快。
return 缓存xDoc.CloneNode(true) as XmlDocument;

以为问题解决时,却出现了样式怪异的问题,一开始以为是克隆还保持引用的问题,花了不少时间,找错了方向。

经反复排查,对比生成的内容,才发现以上的方法,都会造成 div 空节点标签被自闭合了:

成了:<div xxx=xxx />

经测试,确认是该问题(仅.NET版本出问题,.NET Core 在以上方法,不会产生自闭合问题)。

解决问题:

要解决这个问题,网上找找解决方案:

意外发现还有 ImportNode 方式可以用,尝试了一下,似乎更优一丁点,蚊子腿也是肉:

HtmlDocument document = new HtmlDocument();
XmlNode node = document.ImportNode(缓存xDoc.DocumentElement, true);
document.AppendChild(node);
return document;

但,网上只有个别提问题,没有解决方案。

但发现了标签是否闭合和以下属性有关:

设置 XmlElement 的 IsEmpty = false;//不自闭合标签

而 Clone 或 ImportNode,没有提供参数设置该参数。

没思路的时候,就多看看.Net 源码。

17408-20240202191440992-1749717506.png

于是,想到通过继承:XmlDocument,改写 CreateElement 方法,来实现:

class XHtmlDocument : XmlDocument
{
    public override XmlElement CreateElement(string prefix, string localName, string namespaceURI)
    {
        XmlElement xe = base.CreateElement(prefix, localName, namespaceURI);
        switch (localName)
        {
            case "meta":
            case "br":
            case "hr":
            case "img":
            case "link":
            case "base":
            case "area":
            case "input":
            case "source":
            case "!DOCTYPE":
                break;
            default:
                xe.IsEmpty = false;//不自闭合标签
                break;
        }
        
        return xe;
    }
}

Xml 需要标签不自闭合的场景,可能很少,但若需要,这就是个解决方案之一。

再补充 CYQ.Data 关于加载 html 的性能优化说明:

/*
 * 性能优化说明:
 * 1、DTD:如果界面没有实体&xxx;则不加载,否则修改DTD路径到本地。
 * 2、XmlDocument:LoadXml 性能优化:加载后缓存,后续从缓存转化:这里要转化也没那么简单,需要解决以下问题:
 * ---------------------------------------------------------------------------------------
 * A、从缓存拿到,从缓存Doc拿出InnerXml,再重新LoadXml(xml),兼容性最好,但可优化。
 * B、从缓存拿到,从缓存Doc调用:Clone、CloneNode(true)、两个性能差不多,节点多时,后面那个更优。
 * C、从缓存拿到,从缓存Doc发现:XmlDocument ImportNode 方法比CloneNode(true) 更优,准备采用这个。
 * D、上面B、C两种方式,产生新的问题:.NET 下标签自闭合、.NET Core下正常,改动见:GetCloneFrom 方法。
 * E、解决标签自闭合问题:继承XmlDocument,改写CreateElement,根据W3C标准找出需要自闭合的,其余条件设置XmlElement的IsEmpty=false,见:XhtmlDocument
 * F、解决上述问题后,为了性能,从缓存中不加载DTD、引发样式问题:
 * G、解决F的问题是,输出的时候检测没有DTD头,则追加:<!DOCTYPE html>头,见:XHtmlAction OutXml输出。
 * ----------------------------------------------------------------------------------------
 * 4、避免使用 InnerXml 属性,用其它方式替代:InnerText、节点引用等,但引发另一个问题,赋值text,则后续无法通过节点操作,这在循环绑定时会拿不到节点。
 */

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK