可以将 XSLT 样式表添加到序列化的 XML 文档吗?

Posted

技术标签:

【中文标题】可以将 XSLT 样式表添加到序列化的 XML 文档吗?【英文标题】:Possible to add a XSLT Stylesheet to a serialized XML document? 【发布时间】:2021-06-16 15:52:56 【问题描述】:

我有将复杂对象序列化为 XML 并将其保存为文件的代码,有没有一种快速的方法可以在序列化过程中在 xml 中包含样式表?

使用 C# 和 .net 框架 v2。

【问题讨论】:

【参考方案1】:

您可以使用XmlWriterWriteProcessingInstruction

    XmlSerializer s = new XmlSerializer(typeof(myObj));
    using (XmlWriter w = XmlWriter.Create(@"c:\test.xml"))
    
        w.WriteProcessingInstruction("xml-stylesheet", "type=\"text/xsl\" href=\"USED-FILE.xsl\"");
        s.Serialize(w, myObj);
    

【讨论】:

在我的解决方案中,它全部写在一行上。我可以调整以在多行上写吗? 对于懒惰的人,XmlSerializerSystem.Xml.Serialization 命名空间中。【参考方案2】:

对于那些想知道如何在现代 dotnet 核心中实现类似功能的人,您需要进行一些调整:

using System.Collections.Generic;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;

namespace ContentNegotiation

    public class Program
    
        public static void Main(string[] args) => CreateWebHostBuilder(args).Build().Run();

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    

    public class MyXmlSerializerOutputFormatter : XmlSerializerOutputFormatter
    
        protected override void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value)
        
            // TODO: add me only if controller has some kind of custom attribute with XSLT file name
            xmlWriter.WriteProcessingInstruction("xml-stylesheet", "type=\"text/xsl\" href=\"template.xsl\"");
            base.Serialize(xmlSerializer, xmlWriter, value);
        
    

    public class Startup
    
        public void ConfigureServices(IServiceCollection services)
        
            services.AddMvc(options =>
            
                options.RespectBrowserAcceptHeader = true; // default is false
                // options.OutputFormatters.Add(new XmlSerializerOutputFormatter()); // not enough
                options.OutputFormatters.Add(new MyXmlSerializerOutputFormatter());
            )
            // .AddXmlSerializerFormatters() // does not added by default, but not enough
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        
            app.UseStaticFiles();
            app.UseMvc();
        
    

    public class Post
    
        public int Id  get; set; 
        public string Title  get; set; 
        public string Body  get; set; 
    

    [ApiController]
    public class DemoController : ControllerBase
    
        // curl -k -i -s -H 'Accept: text/xml' http://localhost:5000/posts
        // curl -k -i -s -H 'Accept: application/json' http://localhost:5000/posts
        [HttpGet]
        [Route(nameof(Posts))]
        public IEnumerable<Post> Posts() => new[] 
            new Post 
                Id = 1,
                Title = "Hello World",
                Body = "Lorem ipsum dot color"
            ,
            new Post 
                Id = 2,
                Title = "Post 2",
                Body = "Lorem ipsum dot color"
            
        ;
    

我们正在 ConfigureServices 中打开内容协商,并提供 XmlSerializerOutputFormatter 的实现,它将 XSL 添加到输出中

所以现在我们的后端将使用 JSON 响应如下请求:

curl -k -i -s -H 'Accept: application/json' http://localhost:5000/posts

和 XML:

curl -k -i -s -H 'Accept: text/xml' http://localhost:5000/posts

可在此处找到用于演示的 xsl 示例:https://mac-blog.org.ua/dotnet-content-negotiation/

【讨论】:

【参考方案3】:

我写这个是为了减少问题,只在类上添加一个属性,就像我们描述每个其他 xml 构造指令一样:

用法是:

    [XmlStylesheet("USED-FILE.xsl")]
    public class Xxx
    
        // etc
    


    Xxx x = new Xxx();

    XmlSerializer s = new XmlSerializer(typeof(Xxx));
    using (var  tw = File.CreateText(@"c:\Temp\test.xml"))
    using (var xw = XmlWriter.Create(tw))
    
        s.SerializeWithStyle(xw, x);    // only line here that needs to change. 
                                        // rest is standard biolerplate.
    

为此所需的库代码:(保持在同一个命名空间中,因此当 IntelliSense 为属性添加命名空间时,它也会拉入扩展方法)

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml.Serialization;
using System.Reflection;
using System.Xml;

namespace NovelTheory.Xml.Serialization

    public class XmlStylesheetAttribute : Attribute
    
        public string Href  get; set; 
        public XmlStylesheetAttribute(string href)
        
            Href = href;
        
    

    public static class XmlStylesheetAttributeExtenstions
    
        public static void SerializeWithStyle(this XmlSerializer serializer, 
                XmlWriter textWriter, object o)
        
            AddStyleSheet(textWriter, o);
            serializer.Serialize(textWriter, o);
        

        public static void SerializeWithStyle(this XmlSerializer serializer, 
                XmlWriter textWriter, object o, XmlSerializerNamespaces namespaces)
        
            AddStyleSheet(textWriter, o);
            serializer.Serialize(textWriter, o, namespaces);
        
        private static void AddStyleSheet(XmlWriter textWriter, object o)
        
            var dnAttribute = o.GetType()
                                            .GetTypeInfo()
                                            .GetCustomAttribute<XmlStylesheetAttribute>();
            if (dnAttribute != null)
                textWriter.WriteProcessingInstruction("xml-stylesheet", 
                                        $@"type=""text/xsl"" href=""dnAttribute.Href""");
        
    

【讨论】:

【参考方案4】:

createXML.WriteProcessingInstruction("xml-stylesheet", "type='text/xsl' href='gss.xsl'");

            #region // PAGES
            string pages_xmlurl = Server.MapPath(Url.Content("~/xml/pages_" + lng.code + ".xml")).ToString();
            XmlTextWriter pages_XML = new XmlTextWriter(pages_xmlurl, UTF8Encoding.UTF8);
            pages_XML.WriteStartDocument();
            pages_XML.WriteProcessingInstruction("xml-stylesheet", "type='text/xsl' href='gss.xsl'");
            pages_XML.WriteComment("Generator By OS sitemap generator, http://www.oguzhansari.com");
            pages_XML.WriteStartElement("urlset");
            pages_XML.WriteAttributeString("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9");
            pages_XML.WriteAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
            pages_XML.WriteAttributeString("xsi:schemaLocation", "http://www.google.com/schemas/sitemap/0.84");
            pages_XML.WriteEndDocument();
            pages_XML.Close();
            XmlDocument pages_XMLCONTENTS = new XmlDocument();
            pages_XMLCONTENTS.Load(pages_xmlurl);
            var pages = db.pages.Where(w => w.isActive == true & w.isDelete != true).ToList();
            foreach (var pgs in pages)
            
                XmlElement _element = pages_XMLCONTENTS.CreateElement("url", pages_XMLCONTENTS.DocumentElement.NamespaceURI);
                XmlElement loc = pages_XMLCONTENTS.CreateElement("loc", pages_XMLCONTENTS.DocumentElement.NamespaceURI);
                loc.InnerText = www + Tools.CreateLinkSingleLang("[CORPORATEPAGES]", "[CORPORATEPAGE]", pgs.id, pgs.pages_contents.Where(xw => xw.languageID == lng.id).FirstOrDefault().title, lng.id);
                _element.AppendChild(loc);
                XmlElement lastmod = pages_XMLCONTENTS.CreateElement("lastmod", pages_XMLCONTENTS.DocumentElement.NamespaceURI);
                lastmod.InnerText = DateTime.Now.ToString();
                _element.AppendChild(lastmod);
                XmlElement changefreq = pages_XMLCONTENTS.CreateElement("changefreq", pages_XMLCONTENTS.DocumentElement.NamespaceURI);
                changefreq.InnerText = "daily";
                _element.AppendChild(changefreq);
                XmlElement priority = pages_XMLCONTENTS.CreateElement("priority", pages_XMLCONTENTS.DocumentElement.NamespaceURI);
                priority.InnerText = "0.5";
                _element.AppendChild(priority);
                pages_XMLCONTENTS.DocumentElement.AppendChild(_element);
            
            XmlTextWriter pages_write = new XmlTextWriter(pages_xmlurl, null);
            pages_write.Formatting = Formatting.Indented;
            pages_XMLCONTENTS.WriteContentTo(pages_write);
            pages_write.Close();
            #endregion

【讨论】:

这只是重复了现有投票答案的一部分。

以上是关于可以将 XSLT 样式表添加到序列化的 XML 文档吗?的主要内容,如果未能解决你的问题,请参考以下文章

当我将 XSLT 样式表应用到 XML 文件时,啥也没有出现

通过 .NET 将参数传递给 XSLT 样式表

将 XSLT 样式表应用于亚马逊产品广告 API XML 输出

XSLT学习通过JavaScript转化xml

可以使用 XSLT 样式表填充现有 Excel 工作表吗?

使用 XSLT 显示 XML