【问题标题】:Complex CSV to arrays复杂的 CSV 到数组
【发布时间】:2011-10-28 08:11:58
【问题描述】:

我有一个由 MSExcel 创建的 CSV 文件,其中包含元素内的随机回车符。在最终的 CSV 中,每一行都由回车符分隔(我不知道它是否特定于 MSExcel)。

假设我有 10 列和 100 行,最后一列的一些元素本身有回车(因此在最终输出中用双引号引起来),而有些则没有。

无论如何我可以正确拆分这 100 行吗?

即使我准备牺牲那些没有回车并在末尾手动添加一个,使所有第 10、20、30 个元素都以“+\n”结尾,JS 似乎无法识别"\"\n" 作为有效语法。

编辑:这种操作用 php + 数据库会更好吗?如果是这样,我应该从哪里开始?

【问题讨论】:

  • 仅供参考,我不能代表旧版本的 Excel,但我可以确认 2007 及更高版本输出 RFC 4801 有效的 CSV 数据。值中包含的回车是完全有效的,但是您将很难找到可以处理它们的解析器实现。

标签: javascript arrays csv


【解决方案1】:

如果您正在考虑使用 .split() 函数(每个逗号上的简单拆分)来解析 csv 数据,那么您的想法是错误的。典型的通用 Split() 函数对于 csv 是错误的,因为有各种边缘情况会导致它们出错,而且性能不好。

解析 csv 数据的正确方法是使用专门构建的状态机(而不是由正则表达式定义的状态机)。好消息是您不必自己编写该状态机。坏消息是你必须小心,因为谷歌充斥着 bad javascript csv 解析器......例如,我所做的快速搜索的当前第一个结果是一个糟糕的例子托管在这里在堆栈溢出本身。该示例过于依赖正则表达式,它很难处理嵌套的引用文本。可以构建一个可以工作的正则表达式,但它也很容易出错,难以维护,并且表达式的性能通常不会那么好(因为表达式需要进行回溯)。用正则表达式单独解析 CSV 数据几乎和parsing html with regular expressions 一样糟糕。

这是我在 Google 中看到的第一个好的(基于状态机的)示例:

http://yawgb.blogspot.com/2009/03/parsing-comma-separated-values-in.html

这个特定的解析器 假定 逗号作为分隔符(与制表符、分号或管道之类的东西相反)和 假定 双转义引号(引号内的引号文本字段会自行转义,如下所示:"")。如果这与您的数据相匹配,那么这个例子可能会很好——但同样,这是一个快速搜索;我自己也没有仔细看。否则,请继续在 google 上搜索或使用此示例编写您自己的示例。

从那里继续,我很好奇,因为这听起来有点像您可能正在使用 Excel 或平面 csv 文件作为您网站的主要数据存储。这也是一个非常糟糕的主意。 Excel 和平面文件都存在巨大的并发问题,因为您开始有几个人几乎同时使用该页面。性能也可能是一个问题,尽管我不愿将这一点延伸得太远。最好说性能取决于你的能力,但对于平面文件很容易出错。

【讨论】:

  • 谢谢,这很有帮助。传说中的回复的链接读起来很有趣!我正在尝试为 100 多个具有相同类别但内容截然不同的人创建一个简历页面。问题是我们没有对服务器的完全访问权限,因此我在客户端通过 JS 进行操作。源代码必须是可编辑的,以便完全外行。无论如何,非常感谢您帮助新手!
  • 你们没有信心。正则表达式非常适合解析 CSV 数据,你只需要一个该死的好。我同意周围有很多不好的例子,但如果你足够努力的话,也会有一些未经加工的钻石。
  • @Evan -- 正如我所说:"It's _possible_ to build a regular expression that will work, but it's also easy to get wrong, is hard to maintain, and the performance of the expression won't typcially be good 要使正则表达式正常工作,您需要回顾/背后,而那些会损害性能。
【解决方案2】:

比你想象的要难......

好吧,我的第一篇文章被一个 mod 方便地删除了,因为我发布了一个指向我的 OSS CSV 解析器项目的外部链接。

所以...我将发布完整的 ND-FSM(非确定性有限状态机)行拆分器,它是处理包含字符串的值所必需的。

我们开始:

splitLines: function(csv, delimiter) {
  var state = 0;
  var value = "";
  var line = "";
  var lines = [];
  function endOfRow() {
    lines.push(value);
    value = "";
    state = 0;
  };
  csv.replace(/(\"|,|\n|\r|[^\",\r\n]+)/gm, function (m0){
    switch (state) {
      // the start of an entry/value
      case 0:
        if (m0 === "\"") {
          state = 1;
        } else if (m0 === "\n") {
          endOfRow();
        } else if (/^\r$/.test(m0)) {
          // carriage returns are ignored
        } else {
          value += m0;
          state = 3;
        }
        break;
      // delimited input  
      case 1:
        if (m0 === "\"") {
          state = 2;
        } else {
          value += m0;
          state = 1;
        }
        break;
      // delimiter found in delimited input
      case 2:
        // is the delimiter escaped?
        if (m0 === "\"" && value.substr(value.length - 1) === "\"") {
          value += m0;
          state = 1;
        } else if (m0 === ",") {
          value += m0;
          state = 0;
        } else if (m0 === "\n") {
          endOfRow();
        } else if (m0 === "\r") {
          // Ignore
        } else {
          throw new Error("Illegal state");
        }
        break;
      // un-delimited input
      case 3:
        if (m0 === ",") {
          value += m0;
          state = 0;
        } else if (m0 === "\"") {
          throw new Error("Unquoted delimiter found");
        } else if (m0 === "\n") {
          endOfRow();
        } else if (m0 === "\r") {
          // Ignore
        } else {
          throw new Error("Illegal data");
        }
          break;
      default:
        throw new Error("Unknown state");
    }
    return "";
  });
  if (state != 0) {
    endOfRow();
  }
  return lines;
}

如果您了解复杂性理论,以下是映射:

州:

  • 0:新值/条目的开始
  • 1:被引用(即被双引号包围)
  • 2:遇到双引号
  • 3:未引用

注意:这只处理分行部分。其余的由一个单独的(并且非常复杂的)正则表达式例程处理。

我不能把使用词法分析器的想法归功于我。这属于另一个开发者,未经许可我不会公开命名。

如果您想获得处理任何 RFC 4180 兼容的 CSV 数据的能力,而不必担心自己实现它,请查看我的个人资料中提到的项目。否则,祝你好运……

【讨论】:

    【解决方案3】:

    我可能会建议在 PHP 中执行此操作,除非您确实需要在 JS 中执行此操作;我很确定无论如何都有一个用于处理 CSV 文件的 PHP 库。

    【讨论】:

      猜你喜欢
      • 2021-02-21
      • 1970-01-01
      • 2016-10-11
      • 1970-01-01
      • 2013-06-07
      • 2020-07-26
      • 2017-09-27
      • 2015-05-25
      • 2016-02-20
      相关资源
      最近更新 更多