2012-05-07 22 views
9

मेरे पास एक एसक्यूएल-सर्वर 2010 डेटाबेस दो अनुप्रयोगों के बीच साझा किया गया है। एक एप्लिकेशन जिसका हम नियंत्रण करते हैं, और दूसरा एप्लिकेशन एक तृतीय-पक्ष ऐप है जिसने डेटाबेस को पहले स्थान पर बनाया है। हमारा ऐप तीसरे पक्ष के वेबमेल ऐप के शीर्ष पर बनाया गया एक सीआरएम है।खराब यूटीएफ -8 एन्कोडिंग का पता लगाना: खराब पात्रों की सूची स्नीफ करने के लिए?

डेटाबेस में वर्चर्स कॉलम हैं और लैटिन -1 एन्कोडेड है। तृतीय-पक्ष ऐप php में लिखा गया है और डेटा को सही ढंग से एन्कोड करने की परवाह नहीं करता है, इसलिए यह वर्चर्स कॉलम में utf-8 एन्कोडेड बाइट्स को सामान देता है, जहां उन्हें लैटिन -1 के रूप में व्याख्या किया जाता है और कचरा जैसा दिखता है।

हमारा सीआरएम ऐप नेट में लिखा गया है, जो स्वचालित रूप से पता लगाता है कि डेटाबेस संयोजन स्ट्रिंग में स्ट्रिंग के एन्कोडिंग को अलग करता है, इसलिए जब नेट डेटाबेस को लिखता है, तो यह डेटाबेस एन्कोडिंग से मेल खाने के लिए बाइट्स को परिवर्तित करता है।

तो ... हमारे ऐप से डीबी को लिखा गया डेटा डीबी में सही दिखता है, लेकिन तीसरे पक्ष के ऐप से डेटा नहीं होता है।

जब हमारे एप्लिकेशन लिखते प्रथम = Céline, जब वेबमेल एप्लिकेशन प्रथम = Céline लिखते Céline

के रूप में DB में संग्रहीत किया जाता है यह CA © लाइन

हमारे सीआरएम अनुप्रयोग की जरूरत के रूप में DB में संग्रहीत किया जाता है किसी भी सिस्टम में बनाए गए संपर्कों को प्रदर्शित करने के लिए। तो मैं एक एन्कोडिंगस्निफर क्लास लिख रहा हूं जो ध्वजांकित वर्णों को देखता है जो इसकी खराब एन्कोडेड स्ट्रिंग को इंगित करता है और उन्हें परिवर्तित करता है।

वर्तमान में मेरे पास है:

 
private static string[] _flaggedChars = new string[] { 
      "é" 
     }; 

जो Céline के रूप में सीए © लाइन प्रदर्शित करने के लिए अच्छा काम करता है, लेकिन मैं सूची में शामिल करने की जरूरत है।

क्या किसी को भी संसाधनों के बारे में पता है कि सभी संभावित तरीकों को प्राप्त करने के लिए utf-8 विशेष वर्णों को आईएसओ -885 9 -1 के रूप में व्याख्या किया जा सकता है?

धन्यवाद

स्पष्टीकरण: के बाद से मैं नेट में काम कर रहा हूँ। स्ट्रिंग, डेटाबेस से स्मृति में लोड होने पर, यूनिकोड यूटीएफ -16 में परिवर्तित हो जाती है। तो, भले ही इसे डेटाबेस में सही ढंग से एन्कोड किया गया हो। इसे अब यूटीएफ 16 बाइट्स के रूप में दर्शाया गया है। मुझे यूटीएफ -16 बाइट्स का विश्लेषण करने में सक्षम होना चाहिए, और यह निर्धारित करना है कि क्या वे आईएसएफ -8 बाइट्स को आईएसओ -885 9 -1 डेटाबेस में भरने के कारण खराब हो गए हैं .... मिट्टी के रूप में स्पष्ट है?

यहां मेरे पास अभी तक है। इसने सबसे गलत गलतियों के प्रदर्शन को साफ कर दिया है, लेकिन मुझे अभी भी ए के साथ परेशानी हो रही है: उदाहरण के लिए एरिक वेबमेल द्वारा डीबी में संग्रहीत किया जाता है, लेकिन खराब एन्कोडिंग का पता लगाने और इसे वापस बदलने के बाद, यह के रूप में प्रदर्शित होता है ? 'ई 195 में बदला जा रहा है: रिक लिए उपयोगकर्ता के पास 2500 संपर्कों, जिनमें से सैकड़ों मुद्दों एन्कोडिंग था है को देखते हुए, É केवल बात यह है कि सही ढंग से प्रदर्शित नहीं है ...

public static Regex CreateRegex() 
    { 
     string specials = "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö"; 

     List<string> flags = new List<string>(); 
     foreach (char c in specials) 
     { 
      string interpretedAsLatin1 = Encoding.GetEncoding("iso-8859-1").GetString(Encoding.UTF8.GetBytes(c.ToString())).Trim();//take the specials, treat them as utf-8, interpret them as latin-1 
      if (interpretedAsLatin1.Length > 0)//utf-8 chars made up of 2 bytes, interpreted as two single byte latin-1 chars. 
       flags.Add(interpretedAsLatin1); 
     } 

     string regex = string.Empty; 
     foreach (string s in flags) 
     { 
      if (regex.Length > 0) 
       regex += '|'; 
      regex += s; 
     } 
     return new Regex("(" + regex + ")"); 
    } 

    public static string CheckUTF(string data) 
    { 
     Match match = CreateRegex().Match(data); 
     if (match.Success) 
      return Encoding.UTF8.GetString(Encoding.GetEncoding("iso-8859-1").GetBytes(data));//from iso-8859-1 (latin-1) to utf-8 
     else 
      return data; 
    } 

तो है Ã ', 8240' ‰ '

+0

यह UTF-8 में डीबी एन्कोडिंग बदलने के लिए एक विकल्प है, सबसे आसान समाधान की तरह लगता है के बाद से वहाँ यूनिकोड और लैटिन -1 –

+0

परीक्षण के बीच नहीं एक 1 के लिए 1 रूपांतरण स्ट्रिंग वैध UTF है कि क्या है -8 एक बेहतर दृष्टिकोण हो सकता है। (शायद कम महंगी भी।) – Mat

+0

@ मैट, यह अनिवार्य रूप से मैं क्या करने की कोशिश कर रहा हूं, मुझे नहीं पता कि कैसे। और खराब चरित्र स्नीफिंग दृष्टिकोण सबसे अच्छा था जिसके साथ मैं आया था। वैध यूटीएफ -8 के लिए परीक्षण के बारे में आप कैसे जाएंगे? – Michael

उत्तर

0

आपको शायद बाइट स्ट्रिंग को यूटीएफ -8 के रूप में डीकोड करने का प्रयास करना चाहिए, और यदि आपको कोई त्रुटि मिलती है, तो मान लें कि यह आईएसओ -885 9 -1 है।

आईएसओ -885 9 -1 के रूप में एन्कोड किए गए पाठ को शायद ही कभी "होता है" वैध यूटीएफ -8 भी होगा ...जब तक यह आईएसओ -885 9 -1 नहीं है जिसमें केवल वास्तव में एएससीआईआईआई होती है, लेकिन फिर उस स्थिति में आपको बिल्कुल कोई समस्या नहीं है। तो यह विधि उचित रूप से मजबूत है।

अनदेखा करना कि कौन से वर्ण वास्तविक भाषा में दूसरों की तुलना में अधिक बार होते हैं, यहां एक निष्पक्ष विश्लेषण है जो मानता है कि प्रत्येक वर्ण एक ही आवृत्ति के साथ होता है। चलिए यह पता लगाने की कोशिश करते हैं कि यूटीएफ -8 के लिए कितनी बार वैध आईएसओ -885 9 -1 गलत हो सकता है जिसके परिणामस्वरूप मोजीबेक होता है। मैं यह भी मानता हूं कि सी 1 नियंत्रण वर्ण (यू + 0080 यू + 00 9 एफ के माध्यम से) नहीं होता है।

बाइट स्ट्रिंग में दिए गए बाइट के लिए। यदि बाइट स्ट्रिंग के अंत के करीब है तो आप विकृत यूटीएफ -8 का पता लगाने की अधिक संभावना रखते हैं क्योंकि कुछ बाइट अनुक्रम मान्य यूटीएफ -8 होने के लिए पर्याप्त नहीं होंगे। लेकिन यह मानते हुए कि बाइट स्ट्रिंग के अंत के पास नहीं है:

  • पी (ASCII के रूप में बाइट डीकोड) = 0.57। यह इस बारे में कोई जानकारी नहीं देता है कि स्ट्रिंग ASCII, आईएसओ -885 9 -1, या यूटीएफ -8 है या नहीं।
  • यदि यह बाइट 0x80 से 0xc1 या 0xf8 से 0xff है, तो यह यूटीएफ -8 नहीं हो सकता है, इसलिए आप इसका पता लगाएंगे। पी = 0.33
  • यदि यह पहला बाइट 0xc2 0xdf (p = 0.11) के माध्यम से 0xc2 है तो यह वैध यूटीएफ -8 हो सकता है, लेकिन केवल तभी जब यह 0x80 और 0xbf के बीच मान के साथ बाइट हो। संभावना है कि अगली बाइट उस सीमा में विफल होने में विफल है 1 9 2/224 = 0.86। तो यूटीएफ -8 यहां विफल होने की संभावना 0.0 9
  • यदि पहला बाइट 0xe0 0xef के माध्यम से है तो यह वैध यूटीएफ -8 हो सकता है लेकिन केवल तभी 2 निरंतर बाइट्स हो सकता है। संभावना है कि आप खराब यूटीएफ -8 का पता लगाएंगे (16/224) * (1- (0.14 * 0.14)) = 0.07
  • 0xf0 के माध्यम से 0xf7 के समान, संभावना (8/224) * (1- (0.14 * 0.14 * 0.14)) = 0.04।

एक लंबी स्ट्रिंग में प्रत्येक बाइट पर, खराब यूटीएफ -8 का पता लगाने की संभावना 0.33 + 0.0 9 + 0.07 + 0.04 = 0.53 है।

तो लंबी स्ट्रिंग के लिए, आईटीओ -885 9 -1 एक यूटीएफ -8 डिकोडर के माध्यम से चुपचाप गुजरने की संभावना बहुत छोटी है: यह लगभग प्रत्येक अतिरिक्त चरित्र के लिए लगभग आधा है!

पाठ्यक्रम का यह विश्लेषण यादृच्छिक आईएसओ -885 9 -1 अक्षरों को मानता है। व्यावहारिक रूप से गलत पहचान दर उतनी ही अच्छी नहीं होगी (ज्यादातर इस तथ्य के कारण कि वास्तविक दुनिया के पाठ में अधिकांश बाइट वास्तव में एएससीआईआई हैं), लेकिन यह अभी भी बहुत अच्छा होगा।

+0

क्या कोई इस के .NET कोड नमूना प्रदान कर सकता है? मुझे कुछ ऐसा नहीं मिला जो अपवाद फेंकता है। मैं जो कुछ भी कोशिश करता हूं वह एन्कोडिंग को और भी गड़बड़ कर देता है। – Michael

+0

दरअसल, मुझे लगता है कि मैं देखता हूं कि यह क्यों काम नहीं करता है। चूंकि नेट में सभी स्ट्रिंग्स यूटीएफ -16 हैं, इसलिए डेटाबेस से मूल बाइट्स को पहले से ही संशोधित किया गया है जब मैं उन्हें एप्लिकेशन कोड में डीकोड करने का प्रयास करता हूं। तो मुझे एक यूटीएफ -16 स्ट्रिंग से मेरा बाइट सरणी मिल रही है और utf-8 को डीकोड करने की कोशिश कर रहा है ... – Michael

0

धन्यवाद @ माइकल 99% से अधिक काम करने के लिए!

यहां किसी भी व्यक्ति के लिए माइकल की लिपि का पावरशेल संस्करण है जो इससे मदद करता है। यह É समस्या को हल करने के लिए Windows-1252 कोड पृष्ठ/एन्कोडिंग का @ क्यूबेई का सुझाव भी है; हालांकि एन्कोडिंग के एक अलग संयोजन के माध्यम से आपका डेटा दूषित होने पर इन एन्कोडिंग में संशोधन करने की अनुमति देता है।

#based on c# in question: https://stackoverflow.com/questions/10484833/detecting-bad-utf-8-encoding-list-of-bad-characters-to-sniff 
function Convert-CorruptCodePageString { 
    [CmdletBinding(DefaultParameterSetName = 'ByInputText')] 
    param (
     [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ByInputText')] 
     [string]$InputText 
     , 
     [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ByInputObject')] 
     [PSObject]$InputObject 
     , 
     [Parameter(Mandatory = $true, ParameterSetName = 'ByInputObject')] 
     [string]$Property 
     , 
     [Parameter()] 
     [System.Text.Encoding]$SourceEncoding = [System.Text.Encoding]::GetEncoding('Windows-1252') 
     , 
     [Parameter()] 
     [System.Text.Encoding]$DestinationEncoding = [system.Text.Encoding]::UTF8 
     , 
     [Parameter()] 
     [string]$DodgyChars = 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö' 
    ) 
    begin { 
     [string]$InvalidCharRegex = ($DodgyChars.ToCharArray() | %{ 
      [byte[]]$dodgyCharBytes = $DestinationEncoding.GetBytes($_.ToString()) 
      $SourceEncoding.GetString($dodgyCharBytes,0,$dodgyCharBytes.Length).Trim() 
     }) -join '|' 
    } 
    process { 
     if ($PSCmdlet.ParameterSetName -eq 'ByInputText') { 
      $InputObject = $null 
     } else { 
      $InputText = $InputObject."$Property" 
     } 
     [bool]$IsLikelyCorrupted = $InputText -match $InvalidCharRegex 
     if ($IsLikelyCorrupted) { #only bother to decrupt if we think it's corrupted 
      [byte[]]$bytes = $SourceEncoding.GetBytes($InputText) 
      [string]$outputText = $DestinationEncoding.GetString($bytes,0,$bytes.Length) 
     } else { 
      [string]$outputText = $InputText 
     } 
     [pscustomobject]@{ 
      InputString = $InputText 
      OutputString = $outputText 
      InputObject = $InputObject 
      IsLikelyCorrupted = $IsLikelyCorrupted 
     }   
    } 
} 

डेमो

#demo of using a simple string without the function (may cause corruption since this doesn't check if the characters being replaced are those likely to have been corrupted/thus is more likely to cause corruption in many strings). 
$x = 'Strømmen' 
$bytes = [System.Text.Encoding]::GetEncoding('Windows-1252').GetBytes($x) 
[system.Text.Encoding]::UTF8.GetString($bytes,0,$bytes.Length) 

#demo using the function 
$x | Convert-CorruptCodePageString 

#demo of checking all records in a table for an issue/reporting those with issues 
#amend SQL Query, MyDatabaseInstance, and MyDatabaseCatlogue to point to your DB/query the relevant table 
Invoke-SQLQuery -Query 'Select [Description], [RecId] from [DimensionFinancialTag] where [Description] is not null and [Description] > ''''' -DbInstance $MyDatabaseInstance -DbCatalog $MyDatabaseCatalog | 
    Convert-CorruptCodePageString -Property 'Description' | 
    ?{$_.IsLikelyCorrupted} | 
    ft @{N='RecordId';E={$_.InputObject.RecId}}, InputString, OutputString 

अतिरिक्त मेरी डेमो

में प्रयोग किया जाता रहा Invoke-SqlCmd cmdlet के एक प्रशंसक नहीं हूँ समारोह है, तो अपने ही गिर गयी है।

function Invoke-SQLQuery { 
    [CmdletBinding(DefaultParameterSetName = 'ByQuery')] 
    param (
     [Parameter(Mandatory = $true)] 
     [string]$DbInstance 
     , 
     [Parameter(Mandatory = $true)] 
     [string]$DbCatalog 
     , 
     [Parameter(Mandatory = $true, ParameterSetName = 'ByQuery')] 
     [string]$Query 
     , 
     [Parameter(Mandatory = $true, ParameterSetName = 'ByPath')] 
     [string]$Path 
     , 
     [Parameter(Mandatory = $false)] 
     [hashtable]$Params = @{} 
     , 
     [Parameter(Mandatory = $false)] 
     [int]$CommandTimeoutSeconds = 30 #this is the SQL default 
     , 
     [Parameter(Mandatory = $false)] 
     [System.Management.Automation.Credential()] 
     [System.Management.Automation.PSCredential]$Credential=[System.Management.Automation.PSCredential]::Empty 
    ) 
    begin { 
     write-verbose "Call to 'Execute-SQLQuery'" 
     $connectionString = ("Server={0};Database={1}" -f $DbInstance,$DbCatalog) 
     if ($Credential -eq [System.Management.Automation.PSCredential]::Empty) { 
      $connectionString = ("{0};Integrated Security=True" -f $connectionString) 
     } else { 
      $connectionString = ("{0};User Id={1};Password={2}" -f $connectionString, $Credential.UserName, $Credential.GetNetworkCredential().Password)  
      $PSCmdlet.Name  
     } 
     $connection = New-Object System.Data.SqlClient.SqlConnection 
     $connection.ConnectionString = $connectionString 
     $connection.Open()  
    } 
    process { 
     #create the command & assign the connection 
     $cmd = new-object -TypeName 'System.Data.SqlClient.SqlCommand' 
     $cmd.Connection = $connection 

     #load in our query 
     switch ($PSCmdlet.ParameterSetName) { 
      'ByQuery' {$cmd.CommandText = $Query; break;} 
      'ByPath' {$cmd.CommandText = Get-Content -Path $Path -Raw; break;} 
      default {throw "ParameterSet $($PSCmdlet.ParameterSetName) not recognised by Invoke-SQLQuery"} 
     } 
     #assign parameters as required 
     #NB: these don't need declare statements in our query; so a query of 'select @demo myDemo' would be sufficient for us to pass in a parameter with name @demo and have it used 
     #we can also pass in parameters that don't exist; they're simply ignored (sometimes useful if writing generic code that has optional params) 
     $Params.Keys | %{$cmd.Parameters.AddWithValue("@$_", $Params[$_]) | out-null} 

     $reader = $cmd.ExecuteReader() 
     while (-not ($reader.IsClosed)) { 
      $table = new-object 'System.Data.DataTable' 
      $table.Load($reader) 
      write-verbose "TableName: $($table.TableName)" #NB: table names aren't always available 
      $table | Select-Object -ExcludeProperty RowError, RowState, Table, ItemArray, HasErrors 
     } 

    } 
    end { 
     $connection.Close() 
    } 
} 
+0

सिंटैक्स हाइलाइटिंग के साथ कोड यहां उपलब्ध है: https://gist.githubusercontent.com/JohnLBevan/4c791aa60e85a2e992eff4f415267d47/ (थोड़ा tweaked उपरोक्त से, लेकिन महत्वपूर्ण नहीं है)। – JohnLBevan