使用perl对存储在JSON中的utf8文件名进行规范化

时间:2013-07-02 19:19:58

标签: json perl utf-8

我有两个来自不同操作系统的Json文件。

这两个文件均以UTF-8编码,并包含UTF-8编码filenames

一个文件来自OS X,文件名为NFD格式:(od -bc

0000160   166 145 164 154 141 314 201 057 110 157 165 163 145 040 155 145
           v   e   t   l   a    ́  **   /   H   o   u   s   e       m   e

第二个包含相同的文件名,但是采用NFC形式:

000760   166 145 164 154 303 241 057 110 157 165 163 145 040 155 145 163
           v   e   t   l   á  **   /   H   o   u   s   e       m   e   s

据我所知,这称为“不同的规范化”,并且有一个CPAN模块Unicode::Normalize用于处理它。

我正在阅读下一个文件:

my $json1 = decode_json read_file($file1, {binmode => ':raw'}) or die "..." ;
my $json2 = decode_json read_file($file2, {binmode => ':raw'}) or die "..." ;

read_file来自File::Slurp和来自JSON::XS的decode_json。

将JSON读入perl结构,从一个json文件中,文件名进入key位置,第二个文件进入values。我需要搜索第一个哈希中的哈希值key 等价到第二个哈希值的value,因此需要确保它们与“二进制”相同。

尝试下一个:

 grep 'House' file1.json | perl -CSAD -MUnicode::Normalize -nlE 'print NFD($_)' | od -bc

 grep 'House' file2.json | perl -CSAD -MUnicode::Normalize -nlE 'print NFD($_)' | od -bc

为我产生相同的输出。

现在问题:

  • 如何简单地读取这两个json文件,以便将{em>规范化同为$hashrefs

或需要在decode_json之后运行像两个哈希一样?

while(my($k,$v) = each(%$json1)) {
    $copy->{ NFD($k) } = NFD($v);
}

简而言之:

  • 如何读取不同的JSON文件以在perl $href内部获得相同的规范化?可以在每个NFD key上明确地执行value并创建哈希的另一个NFD规范化(大)副本,从而实现更好的效果吗?

一些提示,建议 - 请...

因为我的英语非常糟糕,所以这是问题的模拟

use 5.014;
use warnings;

use utf8;
use feature qw(unicode_strings);
use charnames qw(:full);
use open qw(:std :utf8);
use Encode qw(encode decode);
use Unicode::Normalize qw(NFD NFC);

use File::Slurp;
use Data::Dumper;
use JSON::XS;

#Creating two files what contains different "normalizations"
my($nfc, $nfd);;
$nfc->{ NFC('key') } = NFC('vál');
$nfd->{ NFD('vál') } = 'something';

#save as NFC - this comes from "FreeBSD"
my $jnfc =  JSON::XS->new->encode($nfc);
open my $fd, ">:utf8", "nfc.json" or die("nfc");
print $fd $jnfc;
close $fd;

#save as NFD - this comes from "OS X"
my $jnfd =  JSON::XS->new->encode($nfd);
open $fd, ">:utf8", "nfd.json" or die("nfd");
print $fd $jnfd;
close $fd;

#now read them
my $jc = decode_json read_file( "nfc.json", { binmode => ':raw' } ) or die "No file" ;
my $jd = decode_json read_file( "nfd.json", { binmode => ':raw' } ) or die "No file" ;

say $jd->{ $jc->{key} } // "NO FOUND";    #wanted to print "something"

my $jc2;
#is here a better way to DO THIS?
while(my($k,$v) = each(%$jc)) {
    $jc2->{ NFD($k) } = NFD($v);
}
say $jd->{ $jc2->{key} } // "NO FOUND";    #OK

4 个答案:

答案 0 :(得分:1)

即使现在将一些文件名转换为相同的规范化进行比较可能很重要,但如果JSON数据具有不同的规范化,几乎任何地方都可能出现其他意外问题。

所以我的建议是将两个来源的整个输入规范化,作为进行任何解析之前的第一步(即,在读取文件的同时decode_json之前)。这不应该破坏任何JSON结构,因为它们是使用ASCII字符分隔的。那么你现有的perl代码应该能够盲目地假设所有UTF8字符具有相同的规范化。

$rawdata1 = read_file($file1, {binmode => ':raw'}) or die "...";
$rawdata2 = read_file($file2, {binmode => ':raw'}) or die "...";

my $json1 = decode_json NFD($rawdata1);
my $json2 = decode_json NFD($rawdata2);

为了使这个过程稍快一些(它应该已经足够快,因为模块使用快速的XS过程),你可以找出两个数据文件中的一个是否已经处于某种规范化形式,然后保留该文件保持不变,并将其他文件转换为该格式。

例如:

$rawdata1 = read_file($file1, {binmode => ':raw'}) or die "...";
$rawdata2 = read_file($file2, {binmode => ':raw'}) or die "...";

if (checkNFD($rawdata1)) {
    # then you know $file1 is already in Normalization Form D
    # (i.e., it was formed by canonical decomposition).
    # so you only need to convert $file2 into NFD
    $rawdata2 = NFD($rawdata2);
}
my $json1 = decode_json $rawdata1;
my $json2 = decode_json $rawdata2;

当然,您现在必须在开发时间进行实验,看看是否有一个或另一个输入文件已经处于规范化形式,然后在最终版本的代码中,您将不再需要条件语句,但只是将其他输入文件转换为相同的规范化形式。

另请注意,建议以NFC形式生成输出(如果您的程序生成任何将在以后存储和使用的输出)。请参阅此处,例如:http://www.perl.com/pub/2012/05/perlunicookbook-unicode-normalization.html

答案 1 :(得分:1)

嗯。我不能建议你一些更好的“编程”解决方案。但为什么不运行

perl -CSDA -MUnicode::Normalize -0777 -nle 'print NFD($_)' < freebsd.json >bsdok.json
perl -CSDA -MUnicode::Normalize -0777 -nle 'print NFD($_)' < osx.json     >osxok.json

现在你的脚本可以读取和使用它们,因为它们都在相同的规范化中?因此,在脚本中搜索som编程解决方案 ,然后在进入脚本之前解决问题。 (第二个命令是不必要的 - 在文件级别上进行简单转换。当然,遍历数据结构更容易......

答案 2 :(得分:1)

在搜索正确的解决方案时,我发现:软件是c * rp :)请参阅:https://stackoverflow.com/a/17448888/632407

无论如何,为您的特定问题找到了解决方案 - 如何使用文件名读取json而不管规范化:

而不是你的:

#now read them
my $jc = decode_json read_file( "nfc.json", { binmode => ':raw' } ) or die "No file" ;
my $jd = decode_json read_file( "nfd.json", { binmode => ':raw' } ) or die "No file" ;

使用下一个:

#now read them
my $jc = get_json_from_utf8_file('nfc.json') ;
my $jd = get_json_from_utf8_file('nfd.json') ;
...

sub get_json_from_utf8_file {
    my $file = shift;
    return
      decode_json      #let parse the json to perl
        encode 'utf8', #the decode_json want utf8 encoded binary string, encode it
          NFC          #conv. to precomposed normalization - regardless of the source
            read_file  #your file contains utf8 encoded text, so read it correctly
              $file, { binmode => ':utf8' } ;
}

这应该(至少我希望)确保分解使用JSON内容,NFC将其转换为预编译版本,JSON:XS将读取正确解析为相同的内部perl结构。

所以你的例子打印:

something

没有遍历$json

这个想法来自Joseph Myers和Nemo;)

也许一些更熟练的程序员会给出更多提示。

答案 3 :(得分:-1)

不要手动遍历数据结构,而是让模块为您处理。