【问题标题】:Parsing unstructured documents into XML将非结构化文档解析为 XML
【发布时间】:2009-08-31 08:52:05
【问题描述】:

我正在使用模板将非结构化文档解析为结构化表示 (XML) 来描述预期结果。一个简单的典型问题可能是字符串列表:

"Chapter 1"
"Section background"
"this is something"
"this is another"
"Section methods"
"take some xxx"
"do yyy"
"and some..."
"Chapter apparatus"
"we created..."

我想转换成:

<div role="CHAPTER" title="1">
  <div role="SECTION" title="background">
    <p>this is a paragraph...</p>
    <p>this is another...</p>
  </div>
  <div role="SECTION" title="methods">
    <p>take some xxx</p>
    <p>do yyy</p>
    <p>and some...</p>
  </div>
</div>
<div role="CHAPTER" title="apparatus">
  <div role="SECTION" title="???">
    <p>we created...</p>
  </div>
</div>

标签 CHAPTER 和 SECTION 不存在于字符串中,而是从启发式正则表达式(例如“[Cc]hap(ter)?(\s\d+\.)?.*”)生成并应用于所有字符串。

预期结果由“模板”描述,目前看起来像:

<template count="0," role="CHAPTER">
  <regex>[Cc]hap(ter)?(\s+.*)</regex>
  <template count="0," role="SECTION">
   <regex>[Ss]ec(tion)?(\s+.*)</regex>
    <template count="0," role="p">
     <regex>.*</regex>
    </template>
  </template>
</template>

(在某些情况下,计数可以是范围,例如 2,4)。

我知道这是一个非常困难的问题(SGML 试图解决其中的一部分问题)并且真实的文档并不完全符合此类模板,因此我准备好进行部分解析并失去一些精度和召回率。

多年来,我一直在使用自己的工作代码,该代码适用于各种类型的高达几兆字节的文档。性能不是问题。对于不同的文档类型(论文、日志文件、fortran 输出等),我有不同的模板。一些文档具有嵌套结构(例如,如上所述),而另一些则更扁平,但具有更多类型的标记。

我现在正在重构这个并且想知道:

  • 是否有解决此问题的开源工具包? (最好是 Java)
  • 如果没有,我可以使用 XSLT2 分组策略结合正则表达式吗
  • 还是应该使用自动机?如果是这样,我应该使用工具包还是自己编写?

编辑:@naspinski 和一般。总是可以编写特定的脚本代码来解决特定问题。我想要一个通用的解决方案,因为我可能正在解析许多(甚至数百万)具有相当大(但不是无限)结构可变性的文档。我希望解析文档的结构用 XML 表示,而不是脚本。我相信通过模板(声明式)而不是脚本来添加新的解决方案会更容易。

编辑我几乎可以肯定,我现在最好的方法是使用 ANTLR。它是一个强大的工具,从我最初的探索来看,它可以解析行和行组。

【问题讨论】:

  • 我之前也有类似的要求,已经回答了。请看stackoverflow.com/questions/1130981/…
  • @GustlyWind 谢谢。我认为我的要求与您的要求有很大不同,因为您的要求具有单一(隐式)结构,并且我需要能够针对不同的文档类型重新配置我的要求
  • 编写自己的实用程序来解析它根本不需要很长时间,可能需要 15-20 分钟 - 这是一个选择吗?你会使用什么语言?

标签: xml parsing


【解决方案1】:

这就是 Perl 所做的工作。

#! /opt/perl/bin/perl
use strict;
use warnings;
use 5.10.1;

{
  package My::Full;
  use Moose;
  use MooseX::Method::Signatures;

  has 'chapters' => (
    'is' => 'rw',
    'isa' => 'ArrayRef[My::Chapter]',
    'default' => sub{[]}
  );

  method add_chapter( Str $name ){
    my $chapter = My::Chapter->new( name => "$name" );
    push @{$self->chapters}, $chapter;
    return $chapter;
  }

  method latest(){
    return $self->add_chapter('') unless @{$self->chapters};
    return $self->chapters->[-1];
  }

  method add_section( Str $name ){
    my $latest_chapter = $self->latest;
    $latest_chapter->add_section("$name");
  }

  method add_line( Str $line ){
    $self->latest->add_line( "$line" );
  }

  method xml(){
    my $out = '';
    for my $chapter ( @{ $self->chapters } ){
      $out .= $chapter->xml;
    }
    return $out;
  }
}
{
  package My::Chapter;
  use Moose;
  use MooseX::Method::Signatures;

  has 'name' => (
    'is' => 'rw',
    'isa' => 'Str',
    'required' => 1
  );

  has 'sections' => (
    'is' => 'rw',
    'isa' => 'ArrayRef[My::Section]',
    'default' => sub{[]}
  );

  method latest(){
    return $self->add_section('') unless @{$self->sections};
    return $self->sections->[-1];
  }

  method add_section( Str $name ){
    my $section = My::Section->new(name => "$name");
    push @{$self->sections}, $section;
    return $section;
  }

  method add_line( Str $line ){
    $self->latest->add_line( "$line" );
  }

  method xml(){
    my $name = $self->name;
    $name = '???' unless length $name;

    my $out = qq'<div role="CHAPTER" title="$name">\n';
    for my $section ( @{ $self->sections } ){
      $out .= $section->xml;
    }
    return $out."</div>\n";
  }
}
{
  package My::Section;
  use Moose;
  use MooseX::MultiMethods;

  has 'name' => (
    'is' => 'rw',
    'isa' => 'Str',
    'required' => 1
  );

  has 'lines' => (
    'is' => 'rw',
    'isa' => 'ArrayRef[Str]',
    'default' => sub{[]}
  );

  method add_line( Str $line ){
    push @{$self->lines}, "$line"
  }

  method xml(){
    my $name = $self->name;
    $name = '???' unless length $name;

    my $out = qq'  <div role="SECTION" title="$name">\n';
    for my $line ( @{ $self->lines } ){
      $out .= "    <p>$line</p>\n";
    }
    return $out."  </div>\n";
  }
}

主循环:

my $full = My::Full->new;

while( my $line = <> ){
  chomp $line;

  given( $line ){
    when( /^chap(?:ter)?\s++(.+)/i ){
      $full->add_chapter($1);
    }
    when( /^sec(?:tion)?\s++(.+)/i ){
      $full->add_section($1);
    }
    default{
      $full->add_line($line);
    }
  }
}

say $full->xml

 

<div role="CHAPTER" title="check">
  <div role="SECTION" title="check">
    <p>this is something</p>
    <p>this is another</p>
  </div>
  <div role="SECTION" title="check">
    <p>take some xxx</p>
    <p>do yyy</p>
    <p>and some...</p>
  </div>
</div>
<div role="CHAPTER" title="check">
  <div role="SECTION" title="???">
    <p>we created...</p>
  </div>
</div>

【讨论】:

  • 谢谢。这是特定于一种特定类型文档的脚本,并包含特定于文档的函数 (add_chapter)。我正在寻找一种解决方案,其中代码不需要重新编译并且文档以外部声明方式描述
【解决方案2】:

从非结构化到结构化将需要您编写某种类型的解析器,这很简单。扫描第一个正则表达式,提取数据,并为其发出一个 XML 元素。然后扫描第二个正则表达式,提取它的数据,并在您创建的第一个 XML 元素中发出它。然后扫描所有剩余的输入以查看它是否与 FIRST 正则表达式匹配,如果不匹配,则将其添加到您创建的第二个元素中,否则使用新的上层元素重新开始。继续 EOF,并保存生成的 XML。

【讨论】:

    【解决方案3】:

    我相当确定我正在寻找的答案在 ANTLR (http://www.antlr.org/) 中。这使我可以编写以下形式的表达式:

    document : (chapter)+;
    chapter : 'Chapter ' DIGIT NEWLINE line+;
    

    等等。它还允许将代码嵌入到这些表达式中。

    【讨论】:

      猜你喜欢
      • 2018-10-13
      • 1970-01-01
      • 2018-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-20
      相关资源
      最近更新 更多