0

質問:プレーンテキストリストをHTMLに解析する賢い方法はありますか?

それとも、難解な再帰的手法に頼らなければならないのでしょうか、それとも総当たり攻撃に頼らなければならないのでしょうか。

私はこれをしばらくの間疑問に思っていました。私自身の反芻では、私は何度もブルートフォース、そして奇妙な再帰的な方法に戻ってきました...しかし、それはいつもとても不格好なようです。もっと良い方法があるはずですよね?

それで、賢い方法は何ですか?

仮定

シナリオを設定する必要があるので、これらは私の仮定です。

  1. リストは、順序付けされていないリストまたは順序付けられたリストのいずれかで、(少なくとも)3レベルの深さでネストできます。リストのタイプと深さは、プレフィックスによって制御されます。

    1. プレフィックスの後に必須のスペースがあります。
    2. リストの深さは、プレフィックスにスペースなしの文字がいくつあるかによって制御されます。*****5つのリストの深さでネストされます。
    3. リストタイプは、文字タイプ、*または-順序付けされていないリスト、無秩序なリストによって強制され#ます。
  2. \nアイテムは1文字だけで区切られます。(2つの連続する新しい行が「グループ」、段落、div、またはMarkdownやTextileのような他のHTMLタグとして適格であるとしましょう。)

  3. リストタイプは自由に混在させることができます。

  4. 出力は有効なHTML4である必要があり、できれば末尾に</li>sが付いている必要があります。

  5. 解析は、必要に応じて正規表現を使用して、または使用せずに実行できます。

サンプルマークアップ

* List
*# List
** List
**# List
** List

# List
#* List
## List
##* List
## List

必要な出力

読みやすさのために少し分割しましたが、これの有効なバリエーションである必要があります(私はちょうどそれをうまく間隔を空けていることを覚えておいてください!):

<ul>
  <li>List</li>
  <li>
    <ol><li>list</li></ol>
    <ul><li>List</li></ul>
  </li>
  <li>List</li>
  <li>
    <ol><li>List</li></ol>
  </li>
  <li>List</li>
</ul>


<ol>
  <li>List</li>
  <li>
    <ul><li>list</li></ul>
    <ol><li>List</li></ol>
  </li>
  <li>List</li>
  <li>
    <ul><li>List</li></ul>
  </li>
  <li>List</li>
</ol>

要約すれば

どうやってこれをしますか?予想外に繰り返されるリストを処理するための良い方法を本当に理解したいと思います。なぜなら、それは誰もが絡むための醜い混乱として私を襲うからです。

4

8 に答える 8

2

私が見た最良の説明は、Mark Jason Dominus による Higher-Order Perl からのものです。全文はhttp://hop.perl.plover.com/book/でオンラインで入手できます。

例はすべて Perl で書かれていますが、各領域の背後にあるロジックの内訳は素晴らしいものです。

第 8 章(! PDF リンク) は、特に解析に関するものです。本全体のレッスンは多少関連していますが.

于 2009-06-17T21:37:35.227 に答える
2

いくつかの Pythonic コンセプトを使用した行ごとのソリューション:

cur = ''
for line in lines():
    prev = cur
    cur, text = split_line_into_marker_and_remainder(line)
    if cur && (cur == prev) :
         print '</li><li>'
    else :
         nprev, ncur = kill_common_beginning(prev, cur)
         for c in nprev: print '</li>' + ((c == '#') ? '</ol>' : '</ul>') 
         for c in ncur:  print           ((c == '#') ? '<ol>'  : '<ul>' )  + '<li>'
    print text 

これがどのように機能するかです: 行を処理するには、前の行のマーカーとこの行のマーカーを比較します。

split_line_into_marker_and_remainderマーカーcurとテキスト自体の2 つの結果を返す架空の関数を使用し ます。3 つの引数、入力文字列、2 つの出力文字列を持つ C++ 関数として実装するのは簡単です。

kill_common_beginningコアには、 との繰り返し部分を取り除く 架空の関数がprevありcurます。その後、前のマーカーに残っているものをすべて閉じ、現在のマーカーに残っているものをすべて開く必要があります。文字を文字列にマッピングするか、ループすることで、置換を行うことができます。

この 3 行は、C++ では非常に単純です。

char * saved = prev;
for (; *prev && (*prev == *cur);  prev++, cur++ ); // "kill_common_beginning"
while (*prev) *(prev++) == '#' ? ...
while (*cur)  *(cur++) == '#' ? ...
cur = saved;

ただし、特殊なケースがあることに注意してください。インデントが変更されなかった場合、これらの行は何も出力しません。リストの外にいる場合は問題ありませんが、リスト内では問題ありません。その場合は、</li><li>手動で出力する必要があります。

于 2009-06-17T19:54:00.317 に答える
2

基本的な反復手法:

  1. リストの形式を認識し、各リスト項目 (追加レベルのインデントを含む) をキャプチャする正規表現またはその他の単純なパーサー。
  2. 現在のインデント レベルを追跡するためのカウンター。
  3. 現在のインデント レベルが前のインデント レベルより大きいか小さい場合は常に、各キャプチャを繰り返し、 を書き出し、<li>適切な開始/終了タグ ( <ol></ol>、 ) を挿入し、インデント カウンターをインクリメント/デクリメントするロジック。<ul></ul>

編集:これは、おそらく少し調整するだけでうまくいく簡単な式です:各一致は、名前付きキャプチャの2つのセット、マーカー(文字数はインデントレベル、最後の文字は目的のリストタイプを示します)を持つトップレベルのリストです) とリスト項目のテキスト。

(?:(?:^|\n)[\t ]*(?<marker>[*#]+)[\t ]*(?<text>[^\n\r]+)\r*(?=\n|$))+
于 2009-06-17T18:58:24.980 に答える
1

テキスタイルを見てください。

それは多くの言語で利用可能です。

于 2009-06-17T18:44:33.287 に答える
1

これは、正規表現サイクル(^改行、$終了行の略)でそれを行う方法です。

do { 
    ^#anything$ -> <ol><li>$^anything</li></ol>$
    ^*anything$ -> <ul><li>$^anything</li></ul>$
} while any of those above applies

do {
    </ol><ol> -> 
    </ul><ul> -> 
    </li><li> -> 
} while any of those above applies

これにより、単純な正規表現よりもはるかに簡単になります。仕組み:最初に各行を分離されているかのように展開しますが、次に追加のリストマーカーを使用します。

于 2009-06-17T18:46:01.503 に答える
1

これは、Shog9 の提案 (彼の正規表現のバリエーションであり、Ruby は名前付き一致をサポートしていません) と Ilya の反復メソッドのハイブリッドのようです。私の作業言語は Ruby でした。

いくつかの注意事項: 私はスタックベースのシステムを使用しましたが、その "String#scan(pattern)" は、実際には一致の配列を返す単なる "match-all" メソッドです。

def list(text)
  # returns [['*','text'],...]
  parts = text.scan(/(?:(?:^|\n)([#*]+)[\t ]*(.+)(?=\n|$))/)

  # returns ul/ol based on the byte passed in
  list_type = lambda { |c| (c == '*' ? 'ul' : 'ol') }

  prev = []
  tags = [list_type.call(parts[0][0][0].chr)]
  result = parts.inject("<#{tags.last}><li>") do |output,newline|
    unless prev.count == 0
      # the following comparison says whether added or removed,
      # this is the "how much"
      diff = (prev[0].length - newline[0].length).abs
      case prev[0].length <=> newline[0].length
        when -1: # new tags to add
          part = ((diff > 1) ? newline[0].slice(-1 - diff,-1) : newline[0][-1].chr)
          part.each_char do |c|
            tags << list_type.call(c)
            output << "<#{tags.last}><li>"
          end
        when 0: # no new tags... but possibly changed
          if newline[0] == prev[0]
            output << '</li><li>'
          else
            STDERR.puts "Bad input string: #{newline.join(' ')}"
          end
        when 1: # tags removed
          diff.times{ output << "</li></#{tags.pop}>" }
          output << '</li><li>'
      end
    end

    prev = newline
    output + newline[1]
  end

  tags.reverse.each { |t| result << "</li></#{t}>" }
  result
end

ありがたいことに、このコードは機能し、有効な HTML を生成します。そして、これは私が予想していたよりもうまくいきました。ごつごつした感じもありません。

于 2009-06-18T13:54:42.980 に答える
0

ゼラチンを試してみてください。構文の定義はおそらく5行以下になります。

于 2010-01-19T22:59:43.777 に答える
0

この Perl プログラムはその最初の試みです。

#! /usr/bin/env perl
use strict;
use warnings;
use 5.010;

my $data = [];
while( my $line = <> ){
  last if $line =~ /^[.]{3,3}$/;
  my($nest,$rest) = $line =~ /^([\#*]*)\s+(.*)$/x;
  my @nest = split '', $nest;

  if( @nest ){
    recourse($data,$rest,@nest);
  }else{
    push @$data, $line;
  }
}

de_recourse($data);

sub de_recourse{
  my($ref) = @_;
  my %de_map = (
    '*' => 'ul',
    '#' => 'ol'
  );

  if( ref $ref ){
    my($type,@elem) = @$ref;
    if( ref $type ){
      for my $elem (@$ref){
        de_recourse($elem);
      }
    }else{
      $type = $de_map{$type};

      say "<$type>";
      for my $elem (@elem){
        say "<li>";
        de_recourse($elem);
        say "</li>"
      }
      say "</$type>";
    }
  }else{
    print $ref;
  }
}

sub recourse{
  my($last_ref,$str,@nest) = @_;
  die unless @_ >= 2;
  die unless ref $last_ref;
  my $nest = shift @nest;

  if( @_ == 2 ){
    push @$last_ref, $str;
    return;
  }

  my $previous = $last_ref->[-1];
  if( ref $previous ){
    if( $previous->[0] eq $nest ){
      recourse( $previous,$str,@nest );
      return;
    }
  }

  my $new_ref = [ $nest ];
  push @$last_ref, $new_ref;
  recourse( $new_ref, $str, @nest );
}

それが役に立てば幸い

于 2009-06-18T05:43:11.597 に答える