如何在Ruby中解析打印格式样式的字符串

时间:2013-07-16 15:37:24

标签: ruby parsing

我注意到有几个工具使用这样格式化的字符串(给出一个简单的任意示例):

"Hour %h, minute %m, a total of %S seconds have passed"

其中%(字母)表示变量。例如,PHP的“strptime / strftime”命令使用%(字母)来表示日期/时间的组成部分。 Git提交日志打印机接受一个可选的格式参数,其中%(字母)表示提交的组件(日期,作者,描述等)。

我希望将这个逻辑应用到我自己的Ruby项目中。是否有可以解析这样的字符串的Ruby库或gem?

编辑:非常感谢所有回答的人,但我正在寻找的并不是像Date.strftime / strptime那样的专业任务。我的应用程序从网站下载一组数据,然后将其编译成文档,我希望用户能够选择该文档的格式。所以,如果我有

#<DataObject:0x007fec299de348
@id=123456,
@name="Important data",
@date="1/1/1",
@url="www.url.com">

并且用户输入如下格式字符串:

DATA OBJECT %i
%n
Created on %d
See %u for more

结果应如下所示:

DATA OBJECT 123456
Important data
Created on 1/1/1
See www.url.com for more

是否有一个像这样做一般的东西的库,或者我应该自己编写这个逻辑吗?

编辑#2:第一条评论说得对...一个简单的替换就可以了。

4 个答案:

答案 0 :(得分:3)

它不适用于您拥有的确切字符串,但稍加修改后,您可以使用String#%方法。

s = "Hour %{h}, minute %{m}, a total of %{S} seconds have passed"
s % {h: 3, m: 57, S: 24}
# => "Hour 3, minute 57, a total of 24 seconds have passed"

答案 1 :(得分:2)

编辑2 :这是一个简洁的通用解决方案,基于已编辑的问题:

# Replace %foo in format string with value of calling obj.foo
def custom_format(format, obj)
  format.gsub(/%([a-z]\w*)/i){ |s| obj.respond_to?($1) ? obj.send($1) : s }
end

formatter = "DATA OBJECT %id
%name
Created on %date
See %url for more %infoz
We are %pct% done."

# Create a simple class with accessor methods for these attributes
DataObject = Struct.new(:id,:name,:date,:url,:pct)
data = DataObject.new(123456,"Important data","1/1/1","www.url.com",57)

formatted = custom_format(formatter,data)
puts formatted
#=> DATA OBJECT 123456
#=> Important data
#=> Created on 1/1/1
#=> See www.url.com for more %infoz
#=> We are 57% done

该正则表达式允许%x%xyzzy,甚至%F13%z_x_y。它允许用户在任何地方使用文字%,只要它后面没有已知值。

请注意,如果您的对象没有访问者方法,则可以使用:

# Replace %foo in format string with value @foo inside obj
# If the value is `nil` or `false` the original placeholder will be used
def custom_format(format, obj)
  format.gsub(/%([a-z]\w*)/i){ |s| obj.instance_variable_get( :"@#{$1}" ) || s }
end

...但直接进入对象的实例变量可能不是最好的主意。


给定一般或特定“格式化字符串”

gs = "Hour %d, minute %d"
fs = "Hour %H, minute %M"

...您可以通过以下方式创建“格式化字符串”

  • sprintfString#%与一般字符串

    一起使用
    s = sprintf( gs, 1, 2 ) #=> "Hour 1, minute 2"
    s = gs % [1,2]          #=> "Hour 1, minute 2"
    
  • Time#strftime与时间对象一起使用(根据文档使用正确的占位符值):

    s = Time.now.strftime(fs) #=> "Hour 10, minute 08" 
    

...您可以通过拆分%

来'解析'格式化字符串
pieces = gs.split(/(%[^%\s])/) #=> ["Hour ", "%d", ", minute ", "%d"]

...在大多数情况下,您通常可以使用带有此代码的格式化字符串格式化字符串中提取值(仅经过轻微测试):

# With s="Hour 10, minute 08"
parts = s.match /\A#{fs.gsub(/%([^%\s])/o,'(?<\1>.+?)')}\z/
p parts[:H] #=> "10"
p parts[:M] #=> "08"

# If the formatting string uses the same placeholder more than once
# you will need to ask for the parts by index, not by name
parts = s.match /\A#{gs.gsub(/%([^%\s])/o,'(?<\1>.+?)')}\z/
p parts[1] #=> "10"
p parts[2] #=> "08"

魔术线噪声将格式化字符串转换为正则表达式,捕获并命名每个占位符:

"Hour %H, minute %M"
/\AHour (?<H>.+?), minute (?<M>.+?)\z/

将字符串与此正则表达式匹配时返回的MatchData会按名称和索引跟踪所有部分。


编辑:这是一个更强大的解决方案,可以使用格式化程序扫描字符串,该格式化程序可以处理sprintf格式化占位符,例如%-3d%0.3f

require 'strscan'
def scan_with_format( format, str )
  s = StringScanner.new(format)
  parts = []
  accum = ""
  until s.eos?
    if (a=s.scan( /%%/ )) || (b=s.scan( /%[^a-z]*[a-z]/i ))
      parts << Regexp.escape(accum) unless accum.empty?
      accum = ""
      parts << (a ? '%' : "(?<#{b[-1]}>.+?)")
    else
      accum << s.getch
    end
  end
  parts << Regexp.escape(accum) unless accum.empty?
  re = /\A#{parts.join}\z/
  str.match(re)
end

行动中:

formatter = "Hour %02d, minute %d, %.3fs $%d and %0d%% done"
formatted = formatter % [1, 2, 3.4567, 8, 90 ]
#=> "Hour 01, minute 2, 3.457s $8 and 90% done"

parts = scan_with_format(formatter, formatted)
#=> #<MatchData "Hour 01, minute 2, 3.457s $8 and 90% done" d:"01" d:"2" f:"3.457" d:"8" d:"90">

答案 2 :(得分:1)

您可以使用DateTime.strptime / DateTime#strftime

>> require 'date'
=> false
>> DateTime.strptime("Hour 14, minute 28, a total of 54 seconds have passed", "Hour %H, minute %M, a total of %S seconds have passed")
=> #<DateTime: 2013-07-17T14:28:54+00:00 ((2456491j,52134s,0n),+0s,2299161j)>
>> _.strftime('%H:%M:%S')
=> "14:28:54"

答案 3 :(得分:1)

为了替换普通字符串,有printf / sprintf

> printf('Hello %s', 'World')
Hello World

查看日期字符串Time#strftime

> Time.now.strftime('%Y-%m-%d %H:%M:%S')
=> "2013-07-16 23:49:52"