修改Excel单元格后超链接URI编码异常损坏问题求助
解决Apache POI修改Excel后绘图超链接URI编码损坏问题
使用Apache POI的XSSFWorkbook修改Excel指定单元格值后,发现绘图中的内部超链接(如图片链接到其他工作表)被自动进行URI编码,特殊字符(如波兰语字符ż、ę)和空格被替换为编码值(ż→%C5%BC,空格→%20),导致超链接失效,且未修改数据的工作表中的链接也受影响。
源文件drawing.xml.rels中的超链接节点:
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="#'Rach.Bież. (miesięcznie) '!B3"/>
POI写入后变为:
<Relationship Id="rId2" Target="#'Rach.Bie%C5%BC.%20(miesi%C4%99cznie)%20'!B3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"/>
问题原因
Apache POI在处理XSSF绘图中的超链接时,会默认对Target属性进行URI编码,但Excel自身存储内部工作表链接时,使用的是原始字符而非编码值。POI的编码逻辑与Excel的存储规范不匹配,导致写入时过度编码,破坏原有链接。
解决方案
方案1:遍历并修复绘图超链接的Target值
在写入工作簿前,遍历所有工作表的绘图部件,解码超链接的Target属性,恢复原始字符格式:
import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; import org.apache.poi.xssf.usermodel.XSSFDrawing; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; // ... 原有读取文件、初始化工作簿的代码 ... // 修改单元格的逻辑保持不变 XSSFSheet sheet = workbook.getSheet("test"); Row row = sheet.getRow(0); Cell cell = row.getCell(0); cell = row.createCell(0); cell.setCellValue(1.0); // 遍历所有工作表,修复绘图中的超链接 for (XSSFSheet s : workbook) { XSSFDrawing drawing = s.getDrawingPatriarch(); if (drawing != null) { // 获取绘图的超链接关系集合 PackageRelationshipCollection rels = drawing.getPackagePart().getRelationshipsByType( "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" ); for (PackageRelationship rel : rels) { String target = rel.getTargetURI().toString(); try { // 解码Target值,恢复原始字符 String decodedTarget = URLDecoder.decode(target, "UTF-8"); // 重新设置Target,避免POI再次编码 rel.setTargetURI(new java.net.URI(decodedTarget)); } catch (UnsupportedEncodingException | java.net.URISyntaxException e) { e.printStackTrace(); } } } } // 写入工作簿 ByteArrayOutputStream bos = new ByteArrayOutputStream(); workbook.write(bos); bos.close(); workbook.close();
方案2:直接操作OOM修改drawing.xml.rels文件
如果方案1无效,可以直接操作Open XML底层对象模型,修改drawing.xml.rels中的节点:
import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.xssf.usermodel.XSSFDrawing; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.URLDecoder; // ... 原有读取文件、修改单元格的代码 ... // 处理绘图rels文件 for (XSSFSheet s : workbook) { XSSFDrawing drawing = s.getDrawingPatriarch(); if (drawing != null) { PackagePart relPart = drawing.getPackagePart().getRelationshipsPart(); if (relPart != null) { try { // 读取rels文件内容 InputStream is = relPart.getInputStream(); DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); Document doc = dBuilder.parse(is); is.close(); // 获取所有超链接关系节点 NodeList relNodes = doc.getElementsByTagName("Relationship"); for (int i = 0; i < relNodes.getLength(); i++) { Element relElement = (Element) relNodes.item(i); String type = relElement.getAttribute("Type"); if ("http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink".equals(type)) { String target = relElement.getAttribute("Target"); // 解码Target String decodedTarget = URLDecoder.decode(target, "UTF-8"); relElement.setAttribute("Target", decodedTarget); } } // 写回rels文件 ByteArrayOutputStream os = new ByteArrayOutputStream(); TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); DOMSource source = new DOMSource(doc); StreamResult result = new StreamResult(os); transformer.transform(source, result); relPart.setInputStream(new ByteArrayInputStream(os.toByteArray())); os.close(); } catch (Exception e) { e.printStackTrace(); } } } } // 写入工作簿 ByteArrayOutputStream bos = new ByteArrayOutputStream(); workbook.write(bos); bos.close(); workbook.close();
方案3:升级Apache POI版本
部分较旧版本的POI存在该编码逻辑问题,尝试升级到最新稳定版(如5.2.5+),查看官方是否已修复该Bug。
验证方法
执行修改后的代码后:
- 打开生成的Excel文件,检查
btn工作表中的图片超链接是否能正常跳转 - 解压Excel文件,查看
xl/drawings/_rels/drawing1.xml.rels中的Target值是否恢复为原始未编码格式
内容的提问来源于stack exchange,提问作者Vap Hel
相关产品推荐
相关产品推荐

