Language agnostic 有没有一种聪明的方法可以将纯文本列表解析为HTML? 问:有没有一种聪明的方法可以将纯文本列表解析为HTML?

Language agnostic 有没有一种聪明的方法可以将纯文本列表解析为HTML? 问:有没有一种聪明的方法可以将纯文本列表解析为HTML?,language-agnostic,list,text-parsing,Language Agnostic,List,Text Parsing,或者,我们必须求助于深奥的递归方法,还是纯粹的暴力 我想知道这件事已经有一段时间了。在我自己的沉思中,我一次又一次地回到蛮力和奇怪的递归方法。。。但它看起来总是那么笨重。一定有更好的办法,对吧 那么聪明的方法是什么呢 假设 有必要设置一个场景,所以这些是我的假设 列表可以嵌套在无序列表或有序列表的3层深处(至少)。列表类型和深度由其前缀控制: 前缀后面有一个必填空格 列表深度由前缀中的非间隔字符数控制****将嵌套在五个列表中 列表类型由字符类型强制执行,*或-为无序列表,为无序列表 项目仅由

或者,我们必须求助于深奥的递归方法,还是纯粹的暴力

我想知道这件事已经有一段时间了。在我自己的沉思中,我一次又一次地回到蛮力和奇怪的递归方法。。。但它看起来总是那么笨重。一定有更好的办法,对吧

那么聪明的方法是什么呢

假设 有必要设置一个场景,所以这些是我的假设

  • 列表可以嵌套在无序列表或有序列表的3层深处(至少)。列表类型和深度由其前缀控制:

  • 前缀后面有一个必填空格
  • 列表深度由前缀中的非间隔字符数控制<代码>****将嵌套在五个列表中
  • 列表类型由字符类型强制执行,
    *
    -
    为无序列表,
    为无序列表
  • 项目仅由1个
    \n
    字符分隔。(让我们假设两个连续的新行符合“组”、段落、div或其他HTML标记(如Markdown或Textile中的标记)的条件。)

  • 列表类型可以自由混合

  • 输出应为有效的HTML4,最好以
  • s结尾

  • 可以根据需要使用或不使用正则表达式进行解析

  • 样本标记 期望输出 为了可读性,将其拆分了一点,但它应该是这个的一个有效变体(请记住,我只是很好地将其隔开!):

    • 名单
    • 名单
      • 列表
        • 名单
        • 名单
        • 名单
      • 名单
        • 列表
          • 名单
          • 名单
            • 列表
              • 名单
    总之 你是怎么做到的?我真的很想了解处理不可预测的递归列表的好方法,因为对任何人来说,它都是一个丑陋的烂摊子


    它有多种语言可供使用。

    这是如何使用regexp和cycle实现的(
    ^
    代表换行符,
    $
    代表换行符):

    do{
    ^#任何东西$->
  • $^任何东西
  • $ ^*任何东西$->
    • $^任何东西
      • $ }而上述任何一项均适用 做{ ->
            ->
          • -> }而上述任何一项均适用
    这使得它比简单的regexp简单得多。其工作方式:您首先将每一行展开,就好像它是孤立的一样,然后使用额外的列表标记。

    基本迭代技术:
  • 一个正则表达式或其他一些简单的解析器,可以识别列表的格式,捕获每个列表项(包括具有额外缩进级别的列表项)
  • 用于跟踪当前缩进级别的计数器
  • 循环每次捕获的逻辑,写出
  • 并插入适当的开始/结束标记(
      ),并在当前缩进级别大于或小于前一缩进级别时递增/递减缩进计数器

    • 编辑:这里有一个简单的表达式,可能需要稍加调整:每个匹配项都是一个顶级列表,包含两组命名捕获、标记(字符数是缩进级别,最后一个字符表示所需的列表类型)和列表项文本

      (?:(?:^|\n)[\t ]*(?<marker>[*#]+)[\t ]*(?<text>[^\n\r]+)\r*(?=\n|$))+
      
      (?:(?:^\n)[\t]*(?[*.]+)[\t]*(?[^\n\r]+)\r*(?=\n |$)+
      
      具有一些Python概念的逐行解决方案:

      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 
      

      但是,请注意,有一种特殊情况:当缩进没有改变时,这些行不会输出任何内容。如果我们不在列表中,这很好,但在列表中则不好:因此,在这种情况下,我们应该手动输出

      我所看到的最好解释是来自Mark Jason Dominus的高阶Perl。全文可在以下网址查阅:

      虽然这些例子都是用Perl编写的,但对每个领域背后的逻辑进行分解是非常棒的

      (!PDF link)专门用于解析。尽管本书中的课程有些关联。

      这个Perl程序是第一次尝试。
      #/usr/bin/env perl
      严格使用;
      使用警告;
      使用5.010;
      我的$data=[];
      while(我的$line=){
      最后一个if$line=~/^[.]{3,3}$/;
      我的($nest,$rest)=$line=~/^([\\\*]*)\s+(.*)$/x;
      my@nest=拆分“”,$nest;
      如果(@nest){
      追索权($data,$rest,@nest);
      }否则{
      推送@$数据,$行;
      }
      }
      de_追索权(数据);
      次级追索权{
      我的($ref)=@;
      我的%de_映射=(
      “*”=>“ul”,
      “#”=>“ol”
      );
      如果(参考$ref){
      我的($type,@elem)=@$ref;
      如果(参考$type){
      对于我的$elem(@$ref){
      de_追索权($elem);
      }
      }否则{
      $type=$DEU map{$type};
      说“”;
      我的$elem(@elem){
      说“
    • ”; de_追索权($elem); 说“
    • ” } 说“”; } }否则{ 打印$ref; } } 次级追索权{ 我的($last\u ref,$str,@nest)=; 除非@>=2,否则为模具; 死亡,除非参考$last\u ref; 我的$nest=shift@nest; 如果(@==2){ 推送@$last_ref,$str; 返回; } my$previous=$last_ref->[-1]; 如果(参考$previous){ 如果($previous->[0]eq$nest){ 追索权($previous,$str,@nest); 返回; } } 我的$new_ref=[$nest]; 推送@$last\u ref,$new\u ref; 追索权($new_ref,$str,@nest); }

      希望它能有所帮助

      这里是我自己的解决方案,它似乎是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
      
      def列表(文本)
      #返回[['*','text'],…]
      零件=文本。扫描(/(?):(
      
      (?:(?:^|\n)[\t ]*(?<marker>[*#]+)[\t ]*(?<text>[^\n\r]+)\r*(?=\n|$))+
      
      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 
      
      char * saved = prev;
      for (; *prev && (*prev == *cur);  prev++, cur++ ); // "kill_common_beginning"
      while (*prev) *(prev++) == '#' ? ...
      while (*cur)  *(cur++) == '#' ? ...
      cur = saved;
      
      #! /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 );
      }
      
      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