C#XSLT快速转换大型XML文件

时间:2018-08-08 07:21:28

标签: c# xml xslt

我正在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, "()*%$#@!~&lt;&gt;&apos;&amp;,.?[]=-+/\: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。我是否应该通过喂食更大的块来加快过程?还是我要解决所有这些错误?

3 个答案:

答案 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, "()*%$#@!~&lt;&gt;&apos;&amp;,.?[]=-+/\: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, "()*%$#@!~&lt;&gt;&apos;&amp;,.?[]=-+/\: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, '()*%$#@!~&lt;&gt;''&amp;,.?[]=-+/\: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>