簡體   English   中英

將擴展 ASCII 或 Unicode 轉換為等效的 7 位 ASCII (<128),包括特殊字符

[英]Convert Extended ASCII or Unicode to 7-bit ASCII (<128) equivalent including special characters

如何將 Java 中的字符從擴展 ASCII 或 Unicode 轉換為等效的 7 位 ASCII,包括特殊字符,例如打開 ( 0x93) 和關閉 ( 0x94) 引號到簡單的雙引號 ( " 0x22)。或類似破折號 -0x96) 到連字符減號- -0x2D)。我發現Stack Overflow 問題與此類似,但答案似乎只處理重音符號而忽略特殊字符。

例如,我希望“Caffè – Peña”轉換為"Caffe - Pena"

但是當我使用 java.text.Normalizer 時:

String sample = "“Caffè – Peña”";
System.out.println(Normalizer.normalize(sample, Normalizer.Form.NFD)
                         .replaceAll("\\p{InCombiningDiacriticalMarks}", ""));

Output 是

“Caffe – Pena”

為了闡明我的需要,我正在與使用 EBCDIC 編碼的 IBM i Db2 數據庫進行交互。 例如,如果用戶粘貼從 Word 或 Outlook 復制的字符串,我指定的字符將被轉換為 SUB(EBCDIC 中的 0x3F,ASCII 中的 0x1A)。 這會導致很多不必要的頭痛。 我正在尋找一種方法來清理字符串,以便盡可能少地丟失信息。

您可以按照另一位評論者的建議,只使用 String.replace() 來替換引號字符,並且隨着時間的推移,您可能會增加有問題的字符列表。

您還可以使用更通用的 function 來替換或忽略任何無法編碼的字符。 例如:

    private String removeUnrepresentableChars(final String _str, final String _encoding) throws CharacterCodingException, UnsupportedEncodingException {
        final CharsetEncoder enccoder = Charset.forName(_encoding).newEncoder();
        enccoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
        ByteBuffer encoded = enccoder.encode(CharBuffer.wrap(_str));
        return new String(encoded.array(), _encoding);
    }

    private String replaceUnrepresentableChars(final String _str, final String _encoding, final String _replacement) throws CharacterCodingException, UnsupportedEncodingException {
        final CharsetEncoder encoder = Charset.forName(_encoding).newEncoder();
        encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
        encoder.replaceWith(_replacement.getBytes(_encoding));
        ByteBuffer encoded = encoder.encode(CharBuffer.wrap(_str));
        return new String(encoded.array(), _encoding);
    }

例如,您可以使用_encoding為“IBM-037”來調用它們。

但是,如果您的目標是盡可能少地丟失信息,則應評估數據是否可以存儲在 UTF-8(CCSID 1208)中。 這可以很好地處理智能引號和其他“特殊字符”。 根據您的數據庫和應用程序結構,這樣的更改實施起來可能非常小,或者可能非常大且有風險,但實現無損轉換的唯一方法是使用 unicode 風格。 UTF-8是最明智的。

評論者說您的問題是“主觀的”(不是基於意見的意義上,而是每個人的具體要求與其他人的略有不同)或定義不明確或本質上不可能......在技術上是正確的。

但是你正在尋找一些你可以做的實際的事情來改善這種情況,這也是完全有效的。

在平衡實施難度和結果准確性方面的最佳點是將您已經發現的內容與負面評論較少的建議結合在一起:

  • 使用標准規范化程序處理變音符號和其他“標准規范化”字符。
  • 使用您自己的映射處理其他所有內容(可能包括Unicode General_Category 屬性,但最終可能需要包括您自己選擇的用其他特定字符替換特定字符)。

以上可能涵蓋“所有”未來案例,具體取決於數據的來源。 或者足夠接近您可以實現並完成它的所有內容。 如果你想增加一些健壯性,並且會在一段時間內維持這個過程,那么你也可以列出你想要在清理后的結果中允許的所有字符,然后設置某種異常或日志記錄機制,讓您(或您的繼任者)在出現新的未處理案例時發現它們,然后可用於改進映射的自定義部分。

經過一番挖掘后,我能夠使用org.apache.lucene.analysis.ASCIIFoldingFilter找到基於此答案的解決方案

我能夠找到的所有示例都使用 static 版本的方法foldToASCII ,如本項目中所示:

private static String getFoldedString(String text) {
    char[] textChar = text.toCharArray();
    char[] output = new char[textChar.length * 4];
    int outputPos = ASCIIFoldingFilter.foldToASCII(textChar, 0, output, 0, textChar.length);
    text = new String(output, 0, outputPos);
    return text;
}

但是 static 方法有一條注釋說

此 API 僅供內部使用,在下一個版本中可能會以不兼容的方式進行更改。

因此,經過反復試驗,我想出了這個避免使用 static 方法的版本:

public static String getFoldedString(String text) throws IOException {
    String output = "";
    try (Analyzer analyzer = CustomAnalyzer.builder()
              .withTokenizer(KeywordTokenizerFactory.class)
              .addTokenFilter(ASCIIFoldingFilterFactory.class)
              .build()) {
        try (TokenStream ts = analyzer.tokenStream(null, new StringReader(text))) {
            CharTermAttribute charTermAtt = ts.addAttribute(CharTermAttribute.class);
            ts.reset();
            if (ts.incrementToken()) output = charTermAtt.toString();
            ts.end();
        }
    }
    return output;
}

類似於我在此處提供的答案。

這正是我正在尋找的,並將字符轉換為其 ASCII 7 位等效版本。

然而,通過進一步的研究,我發現因為我主要處理 Windows-1252 編碼,並且由於 jt400 處理 ASCII <-> EBCDIC (CCSID 37) 轉換的方式,如果 ASCII 字符串被轉換為 EBCDIC 並返回到 ACSII,唯一丟失的字符是0x800x9f lucene 的 foldToASCII處理方式的啟發,我將以下方法放在一起,僅處理這些情況:

public static String replaceInvalidChars(String text) {
    char input[] = text.toCharArray();
    int length = input.length;
    char output[] = new char[length * 6];
    int outputPos = 0;
    for (int pos = 0; pos < length; pos++) {
        final char c = input[pos];
        if (c < '\u0080') {
            output[outputPos++] = c;
        } else {
            switch (c) {
                case '\u20ac':  //€ 0x80
                    output[outputPos++] = 'E';
                    output[outputPos++] = 'U';
                    output[outputPos++] = 'R';
                    break;
                case '\u201a':  //‚ 0x82
                    output[outputPos++] = '\'';
                    break;
                case '\u0192':  //ƒ 0x83
                    output[outputPos++] = 'f';
                    break;
                case '\u201e':  //„ 0x84
                    output[outputPos++] = '"';
                    break;
                case '\u2026':  //… 0x85
                    output[outputPos++] = '.';
                    output[outputPos++] = '.';
                    output[outputPos++] = '.';
                    break;
                case '\u2020':  //† 0x86
                    output[outputPos++] = '?';
                    break;
                case '\u2021':  //‡ 0x87
                    output[outputPos++] = '?';
                    break;
                case '\u02c6':  //ˆ 0x88
                    output[outputPos++] = '^';
                    break;
                case '\u2030':  //‰ 0x89
                    output[outputPos++] = 'p';
                    output[outputPos++] = 'e';
                    output[outputPos++] = 'r';
                    output[outputPos++] = 'm';
                    output[outputPos++] = 'i';
                    output[outputPos++] = 'l';

                    break;
                case '\u0160':  //Š 0x8a
                    output[outputPos++] = 'S';
                    break;
                case '\u2039':  //‹ 0x8b
                    output[outputPos++] = '\'';
                    break;
                case '\u0152':  //Π0x8c
                    output[outputPos++] = 'O';
                    output[outputPos++] = 'E';
                    break;
                case '\u017d':  //Ž 0x8e
                    output[outputPos++] = 'Z';
                    break;
                case '\u2018':  //‘ 0x91
                    output[outputPos++] = '\'';
                    break;
                case '\u2019':  //’ 0x92
                    output[outputPos++] = '\'';
                    break;
                case '\u201c':  //“ 0x93
                    output[outputPos++] = '"';
                    break;
                case '\u201d':  //” 0x94
                    output[outputPos++] = '"';
                    break;
                case '\u2022':  //• 0x95
                    output[outputPos++] = '-';
                    break;
                case '\u2013':  //– 0x96
                    output[outputPos++] = '-';
                    break;
                case '\u2014':  //— 0x97
                    output[outputPos++] = '-';
                    break;
                case '\u02dc':  //˜ 0x98
                    output[outputPos++] = '~';
                    break;
                case '\u2122':  //™ 0x99
                    output[outputPos++] = '(';
                    output[outputPos++] = 'T';
                    output[outputPos++] = 'M';
                    output[outputPos++] = ')';
                    break;
                case '\u0161':  //š 0x9a
                    output[outputPos++] = 's';
                    break;
                case '\u203a':  //› 0x9b
                    output[outputPos++] = '\'';
                    break;
                case '\u0153':  //œ 0x9c
                    output[outputPos++] = 'o';
                    output[outputPos++] = 'e';
                    break;
                case '\u017e':  //ž 0x9e
                    output[outputPos++] = 'z';
                    break;
                case '\u0178':  //Ÿ 0x9f
                    output[outputPos++] = 'Y';
                    break;
                default:
                    output[outputPos++] = c;
                    break;
            }
        }
    }
    
    return new String(Arrays.copyOf(output, outputPos));
}

由於事實證明我真正的問題是 Windows-1252 到 Latin-1 (ISO-8859-1) 的翻譯,這里有一個支持材料顯示了上述方法中使用的 Windows-1252 到 Unicode 的翻譯最終得到 Latin-1編碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM