Gsub、Partition和 StringScanner的解析

【翻译于 Jared White 的《Gsub Blocks, Partitions, and StringScanners, Oh My!》】

由于其 Perl 风格的传统,在处理文本时,Ruby 为您提供了很多开箱即用的灵活性。让我们深入研究什么是可能的!

在处理文本方面,Ruby 为您提供了很多开箱即用的灵活性,这不足为奇。毕竟,它起源于 90 年代,当时 Perl 正在崛起,Matz从这种以文本处理能力而闻名的语言中汲取了灵感。

最近我需要做一些解析工作,作为其中的一部分,我已经更加熟悉使用正则表达式查找文本以查找和可能替换标记的一些来龙去脉。这绝不是一份详尽的资源,但它应该让您大致了解日常 Ruby 编程中可能发生的事情。

Gsub

如果您需要在整个字符串中的一个或多个位置进行搜索和替换,这 gsub 通常是一种方法。我认为大多数 Ruby 爱好者在学习字符串操作时会很早就发现这种方法。

直到最近我才知道你可以将一个块传递给 gsub. 对于字符串中的每个匹配项,将评估该块,并且返回值将是该匹配项的替换。这意味着您可以编写代码,根据确切匹配的内容有条件地确定替换值!

例如,如果您想将 <div> 标签更改为 <span> 标签,但前提是没有属性,您可以编写如下内容:

"<div>This is a string</div>" \
"<div class='centered'>This is another string</div>"
.gsub(/(<.*?[ >])(.*?)(<\/.*?>)/) do |match|
    if $1.end_with?(" ")
    match
    else
    "<span>#{$2}</span>"
    end
end

# <span>This is a string</span><div class='centered'>This is another string</div>

现在这不是一个很好的例子,因为它不处理嵌套标签,但你明白了……)

如果您不熟悉捕获组,$1$2 正在引用第一个捕获组,它是一个开始标签(aka <div>)和第二个捕获组,它是标签内的文本。

gsub 还允许您提供一个哈希,其中匹配项将替换为匹配键的值:

"Foo is the nicest bar you'll ever meet."
.gsub(/Foo|bar/, "Foo" => "Joe", "bar" => "guy")

# Joe is the nicest guy you'll ever meet.

我怀疑块语法最终更有价值。

Partition

partition 方法允许您将字符串分成三部分:单个匹配之前的字符串部分、匹配本身以及匹配之后的所有内容。如果您在正则表达式中包含捕获组,您也可以使用它们。您可以利用这种类型的数据的一种方法是使用 partition 在字符串中搜索标记,并在转换标记时通过缓冲区构建一个新字符串。

假设您希望能够在您希望单词长度作为一种脚注出现在世界之后的单词周围放置冒号。你想 text :like: this 变成 text like(4) this.

以下是使用 partition、缓冲区和 until 循环编写它的方法:

string = "This is :something: you'll :want: to try :out: for yourself."
buffer = ""

until string.empty?
text, token, string = string.partition(/ :(.*?): /)

buffer << text

if token.length.positive?
    buffer << " #{$1}"
    buffer << "(#{$1.length}) "
end
end

puts buffer

# This is something(9) you'll want(4) to try out(3) for yourself.

现在,您可以使用 gsub 前面描述的块来执行此操作吗? 确实是的:

string = "This is :something: you'll :want: to try :out: for yourself."
string.gsub!(/ :(.*?): /) do
" #{$1}(#{$1.length}) "
end

puts string

事实上,这要简单得多。但是,在此示例中,您无权访问令牌之前或之后的任何文本。如果这对你来说很重要(也许你需要根据它之前或之后的不同来处理令牌),你会想要使用partition.

还是你会??还有另一种方法!

StringScanner

使用 StringScanner 就像将火箭筒带到彩弹比赛中一样。它非常强大,但它也会给你带来一些严重的麻烦——更不用说如果你不小心的话,会有点心烦意乱。

StringScanner 实际上是标准库 ( stdlib ) 中的 Ruby 类的名称,您需要通过添加 require "strscan" 到代码顶部来导入它。您通过使用字符串实例化扫描仪来使用它,然后使用各种方法扫描字符串以查找模式并推进“指针”。

假设您想将字符串中的“cake”替换为“pie”,但如果关键字前面有“short”或后面有“pops”,则不需要。我们将使用缓冲区并像前面的示例一样进行字符串替换,但是因为我们拥有扫描仪的所有优点,所以很容易向后和向前查看并确定我们的下一步行动。

require "strscan"

string = "Let them eat cake and then more shortcake and finally cake pops!"
scanner = StringScanner.new(string)
buffer = ""

until scanner.eos?
portion = scanner.scan_until(/cake/)
if portion.nil?
    buffer << scanner.rest
    scanner.terminate
    next
end
unless scanner.pre_match =~ /short$/ or scanner.check(/\s+pops/)
    buffer << portion.sub(/cake/, "pie")
else
    buffer << portion
end
end

puts buffer

# Let them eat pie and then more shortcake and finally cake pops!

哇,这是怎么回事?

首先,我们建立一个 until scanner.eos? 循环。这意味着循环将迭代直到我们到达字符串的末尾。

scan_until方法查找模式并将当前指针前进到该位置。puts scanner.pointer (您可以通过在下面添加来验证这一点scan_until。)它返回与模式匹配的字符串部分,因此我们可以使用它来执行字符串替换,将“cake”更改为“pie”。

但是,如果 cake 紧接在“short”之前,我们不想进行替换,因此我们将检查部分 ( scanner.pre_match ) 之前的所有内容的正则表达式匹配,以查看它是否以“short”结尾。我们还想检查字符串的下一部分是否是单词“pops”,所以我们将使用该 scanner.check 方法。这会检查字符串中紧随其后的内容,但不会推进指针。(还有一种类似于check_until 的方法scan_until。)通过不推进指针,我们避免弄乱我们在字符串中的位置,并且可以继续正常循环。

循环顶部附近的 if portion.nil? 块处理字符串中没有更多“cake”实例但我们需要考虑的字符串还有更多的情况。通过将 .rest 字符串的 加入我们的缓冲区并调用 scanner.terminate ,我们强制扫描器前进到字符串的末尾,在这种情况下 until scanner.eos? 将评估为真并结束循环。

这个例子相当简单,因为它只是将一个单词更改为另一个单词,因此替换本身不需要任何花哨的正则表达式。但是结合 StringScanner 我们已经学过的所有技术( gsub 块,甚至partition),您可以构建极其复杂的例程来处理几乎任何可以想象的文本处理。

概括
唷,有很多东西要吸收!今天你已经倾向于这 gsub 不仅仅是一种说“a”应该变成“b”的方式。通过提供一个块,您可以通过首先检查源字符串的每个匹配项来精确控制替换字符串。

此外,partition string 方法允许您将字符串划分为匹配前、匹配和后匹配组件,通过在循环中反复执行此操作并使用缓冲区,您可以逐段转换大而复杂的字符串-部分。

最后,为了最精确地控制在文本中搜索一个或多个标记,并根据这些标记与文本其余部分的关系执行精细的搜索和替换操作,StringScanner 对象就在那里等待释放其全部力量。不仅如此,您的代码还可以从以前的技术中受益, StringScanner 以最大限度地发挥 Ruby 文本处理能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值