解析word内嵌文件名中文乱码
原文地址 https://blog.peoplevip.cn/2021/3213641848.html
Apache POI 简介是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office(Excel、WORD、PowerPoint、Visio等)格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“可怜的模糊实现”。
官方主页: http://poi.apache.org/index.html
API文档: http://poi.apache.org/apidocs/index.html
问题发现
我们知道word是支持插入其他文件的
插入后入下图所示
在使用Apache POI过程中发现在读取文件名时会发生乱码问题
问题定位
经过代码排查定位发现文件名是取自一个label
的元素值,
label
是在包org.apache.poi.poifs.filesystem.Ole10Native
的第165行,代码如下
case parsed: {
flags1 = LittleEndian.getShort(data, ofs);
// structured format
ofs += LittleEndianConsts.SHORT_SIZE;
int len = getStringLength(data, ofs);
label = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
ofs += len;
len = getStringLength(data, ofs);
fileName = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
ofs += len;
flags2 = LittleEndian.getShort(data, ofs);
ofs += LittleEndianConsts.SHORT_SIZE;
unknown1 = LittleEndian.getShort(data, ofs);
ofs += LittleEndianConsts.SHORT_SIZE;
继续跟踪源码进入StringUtil.getFromCompressedUnicode
方法中,其代码中明确写出二进制目标编码为ISO_8859_1
,
/**
* Read 8 bit data (in ISO-8859-1 codepage) into a (unicode) Java
* String and return.
* (In Excel terms, read compressed 8 bit unicode as a string)
*
* @param string byte array to read
* @param offset offset to read byte array
* @param len length to read byte array
* @return String generated String instance by reading byte array
*/
public static String getFromCompressedUnicode(
final byte[] string,
final int offset,
final int len) {
int len_to_use = Math.min(len, string.length - offset);
return new String(string, offset, len_to_use, ISO_8859_1);
}
问题解决
通过在IDEA的debug运算中发现将编码修改为GBK
正常转换中文,看来这里是乱码的根本原因了,后续又新建文件测试,发现wps、word2019、wps linux、永中office新建的文件在此处改为GBK
均可正常显示中文文件名。问题成功定位,接下来进行代码修改
将源码拉取到本地,在StringUtil中添加如下方法,这里自定义了一个charset,我们在调用处调用这个方法就可以了,这样也能保证不会干扰其他代码
public static String getFromCompressedUnicode(
final byte[] string,
final int offset,
final int len, final Charset charset) {
int len_to_use = Math.min(len, string.length - offset);
return new String(string, offset, len_to_use, charset);
}
调用处(Ole10Native)
修改
label = StringUtil.getFromCompressedUnicode(data, ofs, len - 1,Charset.forName("GBK"));
// 可以顺便把下边隔一行的fileName一块改了
fileName = StringUtil.getFromCompressedUnicode(data, ofs, len - 1,Charset.forName("GBK"));
上传到私有仓库
我们公司是有自己的私有仓库的,既然改好了,就将代码上传上去吧
打开上一级的build.gradle
在subprojects
中添加
apply plugin: 'maven-publish'
apply plugin: 'net.linguica.maven-settings' // 看需要,我们仓库是要加这个的
group = "org.apache.poi"
version = '4.1.2-xxx.1' // 随便.1代表第一次修改,4.1.2是原始的版本号
publishing {
publications {
basic(MavenPublication) {
from components.java
}
}
repositories {
maven {
url = uri("xxx/maven/v1") // 仓库上传地址
// 下面是一些鉴权信息,参考其他项目
name = "xxx"
authentication {
basic(BasicAuthentication)
}
}
}
}
}
引用修改后包
需要实际引用项目在IDEA右侧gradle中搜索一下那些包额外引用了org.apache.poi
,需要手动排除掉
然后引入我们刚才修改的包就可以了