6

我在通过 YAML 在 Perl 和 Ruby 之间交换数据时遇到问题。我有一些看起来像 number:number 的值,例如1:16.

Perl 的 YAML 库(Tiny 和 XS)将其编码为1:16不带引号。Ruby 的 YAML 库 (Psych) 不会将其解释为字符串,而是以某种方式变成了 Fixnum 值4560。我无法弄清楚如何在任何一方解决此转换问题。

我的用例的 YAML 中的每个值都应该是一个对象或字符串。因此,如果存在这样的选项,我可以告诉 Perl YAML 库引用所有值。或者有什么方法可以告诉 Ruby YAML 库将所有​​值解释为字符串?有任何想法吗?

从逻辑上讲,更改任何一方的语言都不是一种选择。

珀尔:

use YAML::XS qw(DumpFile);
my $foo={'abc'=>'1:16'};
DumpFile('test.yaml',$foo);

红宝石:

require('yaml')
foo=YAML.load_file('test.yaml')
puts(foo['abc'])

Ruby 代码将打印4560. 其中一条评论弄清楚了您是如何45601:161 小时 16 分钟转换为秒的。嗯,好吧。

4

3 回答 3

6

根据Yaml 1.1 规范1:16是十六进制(以 60 为底)格式的整数。

另请参阅http://yaml.org/type/int.html,其中说:

使用“:”可以表示以 60 为基数的整数,这便于时间和角度值。

Ruby 中包含的 Yaml 解析器 Psych识别这种格式并将值转换为整数(错误地,1:16应该是 71 - Psych 代码似乎假定所有这些值都将采用这种形式a:b:c,但正则表达式不会强制执行) . Perl 发射器(至少是我测试过的 YAML::XS)无法识别这种格式,因此在写入文件时不会引用字符串。YAML::XS确实识别并引用了一些整数,但不是全部。YAML::XS 也无法识别 Psych 可以识别的许多其他格式(例如日期)。

(看来,六十进制格式已从 Yaml 1.2 规范中删除。)

Psych 在解析中提供了相当大的灵活性——YAML.load_file只是常见用例的简单接口。

您可以使用parsePsych 的方法创建 yaml 的树表示,然后使用自定义ScalarScanner(将某些格式的字符串转换为适当的 Ruby 类型的对象)将其转换为 Ruby 数据结构:

require('yaml')

class MyScalarScanner < Psych::ScalarScanner
  def tokenize string
    #this is the same regexp as Psych uses to detect base 60 ints:
    return string if string =~ /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+$/
    super
  end
end

tree = YAML::parse_file 'test.yaml'
foo = Psych::Visitors::ToRuby.new(MyScalarScanner.new).accept tree

这与您使用 时发生的过程基本相同YAML.load_file,只是它使用自定义的扫描仪类。

一种类似的替代方法是开放并用定制的方法ScalarScanner替换该方法。tokenize这将允许您使用更简单的load_file界面,但通常需要注意猴子修补类:

class Psych::ScalarScanner
  alias :orig_tokenize :tokenize
  def tokenize string
    return string if string =~ /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+$/
    orig_tokenize string
  end
end

foo = YAML.load_file 'test.yaml'

请注意,这些示例仅考虑格式为1:16. 根据您的 Perl 程序发出的内容,您可能还需要覆盖其他模式。您可能特别想看的一个是六十进制浮点数(例如1:16.44)。

于 2012-09-27T16:38:56.783 回答
1

您正在使用的解析器中存在错误。似乎认为1:16是某种时间(因为 4560 是一小时 16 分钟内的秒数),但我找不到任何可以验证这种解释的东西。

最好的解决方案是使用没有错误的解析器。

  • libyaml,由 YAML::XS 使用,据说有 Ruby 绑定。
  • libsyck,由 YAML::Syck 使用,据说有 Ruby 绑定。

另一种方法是生成 YAML,其中始终引用字符串(或至少在将它们视为时间时)。

YAML::Syck可以选择这样做。

$ perl -e'
   use YAML::Syck qw( Dump );
   local $YAML::Syck::SingleQuote = 1;
   print(Dump({abc=>"1:16"}));
'
--- 
"abc": '1:16'

(不知道我之前是怎么错过这个选项的!)

于 2012-09-27T00:00:49.003 回答
-4

Ruby 将所有 YAML 条目解释为字符串,除非它们适合少数特殊格式​​。该条目1:16看起来与特殊格式匹配了一段时间,因此 Ruby 错误地解释了它。

您需要强制 Ruby 将该字段解释为字符串。有两种方法可以做到这一点。以下任一 YAML 输出都应为您提供所需的结果:

abc: !str 1:16
abc: '1:16'

要生成此输出,请尝试以下 Perl 代码:

my $foo={'abc'=>'!str 1:16'};
my $foo={'abc'=>"'1:16'"};

更新: 我能够使用以下代码在 Perl 和 Ruby 之间传递数据:

珀尔:

use YAML::XS qw(DumpFile);
my $foo={'abc'=>'1:16'};
DumpFile('test.yaml',$foo);

红宝石:

require 'yaml'
foo=YAML.parse_file('test.yaml')
foo['abc'].value
=> "1:16"
foo['abc'].value.class
=> String

结果使用起来比load_file返回的简单哈希要复杂一些,但看起来它至少可以按预期解析文件。

于 2012-09-26T21:26:44.383 回答