公司有需求,将翻译的文件内容尽可能的原格式会填的excel表格中。本来没什么难度,POI我们yyds嘛,但是后期发现POI解析excel文件很是容易导致内存的溢出。所以后来利用easyExcel来进行文件的读取,但原格式回填还是只能用POI的单元格对象调用setCellValue()方法进行回填,这成为处理excel文件的短板,根据木桶理论,这成为最大的瓶颈,调研市场半年以上后,并没有让人满意的方法。虽然POI有流式写入的工具,但貌似流式写入并没有不改变格式,即可将文件内容导出的方法。
新的发现,office的产品大部分可以将尾缀修改为zip格式,然后解压,会看到excel文件(以下均以excel文件举例)是由若干xml文件组合而成的,里边有保存样式的、表格的、内容的xml文件。尤其是看到sharedStrings.xml文件时,就有预感问题即将解决了。话不多少,上思路上代码。。。
- 将待处理文件尾缀改为zip格式
- 解压
- 找到xl路径下的sharedStrings.xml文件
- 利用dom4j来解析xml文件
- 将文件组进行压缩,并将尾缀改回xlsx
import cn.hutool.core.util.ZipUtil;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
@Slf4j
public class ExcelUtils {
public static void main(String[] args) {
}
/**
* excel文件回填
* @param srcPath 文件路径
* @param output 文件输出位置
*/
public void excelBackFill(String srcPath, String output) {
log.info("【excelBackFill】");
int index = srcPath.lastIndexOf(".");
if (index < 0) {
log.info("Suffix obtaining failed");
}
//将尾缀改为zip格式
String toFilePath = srcPath.substring(0, index + 1) + "zip";
String unzipFile = srcPath.substring(0, index) ;
//生成目标excel文件的zip文件
FileUtil.formatConversion(srcPath, "zip");
FileUtil.deletefolder(unzipFile);
//解压压缩文件
File gbk = ZipUtil.unzip(toFilePath, unzipFile, Charset.forName("gbk"));
if(!gbk.exists()){
log.info("Unzip file failed");
return;
}
//获取excel文件的共享字符串文件
String sharedStringsFilePath= unzipFile+"/xl/sharedStrings.xml";
try {
contentBackFill(sharedStringsFilePath);
} catch (DocumentException e) {
log.error("【excelBackFill】出现异常:",e);
}
compressedFile(unzipFile,output);
}
/**
* 压缩文件
* @param unzipFile
* @param output
*/
public void compressedFile(String unzipFile,String output){
log.info("【compressedFile】 start");
Map<String, String> pathAndAbsolutePathMap=new HashMap<>();
List<String> filePathList=new ArrayList<>();
File file = new File(unzipFile);
FileUtil.recursiveGetFileInfo(file,filePathList);
//拆分文件路径 与文件名
for (String filePath:filePathList ) {
pathAndAbsolutePathMap.put(filePath,filePath.replace(file+File.separator,""));
}
String outputFolder=output.substring(0,output.lastIndexOf("/"));
String outputfile=output.substring(output.lastIndexOf("/")+1);
try {
ZipUtils.getZipFile(pathAndAbsolutePathMap, outputFolder,outputfile);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
log.info("【compressedFile】 end");
}
/**
*表格内容回填
*/
private void contentBackFill(String path) throws DocumentException {
log.info("【contentBackFill】 start");
//1.创建Reader对象
SAXReader reader = new SAXReader();
//2.加载xml
Document document = reader.read(new File(path));
//3.获取根节点
Element rootElement = document.getRootElement();
//4.迭代元素根
Iterator iterator = rootElement.elementIterator();
while (iterator.hasNext()) {
//5.获取根元素的内容,并翻译
Element stu = (Element) iterator.next();
String dst = "目标内容";
//6.将内部元素全部删除(也可以判断,如果下边只有t标签元素,可以直接回填)
Iterator iterator1 = stu.elementIterator();
while (iterator1.hasNext()) {
Element stuChild = (Element) iterator1.next();
stu.remove(stuChild);
}
//创建新的节点对象并回填译文内容
Element t = stu.addElement("t");
t.setText(dst);
}
writeXmlFile(path,document);
log.info("【contentBackFill】 end");
}
public void writeXmlFile(String outPath,Document doc){
OutputStream outputStream = null;
XMLWriter xmlWriter = null;
try {
OutputFormat outputFormat = new OutputFormat();
outputFormat.setEncoding("UTF-8");
outputStream = new FileOutputStream(outPath);
xmlWriter = new XMLWriter(outputStream,outputFormat);
xmlWriter.write(doc);
}catch (Exception e){
e.printStackTrace();
log.error(e.getMessage());
}finally {
close(xmlWriter,outputStream,null);
}
}
public static void close(XMLWriter xmlWriter, OutputStream outputStream, InputStream inputStream){
if (xmlWriter != null){
try{
xmlWriter.close();
} catch (IOException e) {
log.error("XMLUtil.close error: "+ e);
}
xmlWriter = null;
}
if (outputStream != null){
try{
outputStream.close();
} catch (IOException e) {
log.error("XMLUtil.close error: "+ e);
}
outputStream = null;
}
if (inputStream != null){
try{
inputStream.close();
} catch (IOException e) {
log.error("XMLUtil.close error: "+ e);
}
inputStream = null;
}
}
}
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import java.io.*;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Slf4j
public class ZipUtils {
/**
* 通过指定路径和文件名来获取文件对象,当文件不存在时自动创建
*
* @param path
* @param fileName
* @return
* @throws IOException
*/
public static File getFile(String path, String fileName) throws IOException {
// 创建文件对象
File file;
if (StringUtils.isNotEmpty(path))
file = new File(path, fileName);
else
file = new File(fileName);
if (!file.getParentFile().exists()) {
try {
boolean mkdir = file.getParentFile().mkdirs();
System.out.println(mkdir);
if (!file.exists()) {
file.createNewFile();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return file;
}
/**
* 将多个文件压缩
*
* @param fileMap 待压缩的文件列表
* @param zipFileName 压缩文件名
* @return 返回压缩好的文件
* @throws IOException
*/
public static File getZipFile(Map<String, String> fileMap, String zipFilePath, String zipFileName) throws IOException {
File zipFile = getFile(zipFilePath, zipFileName);
log.info("文件开始压缩");
// 文件输出流
FileOutputStream outputStream =new FileOutputStream(zipFile);
// 压缩流
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
int i = 1;
for (Map.Entry<String, String> entry : fileMap.entrySet()) {
String filePath = entry.getKey();
String fileName = entry.getValue();
File file = new File(filePath);
zipFile(i, file, fileName, zipOutputStream);
i++;
}
// 关闭压缩流、文件流
zipOutputStream.close();
outputStream.close();
log.info("文件压缩完成");
return zipFile;
}
/**
* 将文件数据写入文件压缩流
*
* @param file 带压缩文件
* @param zipOutputStream 压缩文件流
* @throws IOException
*/
private static void zipFile(int num, File file, String fileName, ZipOutputStream zipOutputStream) throws IOException {
if (file.exists()) {
if (file.isFile()) {
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
ZipEntry entry = new ZipEntry(fileName);
try{
zipOutputStream.putNextEntry(entry);
}catch (java.util.zip.ZipException z){
ZipEntry entry1 = new ZipEntry(num+"-"+fileName);
zipOutputStream.putNextEntry(entry1);
}
final int MAX_BYTE = 10 * 1024 * 1024; // 最大流为10MB
long streamTotal = 0; // 接收流的容量
int streamNum = 0; // 需要分开的流数目
int leaveByte = 0; // 文件剩下的字符数
byte[] buffer; // byte数据接受文件的数据
streamTotal = bis.available(); // 获取流的最大字符数
streamNum = (int) Math.floor(streamTotal / MAX_BYTE);
leaveByte = (int) (streamTotal % MAX_BYTE);
if (streamNum > 0) {
for (int i = 0; i < streamNum; i++) {
buffer = new byte[MAX_BYTE];
bis.read(buffer, 0, MAX_BYTE);
zipOutputStream.write(buffer, 0, MAX_BYTE);
}
}
// 写入剩下的流数据
buffer = new byte[leaveByte];
bis.read(buffer, 0, leaveByte); // 读入流
zipOutputStream.write(buffer, 0, leaveByte); // 写入流
zipOutputStream.closeEntry(); // 关闭当前的zip entry
// 关闭输入流
bis.close();
fis.close();
}
}
}
}
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.util.List;
@Slf4j
public class FileUtil {
/**
* 文件格式转换
* @param srcPath 原文件路径
* @param suffix 目标格式尾缀
* @return 是否转换成功
*/
public static boolean formatConversion(String srcPath, String suffix) {
File from = new File(srcPath);
if (!from.exists()) {
log.info("file does not exist: {}", srcPath);
return false;
}
int index = srcPath.lastIndexOf(".");
if (index < 0) {
log.info("Suffix obtaining failed");
return false;
}
String toFile = srcPath.substring(0, index + 1) + suffix;
FileInputStream fis;
try {
fis = new FileInputStream(srcPath);
} catch (FileNotFoundException e) {
log.error("The input file FileNotFoundException:"+e);
return false;
}
FileOutputStream out;
try {
out = new FileOutputStream(toFile);
} catch (FileNotFoundException e) {
log.error(" The output file FileNotFoundException:"+e);
return false;
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
byte[] b = new byte[1024];
int n;
while ((n = fis.read(b)) != -1) {
bos.write(b, 0, n);
}
out.write(bos.toByteArray());
return true;
} catch (IOException e) {
log.error("file conversion exception:"+e);
return false;
}finally {
try {
fis.close();
bos.close();
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 递归获取文件的路径
* @param file 文件
*/
public static void recursiveGetFileInfo(File file,List<String> filePathList){
File[] fs = file.listFiles();
for(File f:fs){
if(f.isDirectory()) //若是目录,则递归打印该目录下的文件
recursiveGetFileInfo(f,filePathList);
if(f.isFile()) //若是文件,直接打印·
filePathList.add(f.getPath());
}
}