匹配多行字符串中的重复组

时间:2014-12-12 12:41:30

标签: ruby regex string

我有这样的自定义语法:

###############
Heading 1
###############

Body1
Body1

###############
Heading 2
###############

Body2
Body2

我想我可以用scan拆分每个部分,但由于“Ruby - Splitting multiple strings with scan”中解释的原因,这并不是那么简单。

理想情况下,我只想指定每个部分的DRY正则表达式,例如:

/^\#+\s+(^.*)\#+\s+(^.*)\s+/

投掷scan或类似内容会为每个部分返回[headerText, bodyText]数组。

(我意识到这与MarkDown一样,但我想添加一个自定义结构和类属性。)

6 个答案:

答案 0 :(得分:4)

您知道您的示例有效吗?Markdown?

您可以让Markdown处理器为您完成工作。一个例子是Kramdown。除了转换为各种输出格式之外,它还可以创建自己的类似DOM的内部表示,您可以遍历:

require 'kramdown'

d = Kramdown::Document.new(text)

puts d.root.children.map(&:type)
#=>  [:header, :p, :blank, :p, :blank, :header, :p, :blank, :p]

您可以使用各种方法来获取内容。

事实上,如果你坚持使用Markdown作为你的格式,你可以获得很多表现力,而且只需要很少的努力。

*虽然有效,但建议对标题格式进行微小更改,以便标记以识别标题文本

答案 1 :(得分:2)

text.scan /(?<=#\n)([^#]+)\n*#*\n*([^#]+)\n*#*/

试试这个。抓住捕获。参见演示。

https://regex101.com/r/eZ0yP4/24

答案 2 :(得分:0)

尝试以下

source = <<EOF
###############
Heading 1
###############

Body1
Body1

###############
Heading 2
###############

Body2
Body2
EOF
groups = source.scan /(#*\n([^#]*)#*\n([^#]*))/
groups[0][1,2]
groups[1][1,2]

我得到输出

["Heading 1\n", "\nBody1\nBody1\n\n"]
["Heading 2\n", "\nBody2\nBody2\n"]

扫描包括组的组,因此组是数组的数组,最外面的数组是每个Header,Body组,第一个索引包括两个,索引1和2是Header和Body。

要访问所有组,请使用

groups.each do | group |
    puts group[1,2]
end

应该导致类似

的内容
Heading 1

Body1 
Body1 

Heading 2

Body2 
Body2 

答案 3 :(得分:0)

String#scan一起使用时,这个正则表达式可以为您提供所需内容:

/(#+)(?<heading>[^#]*)(#+)(?<body>[^#\z]*)/

以下是我使用示例字符串收到的输出。

regex = /(#+)(?<heading>[^#]*)(#+)(?<body>[^#\z]*)/
string = "###############\nHeading 1\n###############\n\nBody1\nBody1\n\n###############\nHeading 2\n###############\n\nBody2\nBody2"

string.scan regex
=> [["\nHeading 1\n", "\n\nBody1\nBody1\n\n"], ["\nHeading 2\n", "\n\nBody2\nBody2"]]

拉出的字符串有一些需要删除的换行符。我试图改进正则表达式来消除它们,但是很难让最后一部分的主体正确解析。虽然清理起来并不算太糟糕。

string.scan(regex).map { |section| section.map(&:strip) }
=> [["Heading 1", "Body1\nBody1"], ["Heading 2", "Body2\nBody2"]]

答案 4 :(得分:0)

这是使用slice_before的好机会:

text = <<EOT
###############
Heading 1
###############

Body1
Body1

###############
Heading 2
###############

Body2
Body2
EOT

chunks = text.split("\n")
             .reject{ |s| s.strip.empty? || s[/^#+$/] }
             .slice_before(/^Heading/)
             .to_a
chunks # => [["Heading 1", "Body1", "Body1"], ["Heading 2", "Body2", "Body2"]]

这里发生了什么:

text.split("\n").reject{ |s| s.strip.empty? || s[/^#+$/] }
# => ["Heading 1", "Body1", "Body1", "Heading 2", "Body2", "Body2"]

slice_before遍历数组,寻找与其模式匹配的内容。一旦找到它,它就会生成一个新的子数组并继续查找。最终结果是一个数组数组,每个子数组包含每个目标之间的元素/行。

答案 5 :(得分:0)

虽然不像单个正则表达式那样紧凑,但以下方法可能更容易调试,测试和理解:

str.gsub(/^#+\n\n+/,'')
   .gsub(/^#+\n/,'')
   .split(/\n\n+/)
   .map { |s| s.split("\n") }
  #=> [["Heading 1", "Body1", "Body1"], ["Heading 2", "Body2", "Body2"]]

我通过以下四个步骤中的每个步骤构建此表达式,对其进行测试,然后继续进行下一步。当所有步骤都正常工作时,我只是将它们链接在一起。我假设块被一个或多个空行分隔。

str =<<THE_END
###############
Heading 1
###############

Body1
Body1

###############
Heading 2
###############

Body2
Body2
THE_END

# remove lines ###\n\n+ between each heading and body
s1 = str.gsub(/^#+\n\n+/,'')
  #=> "###############\nHeading 1\nBody1\nBody1\n\n" +
  #   "###############\nHeading 2\nBody2\nBody2\n"

# remove line ###\n above each header
s2 = s1.gsub(/^#+\n/,'')
  #=> "Heading 1\nBody1\nBody1\n\n" +
  #   "Heading 2\nBody2\nBody2\n"

# split on remaining blank lines
s3 = s2.split(/\n\n+/)
  #=> ["Heading 1\nBody1\nBody1", "Heading 2\nBody2\nBody2\n"]

# split each string in array into heading and body elements
s3.map { |s| s.split(/\n/) }
  #=> [["Heading 1", "Body1", "Body1"], ["Heading 2", "Body2", "Body2"]]