如何在Java中解析由Word创建的特殊字符

3

我正在尝试使用Java解析一些Word文档。其中一些值是日期范围,但是它们显示为一些奇怪的字符,而不是像“开始日期 - 结束日期”这样的格式。

StartDate ΓÇô EndDate

这是Word插入特殊字符连字符的位置。你能搜索这些字符并将它们替换为常规的“-”或字符串中的其他字符吗?这样我就可以在“-”上进行标记化了。那个字符是ASCII、Unicode还是其他的什么?

编辑以添加一些代码:

 String projDateString = "08/2010 ΓÇô Present"
                Charset charset = Charset.forName("Cp1252");
                CharsetDecoder decoder = charset.newDecoder();
                ByteBuffer buf = ByteBuffer.wrap(projDateString.getBytes("Cp1252"));
                CharBuffer cbuf = decoder.decode(buf); 
                String s = cbuf.toString();
                println ("S: " + s)

                println("projDatestring: " + projDateString)

输出以下内容:
S: 08/2010 ΓÇô Present
projDatestring: 08/2010 ΓÇô Present

此外,如果我使用上面相同的projDateString,执行以下操作:
projDateString.replaceAll("\u0096", "\u2013");
projDateString.replaceAll("\u0097", "\u2014");

然后打印projDateString,它仍然打印为
projDatestring: 08/2010 ΓÇô Present

那么我应该如何正确地表达这个问题呢?我正在寻找一种方法来检测连字符所使用的编码。 - Derek
哦,我明白你的意思了。我会编辑我的帖子。 - Pops
你得到的可能是一个 En Dash 或者是一个 Em Dash - Stephen P
4个回答

6
您可能得到的是Windows-1252字符集,而不是编码方式。(Torgamus - 搜索Windows-1232没有给我任何结果。)
Windows-1252,以前称为"Cp1252",几乎是Unicode,但保留了一些来自Cp1252的字符在它们原来的位置上。短划线是字符150(0x96),它位于Unicode C1保留控制字符范围内,不应该在那里。
您可以搜索字符150并将其替换为\u2013,这是短划线的正确Unicode代码点。
微软公司在0x80到0x9f范围内拥有相当多的其他字符,这些字符在Unicode标准中被保留,包括Em Dash、子弹符和它们的“智能”引号。
编辑:顺便说一下,Java在内部使用Unicode代码点值来表示字符。UTF-8是一种编码方式,当将字符串写入文件或网络连接时,Java使用它作为默认编码方式。
假设您有以下内容:
String stuff = MSWordUtil.getNextChunkOfText();

MSWordUtil是您编写的用于获取MS-Word .doc文件片段的工具。它可能归结为:

File myDocFile = new File(pathAndFileFromUser);
InputStream input = new FileInputStream(myDocFile);
// and then start reading chunks of the file

默认情况下,当您从文件中读取字节缓冲区并将其转换为字符串时,Java会将其视为UTF-8编码的文本。正如Lord Torgamus所说,有方法可以“告诉”应使用哪种编码,但如果不这样做,则Windows-1252接近UTF-8,除了那些位于C1控制范围内的烦人字符。

在获取像上面的stuff一样的字符串后,您不会在其中找到\u2013\u2014,而是会找到0x96和0x97。

此时,您应该能够执行以下操作:

stuff.replaceAll("\u0096", "\u2013");

在我处理这个问题的代码中,我不会使用replaceAll()方法。而是通过循环遍历一个输入的CharSequence,逐个字符地进行判断,如果满足0x80 <= charValue <= 0x9f则替换为数组中指定的内容。如果只关心1252 En Dash和Unicode En Dash之间的区别,则用上述的replaceAll()方法更简单。


所以我的输入字符串,来自doc文件,是Cp1252编码的,对吗?如果我要去掉其中的短划线,该怎么做?我以为可以像这样:String newString = new String(oldString.getBytes("CP1252), "UTF-8"),但似乎行不通——newString仍然打印出奇怪的字符,我搜寻了\u2013和\u2014也没有找到。 - Derek
1
给定从磁盘上的 Word 文档名创建的 File input 对象,您可以尝试 char[] chars = new char[(int) (input.length())]; Reader in = new InputStreamReader(new FileInputStream(input), encoding); in.read(chars); in.close(); String s = new String(chars); 其中 encoding 应该是您的 Word 文件的字符编码。从那时起,s 应该在内部使用 UTF-8,因此您可以轻松搜索 \u2013 或其他任何内容。 - Giulio Piancastelli
@ Derek:请看我的更新。我必须这样做,因为我得到了混合输入。正如Giulio在他的评论中所说,Torgamus在他的答案中提到的,如果您可以指定您的输入文本是"Windows-1252"作为InputStreamReader构造函数的第二个参数,您将实际上在您的java字符串中获得"\u2013",并且不必担心它。 - Stephen P
好的 - 我也编辑了我的帖子并附上了我的尝试代码 - 但那并没有起作用。你能找出原因吗? - Derek
1
@Derek:是的,你更新中的projDateString =“08/2010 ΓÇô Present”开始出问题了。你最初发布的那3个奇怪的字符实际上并不存在。你将..10 – Pres..中的字节视为UTF-8编码时会看到这些字符,但这些字节实际上代表的是windows-1252编码的字符串。在1252中,“0 – Pr”的字节(十六进制)为30 20 96 20 50 72,而在UTF-8中它们为30 20 E2 80 93 20 50 72。为了测试,你需要将字节30 38 2F 32 30 31 30 20 E2 80 93 20 50 72 65 73 65 6E 74写入文件,并从Java中读取该文件。 - Stephen P

4
s = s.replace( (char)145, (char)'\'');

s = s.replace( (char)8216, (char)'\''); // left single quote

s = s.replace( (char)146, (char)'\'');

s = s.replace( (char)8217, (char)'\''); // right single quote

s = s.replace( (char)147, (char)'\"');

s = s.replace( (char)148, (char)'\"');

s = s.replace( (char)8220, (char)'\"'); // left double

s = s.replace( (char)8221, (char)'\"'); // right double

s = s.replace( (char)8211, (char)'-' ); // em dash??    

s = s.replace( (char)150, (char)'-' );

http://www.coderanch.com/how-to/java/WeirdWordCharacters


2
你的问题几乎肯定与编码方案不匹配有关,因为你的编码方案与 Word 保存的编码方案不一致。你的代码可能使用了 Java 默认的编码方案,如果你没有对其进行任何更改,那么很可能是 UTF-8。另一方面,你的输入很可能是 Windows-1252,这是 Microsoft Word 的 .doc 文档的默认编码方案。请参见this site以获取更多信息。值得注意的是,

在 Windows 中,ISO-8859-1 被 Windows-1252 替换,这通常意味着从 Microsoft Word 文档复制并直接粘贴到网页中的文本会产生 HTML 验证错误。

这对你意味着什么?你需要告诉你的程序输入是使用Windows-1252编码,并将其转换为UTF-8。你可以以不同的方式手动完成这个过程。可能最自然的方法是利用Java内置的 Charset
Windows-1252被IANA Charset Registry所认可。

Name: windows-1252
MIBenum: 2252
Source: Microsoft (http://www.iana.org/assignments/charset-reg/windows-1252) [Wendt]
Alias: None

因此,它应该是与Charset兼容的。我自己之前没有做过这个,所以我不能给你一个代码示例,但我会指出有一个String构造函数,它接受一个byte[]和一个Charset作为参数。

ASCII和Unicode是字符集,而不是编码。当您从字符集中获得特定字符值时,您必须决定如何将该值写入磁盘。这就是编码的作用。 - Stephen P
@Stephen,嗯,我学到了一些关于语义学的东西。看来我们两个都没有完全正确。 - Pops
@Stephen,谢谢!我本来只是想进行一个快速的跟进编辑,但是我越研究,就越意识到原始答案需要改进,所以...是的。 - Pops
到目前为止,我尝试了几种不同的设置 - 但是还没有成功。我忘了提到这是Word 2007。它有不同的编码吗? - Derek
1
@Derek,Word 2007使用以下所有编码来处理英语:Unicode,“Windows 1250、1252-1254、1257、ISO8859-x”。来源:Microsoft Office帮助页面 - Pops

1

可能,那个字符是一个en dash,你看到的奇怪的文本是由于Word编码该字符的方式与您使用的其他系统解码该字符的方式之间的差异。

如果我记得没错的话,当我在Java中进行字符编码方面的工作时,String实例总是内部使用UTF-8;因此,在这样的实例中,您可以通过其Unicode形式搜索和替换单个字符。例如,假设您想用普通双引号替换智能引号:给定一个String s,您可以编写

s = s.replace('\u201c', '"');
s = s.replace('\u201d', '"');

其中201c201d是智能引号的Unicode代码点。根据上面维基百科的链接,短划线的Unicode代码点为2013


如果Word自动用自己的字符替换用户的字符,我会怀疑是使用了破折号而不是短横线。 - Pops
在回答之前,我在Word文档上进行了一个简单的测试:在我的屏幕上,该字符似乎是一个短线,但你可能是对的。 - Giulio Piancastelli
在Word中,如果你输入 2010 -- Present,它会用一个 en dash(长短线)替换掉两个短横线。 - Stephen P
据我所知,即使您只键入一个“-”,替换也会被触发。 - Giulio Piancastelli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接