1

我有大量文本文件需要加载到数据库中。它们的结构不是更常见的 csv 格式,而是如下所示:

TY  - JOUR
T1  - On the Structure and Life-History of Entyloma ranunculi (Bonorden)
JF  - Philosophical Transactions of the Royal Society of London. B (1887-1895)
VL  - 178
SP  - 173
EP  - 185
PY  - 1887/01/01/
UR  - http://dx.doi.org/10.1098/rstb.1887.0008
M3  - doi:10.1098/rstb.1887.0008
AU  - Ward, H.
ER  -

其中每一行是一个不同的字段,字段名称由前导字符表示。

我想做的是将每一行加载到记录中的相应字段中。我想通过 pentaho 做到这一点,任何人都知道如何做到这一点。文本输入步骤是为 csv 输入设置的。

4

2 回答 2

5

那个文本样本看起来非常熟悉......

匆匆忙忙去检查一些东西

如果该文本示例是我认为的“皇家科学学会期刊描述文件”,那么您将无法编写 pentaho 脚本来解析这些内容。

我去过那里,试过了,很痛苦,很痛苦。

为什么?

很多事情。

首先,文件格式没有经过严格检查,因此您会找到一些没有 2 个字符 ID 后跟 2 个空格、破折号 1 个空格和数据格式行的文件。

您还会发现一些文件中包含未解析的 LATEX 命令和/或未处理的变量替换

简而言之,这些文件(至少是我上次用这些文件做任何事情时查看的文件)处于可怕的状态。

您将遇到的另一个主要问题是缺少行。

每个描述符应该有 11 个主要标签,如下所示:

TY
T1
JF
VL
SP
EP
PY
UR
M3
AU
ER

从记忆中是:

TY - Title
T1 - Description
JF - ???
VL - Volume number
SP - Start page
EP - End page
PY - Published Year
UR - Url
M3 - ???
AU - Author name
ER - ???

您经常会发现并非所有这些行都存在,但要使列在 CSV 中对齐,您仍然需要添加空白条目。

还要注意 AU 字段,它可以而且经常确实包含文件的多个条目,因此您经常会得到:

TY  - ....
T1  - ....
....
AU  - ....
AU  - ....
....

使用上面 Carey 回答中的 pentaho 方法处理这个问题会使你的很多行不同步,因为每个文件的每个标签都会有一行

关于凯莉的答案,我不得不说这是一个非常好的答案,并且比我放弃之前所做的更接近于进行良好的转换,但冷酷的事实是文件只是不适合由pentaho可靠地处理。

对此,我拖出了我写的一些 C# 来获取一个装满这些文本文件的文件夹并将它们转换为扁平的 CSV。

生成的 CSV 并不完美,仍然需要进行少量调整,但它会让你完成 99.9% 的工作,并且使用 pentaho 处理生成的文件比使用源文件本身更容易。

代码是相当通用的 C#,所以它应该在 windows 和 mono 上编译(虽然我不得不承认,我没有在后面测试它)

这是代码:

using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

namespace SciDataParse
{
  class RecordData
  {
    public string TY { get; set; }
    public string T1 { get; set; }
    public string JF { get; set; }
    public string VL { get; set; }
    public string SP { get; set; }
    public string EP { get; set; }
    public string PY { get; set; }
    public string UR { get; set; }
    public string M3 { get; set; }
    public List<string> AU { get; set; }
    public string ER { get; set; }

    public RecordData()
    {
      AU = new List<string>();
      TY = string.Empty;
      T1 = string.Empty;
      JF = string.Empty;
      VL = string.Empty;
      SP = string.Empty;
      EP = string.Empty;
      PY = string.Empty;
      UR = string.Empty;
      M3 = string.Empty;
      ER = string.Empty;
    }
  }

  class Program
  {
    static RecordData ProcessFile(string inputName)
    {
      RecordData result = new RecordData();

      using (StreamReader reader = new StreamReader(inputName))
      {
        string inputLine = reader.ReadLine();
        while(!string.IsNullOrEmpty(inputLine))
        {
          if (!Regex.IsMatch(inputLine, @"^[A-Z,0-9][A-Z,0-9]\s+-\s+.*$"))
          {
            inputLine = reader.ReadLine();
            continue; // Regex match to ensure lines are valid format
          }
          string[] lineItems = inputLine.Split('-');
          string tag = lineItems[0].Trim();
          string data = lineItems[1].Trim();
          switch (tag)
          {
            // Sort and add lines to our result object.  Note we check and change null to empty strings and filter commas
            // so that we don't create any problems with outputting CSV data
            case "TY" :
              result.TY = !string.IsNullOrEmpty(data) ? data : string.Empty;
              break;

            case "T1":
              result.T1 = !string.IsNullOrEmpty(data) ? data.Replace(",", string.Empty) : string.Empty;
              break;

            case "JF":
              result.JF = !string.IsNullOrEmpty(data) ? data.Replace(",", string.Empty) : string.Empty;
              break;

            case "VL":
              result.VL = !string.IsNullOrEmpty(data) ? data : string.Empty;
              break;

            case "SP":
              result.SP = !string.IsNullOrEmpty(data) ? data : string.Empty;
              break;

            case "EP":
              result.EP = !string.IsNullOrEmpty(data) ? data : string.Empty;
              break;

            case "PY":
              result.PY = !string.IsNullOrEmpty(data) ? data : string.Empty;
              break;

            case "UR":
              result.UR = !string.IsNullOrEmpty(data) ? data : string.Empty;
              break;

            case "M3":
              result.M3 = !string.IsNullOrEmpty(data) ? data : string.Empty;
              break;

            case "AU":
              // AU = Author items of which there can be multiple, note we also replace blank author names with "Unknown"
              result.AU.Add(!string.IsNullOrEmpty(data) ? data.Replace(",", string.Empty) : "Unknown");
              break;

            case "ER":
              result.ER = !string.IsNullOrEmpty(data) ? data : string.Empty;
              break;
          }
          inputLine = reader.ReadLine();
        }
      }

      return result;
    }

    static void Main()
    {
      List<RecordData> fileRecords = new List<RecordData>();
      List<string> headerColumns = new List<string> {"TY", "T1", "JF", "VL", "SP", "EP", "PY", "UR", "M3", "AU", "ER"};

      string baseFolder = Directory.GetCurrentDirectory();

      string[] fileNames = Directory.GetFiles(baseFolder, "*.txt");

      foreach (string fileName in fileNames)
      {
        fileRecords.Add(ProcessFile(fileName));
      }

      using (StreamWriter writer = new StreamWriter("consolodated_data.csv"))
      {
        string headerRow = string.Join(",", headerColumns);
        writer.WriteLine(headerRow);

        foreach (RecordData fileRecord in fileRecords)
        {
          string fileLine = string.Empty;
          fileLine += fileRecord.TY + ",";
          fileLine += fileRecord.T1 + ",";
          fileLine += fileRecord.JF + ",";
          fileLine += fileRecord.VL + ",";
          fileLine += fileRecord.SP + ",";
          fileLine += fileRecord.EP + ",";
          fileLine += fileRecord.PY + ",";
          fileLine += fileRecord.UR + ",";
          fileLine += fileRecord.M3 + ",";
          fileLine += string.Join("|",fileRecord.AU) + ","; // Join author names with a |
          fileLine += fileRecord.ER;
          writer.WriteLine(fileLine);
        }
      }

    }
  }
}

编译文件,然后将生成的 EXE 复制到所有 txt 文件所在的文件夹中并运行它。

现在,在你们中的任何 C# 纯粹主义者开始跳入这里并挑剔我的代码之前,请注意这一点...

A)它是前一段时间写的,作为解决我遇到的问题的快速工具,它从来都不是生产代码。

B)是的,我知道有更好的方法来做事情,例如字符串构建器与连接,请参见 A 点

C)如果您确实进来并开始在我的代码中挑毛病,而不是试图帮助 OP 解决他的问题(就像我一样),那么您只是一个无所事事的混蛋。

D)你试图用我的代码指出的任何错误都不会导致我失眠,因为坦率地说,我不在乎。它有效,我知道它有效(因为它做了我当时需要它做的事情),这就是我所关心的。

回到OP的问题。

正如我所说,它并不完美,您将不得不进行最少的编辑。

你必须做的任何编辑,你会想要使用纯文本编辑器,如果内存服务一些描述行非常长并且会超过 excel 中可用的最大列宽,从而导致一行'### ################'

您当然可以使用 pentaho 将 CSV 直接导入数据库,然后编辑其中的记录以整理数据(我就是这样做的)

最后要注意的一件事是,作者姓名(或至少在有多个姓名的情况下)使用 | 连接。字符放入“AU”标签下的一个 CSV 字段中,因此当您进一步处理它们时,您可能需要考虑将它们放在自己的表中,并使用自己的 ID 和指向源记录的外键对它们进行反规范化.

如果你不能编译它(或不想编译它)给我写信,告诉我你的平台,我会为你构建它并将二进制文件发送给你。

于 2013-01-29T00:16:05.300 回答
3

您需要使用 Row Denormaliser 步骤对行进行非规范化。

脚步:

  1. 使用您的文本文件输入将数据读入一个字段

  2. 使用字段拆分器拆分“-”上的字段

  3. 对组字段上的数据进行排序(我没有在您的示例中识别组 ID)。如果没有可用的组 ID,那么希望每个组的行数固定,然后您可以添加计算的组 ID

  4. 将行传递给 Row Normaliser 并指定以下内容:

    4.1。将您的组 ID 字段添加到组 ID 到组 ID 规范中。

    4.2. 在字段中为您需要的每一行添加目标字段名称。我在
    TY、T1、JF 等的示例中添加了 11 个。它们可以是您选择的任何名称。

    4.3. 为每个新字段指定值字段名称,您分配给
    拆分产生的第二个字段的字段。在字段拆分器的示例中,我分配了两个字段 - fld_hdr 和 fld_content。我的值字段包含 fld_content fld。

    4.4. 指定字段类型和可选的每行上的剩余列。

我创建了一个示例,但看不到在哪里上传 ktr 文件。

于 2013-01-27T01:18:32.143 回答