2009-05-10 14 views
5

मेरा पर्ल ऐप और MySQL डेटाबेस अब आने वाले यूटीएफ -8 डेटा को सही तरीके से संभालता है, लेकिन मुझे पूर्व-मौजूदा डेटा को कन्वर्ट करना होगा। ऐसा लगता है कि कुछ डेटा सीपी -1252 के रूप में एन्कोड किए गए हैं और यूटीएफ -8 के रूप में एन्कोड किए जाने से पहले और MySQL में संग्रहीत किए जाने से पहले डीकोड नहीं किए गए हैं। मैंने O'Reilly लेख Turning MySQL data in latin1 to utf8 utf-8 पढ़ा है, लेकिन हालांकि इसे अक्सर संदर्भित किया जाता है, यह एक निश्चित समाधान नहीं है।मैं संग्रहीत misencoded डेटा कैसे परिवर्तित करूं?

मैंने Encode::DoubleEncodedUTF8 और Encoding::FixLatin पर देखा है, लेकिन न ही मेरे डेटा पर काम किया है।

यह मैं अब तक क्या किया है है:

#Return the $bytes from the DB using BINARY() 
my $characters = decode('utf-8', $bytes); 
my $good = decode('utf-8', encode('cp-1252', $characters)); 

कि अधिकांश मामलों को ठीक करता है, लेकिन अगर proplerly एन्कोड रिकॉर्ड के खिलाफ चलाने के लिए, यह उनके mangles। मैंने Encode::Guess और Encode::Detect का उपयोग करने का प्रयास किया है, लेकिन वे ठीक से एन्कोड किए गए और गलत तरीके से रिकॉर्ड किए गए रिकॉर्ड के बीच अंतर नहीं कर सकते हैं। तो रूपांतरण के बाद \x{FFFD} character पाया जाता है तो मैं रूपांतरण को पूर्ववत करता हूं।

कुछ रिकॉर्ड, हालांकि, केवल आंशिक रूप से परिवर्तित हो गए हैं। यहां एक उदाहरण दिया गया है जहां बाएं घुंघराले उद्धरण ठीक से परिवर्तित हो जाते हैं, लेकिन सही घुंघराले उद्धरण उलझ जाते हैं।

perl -CO -MEncode -e 'print decode("utf-8", encode("cp-1252", decode("utf-8", "bob\xC3\xAF\xC2\xBF\xC2\xBDs")))' 

मैं भी डबल एन्कोडेड डेटा यहाँ के साथ काम कर रहा हूँ:

perl -CO -MEncode -e 'print decode("utf-8", encode("cp-1252", decode("utf-8", "\xC3\xA2\xE2\x82\xAC\xC5\x93four score\xC3\xA2\xE2\x82\xAC\xC2\x9D")))' 

और और यहाँ एक उदाहरण है जहां एक सही एकल उद्धरण रूपांतरण नहीं किया है? इन अभिलेखों को बदलने के लिए मुझे और क्या करना चाहिए?

उत्तर

6

"चार स्कोर" उदाहरण के साथ, यह लगभग निश्चित रूप से दोगुनी-एन्कोडेड डेटा है।

  1. cp1252 डेटा कि UTF8 प्रक्रिया के लिए एक cp1252 के माध्यम से दो बार चलाया गया था, या
  2. UTF8 डेटा कि

(स्वाभाविक रूप से UTF8 प्रक्रिया के लिए एक cp1252 के माध्यम से चलाया गया था, दोनों ही मामलों: ऐसा लगता है कि या तो लग रहा है समान दिखें)

अब, यही वही है जो आपने अपेक्षित था, तो आपका कोड क्यों काम नहीं करता?

सबसे पहले, मैं आपको this table पर संदर्भित करना चाहता हूं जो सीपी 1252 से यूनिकोड में रूपांतरण दिखाता है। महत्वपूर्ण बात यह है कि मैं आपको नोट करना चाहता हूं कि कुछ बाइट्स (जैसे 0x9D) हैं जो cp1252 में मान्य नहीं हैं।

जब मैं utf8 कनवर्टर को एक cp1252 लिखने की कल्पना करता हूं, इसलिए मुझे उन बाइट्स के साथ कुछ करने की ज़रूरत है जो cp1252 में नहीं हैं। एकमात्र समझदार चीज जिसे मैं सोच सकता हूं, अज्ञात बाइट्स को उसी मूल्य पर यूनिकोड वर्णों में परिवर्तित करना है। वास्तव में, ऐसा लगता है कि क्या हुआ। चलिए अपने "चार अंक" उदाहरण को एक समय में एक कदम वापस लेते हैं।

$ perl -CO -MEncode -e '$a=decode("utf-8", 
    "\xC3\xA2\xE2\x82\xAC\xC5\x93" . 
    "four score" . 
    "\xC3\xA2\xE2\x82\xAC\xC2\x9D"); 
    for $c (split(//,$a)) {printf "%x ",ord($c);}' | fmt 

यह यूनिकोड कोड अंक के इस क्रम पैदावार:

सबसे पहले, के बाद से यह है वैध UTF-8, के डिकोड साथ जाने

e2 20ac 153 66 6f 75 72 20 73 63 6f 72 65 e2 20ac 9d 

("fmt" एक यूनिक्स कमांड है कि बस टेक्स्ट को दोबारा सुधारता है ताकि हमारे पास लंबे डेटा के साथ अच्छी लाइन ब्रेक हो)

अब, इनमें से प्रत्येक को cp1252 में बाइट के रूप में प्रस्तुत करते हैं, लेकिन जब यूनिकोड चरित्र को cp1252 में प्रदर्शित नहीं किया जा सकता है, तो चलिए इसे एक बाइट के साथ प्रतिस्थापित करें जिसमें समान संख्यात्मक मान हो। (डिफ़ॉल्ट के बजाय, जिसे इसे एक प्रश्न चिह्न के साथ प्रतिस्थापित करना है) हमें तब करना चाहिए, अगर हम डेटा के साथ क्या हुआ, तो एक वैध utf8 बाइट स्ट्रीम है।

$ perl -CO -MEncode -e '$a=decode("utf-8", 
    "\xC3\xA2\xE2\x82\xAC\xC5\x93" . 
    "four score" . 
    "\xC3\xA2\xE2\x82\xAC\xC2\x9D"); 
    $a=encode("cp-1252", $a, sub { chr($_[0]) }); 
    for $c (split(//,$a)) {printf "%x ",ord($c);}' | fmt 

तीसरा तर्क एन्कोड करने के लिए है कि - जब यह एक उप है - बताता है कि unrepresentable पात्रों के साथ क्या करना है।

यह पैदावार:

e2 80 9c 66 6f 75 72 20 73 63 6f 72 65 e2 80 9d 

अब, यह एक मान्य UTF8 बाइट धारा है। निरीक्षण द्वारा यह नहीं बता सकते हैं? ठीक है, पर्ल UTF8 के रूप में इस बाइट धारा डिकोड करने के लिए पूछना है:

$ perl -CO -MEncode -e '$a=decode("utf-8", 
    "\xC3\xA2\xE2\x82\xAC\xC5\x93" . 
    "four score" . 
    "\xC3\xA2\xE2\x82\xAC\xC2\x9D"); 
    $a=encode("cp-1252", $a, sub { chr($_[0]) }); 
    $a=decode("utf-8", $a, 1); 
    for $c (split(//,$a)) {printf "%x ",ord($c);}' | fmt 

पासिंग "1" के रूप में डिकोड करने के लिए तीसरा तर्क सुनिश्चित करता है कि हमारे कोड यदि बाइट धारा अमान्य है croak होगा। यह पैदावार:

201c 66 6f 75 72 20 73 63 6f 72 65 201d 

या मुद्रित:

$ perl -CO -MEncode -e '$a=decode("utf-8", 
    "\xC3\xA2\xE2\x82\xAC\xC5\x93" . 
    "four score" . 
    "\xC3\xA2\xE2\x82\xAC\xC2\x9D"); 
    $a=encode("cp-1252", $a, sub { chr($_[0]) }); 
    $a=decode("utf-8", $a, 1); 
    print "$a\n"' 
“four score” 

तो मुझे लगता है कि पूरा एल्गोरिथ्म इस होना चाहिए:

  1. ले लो mysql से बाइट धारा। इसे $ bytestream पर असाइन करें।
  2. जबकि $ bytestream एक वैध UTF8 बाइट धारा है:
    1. $ अच्छा
    2. $ bytestream सभी ASCII (यानी, हर बाइट 0x80 कम है) है, तो तोड़ने के लिए $ bytestream के वर्तमान मूल्य निरुपित इनमें से "जबकि ... वैध utf8" लूप।
    3. "डेमंगल ($ bytestream)" के परिणामस्वरूप $ bytestream सेट करें, जहां डिमंगल नीचे दिया गया है। यह दिनचर्या cp1252-to-utf8 कनवर्टर को कम करता है जो हमें लगता है कि इस डेटा का सामना करना पड़ा है।
  3. डेटाबेस में $ अच्छा वापस रखें यदि यह अनिश्चित नहीं है। अगर $ अच्छा कभी सौंपा नहीं गया था, तो मान लें कि $ bytestream एक cp1252 बाइट स्ट्रीम था और इसे utf8 में परिवर्तित कर दिया गया था। (बेशक, ऑप्टिमाइज़ करें और ऐसा न करें अगर चरण 2 में लूप कुछ भी नहीं बदले, आदि)

sub demangle { 
    my($a) = shift; 
    eval { # the non-string form of eval just traps exceptions 
     # so that we return undef on exception 
    local $SIG{__WARN__} = sub {}; # No warning messages 
    $a = decode("utf-8", $a, 1); 
    encode("cp-1252", $a, sub {$_[0] <= 255 or die $_[0]; chr($_[0])}); 
    } 
} 

यह धारणा है कि यह वास्तव में एक स्ट्रिंग है जो है सब-ASCII एक वैध UTF-8 बाइट धारा होने के लिए नहीं है जब तक कि यह वास्तव में UTF-8 है के लिए बहुत ही दुर्लभ है पर आधारित है। यही है, यह ऐसी चीज नहीं है जो गलती से होती है।

संपादित जोड़ने के लिए:

ध्यान दें कि यह तकनीक अपने "बॉब के" उदाहरण के साथ बहुत ज्यादा मदद नहीं करता है, दुर्भाग्य से। मुझे लगता है कि वह स्ट्रिंग सीपी 1252-टू-यूटीएफ 8 रूपांतरण के दो राउंड से भी गुजरती है, लेकिन दुर्भाग्यवश कुछ भ्रष्टाचार भी था। उसी तकनीक का उपयोग करते हुए पहले की तरह, हम पहले UTF8 के रूप में बाइट क्रम पढ़ सकते हैं और यूनिकोड केरेक्टर सन्दर्भ के अनुक्रम को देखो हम पाते हैं:

$ perl -CO -MEncode -e '$a=decode("utf-8", 
    "bob\xC3\xAF\xC2\xBF\xC2\xBDs"); 
    for $c (split(//,$a)) {printf "%x ",ord($c);}' | fmt 

यह पैदावार:

62 6f 62 ef bf bd 73 

अब, यह सिर्फ इतना होता है कि तीन बाइट्स ef bf bd के लिए, यूनिकोड और cp1252 सहमत हैं। तो cp1252 में यूनिकोड कोड पॉइंट्स के इस अनुक्रम का प्रतिनिधित्व करना केवल है:

62 6f 62 ef bf bd 73 

यह संख्याओं का एक ही अनुक्रम है। अब, यह एक वैध UTF-8 बाइट धारा वास्तव में है, लेकिन क्या यह आपको हैरान कर सकते करने के लिए डीकोड:

$ perl -CO -MEncode -e '$a=decode("utf-8", 
    "bob\xC3\xAF\xC2\xBF\xC2\xBDs"); 
    $a=encode("cp-1252", $a, sub { chr(shift) }); 
    $a=decode("utf-8", $a, 1); 
    for $c (split(//,$a)) {printf "%x ",ord($c);}' | fmt 

62 6f 62 fffd 73 

यही है, UTF-8 बाइट धारा है, हालांकि एक वैध UTF-8 बाइट धारा, एन्कोडेड चरित्र 0xFFFD, जिसे आमतौर पर "अप्रचलित चरित्र" के लिए उपयोग किया जाता है। मुझे संदेह है कि यहां क्या हुआ यह है कि पहले * -to-utf8 परिवर्तन ने एक ऐसा चरित्र देखा जो इसे पहचान नहीं पाया और इसे "अप्रचलित" के साथ बदल दिया। फिर मूल चरित्र को प्रोग्रामेटिक रूप से पुनर्प्राप्त करने का कोई तरीका नहीं है।

एक परिणाम यह है कि आप यह पता नहीं लगा सकते कि बाइट्स की धारा वैध यूटीएफ 8 (ऊपर दिए गए एल्गोरिदम के लिए आवश्यक है) बस एक डिकोड करके और फिर 0xFFFD की तलाश कर रही है। इसके बजाए, आपको इस तरह कुछ उपयोग करना चाहिए:

sub is_valid_utf8 { 
    defined(eval { decode("utf-8", $_[0], 1) }) 
} 
संबंधित मुद्दे