C# - 如何使用SgmlReader执行XSL转换?

6

我需要使用XSLT转换一个HTML网页的内容。因此,我使用了SgmlReader并编写了下面显示的代码片段(最终我认为它也是一个XmlReader...)

XmlReader xslr = XmlReader.Create(new StringReader(
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
    "<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" version=\"1.0\">" +
    "<xsl:output method=\"xml\" encoding=\"UTF-8\" version=\"1.0\" />" +
    "<xsl:template match=\"/\">" +
    "<XXX xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><xsl:value-of select=\"count(//br)\" /></XXX>" +
    "</xsl:template>" +
    "</xsl:stylesheet>"));

XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(xslr);

using (SgmlReader html = new SgmlReader())
{
    StringBuilder sb = new StringBuilder();
    using (TextWriter sw = new StringWriter(sb))
    using (XmlWriter xw = new XmlTextWriter(sw))
    {
        html.InputStream = new StringReader(Resources.html_orig);
        html.DocType = "HTML";

        try
        {
            xslt.Transform(html, xw);
            string output = sb.ToString();
            System.Console.WriteLine(output);
        }
        catch (Exception exc)
        {
            System.Console.WriteLine("{0} : {1}", exc.GetType().Name, exc.Message);
            System.Console.WriteLine(exc.StackTrace);
        }
    }
}

然而,我收到了这个错误信息。
NullReferenceException : Object reference not set to an instance of an object.
   at MS.Internal.Xml.Cache.XPathDocumentBuilder.Initialize(XPathDocument doc, IXmlLineInfo lineInfo, String baseUri, LoadFlags flags)
   at MS.Internal.Xml.Cache.XPathDocumentBuilder..ctor(XPathDocument doc, IXmlLineInfo lineInfo, String baseUri, LoadFlags flags)
   at System.Xml.XPath.XPathDocument.LoadFromReader(XmlReader reader, XmlSpace space)
   at System.Xml.XPath.XPathDocument..ctor(XmlReader reader, XmlSpace space)
   at System.Xml.Xsl.Runtime.XmlQueryContext.ConstructDocument(Object dataSource, String uriRelative, Uri uriResolved)
   at System.Xml.Xsl.Runtime.XmlQueryContext..ctor(XmlQueryRuntime runtime, Object defaultDataSource, XmlResolver dataSources, XsltArgumentList argList, WhitespaceRuleLookup wsRules)
   at System.Xml.Xsl.Runtime.XmlQueryRuntime..ctor(XmlQueryStaticData data, Object defaultDataSource, XmlResolver dataSources, XsltArgumentList argList, XmlSequenceWriter seqWrt)
   at System.Xml.Xsl.XmlILCommand.Execute(Object defaultDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlSequenceWriter results)
   at System.Xml.Xsl.XmlILCommand.Execute(Object defaultDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlWriter writer, Boolean closeWriter)
   at System.Xml.Xsl.XmlILCommand.Execute(XmlReader contextDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlWriter results)
   at System.Xml.Xsl.XslCompiledTransform.Transform(XmlReader input, XmlWriter results)

我发现一种解决方法是将 HTML 转换为 XML,然后应用变换,但这是一种低效的解决方案,因为:
  1. 中间的 XHTML 输出会进入缓冲区,因此需要额外的内存。
  2. 转换过程需要额外的 CPU 处理,并且相同的层次结构被遍历两次(在理论上是不必要的)。
所以(因为我知道 StackOverflow 社区总是提供很好的答案,而其他 C# 论坛完全让我失望 ;o)),我将寻求反馈和建议,以便直接使用 HTML 执行 XSL 变换(即使需要用另一个类似的库替换 SgmlReader)。

1
关于下划线问题:XSLT 1.0 可以处理 XML 输入树(XSLT 2.0 可以使用未解析资源)。如果你有一些不是 XML 树的东西,那么你需要使用某种方法将其映射到 XML 树。 - user357812
1
Olemis,只是提醒一下,XslCompiledTransform是一个XSLT 1.0处理器,因此如果您在样式表中使用version="2.0",它将以向前兼容模式运行,并且您将无法报告所有XSLT 1.0语法错误。因此,我建议您在样式表中开始设置version="1.0",然后XslCompiledTransform将在Load调用时通知您,您的样式表在语法上不正确,因为xsl:output不能在xsl:template内部使用。至于这是否有助于解决您在馈送SgmlReader方面的问题,我不确定,您需要提供一个使用异常的示例HTML。 - Martin Honnen
2个回答

3
即使 SgmlReader 类继承了 XmlReader 类,它也不意味着它的行为就像一个 XmlReader
从技术上讲,SgmlReaderXmlReader 的子类并没有意义,因为 SGML 是 XML 的超集而不是子集。
虽然你没有写出转换的目的,但通常来说,HTML Agility Pack 是操纵 HTML 的好选择。

尊重地说,从面向对象的角度来看,这真的很有道理,因为XmlReader是一种类型,实现此接口的任何内容都可以被操作(在本例中读取),就好像它是一个XML文档一样。事实上,实现XmlReader用于其他结构化格式(如YAML、INI文件等)也是有意义的,即使它们根本不是标记,它们也是你可能想要以结构化方式读取和转换的结构化文档。这只是我的观点。 - Olemis Lang
@Olemis Lang:在我看来,这是没有意义的,因为XmlReader期望一个格式良好的文档,即具有树形结构的文档。SGML并不提供这样的功能,因此诸如ReadSubtreeReadInnerXml之类的方法是没有意义的。因此,在对SgmlReader运行XSLT的情况下,您可能会遇到底层引擎调用其中一个这些方法但未得到预期结果的情况。请参见Alejandro关于XSLT所期望的内容的评论。 - Dirk Vollmar
& @Alejandro:我认为这就是SgmlReader的作用(即在特定情况下将格式不正确的HTML视为其XHTML等效项)。实际上,如果您查看跟踪记录,似乎读取器在内部用于构建System.Xml.XPath.XPathDocument的实例,该实例在编译的XSL变换在幕后使用。无论如何,我稍后会尝试这些方法,以确认ReadSubtree等发生了什么。谢谢 - Olemis Lang

1

您是否尝试过使用 HTML Agility Pack 而不是 SgmlReader?您可以将 HTML 加载到其中,并直接对其运行转换。我不确定是否在内部创建了 XML 文档,但似乎没有,您可能想要将内存和 CPU 使用情况与您尝试并且舍弃的转换方法进行比较。

//You already have your xslt loaded into var xslt...

HtmlDocument doc = new HtmlDocument();
doc.Load( ... );  //load your HTML doc, or use LoadXML from a string, etc  
xslt.Transform(doc, xw);

请参考这个问题:如何使用HTML Agility Pack


感谢Philip的回复,但构建HTML DOM可能会耗费时间并使用额外的内存。我真的很想避免将对象加载到内存中和进行额外的处理,因为应用程序应该在具有有限功能的设备上运行。这就是为什么我正在寻找一种直接将HTML XMLReader提供给XSLT的方法(但考虑到tracebacks,似乎它在内部构建了一个System.Xml.XPath.XPathDocument,所以我可以想象的任何优化都只是浪费时间...) - Olemis Lang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接