根据 XSD 兼容 XML 输入 -> XSD 兼容 XML 输出的 1:1 映射从 XSD 生成 XSLT 文件

Posted

技术标签:

【中文标题】根据 XSD 兼容 XML 输入 -> XSD 兼容 XML 输出的 1:1 映射从 XSD 生成 XSLT 文件【英文标题】:Generating an XSLT file from XSD based upon a 1:1 mapping XSD compliant XML in -> XSD compliant XML out 【发布时间】:2020-04-27 16:53:48 【问题描述】:

我知道这个问题的开头是有争议的,所以我希望我已经添加了足够的说明,并且人们阅读了这些。

我有一个相对复杂的 XSD 文件(以及一组 XSD 文件,用于每个版本的架构)。 我最终要寻找的是一组 XSLT 文件,它们可以采用 XSD v12 兼容的 XML 文件,并将其转换(丢弃很多东西),直到它成为 XSD v3 兼容的 XML 文件。一些转换可能会更智能一些,例如采用 gradientStartColor 并将其分配给 backgroundFillColor 如果 gradientMode="3"... 但我不希望这部分自动完成。

所以我的第 1 步是: 生成一个“匹配”一个 XSD 文件的 XSLT 文件,这样一个符合模式的 XML 文件就可以原封不动地通过。但是,架构不兼容的 XML 文件会删除所有这些不兼容的属性/元素。我什至不会关心值的验证。

我原以为会有一种方法可以自动生成这样的 XSLT 文件。但是我的谷歌搜索没有结果。

我知道 XSLT 本身并不是模式感知的(至少在 XSLT 1 中),但我希望 XSLT 模板的某些自动生成可以通过 XSD 进行枚举,以添加足够的“锚定”来模拟模式. 还是我被这个想法误导了?

谢谢

【问题讨论】:

您是要完成工作还是要进行研究计划?如果是前者,请卷起袖子,亲手编写 XSLT(或聘请 XSLT 专家);如果是后者,并且您希望有一种简单的自动生成此类 XSLT 文件的方法,那么您就大大低估了复杂性。无论哪种方式,您手头上的内容远不止 SO Q/A。祝您好运,如果您在通用解决方案上取得进展,请与我们联系。 【参考方案1】:

这是一件很难尝试的事情,没有人做过这件事我并不感到惊讶。人们已经针对问题的一小部分撰写了博士论文,例如采用两种语法(内容模型)并确定一个是否是另一个的子集。

很明显,有些映射是无法自动化的:虽然删除不再允许的元素很容易,但添加已成为强制性的元素,或重命名已更改名称的元素,或更新已更改类型的元素,正在更难。

如果您将雄心壮志限制在几个简单的案例上,您或许能够取得进展。但我会专注于使其对您的特定用例有用,而不是使其通用。

【讨论】:

我猜我期待可能会有一些东西可以为每个 xsd 复杂类型生成一个 xsl:template,每个属性都有适当的映射,并为每个子元素应用模板。 . 我想将 XML 模式转换成 XmlSerializer 类结构会更容易(使用 CodeDom),将其编译成程序集,反序列化为 XSD 生成的类,然后将其重新序列化回新的 XML 文件.感觉 XSLT 应该让它变得更容易。 如果有人在您之前编写了您需要的程序,那总是很好,但遗憾的是,这并不总是发生。【参考方案2】:

好的,所以解决方案是一些 .NET 代码。

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using Microsoft.CSharp;

namespace Reverter

    class Program
    
        static int Main(string[] args)
        
            //try
            
                if (args.Length < 2)
                
                    Console.Error.WriteLine("Reverter schema.xsd inputfile1 inputfile2...");
                    return 1;
                
                else
                
                    var schema = args[0];
                    List<string> srcFiles = new List<string>(args);
                    srcFiles.RemoveAt(0); // we get rid of the first entry, the schema


                    XmlSchemas xsds = new XmlSchemas();
                    XmlSchema xsd;
                    using (var r = File.OpenText(schema))
                    
                        xsd = XmlSchema.Read(r, null);
                        xsds.Add(xsd);
                    

                    xsds.Compile(null, true);

                    XmlSchemaImporter schemaImporter = new XmlSchemaImporter(xsds);

                    // create the codedom
                    CodeNamespace codeNamespace = new CodeNamespace("Schema");
                    XmlCodeExporter codeExporter = new XmlCodeExporter(codeNamespace);

                    List<XmlTypeMapping> maps = new List<XmlTypeMapping>();

                    foreach (XmlSchemaElement schemaElement in xsd.Elements.Values)
                    
                        maps.Add(schemaImporter.ImportTypeMapping(schemaElement.QualifiedName));
                    

                    foreach (XmlSchemaType schemaElement in xsd.Items.OfType<XmlSchemaType>())
                    
                        maps.Add(schemaImporter.ImportSchemaType(schemaElement.QualifiedName));
                    

                    foreach (XmlTypeMapping map in maps)
                    
                        codeExporter.ExportTypeMapping(map);
                    

                    codeNamespace.Types.OfType<CodeTypeDeclaration>().First(x => x.Name == "ROOTELEMENTNAME").Members.Add(
                        new CodeMemberProperty()
                        
                            Name = "xsiSchemaLocation",
                            Attributes = MemberAttributes.Public | MemberAttributes.Final,
                            CustomAttributes =
                            
                                new CodeAttributeDeclaration("System.Xml.Serialization.XmlAttribute",
                                    new CodeAttributeArgument[]
                                    
                                        new CodeAttributeArgument(new CodePrimitiveExpression("noNamespaceSchemaLocation")),
                                        new CodeAttributeArgument("Namespace", new CodePrimitiveExpression(XmlSchema.InstanceNamespace)),
                                    
                                )
                            ,
                            Type = new CodeTypeReference(typeof(string)),
                            HasGet = true,
                            GetStatements =
                            
                                new CodeMethodReturnStatement(new CodePrimitiveExpression(schema))
                            ,
                            HasSet = true,
                        );

                    // Check for invalid characters in identifiers
                    CodeGenerator.ValidateIdentifiers(codeNamespace);

                    CodeCompileUnit ccu = new CodeCompileUnit();
                    ccu.Namespaces.Add(codeNamespace);

                    CompilerParameters comParams = new CompilerParameters(
                        new string[]  "System.dll", "System.Xml.dll"  );
                    comParams.GenerateInMemory = true;
                    comParams.CompilerOptions = "/optimize";

                    CodeGeneratorOptions codeOptions = new CodeGeneratorOptions();
                    codeOptions.VerbatimOrder = true;

                    TextWriter memText = new StringWriter();

                    // output the C# code
                    CodeDomProvider codeProvider = new CSharpCodeProvider();
                    var codeResult = codeProvider.CompileAssemblyFromDom(comParams, new CodeCompileUnit[]  ccu );

                    XmlSerializer ser = new XmlSerializer(codeResult.CompiledAssembly.GetType("Schema.ROOTELEMENTTYPE", true, true));
                    Object obj;

                    XmlWriterSettings xmlSettings = new XmlWriterSettings();
                    xmlSettings.Indent = true;
                    xmlSettings.Encoding = System.Text.Encoding.UTF8;
                    xmlSettings.OmitXmlDeclaration = false;


                    foreach (string srcFile in srcFiles)
                    
                        var dstFile = "New" + srcFile;
                        // using our XmlSerializer, we will load and then save the XMLfile
                        using (var file = new XmlTextReader(srcFile))
                        using(var outFile = XmlWriter.Create(dstFile, xmlSettings))
                        
                            obj = ser.Deserialize(file);
                            ser.Serialize(outFile, obj);
                        
                    
                
            
            /*catch (Exception ex)
            
                Console.Error.WriteLine("Revert code generation failed.");
                Console.Error.Write(ex.ToString());
                return 2;
            */

            return 0;
        
    

几乎只需将 XSD 文件作为 CodeDom 引擎的输入,生成编译的程序集,从新程序集中获取根类型,然后反序列化并重新序列化对象。如果您想要一些控制台打印被丢弃的内容,那么您可以为 XmlDeserializer 上的 UnknownElement、UnknownAttribute 或 UnknownNode 事件生成回调。

【讨论】:

以上是关于根据 XSD 兼容 XML 输入 -> XSD 兼容 XML 输出的 1:1 映射从 XSD 生成 XSLT 文件的主要内容,如果未能解决你的问题,请参考以下文章

使用Notepad ++ XML Tools创建XSD

如何使用 System.Xml.Schema 从 xs:choice 解析 xs:annotation

带有“必需”属性选项的 XSD 相关查询

使用 XSD 文件在 C# 中生成 XML 文件

在定义 XML 模式 (XSD) 时“选择”“组”元素是不是有效

WinRT 与 xsd.exe 生成的序列化/无权访问 System.Xml.XmlNode