或者,我们必须采用深奥的递归方法,或纯粹的蛮力?
我一直想知道这一段时间了。在我自己的反思中,我一次又一次地回到蛮力,奇怪的递归方法......但它似乎总是如此笨重。必须有更好的方法,对吧?
那么聪明的方式是什么?
有必要设置一个场景,所以这些是我的假设。
列表可以嵌套3个级别(至少),无序列表或有序列表。列表类型和深度由其前缀:
控制*****
将嵌套五个深度列表。*
或-
是无序列表,#
是无序列表。项目仅由1 \n
个字符分隔。 (让我们假设两个连续的新行符合“组”,段落,div或其他一些HTML标记,例如Markdown或Textile。)
列表类型可以自由混合。
输出应该是有效的HTML 4,最好是结尾</li>
s
可以根据需要使用或不使用Regex进行解析。
* 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>
你是如何这样做的?我真的很想理解处理不可预测的递归列表的好方法,因为它让任何人都纠缠不清,这让我感到很难受。
答案 0 :(得分:2)
<li>
并插入适当的开始/结束标记(<ol></ol>
,<ul></ul>
)并在每当当前缩进级别时递增/递减缩进计数器大于或小于前一个。编辑:这是一个简单的表达式,通过一些调整可能对你有用:每个匹配都是一个顶级列表,有两组命名的捕获,标记(字符数)是缩进级别,最后一个char表示所需的列表类型)和列表项文本。
(?:(?:^|\n)[\t ]*(?<marker>[*#]+)[\t ]*(?<text>[^\n\r]+)\r*(?=\n|$))+
答案 1 :(得分: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
和文本本身。将它实现为具有3个参数的C ++函数,输入和2个输出字符串是微不足道的。
核心是一个虚构的函数kill_common_beginning
,它将带走prev
和cur
的重复部分。之后,我需要关闭前一个标记中剩余的所有内容,并打开当前标记中剩余的所有内容。我可以通过替换,将字符映射到字符串或循环来实现。
这三行在C ++中非常简单:
char * saved = prev;
for (; *prev && (*prev == *cur); prev++, cur++ ); // "kill_common_beginning"
while (*prev) *(prev++) == '#' ? ...
while (*cur) *(cur++) == '#' ? ...
cur = saved;
但请注意,有一种特殊情况:当缩进没有改变时,这些行不输出任何内容。如果我们在列表之外,这很好,但是在列表中不是很好:所以在这种情况下我们应该手动输出</li><li>
。
答案 2 :(得分:2)
我见过的最佳解释来自Mark Jason Dominus的高阶Perl。全文可在http://hop.perl.plover.com/book/在线获取。
虽然这些例子都在Perl中,但每个区域背后的逻辑细分都很棒。
Chapter 8(!PDF链接)专门用于解析。尽管本书的教训有些相关。
答案 3 :(得分:1)
请看Textile。
它有多种语言版本。
答案 4 :(得分:1)
这是如何使用regexp 和循环来实现的(^
代表换行符,$
表示结束语:
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
这使它比简单的正则表达式简单得多。它的工作方式:首先将每条线展开,就像它被隔离一样,然后吃额外的列表标记。
答案 5 :(得分:1)
这是我自己的解决方案,它似乎是Shog9的建议(他的正则表达式的变体,Ruby不支持命名匹配)和Ilya的迭代方法的混合。我的工作语言是Ruby。
有些注意事项:我使用了基于堆栈的系统,而“String#scan(pattern)”实际上只是一个“匹配所有”方法,它返回一组匹配。
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。这确实比我预期的要好。它甚至不觉得笨重。
答案 6 :(得分:0)
#! /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 );
}
希望有所帮助
答案 7 :(得分:0)
试试Gelatin。语法定义可能是5行或更少。