如何在Ruby中对此字符串进行标记?

时间:2009-04-03 11:28:35

标签: ruby parsing tokenize text-parsing

我有这个字符串:

%{Children^10 Health "sanitation management"^5}

我想将其转换为将其标记为哈希数组:

[{:keywords=>"children", :boost=>10}, {:keywords=>"health", :boost=>nil}, {:keywords=>"sanitation management", :boost=>5}]

我知道StringScanner和Syntax gem,但我找不到足够的代码示例。

任何指针?

3 个答案:

答案 0 :(得分:17)

对于真正的语言,词法分析器是最佳选择 - like Guss said。但是如果完整的语言只是像你的例子一样复杂,你可以使用这个快速的黑客:

irb> text = %{Children^10 Health "sanitation management"^5}
irb> text.scan(/(?:(\w+)|"((?:\\.|[^\\"])*)")(?:\^(\d+))?/).map do |word,phrase,boost|
       { :keywords => (word || phrase).downcase, :boost => (boost.nil? ? nil : boost.to_i) }
     end
#=> [{:boost=>10, :keywords=>"children"}, {:boost=>nil, :keywords=>"health"}, {:boost=>5, :keywords=>"sanitation management"}]

如果您正在尝试解析常规语言,那么这种方法就足够了 - 尽管使语言不规则并不会带来更多复杂性。

正则表达式的快速细分:

  • \w+匹配任何单项关键字
  • (?:\\.|[^\\"]])*使用非捕获括号((?:...))来匹配转义双引号字符串的内容 - 转义符号(\n\",{{ 1}}等等或任何不是转义符号或结束引号的单个字符。
  • \\仅捕获引用的关键字词组的内容。
  • "((?:\\.|[^\\"]])*)"匹配任何关键字 - 单个术语或短语,将单个术语捕获为(?:(\w+)|"((?:\\.|[^\\"])*)"),将短语内容捕获到$1
  • $2匹配一个号码。
  • \d+会在插入符后面捕获一个数字(\^(\d+))。由于这是第三组捕获括号,因此它将被限制为^
  • $3在插入符后面捕获一个数字(如果它在那里),否则匹配空字符串。

(?:\^(\d+))?尽可能多地匹配字符串的正则表达式,输出“匹配”数组。如果正则表达式包含捕获的parens,则“匹配”是捕获的项目数组 - 因此String#scan(regex)变为$1match[0]变为$2等。任何捕获括号都不会在得到的“匹配”中,将部分字符串映射与match[1]条目匹配。

然后nil接受这些匹配,使用一些块魔法将每个捕获的术语分解为不同的变量(我们可以完成#map),然后创建所需的哈希值。正好do |match| ; word,phrase,boost = *matchword中的一个将是phrase,因为两者都无法与输入匹配,因此nil将返回非(word || phrase)一个,nil会将其转换为全部小写。 #downcase会将字符串转换为整数,boost.to_i会确保(boost.nil? ? nil : boost.to_i)提升为nil

答案 1 :(得分:12)

这是使用StringScanner的非健壮示例。这是我刚刚从Ruby Quiz: Parsing JSON改编的代码,它有一个很好的解释。

require 'strscan'

def test_parse
  text = %{Children^10 Health "sanitation management"^5}
  expected = [{:keywords=>"children", :boost=>10}, {:keywords=>"health", :boost=>nil}, {:keywords=>"sanitation management", :boost=>5}]


  assert_equal(expected, parse(text))
end

def parse(text)
  @input = StringScanner.new(text)

  output = []

  while keyword = parse_string || parse_quoted_string
    output << {
      :keywords => keyword,
      :boost => parse_boost
    }
    trim_space
  end

  output
end

def parse_string
  if @input.scan(/\w+/)
    @input.matched.downcase
  else
    nil
  end
end

def parse_quoted_string
  if @input.scan(/"/)
    str = parse_quoted_contents
    @input.scan(/"/) or raise "unclosed string"
    str
  else
    nil
  end
end

def parse_quoted_contents
  @input.scan(/[^\\"]+/) and @input.matched
end

def parse_boost
  if @input.scan(/\^/)
    boost = @input.scan(/\d+/)
    raise 'missing boost value' if boost.nil?
    boost.to_i
  else
    nil
  end
end

def trim_space
  @input.scan(/\s+/)
end

答案 2 :(得分:3)

你在这里有一个任意的语法,并解析它你真正想要的是一个词法分析器 - 你可以编写一个描述你的语法的语法文件,然后使用词法分析器从你的语法生成一个递归的解析器。

编写词法分析器(甚至是递归解析器)并不是一件容易的事 - 虽然它在编程中是一个很有用的练习 - 但你可以在这个电子邮件中找到一个Ruby词法分析器/解析器列表:http://newsgroups.derkeiler.com/Archive/Comp/comp.lang.ruby/2005-11/msg02233.html < / p>

RACC作为Ruby 1.8的标准模块提供,所以我建议你专注于它,即使它的手册不是很容易理解,也需要熟悉yacc。