基于 1:1 映射从 XSD 生成 XSLT 文件 XSD 兼容 XML in -> XSD 兼容 XML out

Generating an XSLT file from XSD based upon a 1:1 mapping XSD compliant XML in -> XSD compliant XML out

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

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

所以我的第 1 步是: 生成一个 XSLT 文件,其中 'matches' 一个 XSD 文件,这样一个符合模式的 XML 文件可以原封不动地通过。但是,如果架构不兼容 XML 文件,则所有那些不兼容 attributes/elements 的文件都会被删除。我什至不关心值的验证。

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

我知道 XSLT 本身不是模式感知的(至少在 XSLT 1 中),但我希望一些自动生成的 XSLT 模板可以通过 XSD 枚举以添加足够的 'anchoring' 它模拟模式。
还是我被这个想法误导了?

谢谢

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

而且显然有些映射您无法自动化:虽然删除不再允许的元素很容易,但添加已成为必需的元素、重命名名称已更改的元素或更新类型已更改的元素,会比较难。

如果你把你的野心限制在几个简单的案例上,你可能会取得进步。但我会专注于让它对您的特定用例有用,而不是让它通用。

好的,所以解决方案是一些 .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 事件生成回调。