【问题标题】:XML to CSV using c# without using xsltXML到CSV使用c#而不使用xslt
【发布时间】:2014-01-24 22:05:48
【问题描述】:

我一直在网上搜索,我假设有人必须在我之前需要这个并且做得更好,以获得 xml 到 csv 转换器。我下面有一个非常标准的xml:

<ArrayOfDealer xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Dealer>
        <Cmf>76066699</Cmf>
        <DealerNumber/>
        <DealershipName>BROWARD MOTORSPORTS - WPB</DealershipName>
    </Dealer>
    <Dealer>
        <Cmf>76071027</Cmf>
        <DealerNumber/>
        <DealershipName>BROWARD MOTORSPORTS OF FT LAUDERDALE LLC</DealershipName>
    </Dealer>
    <Dealer>
        <Cmf>76014750</Cmf>
        <DealerNumber/>
        <DealershipName>Jet Ski of Miami</DealershipName>
    </Dealer>
    <Dealer>
        <Cmf>76066987</Cmf>
        <DealerNumber/>
        <DealershipName>BROWARD MOTORSPORTS - Davie</DealershipName>
    </Dealer>
</ArrayOfDealer>

我想把它解析成类似的东西

cmf      dealernumber    dealershipname
76066699                 BROWARD MOTORSPORTS - WPB
76014750                 Jet Ski of Miami
76066987                 BROWARD MOTORSPORTS - Davie

XML 来自我存储为字符串的 api。

想法?

编辑:澄清一下,我知道结构会像上面那样,没有进一步的节点嵌套,但实际的标签名称可以非常。

【问题讨论】:

  • 我在评论中看到您不知道 XML 元素名称;你至少可以确定结构是相同的,即&lt;ArrayOfWhatever&gt;&lt;Whatever&gt;&lt;WhateverProperties&gt;&lt;/Whatever&gt;&lt;/ArrayOfWhatever&gt;——这意味着你知道根目录下有一个重复节点,然后包含相同数量的子节点,这些子节点将成为 csv 值?
  • 是的,这就是我的意思,是的,结构是一样的。
  • 结果必须是正确的 CSV(逗号分隔)还是制表符分隔的文本文件?

标签: c# xml csv


【解决方案1】:

这样的东西能满足你的需要吗:

Func<string, string> csvFormat =
    t => String.Format("\"{0}\"", t.Replace("\"", "\"\""));

var xml = XDocument.Parse(/* xml text here */);

Func<XDocument, IEnumerable<string>> getFields =
    xd =>
        xd
            .Descendants("Dealer")
            .SelectMany(d => d.Elements())
            .Select(e => e.Name.ToString())
            .Distinct();

var headers =
    String.Join(",",
        getFields(xml)
            .Select(f => csvFormat(f)));

var query =
    from dealer in xml.Descendants("Dealer")
    select string.Join(",",
        getFields(xml)
            .Select(f => dealer.Elements(f).Any()
                ? dealer.Element(f).Value
                : "")
            .Select(x => csvFormat(x)));

var csv =
    String.Join(Environment.NewLine,
        new [] { headers }.Concat(query));

这仍然假设 &lt;ArrayOfDealer&gt;&lt;Dealer&gt; 结构说的相同,但下面的字段可能会改变。

【讨论】:

  • 这依赖于元素的名称,OP 在评论中说他不会知道也不会保持不变。
  • @DavidKhaykin - 我已经相应地更新了我的解决方案。如果 OP 编辑​​他的问题来澄清这一点,那就太好了。最终的问题和答案应该匹配,无需阅读 cmets。
  • 我同意这一点。作为更有经验的成员,我们总是可以在问题中编辑他们的评论,并发表评论说明未来的礼仪。我打算这样做,但后来我开始喝酒。 :) 开个玩笑……
  • @DavidKhaykin - 有时喝酒是最简单的选择。 ;-)
【解决方案2】:

这很粗略,但只要结构保持不变,它就会创建一个 CSV 文件或制表符分隔的文本文件

Root -> Main Element (for each row) -> Child Elements (any number)

我包含了 2 个不同的 XML 测试集,因此您可以看到正确生成的结果。

工作示例:

namespace XmlToCsv
{
    class Program
    {
        const int TabSpaces = 8;

        static void GenerateCsvFromXml(string xmlString, string resultFileName, bool isTabDelimited)
        {
            XDocument xDoc = XDocument.Parse(xmlString);

            var tabsNeededList = new List<int>(); // only used for TabDelimited file

            string delimiter = isTabDelimited
                ? "\t"
                : ",";

            // Get title row 
            var titlesList = xDoc.Root
                .Elements()
                .First()
                .Elements()
                .Select(s => s.Name.LocalName)
                .ToList();

            // Get the values
            var masterValuesList = xDoc.Root
                .Elements()
                .Select(e => e
                    .Elements()
                    .Select(c => c.Value)
                    .ToList())
                .ToList();

            // Add titles as first row in master values list
            masterValuesList.Insert(0, titlesList);

            // For tab delimited, we need to figure out the number of tabs
            // needed to keep the file uniform, for each column
            if (isTabDelimited)
            {
                for (var i = 0; i < titlesList.Count; i++)
                {
                    int maxLength =
                        masterValuesList
                            .Select(vl => vl[i].Length)
                            .Max();

                    // assume tab is 4 characters
                    int rem;
                    int tabsNeeded = Math.DivRem(maxLength, TabSpaces, out rem);
                    tabsNeededList.Add(tabsNeeded);
                }
            }

            // Write the file
            using (var fs = new FileStream(resultFileName, FileMode.Create))
            using (var sw = new StreamWriter(fs))
            {
                foreach (var values in masterValuesList)
                {
                    string line = string.Empty;

                    foreach (var value in values)
                    {
                        line += value;
                        if (titlesList.IndexOf(value) < titlesList.Count - 1)
                        {
                            if (isTabDelimited)
                            {
                                int rem;
                                int tabsUsed = Math.DivRem(value.Length, TabSpaces, out rem);
                                int tabsLeft = tabsNeededList[values.IndexOf(value)] - tabsUsed + 1; // one tab is always needed!

                                for (var i = 0; i < tabsLeft; i++)
                                {
                                    line += delimiter;
                                }
                            }
                            else // comma delimited
                            {
                                line += delimiter;
                            }
                        }
                    }

                    sw.WriteLine(line);
                }
            }
        }

        static void Main(string[] args)
        {
            String xmlString = @"<ArrayOfDealer xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
                <Dealer>
                    <Cmf>76066699</Cmf>
                    <DealerNumber/>
                    <DealershipName>BROWARD MOTORSPORTS - WPB</DealershipName>
                </Dealer>
                <Dealer>
                    <Cmf>76071027</Cmf>
                    <DealerNumber/>
                    <DealershipName>BROWARD MOTORSPORTS OF FT LAUDERDALE LLC</DealershipName>
                </Dealer>
                <Dealer>
                    <Cmf>76014750</Cmf>
                    <DealerNumber/>
                    <DealershipName>Jet Ski of Miami</DealershipName>
                </Dealer>
                <Dealer>
                    <Cmf>76066987</Cmf>
                    <DealerNumber/>
                    <DealershipName>BROWARD MOTORSPORTS - Davie</DealershipName>
                </Dealer>
            </ArrayOfDealer>";

            String xmlString2 = @"<ArrayOfUnicorn xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
                <Unicorn>
                    <UnicornColor>Red</UnicornColor>
                    <Cmf>76066699</Cmf>
                    <UnicornNumber/>
                    <UnicornshipName>BROWARD MOTORSPORTS - WPB</UnicornshipName>
                </Unicorn>
                <Unicorn>
                    <UnicornColor>Red</UnicornColor>
                    <Cmf>76071027</Cmf>
                    <UnicornNumber/>
                    <UnicornshipName>BROWARD MOTORSPORTS OF FT LAUDERDALE LLC</UnicornshipName>
                </Unicorn>
                <Unicorn>
                    <UnicornColor>Red</UnicornColor>
                    <Cmf>76014750</Cmf>
                    <UnicornNumber/>
                    <UnicornshipName>Jet Ski of Miami</UnicornshipName>
                </Unicorn>
                <Unicorn>
                    <UnicornColor>Red</UnicornColor>
                    <Cmf>76066987</Cmf>
                    <UnicornNumber/>
                    <UnicornshipName>BROWARD MOTORSPORTS - Davie</UnicornshipName>
                </Unicorn>
            </ArrayOfUnicorn>";

            // Comma delimited
            GenerateCsvFromXml(xmlString, "Dealer.csv", false);
            GenerateCsvFromXml(xmlString2, "Unicorn.csv", false);

            // Tab delimited
            GenerateCsvFromXml(xmlString, "Dealer.txt", true);
            GenerateCsvFromXml(xmlString2, "Unicorn.txt", true);

        }
    }
}

结果:

Dealer.csv:

Cmf,DealerNumber,DealershipName
76066699,,BROWARD MOTORSPORTS - WPB,
76071027,,BROWARD MOTORSPORTS OF FT LAUDERDALE LLC,
76014750,,Jet Ski of Miami,
76066987,,BROWARD MOTORSPORTS - Davie,

独角兽.csv:

UnicornColor,Cmf,UnicornNumber,UnicornshipName
Red,76066699,,BROWARD MOTORSPORTS - WPB,
Red,76071027,,BROWARD MOTORSPORTS OF FT LAUDERDALE LLC,
Red,76014750,,Jet Ski of Miami,
Red,76066987,,BROWARD MOTORSPORTS - Davie,

Unicorn.txt(制表符分隔):

UnicornColor    Cmf         UnicornNumber   UnicornshipName
Red             76066699                    BROWARD MOTORSPORTS - WPB           
Red             76071027                    BROWARD MOTORSPORTS OF FT LAUDERDALE LLC    
Red             76014750                    Jet Ski of Miami                
Red             76066987                    BROWARD MOTORSPORTS - Davie         

【讨论】:

    【解决方案3】:

    使用XmlSerializer 将您的 xml 反序列化为对象。然后使用this 问题中的方法将您的对象序列化为 csv 文件。

    【讨论】:

    • 不知道命名约定是否会成为这种方法的问题?我正在写的内容必须对标签的名称“愚蠢”。
    • 所以你永远不会知道你的xml的结构?您将无法生成架构文件?
    • 我知道结构会像上面那样,没有进一步嵌套节点,但实际的标签名称可以非常。
    • @ZachM。我已将此答案中的评论编辑为您的问题。在您的问题中包含此类相关信息始终是一个好主意,以便尽可能获得最适用的答案。
    【解决方案4】:

    下面的代码以通用方式将简单的xml 内容(由问题提供)转换为csv 格式的string(标题+行)序列,只需要实现一个更可靠的函数来转义值(下面示例中的身份转换)。

    string csvSeparator = ",";
    Func<string, string> escapeValue = val => val;
    
    string xml = "xml content";
    XDocument doc = XDocument.Parse(xml);
    
    var headers = doc.Root
                    .Elements()
                    .First()
                    .Elements()
                    .Select(el => el.Name.LocalName);
    
    var headerRow = string.Join(csvSeparator, headers);
    
    var rows = from el in doc.Root.Elements()
                let values = from prop in el.Elements()
                            select escapeValue(prop.Value)
                let row = string.Join(csvSeparator, values)
                select row;
    
    
    IEnumerable<string> csvLines = new[] { headerRow }.Concat(rows);
    

    如果需要csv文件的全部内容,可以这样做:

    string csvContent = string.Join(Environment.NewLine, csvLines);
    

    【讨论】:

      猜你喜欢
      • 2014-08-05
      • 2011-02-20
      • 2017-11-06
      • 2017-05-23
      • 2011-10-28
      相关资源
      最近更新 更多