基于 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 事件生成回调。
我知道这个问题的开头是有争议的,所以我希望我已经添加了足够的说明,并且希望人们阅读这些内容。
我有一个相对复杂的 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 事件生成回调。