我正在XSLT中使用查找模板转换> 2GB的文件。 我希望它能运行得更快,但找不到任何低落的果实来提高性能。任何帮助将不胜感激。 在转换方面,我是个新手。
这是XML文件的当前格式。
<?xml version="1.0" encoding="utf-8" ?>
<contacts>
<contact>
<attribute>
<name>text12</name>
<value>B00085590</value>
</attribute>
<attribute>
<name>text34</name>
<value>Atomos</value>
</attribute>
<attribute>
<name>date866</name>
<value>02/21/1991</value>
</attribute>
</contact>
<contact>
<attribute>
<name>text12</name>
<value>B00058478</value>
</attribute>
<attribute>
<name>text34</name>
<value>Balderas</value>
</attribute>
<attribute>
<name>date866</name>
<value>11/24/1997</value>
</attribute>
</contact>
</contacts>
我用于转换的xslt。
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<!--Identify location of the lookup xml-->
<xsl:param name="lookupDoc" select="document('C:\Projects\Attributes.xml')" />
<!--Main Template-->
<xsl:template match="/contacts">
<!--Apply Formatted Contacts Template-->
<xsl:apply-templates select="contact" />
</xsl:template>
<!--Formatted Contacts Template-->
<xsl:template match="contact">
<contact>
<xsl:for-each select="attribute">
<!--Create variable to hold New Name after passing the Data Name to the Lookup Template-->
<xsl:variable name="newName">
<xsl:apply-templates select="$lookupDoc/attributes/attribute">
<xsl:with-param name="nameToMatch" select="name" />
</xsl:apply-templates>
</xsl:variable>
<!--Format Contact Element with New Name variable-->
<xsl:element name="{$newName}">
<xsl:value-of select="value"/>
</xsl:element>
</xsl:for-each>
</contact>
</xsl:template>
<!--Lookup Template-->
<xsl:template match="attributes/attribute">
<xsl:param name="nameToMatch" />
<xsl:value-of select='translate(translate(self::node()[name = $nameToMatch]/mappingname, "()*%$#@!~<>'&,.?[]=-+/\:1234567890", "")," ","")' />
</xsl:template>
</xsl:stylesheet>
样本查找XML
<?xml version="1.0" encoding="utf-8" ?>
<attributes>
<attribute>
<name>text12</name>
<mappingname>ID</mappingname>
<datatype>Varchar2</datatype>
<size>30</size>
</attribute>
<attribute>
<name>text34</name>
<mappingname>Last Name</mappingname>
<datatype>Varchar2</datatype>
<size>30</size>
</attribute>
<attribute>
<name>date866</name>
<mappingname>DOB</mappingname>
<datatype>Date</datatype>
<size></size>
</attribute>
</attributes>
转换后的XML
<?xml version="1.0" encoding="utf-8" ?>
<contacts>
<contact>
<ID>B00085590</ID>
<LastName>Brady</LastName>
<DOB>02/21/1991</DOB>
</contact>
<contact>
<ID>B00058478</ID>
<LastName>Balderas</LastName>
<DOB>11/24/1997</DOB>
</contact>
</contacts>
C#
XsltSettings settings = new XsltSettings(true, true);
XslCompiledTransform ContactsXslt = new XslCompiledTransform();
ContactsXslt.Load(@"C:\Projects\ContactFormat.xslt", settings, new XmlUrlResolver());
using (XmlReader r = XmlReader.Create(@"C:\Projects\Contacts.xml")){
using (XmlWriter w = XmlWriter.Create(@"C:\Projects\FormattedContacts.xml")) {
w.WriteStartElement("contacts");
while (r.Read()) {
if (r.NodeType == XmlNodeType.Element && r.Name == "contact") {
XmlReader temp = new XmlTextReader(new StringReader(r.ReadOuterXml()));
ContactsXslt.Transform(temp, null, w);
}
}
}
}
我正在采用的方法是一次转换1个节点,以避免OutOfMemoryException。我是否应该通过喂食更大的块来加快过程?还是我要解决所有这些错误?
答案 0 :(得分:1)
我认为您可以简化XSLT代码
<xsl:for-each select="attribute">
<!--Create variable to hold New Name after passing the Data Name to the Lookup Template-->
<xsl:variable name="newName">
<xsl:apply-templates select="$lookupDoc/attributes/attribute">
<xsl:with-param name="nameToMatch" select="name" />
</xsl:apply-templates>
</xsl:variable>
使用模板
<xsl:template match="attributes/attribute">
<xsl:param name="nameToMatch" />
<xsl:value-of select='translate(translate(self::node()[name = $nameToMatch]/mappingname, "()*%$#@!~<>'&,.?[]=-+/\:1234567890", "")," ","")' />
</xsl:template>
到
<xsl:for-each select="attribute">
<!--Create variable to hold New Name after passing the Data Name to the Lookup Template-->
<xsl:variable name="newName">
<xsl:apply-templates select="$lookupDoc/attributes/attribute[name = current()/name]"/>
</xsl:variable>
模板已简化为
<xsl:template match="attributes/attribute">
<xsl:value-of select='translate(translate(mappingname, "()*%$#@!~<>'&,.?[]=-+/\:1234567890", "")," ","")' />
</xsl:template>
我认为,肯定是一种更简洁,更XSLT的表达方式,您是否需要测试它是否可以提高性能。
通常与XSLT一起使用,以提高交叉引用/查找的性能,建议使用密钥,以便您使用
<xsl:key name="att-lookup" match="attributes/attribute" use="name"/>
然后将其用作
<xsl:variable name="name" select="name"/>
<xsl:variable name="newName">
<!-- in XSLT 1 we need to change the context doc for the key lookup -->
<xsl:for-each select="$lookupDoc">
<xsl:apply-templates select="key('att-lookup', $name)"/>
</xsl:variable>
我认为这将大大加快一次转换的速度,因为您将XmlReader和XSLT结合使用以在XmlReader发现的许多元素上多次运行XSLT,我无法分辨它是否有很大帮助,您需要尝试。
如XSLT 3建议中所指出,我还将考虑先转换查找文件一次,以避免重复所有用于创建正确XML元素名称的translate
调用。在现有XSLT外部执行此操作,或者在内部使用变量进行操作,然后exsl:node-set
将结果树片段转换为变量。但是对于您的情况,当您反复运行XSLT时,我认为最好先在主XSLT外部转换查找文档,以避免一次又一次地执行所有这些translate
。
答案 1 :(得分:0)
在读取巨大的xml文件时,请始终使用XmlReader。我喜欢结合使用XmlReader和Xml linq。我也喜欢使用字典。参见下面的代码:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = @"c:\temp\test.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(FILENAME);
while (!reader.EOF)
{
if (reader.Name != "contact")
{
reader.ReadToFollowing("contact");
}
if (!reader.EOF)
{
XElement xContact = (XElement)XElement.ReadFrom(reader);
Contact newContact = new Contact();
Contact.contacts.Add(newContact);
newContact.attributes = xContact.Descendants("attribute")
.GroupBy(x => (string)x.Element("name"), y => (string)y.Element("value"))
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
}
}
public class Contact
{
public static List<Contact> contacts = new List<Contact>();
public Dictionary<string, string> attributes { get; set; }
}
}
答案 2 :(得分:0)
作为替代方案,您可能想研究使用XSLT 3及其流功能(https://www.w3.org/TR/xslt-30/#streaming-concepts)解决任务,因为您可以以仅转发但声明性的方式处理巨大的输入文件,而您只能您需要确保attribute
元素的模板,然后使用该元素的故意创建的完整副本来允许XPath导航到子元素。另外,我认为只读取一次查找文档并执行translate
调用一次以创建正确的元素名称是有意义的。因此,以下是可与Saxon 9.8 EE一起运行的流式XSLT 3解决方案,该解决方案将查找文档转换为XPath 3.1映射(https://www.w3.org/TR/xpath-31/#id-maps),否则使用流式模式来处理大型主输入:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="xs map"
version="3.0">
<!-- could of course load the document using select="document('lookup.xml')" instead of inlining it as done here just for the example and testing -->
<xsl:param name="lookup-doc">
<attributes>
<attribute>
<name>text12</name>
<mappingname>ID</mappingname>
<datatype>Varchar2</datatype>
<size>30</size>
</attribute>
<attribute>
<name>text34</name>
<mappingname>Last Name</mappingname>
<datatype>Varchar2</datatype>
<size>30</size>
</attribute>
<attribute>
<name>date866</name>
<mappingname>DOB</mappingname>
<datatype>Date</datatype>
<size></size>
</attribute>
</attributes>
</xsl:param>
<xsl:variable
name="lookup-map"
as="map(xs:string, xs:string)"
select="map:merge(
$lookup-doc/attributes/attribute
!
map {
string(name) : translate(translate(mappingname, '()*%$#@!~<>''&,.?[]=-+/\:1234567890', ''), ' ','')
}
)"/>
<xsl:mode on-no-match="shallow-copy" streamable="yes"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="contact/attribute">
<xsl:variable name="attribute-copy" select="copy-of()"/>
<xsl:element name="{$lookup-map($attribute-copy/name)}">
<xsl:value-of select="$attribute-copy/value"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
在线示例(使用Saxon 9.8 HE运行,它忽略了流传输并执行正常的XSLT处理)位于https://xsltfiddle.liberty-development.net/bFDb2Ct/1。
要使用Saxon 9.8和C#运行XSLT 3流,请使用http://saxonica.com/html/documentation/dotnetdoc/Saxon/Api/Xslt30Transformer.html,并使用巨大的输入XML(http://saxonica.com/html/documentation/dotnetdoc/Saxon/Api/Xslt30Transformer.html#ApplyTemplates(System.IO.Stream,Saxon.Api.XmlDestination))在输入ApplyTemplates
上设置Stream
。 / p>