【问题标题】:CSV to XML ConversionCSV 到 XML 的转换
【发布时间】:2014-12-16 16:11:40
【问题描述】:

程序以csv文件为输入输出XML。代码如下

    private static void ConvertCSVToXML()
    {
        string[] source = File.ReadAllLines("data.csv");
        string RootNameStartTag = "<" + Path.GetFileName("\\CSVTOXML\\CSV-XML\\bin\\Debug\\data.csv") + ">";
        RootNameStartTag = RootNameStartTag.Replace(".csv", "");
        string RootNameEndTag = RootNameStartTag.Insert(1, "/");
        StreamWriter writeFile = new StreamWriter("Output.xml");
        string[] headers = source[0].Split(',');
        source = source.Where(w => w != source[0]).ToArray();
        string[] fields = new string[] { };
        XElement xmlElement ;          
        for (int i = 0; i < source.Length; i++)
        {           

            writeFile.WriteLine(RootNameStartTag);

            fields = source[i].Split(',');                
            for (int j = 0; j < fields.Length; j++)
            {

                xmlElement = new XElement(new XElement(headers[j], fields[j]));
                writeFile.Write(xmlElement);
                writeFile.WriteLine();

            }
            writeFile.WriteLine(RootNameEndTag);
            fields = null;  
        }
    }

上述代码的唯一问题是它根据逗号 (,) 拆分数据,所以如果我在 csv 中有一行 A,"DEF,XYZ,GHI","FDNFB,dfhjd"

然后 field[0]=A field[1]="DEF field[3]=XYZ field[4]=GHI" field[5]="FDNB field[6]=dfhjd" 但我需要输出为 field[0]=A field[1]=DEF,XYZ,GHI field[2]=FDFNFB,dfhjd 请根据上述模式帮助我拆分

【问题讨论】:

  • 不要手动解析 CSV 文件。有很多优秀的解析器可以快速为您提供正确的结果。使用其中之一。
  • @paqogomez 这是给我的任务
  • 然后开始循环遍历每个字符并测试它是否是引号或逗号。
  • @paqogomez 引号内有一个逗号。因此,如果我使用 source[i].split(',','/"')
  • @JohnSaunders 您忘记删除 CSV 和 XML 标记。标题应该只是“转换”;-)

标签: c# .net regex xml csv


【解决方案1】:

.Net 中内置的TextFieldParser 处理带引号的字段。它位于 Microsoft.VisualBasic.FileIO 中,但可以从 c# 或任何其他 .Net 语言中使用。以下测试代码:

public static class TextFieldParserTest
{
    public static void Test()
    {
        var csv = @"""DEF,XYZ,GHI"",""FDNFB,dfhjd""";

        using (var stream = new StringReader(csv))
        using (TextFieldParser parser = new TextFieldParser(stream))
        {
            parser.SetDelimiters(new string[] { "," });
            parser.HasFieldsEnclosedInQuotes = true; // Actually already default

            while (!parser.EndOfData)
            {
                string[] fields = parser.ReadFields();
                Debug.WriteLine(fields.Length);
                foreach (var field in fields)
                    Debug.WriteLine(field); 
            }
        }
    }
}

给出以下输出:

2
DEF,XYZ,GHI
FDNFB,dfhjd

【讨论】:

  • 非常感谢你。会尝试
【解决方案2】:

查看以下解决方案 [Convert CSV to XML when CSV contains both character and number data]

他建议使用正则表达式来解析 CSV 行,使用 SplitCSV(line) 而不是 line.Split(",")

【讨论】:

  • 使用正则表达式解析 CSV 文件可能会非常慢,因为所有的前瞻和后视检查。此外,我不确定他们处理转义引号的效果如何,例如“此字段有一个引号 ("") 和一个逗号 (,) in"
  • @MartinBrown 建议的解决方案工作正常,我有同样的问题,我使用了正则表达式,它并不像你想象的那么慢,即使是大数据!
  • 因为我今天在病床上,有点无聊,所以我做了一些性能测试。 100 万次 "text,\"text with quote(\"\") 和逗号 (,)\",text" 的解析。这需要 755 毫秒,我的函数使用 Thorsten Dittmer 的解决方案需要 2,087 毫秒,而使用此答案所指向的正则表达式解决方案需要 9,761 毫秒。那是慢 12 倍! (在 I7 Surface 3 上运行三次的平均时间)。即使您将正则表达式实例移出循环并重用它,您仍然只能得到 2,682 毫秒的数字。
【解决方案3】:

Cinchoo ETL - 一个开源库简化了 CSV 到 Xml 文件的转换过程。

对于示例 CSV:

Id, Name, City
1, Tom, NY
2, Mark, NJ
3, Lou, FL
4, Smith, PA
5, Raj, DC

使用下面的代码可以生成 Xml

string csv = @"Id, Name, City
    1, Tom, NY
    2, Mark, NJ
    3, Lou, FL
    4, Smith, PA
    5, Raj, DC";

StringBuilder sb = new StringBuilder();
using (var p = ChoCSVReader.LoadText(csv).WithFirstLineHeader())
{
    using (var w = new ChoXmlWriter(sb)
        .Configure(c => c.RootName = "Emps")
        .Configure(c => c.NodeName = "Emp")
        )
    {
        w.Write(p);
    }
}

Console.WriteLine(sb.ToString());

输出 Xml:

<Emps>
  <Emp>
    <Id>1</Id>
    <Name>Tom</Name>
    <City>NY</City>
  </Emp>
  <Emp>
    <Id>2</Id>
    <Name>Mark</Name>
    <City>NJ</City>
  </Emp>
  <Emp>
    <Id>3</Id>
    <Name>Lou</Name>
    <City>FL</City>
  </Emp>
  <Emp>
    <Id>4</Id>
    <Name>Smith</Name>
    <City>PA</City>
  </Emp>
  <Emp>
    <Id>5</Id>
    <Name>Raj</Name>
    <City>DC</City>
  </Emp>
</Emps>

查看 CodeProject 文章以获得更多帮助。

免责声明:我是这个库的作者。

【讨论】:

    【解决方案4】:

    这似乎是一个不错的选择,可以解决您的问题: http://msdn.microsoft.com/en-GB/library/bb387090.aspx

    // Create the text file.
    string csvString = @"GREAL,Great Lakes Food Market,Howard Snyder,Marketing Manager,(503) 555-7555,2732 Baker Blvd.,Eugene,OR,97403,USA
    HUNGC,Hungry Coyote Import Store,Yoshi Latimer,Sales Representative,(503) 555-6874,City Center Plaza 516 Main St.,Elgin,OR,97827,USA
    LAZYK,Lazy K Kountry Store,John Steel,Marketing Manager,(509) 555-7969,12 Orchestra Terrace,Walla Walla,WA,99362,USA
    LETSS,Let's Stop N Shop,Jaime Yorres,Owner,(415) 555-5938,87 Polk St. Suite 5,San Francisco,CA,94117,USA";
    File.WriteAllText("cust.csv", csvString);
    
    // Read into an array of strings.
    string[] source = File.ReadAllLines("cust.csv");
    XElement cust = new XElement("Root",
        from str in source
        let fields = str.Split(',')
        select new XElement("Customer",
            new XAttribute("CustomerID", fields[0]),
            new XElement("CompanyName", fields[1]),
            new XElement("ContactName", fields[2]),
            new XElement("ContactTitle", fields[3]),
            new XElement("Phone", fields[4]),
            new XElement("FullAddress",
                new XElement("Address", fields[5]),
                new XElement("City", fields[6]),
                new XElement("Region", fields[7]),
                new XElement("PostalCode", fields[8]),
                new XElement("Country", fields[9])
            )
        )
    );
    Console.WriteLine(cust);
    

    此代码产生以下输出:

    Xml
        <Root>
          <Customer CustomerID="GREAL">
            <CompanyName>Great Lakes Food Market</CompanyName>
            <ContactName>Howard Snyder</ContactName>
            <ContactTitle>Marketing Manager</ContactTitle>
            <Phone>(503) 555-7555</Phone>
            <FullAddress>
              <Address>2732 Baker Blvd.</Address>
              <City>Eugene</City>
              <Region>OR</Region>
              <PostalCode>97403</PostalCode>
              <Country>USA</Country>
            </FullAddress>
          </Customer>
          <Customer CustomerID="HUNGC">
            <CompanyName>Hungry Coyote Import Store</CompanyName>
            <ContactName>Yoshi Latimer</ContactName>
            <ContactTitle>Sales Representative</ContactTitle>
            <Phone>(503) 555-6874</Phone>
            <FullAddress>
              <Address>City Center Plaza 516 Main St.</Address>
              <City>Elgin</City>
              <Region>OR</Region>
              <PostalCode>97827</PostalCode>
              <Country>USA</Country>
            </FullAddress>
          </Customer>
          <Customer CustomerID="LAZYK">
            <CompanyName>Lazy K Kountry Store</CompanyName>
            <ContactName>John Steel</ContactName>
            <ContactTitle>Marketing Manager</ContactTitle>
            <Phone>(509) 555-7969</Phone>
            <FullAddress>
              <Address>12 Orchestra Terrace</Address>
              <City>Walla Walla</City>
              <Region>WA</Region>
              <PostalCode>99362</PostalCode>
              <Country>USA</Country>
            </FullAddress>
          </Customer>
          <Customer CustomerID="LETSS">
            <CompanyName>Let's Stop N Shop</CompanyName>
            <ContactName>Jaime Yorres</ContactName>
            <ContactTitle>Owner</ContactTitle>
            <Phone>(415) 555-5938</Phone>
            <FullAddress>
              <Address>87 Polk St. Suite 5</Address>
              <City>San Francisco</City>
              <Region>CA</Region>
              <PostalCode>94117</PostalCode>
              <Country>USA</Country>
            </FullAddress>
          </Customer>
        </Root>
    

    编辑 我之前没有看到第一个问题。首先对您的 CSV 进行一些预处理,替换列分隔符。

    使用这个:

        var filePath = "Your csv file path here including name";
        var newFilePath = filePath + ".tmp";
    
        using (StreamReader vReader = new StreamReader(filePath))
        {
            using (StreamWriter vWriter = new StreamWriter(newFilePath, false, Encoding.ASCII))
            {
                int vLineNumber = 0;
                while (!vReader.EndOfStream)
                {
                    string vLine = vReader.ReadLine();
                    vWriter.WriteLine(ReplaceLine(vLine, vLineNumber++));
                }
            }
        }
    
        File.Delete(filePath);
        File.Move(newFilePath, filePath);
    
        Dts.TaskResult = (int)ScriptResults.Success;
    }
    
    protected string ReplaceLine(string Line, int LineNumber)
    {
        var newLine = Line.Replace("\",\"", "|");
        newLine = newLine.Replace(",\"", "|");
        newLine = newLine.Replace("\",", "|");
        return newLine;
    }
    

    【讨论】:

    • 它不涵盖我的情况,即如果引号内有逗号,则拆分引号而不是逗号
    • 您可以先解析您的 CSV 文件,然后将正确的逗号替换为其他内容。 Google Cloud 使用 thorn 字符,但您可以在每个示例中使用竖线 (|)。我会在文件上做一个字符串替换,然后处理它。
    • 您的字符串似乎也被引号包围。您可以轻松地将 '",', '","' 和 ',"' 替换为管道,然后将此管道用作列分隔符。
    • 但这将是极其低效的,因为您必须解析文件两次而不是一次。此外,在 CSV 文件中,通过将引号加倍来转义引号也很常见,因此简单地替换 '",' 可能仍然无法给出正确的结果。
    【解决方案5】:

    Excel 生成的 CSV 文件也有同样的问题。问题是(这很好),如果字段内容包含分隔符,则内容会像您的示例一样被引用(如果内容也包含引号字符,则它会加倍)。

    我也没有使用现成的解析器,但实现如下:

        private string[] ParseLine(string line, char fieldSeparator, char? textSeparator)
        {
            List<string> items = new List<string>();
    
            StringBuilder itemBuilder = new StringBuilder();
            bool textSeparatorFound = false;
    
            for (int i = 0; i < line.Length; i++)
            {
                // Get current character
                char currentChar = line[i];
    
                // In case it is a field separator...
                if (currentChar == fieldSeparator)
                {
                    // a) Did we recognize a quote before => Add the character to the item
                    if (textSeparatorFound)
                    {
                        itemBuilder.Append(currentChar);
                    }
    
                    // b) We're not within an open quote => We've finished a field
                    else
                    {
                        string item = itemBuilder.ToString();
                        itemBuilder.Remove(0, itemBuilder.Length);
    
                        // Replace doubled text separators
                        if (textSeparator != null)
                        {
                            string replaceWhat = String.Concat(textSeparator, textSeparator);
                            string replaceWith = textSeparator.ToString();
                            item = item.Replace(replaceWhat, replaceWith);
                        }
    
                        items.Add(item);
                    }
                }
    
                // If it is a quote character
                else if (currentChar == textSeparator)
                {
                    // a) If we have no open quotation, we open one
                    if (!textSeparatorFound)
                    {
                        textSeparatorFound = true;
                    }
    
                    // b) If we have an open quotation we have to decide whether to close it or not
                    else
                    {
                        // If this character is followed by the field separator or the end of the string, 
                        // this ends a quoted block. Otherwise we just add it to the output to
                        // handle quoted quotes.
                        if (i < line.Length - 1 && line[i + 1] != fieldSeparator)
                            itemBuilder.Append(currentChar);
                        else
                            textSeparatorFound = false;
                    }
                }
    
                // All other characters are appended to the current item
                else
                    itemBuilder.Append(currentChar);
            }
    
            // All other text is just appended
            if (itemBuilder.Length > 0)
            {
                string item = itemBuilder.ToString();
                itemBuilder.Remove(0, itemBuilder.Length);
    
                // Remember to replace quoted quotes
                if (textSeparator != null)
                {
                    string replaceWhat = String.Concat(textSeparator, textSeparator);
                    string replaceWith = textSeparator.ToString();
                    item = item.Replace(replaceWhat, replaceWith);
                }
    
                items.Add(item.Trim());
            }
    
            return items.ToArray();
        }
    

    【讨论】:

    • 哦,真的吗?否决票?愿意发表评论吗?真的......应该禁止在没有评论的情况下投反对票。
    • 我真的很想看看谁投了反对票,但这可能是可选的。
    【解决方案6】:

    CSV 的问题在于它是一种不规则的语言。这意味着字符具有不同的含义,具体取决于字符流中它们之前或之后的内容。如您所见,使用字符串进行拆分。Split 方法无法正确识别用引号转义的字段中的逗号。

    虽然可以使用正则表达式对 CSV 行进行粗略的解析,并使用回顾和展望技术,但这些技术通常是错误且缓慢的。这是因为正则表达式是为正则语言设计的。一种更好的方法是使用像这样的简单函数来简单地解析字符:

    using System;
    using System.Collections.Generic;
    using System.Text;
    
        class Program
        {
            static void Main(string[] args)
            {
                IList<string> fields = ParseCSVLine("text,\"text with quote(\"\") and comma (,)\",text");
    
                foreach (string field in fields)
                {
                    Console.WriteLine(field);
                }
            }
    
            public static IList<string> ParseCSVLine(string csvLine)
            {
                List<string> result = new List<string>();
                StringBuilder buffer = new StringBuilder(csvLine.Length);
    
                bool inQuotes = false;
                char lastChar = '\0';
    
                foreach (char c in csvLine)
                {
                    switch (c)
                    {
                        case '"':
                            if (inQuotes)
                            {
                                inQuotes = false;
                            }
                            else
                            {
                                // This next if handles the case where 
                                // we have a doubled up quote
                                if (lastChar == '"')
                                {
                                    buffer.Append('"');
                                }
                                inQuotes = true;
                            }
                            break;
    
                        case ',':
                            if (inQuotes)
                            {
                                buffer.Append(',');
                            }
                            else
                            {
                                result.Add(buffer.ToString());
                                buffer.Clear();
                            }
                            break;
    
                        default:
                            buffer.Append(c);
                            break;
                    }
    
                    lastChar = c;
                }
                result.Add(buffer.ToString());
    
                return result;
            }
        }
    

    以上输出:

    text
    text with quote(") and comma (,)
    text
    

    【讨论】:

    • 当字段值包含" 字符文字时,您的解决方案无法处理这种情况。在这种情况下,它加倍(至少按照惯例)。
    • 确实如此,这就是为什么它会查看 lastChar 并将其与 " 进行比较,然后再附加它并切换回 inQuotes 模式。
    • 我明白了——你往后看而不是往前看。搞糊涂了。抱歉打扰了 ;-)
    • 这实际上是对一段从流中解析出来的代码的轻微修改。当使用流而不是字符串时,向后看比向前看更容易。
    猜你喜欢
    • 2018-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-02
    相关资源
    最近更新 更多