From 89b6e71aebbed07dbaa1d816697109b1040ae0ff Mon Sep 17 00:00:00 2001 From: Nikita Kuprins Date: Sat, 26 Sep 2026 00:14:34 +0300 Subject: [PATCH 1/2] fix: keep hyperlink URL when it has a fragment --- .../v07/handlers/HyperlinkTagHandler.java | 35 +++++++++--------- .../fesod/sheet/converter/ExtraDataTest.java | 27 ++++++++++++++ .../extra/extraHyperlinkFragment.xlsx | Bin 0 -> 5869 bytes 3 files changed, 45 insertions(+), 17 deletions(-) create mode 100644 fesod-sheet/src/test/resources/extra/extraHyperlinkFragment.xlsx diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/v07/handlers/HyperlinkTagHandler.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/v07/handlers/HyperlinkTagHandler.java index 9323edc0f..46eed44ae 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/v07/handlers/HyperlinkTagHandler.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/v07/handlers/HyperlinkTagHandler.java @@ -25,7 +25,6 @@ package org.apache.fesod.sheet.analysis.v07.handlers; -import java.util.Optional; import org.apache.fesod.common.util.StringUtils; import org.apache.fesod.sheet.constant.ExcelXmlConstants; import org.apache.fesod.sheet.context.xlsx.XlsxReadContext; @@ -54,27 +53,29 @@ public void startElement(XlsxReadContext xlsxReadContext, String name, Attribute return; } // Hyperlink has 2 case: - // case 1,In the 'location' tag + // case 1, In the 'r:id' tag, Then go to 'PackageRelationshipCollection' to get inside; + // a 'location' next to it is the URI fragment String location = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_LOCATION); - if (location != null) { - CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.HYPERLINK, location, ref); - xlsxReadContext.readSheetHolder().setCellExtra(cellExtra); - xlsxReadContext.analysisEventProcessor().extra(xlsxReadContext); - return; - } - // case 2, In the 'r:id' tag, Then go to 'PackageRelationshipCollection' to get inside String rId = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_RID); PackageRelationshipCollection packageRelationshipCollection = xlsxReadContext.xlsxReadSheetHolder().getPackageRelationshipCollection(); - if (rId == null || packageRelationshipCollection == null) { + PackageRelationship relationship = rId == null || packageRelationshipCollection == null + ? null + : packageRelationshipCollection.getRelationshipByID(rId); + String address; + if (relationship != null) { + address = relationship.getTargetURI().toString(); + if (location != null) { + address += "#" + location; + } + } else if (location != null) { + // case 2,In the 'location' tag + address = location; + } else { return; } - Optional.ofNullable(packageRelationshipCollection.getRelationshipByID(rId)) - .map(PackageRelationship::getTargetURI) - .ifPresent(uri -> { - CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.HYPERLINK, uri.toString(), ref); - xlsxReadContext.readSheetHolder().setCellExtra(cellExtra); - xlsxReadContext.analysisEventProcessor().extra(xlsxReadContext); - }); + CellExtra cellExtra = new CellExtra(CellExtraTypeEnum.HYPERLINK, address, ref); + xlsxReadContext.readSheetHolder().setCellExtra(cellExtra); + xlsxReadContext.analysisEventProcessor().extra(xlsxReadContext); } } diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ExtraDataTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ExtraDataTest.java index 1c13ad43c..de440fd00 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ExtraDataTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ExtraDataTest.java @@ -26,6 +26,8 @@ package org.apache.fesod.sheet.converter; import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.fesod.sheet.FesodSheet; import org.apache.fesod.sheet.context.AnalysisContext; @@ -124,4 +126,29 @@ public void extra(CellExtra extra, AnalysisContext context) { .sheet() .doRead(); } + + @Test + void readHyperlinkWithUrlFragment() { + // A1 links to https://example.com/page#section, stored as r:id plus location="section" + File file = readFile("extra" + File.separator + "extraHyperlinkFragment.xlsx"); + List hyperlinks = new ArrayList<>(); + FesodSheet.read(file, new ReadListener() { + @Override + public void invoke(Object data, AnalysisContext context) {} + + @Override + public void doAfterAllAnalysed(AnalysisContext context) {} + + @Override + public void extra(CellExtra extra, AnalysisContext context) { + hyperlinks.add(extra.getText()); + } + }) + .extraRead(CellExtraTypeEnum.HYPERLINK) + .sheet() + .doRead(); + + Assertions.assertEquals( + Arrays.asList("https://example.com/page#section", "https://example.com/plain"), hyperlinks); + } } diff --git a/fesod-sheet/src/test/resources/extra/extraHyperlinkFragment.xlsx b/fesod-sheet/src/test/resources/extra/extraHyperlinkFragment.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d5cb4a54df28ed579351a70b2aa1feec64233a34 GIT binary patch literal 5869 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 literal 0 HcmV?d00001 From 60461329c37ffdd1c543a1b7e612d538d5941850 Mon Sep 17 00:00:00 2001 From: Nikita Kuprins Date: Sat, 26 Sep 2026 13:48:12 +0300 Subject: [PATCH 2/2] docs: clarify hyperlink location comments --- .../sheet/analysis/v07/handlers/HyperlinkTagHandler.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/v07/handlers/HyperlinkTagHandler.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/v07/handlers/HyperlinkTagHandler.java index 46eed44ae..e8cdae12e 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/v07/handlers/HyperlinkTagHandler.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/v07/handlers/HyperlinkTagHandler.java @@ -52,9 +52,8 @@ public void startElement(XlsxReadContext xlsxReadContext, String name, Attribute if (StringUtils.isEmpty(ref)) { return; } - // Hyperlink has 2 case: - // case 1, In the 'r:id' tag, Then go to 'PackageRelationshipCollection' to get inside; - // a 'location' next to it is the URI fragment + // External hyperlinks store the target in the r:id relationship. + // When present, location contains the URI fragment. String location = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_LOCATION); String rId = attributes.getValue(ExcelXmlConstants.ATTRIBUTE_RID); PackageRelationshipCollection packageRelationshipCollection = @@ -69,7 +68,7 @@ public void startElement(XlsxReadContext xlsxReadContext, String name, Attribute address += "#" + location; } } else if (location != null) { - // case 2,In the 'location' tag + // Internal hyperlinks store their destination directly in location. address = location; } else { return;