2012-09-26 52 views
5

我在通過YAML在Perl和Ruby之間交換數據時遇到了問題。我有一些看起來像number:number的值,如1:16Perl與Ruby之間的YAML數據交換問題

Perl的YAML庫(Tiny和XS)將其編碼爲1:16而不含引號。 Ruby的YAML庫(Psych)不會將其解釋爲字符串,而是以某種方式成爲Fixnum值4560。我無法弄清楚如何解決這個轉換問題。

對於我的用例,YAML中的每個值應該是一個對象或字符串。所以,如果存在這樣的選項,我可以告訴Perl YAML庫引用所有值。或者有什麼辦法可以告訴Ruby YAML庫將所有值解釋爲字符串?有任何想法嗎?

改變任何一方的語言在邏輯上不是一種選擇。

的Perl:

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。其中一條評論告訴你如何從1:16得到4560,這是1小時16分鐘轉換爲秒數。呃,好吧。

+1

您能否提供一些示例YAML數據以及您用於解碼的Ruby和Perl代碼? – Schwern

+3

4560是1小時16分鐘內的秒數(假設沒有閏秒)。 – ikegami

+1

生成的YAML文件是什麼樣的? – bta

回答

5

按照Yaml 1.1 spec1:16是六十進制整數(基地60)格式。

http://yaml.org/type/int.html見,這表示:

使用「:」允許在基座60,這是方便的時間和角度值表達整數。

包括在紅寶石,精極度緊張,recognises this format and converts the value into an integer YAML的解析器(錯,1:16 768,16是71 - 精極度緊張的代碼似乎asume的所有這樣的值將是在形式a:b:c但是該正則表達式並不強制)。 Perl發射器(至少是我測試過的YAML :: XS)不能識別這種格式,所以在寫入文件時不要引用字符串。 YAML :: XS 確實識別並引用一些整數,但不是全部。 YAML :: XS也不能識別Psych所做的許多其他格式(例如日期)。

(看來,六十進制格式has been removed from the Yaml 1.2 spec

精極度緊張允許在其分析的靈活性相當便宜 - YAML.load_file僅僅是爲了共同使用情況的簡單接口。

您可以使用Psych的parse方法創建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)。

-4

Ruby將所有YAML條目解釋爲字符串,除非它們適合a handful of special formats。條目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之間的數據使用以下代碼:

Perl:

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回報,但它看起來像它的至少解析文件按預期。

+0

分別正確產生'abc:'!str 1:16''和'abc:'''1:16''''。我相信在Ruby中都不會產生正確的結果。 – ikegami

+0

'1:16'與您鏈接的文檔中列出的任何格式都不匹配。 – ikegami

+0

@ikegami-它與簡單時間的格式相匹配,但該頁面上的有限示例使用更復雜的樣本。 Ruby爲日期和時間對象提供了許多可能的輸出格式選項,看起來YAML解析器正在標記任何可能是日期的東西。我的Perl技能非常生疏,所以你可能需要調整它以獲得我列出的格式的YAML輸出。 – bta

1

您正在使用的解析器中存在一個錯誤。似乎認爲1:16是某種時間(因爲4560是1小時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' 

(不知道我怎麼錯過了這個選項前面!)

+0

我試過YAML :: XS。它有同樣的問題。 YAML :: Syck讓我非常緊張。以下是來自CPAN頁面的引用:YAML :: Syck - 「此模塊有許多已知問題,並且自2007年以來只進行了半主動維護。如果遇到問題,它可能不會被修復,除非您提供在Git中準備發佈補丁。「 Ruby文檔說基本上是一樣的東西。 –

+0

我沒有說你應該使用YAML :: XS - 其實你已經根據你所說的 - 我說你應該嘗試在Ruby中使用libyaml或libsyck \。 – ikegami