一见未必钟情
这是第一次遇到 .rtf 格式文件乱码问题,之前也处理过类似的问题:从 .rtf 文件中读取字符串,但一直都没有遇到乱码直到今天。
原始文件内容如下:(在 Windows 下用 Word 和 写字板就可以打开)
大小便失禁及心悸、胸闷、发热,亦无头痛,视物旋转,复视,黑曚,意识障碍等症。
原始文件对应的 .rtf 格式字符串序列(用记事本直接打开就可以查看):
{\rtf1\ansi\ansicpg936\deff0\nouicompat\deflang1033\deflangfe2052{\fonttbl{\f0\fnil\fcharset134 \’cb\’ce\’cc\’e5;}}
{*\generator Riched20 6.3.9600}\viewkind4\uc1
\pard\nowidctlpar\qj\kerning2\f0\fs24\’b4\’f3\’d0\’a1\’b1\’e3\’ca\’a7\’bd\’fb\’bc\’b0\’d0\’c4\’bc\’c2\’a1\’a2\’d0\’d8\’c3\’c6\’a1\’a2\’b7\’a2\’c8\’c8\’a3\’ac\’d2\’e0\’ce\’de\’cd\’b7\’cd\’b4\’a3\’ac\’ca\’d3\’ce\’ef\’d0\’fd\’d7\’aa\’a3\’ac\’b8\’b4\’ca\’d3\’a3\’ac\’ba\’da\’95\’e4\’a3\’ac\’d2\’e2\’ca\’b6\’d5\’cf\’b0\’ad\’b5\’c8\’d6\’a2\’a1\’a3\par
}
一般 Java 从 .rtf 文件读取字符串的处理方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream;
import javax.swing.text.BadLocationException; import javax.swing.text.DefaultStyledDocument; import javax.swing.text.rtf.RTFEditorKit;
public class TestRtf {
* 从 .rtf 文件中读取字符串。 * * @param rtfFile * 文件 * * @return 获得的字符串 * * @see {@link #getTextFromRtfFile(File, String)} outputCharset="GB2312" */ public static String readFromRtfFile( File rtfFile ) { return readFromRtfFile( rtfFile, "GB2312" ); }
* 从 .rtf 文件中读取字符串。 * * @param rtfFile * 带读取的文件 * @param outputCharset * 输出字符串的编码 * * @return 读取到的字符串 */ public static String readFromRtfFile( File rtfFile, String outputCharset ) { String result = null; try { DefaultStyledDocument styledDoc = new DefaultStyledDocument(); InputStream is = new FileInputStream( rtfFile ); new RTFEditorKit().read( is, styledDoc, 0 ); result = new String( styledDoc.getText( 0, styledDoc.getLength() ).getBytes( "ISO8859_1" ), outputCharset ); } catch( IOException e ) { e.printStackTrace(); } catch( BadLocationException e ) { e.printStackTrace(); } return result; }
* 从 Rtf 格式的字符序列中读取字符串。 * * @param rftString * Rtf 格式的字符序列 * @param outputCharset * 输出字符串的编码 * * @return 输出的字符串 */ public static String readFromRtfString( String rftString, String outputCharset ) { String result = null; try { DefaultStyledDocument styledDoc = new DefaultStyledDocument(); InputStream inStream = new ByteArrayInputStream( rftString.getBytes() );
new RTFEditorKit().read( inStream, styledDoc, 0 );
result = new String( styledDoc.getText( 0, styledDoc.getLength() ).getBytes( "ISO8859_1" ), outputCharset ); } catch( IOException e ) { e.printStackTrace(); } catch( BadLocationException e ) { e.printStackTrace(); } return result; }
* 从 Rtf 格式的字符序列中读取字符串。 * * @param rftString * Rtf 格式的字符序列 * * @return 输出的字符串 * * @see {@link #readFromRtfString(String, String)} */ public static String readFromRtfString( String rftString ) { return readFromRtfString( rftString, "GB2312" ); }
}
|
然后加上 main 方法进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static void main( String[] args ) { String rtfString = "{\\rtf1\\ansi\\ansicpg936\\deff0\\nouicompat\\deflang1033\\deflangfe2052{\\fonttbl{\\f0\\fnil\\fcharset134 \\'cb\\'ce\\'cc\\'e5;}}" + "{\\*\\generator Riched20 6.3.9600}\\viewkind4\\uc1 " + "\\pard\\nowidctlpar\\qj\\kerning2\\f0\\fs24\\'b4\\'f3\\'d0\\'a1\\'b1\\'e3\\'ca\\'a7\\'bd\\'fb\\'bc\\'b0\\'d0\\'c4\\'bc\\'c2\\'a1\\'a2\\'d0\\'d8\\'c3\\'c6\\'a1\\'a2\\'b7\\'a2\\'c8\\'c8\\'a3\\'ac\\'d2\\'e0\\'ce\\'de\\'cd\\'b7\\'cd\\'b4\\'a3\\'ac\\'ca\\'d3\\'ce\\'ef\\'d0\\'fd\\'d7\\'aa\\'a3\\'ac\\'b8\\'b4\\'ca\\'d3\\'a3\\'ac\\'ba\\'da\\'95\\'e4\\'a3\\'ac\\'d2\\'e2\\'ca\\'b6\\'d5\\'cf\\'b0\\'ad\\'b5\\'c8\\'d6\\'a2\\'a1\\'a3\\par" + "}";
System.out.println( readFromRtfString( rtfString ) ); System.out.println( readFromRtfString( rtfString, "GBK" ) ); System.out.println( "===============================================" ); System.out.println( readFromRtfFile( new File( "C:\\Users\\Leo\\Desktop\\1.rtf" ) ) ); System.out.println( readFromRtfFile( new File( "C:\\Users\\Leo\\Desktop\\1.rtf" ), "GBK" ) ); }
|
哎呀,天呐,乱码出现了!My God!
山重水复疑无路
细心的你也许看到了方法中的注释,读取中文的时候要使用 ISO8859_1 编码读取,实验证明确实如此(哎呀,原来是中文大神!)。输出编码也许你会说我只用了 GBK、GB2312 编码,还没有使用 UTF-8 编码,那么我告诉你 UTF-8 更惨(惨目忍睹啊!)。
比较原始文件和输出日志,你应该发现了,原来是“曚”这个字捣的鬼。思路有了,查看下“曚”的 GBK 编码,发现编码为 95E4
(为啥不查 GB2312,额 (⊙o⊙)… 因为 GB2312 编码中不包含这个字,^O^ 。),那我们只要根据 rtf 文件编码格式来核查下“曚”这个字的编码(嘿嘿 ~~~),就知道问题出现在哪儿。在【原始文件对应的 .rtf 格式字符串序列】中确定了这个字的编码为 \'95\'e4
,尼玛,这不就是 GBK 编码格式吗,为啥还乱码。
凌乱中。。。。。。
柳暗花明又一村
凌乱了 n 久,已经接近要放弃治疗了的那一刻,我突然想到了我们是以 ISO8859_1 编码来读取的,以 GBK 或 GB2312 编码输出的。那是不是在以 ISO8859_1 读取的过程中出现了问题?
敢想敢干才是真本事,我们将“曚”的 16 进制编码 \'95\'e4
中的 95 转换成十进制数,转换后是 149 。到维基百科 ISO_8859-1 上面一查,发现了为【未建立】(英文为:nicht belegt)。啊哈哈,问题就在这里了。那对于未建立的编码,str.getBytes( "ISO8859_1" )
会如何处理呢,答案是忽略掉,对,直接忽略掉 \'95
, 下面我们验证下,怎么验证,只需要修改下 readFromRtfString( String rftString, String outputCharset )
方法即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| * 从 Rtf 格式的字符序列中读取字符串(输出中间态)。 * * @param rftString * Rtf 格式的字符序列 * @param outputCharset * 输出字符串的编码 * * @return 输出的字符串 */ public static String readFromRtfString( String rftString, String outputCharset ) { String result = null; try { DefaultStyledDocument styledDoc = new DefaultStyledDocument(); InputStream inStream = new ByteArrayInputStream( rftString.getBytes() );
new RTFEditorKit().read( inStream, styledDoc, 0 );
try { for( byte b : styledDoc.getText( 0, styledDoc.getLength() ).getBytes( "ISO8859_1" ) ) { System.out.printf( "\\\'%x", b );
} } catch( UnsupportedEncodingException e ) { e.printStackTrace(); }
System.out.println();
result = new String( styledDoc.getText( 0, styledDoc.getLength() ).getBytes( "ISO8859_1" ), outputCharset ); } catch( IOException e ) { e.printStackTrace(); } catch( BadLocationException e ) { e.printStackTrace(); } return result; }
public static void main( String[] args ) { String rtfString = "{\\rtf1\\ansi\\ansicpg936\\deff0\\nouicompat\\deflang1033\\deflangfe2052{\\fonttbl{\\f0\\fnil\\fcharset134 \\'cb\\'ce\\'cc\\'e5;}}" + "{\\*\\generator Riched20 6.3.9600}\\viewkind4\\uc1 " + "\\pard\\nowidctlpar\\qj\\kerning2\\f0\\fs24\\'b4\\'f3\\'d0\\'a1\\'b1\\'e3\\'ca\\'a7\\'bd\\'fb\\'bc\\'b0\\'d0\\'c4\\'bc\\'c2\\'a1\\'a2\\'d0\\'d8\\'c3\\'c6\\'a1\\'a2\\'b7\\'a2\\'c8\\'c8\\'a3\\'ac\\'d2\\'e0\\'ce\\'de\\'cd\\'b7\\'cd\\'b4\\'a3\\'ac\\'ca\\'d3\\'ce\\'ef\\'d0\\'fd\\'d7\\'aa\\'a3\\'ac\\'b8\\'b4\\'ca\\'d3\\'a3\\'ac\\'ba\\'da\\'95\\'e4\\'a3\\'ac\\'d2\\'e2\\'ca\\'b6\\'d5\\'cf\\'b0\\'ad\\'b5\\'c8\\'d6\\'a2\\'a1\\'a3\\par" + "}";
System.out.println( rtfString ); readFromRtfString( rtfString, "GB2312" ); }
|
可以在输出中清楚的看到 \'95
被直接被忽略掉了,这也难怪后面持续显示乱码了。
究极还是纠结
怎么处理呢?
方法一:替换生僻字为常用字,比如将“曚”替换成“蒙”,对应的编码替换为将 \'95\'e4
替换成 \'c3\'c9
。
缺点:太明显了,那么多生僻字怎么替换的完。
方法二:自己解析 .rtf 格式的字符序列,然后存为 byte
数组。然后用 GBK 编码解析。
缺点:可能要多花一点时间。