diff --git a/src/main/java/org/wordinator/xml2docx/generator/DocxGenerator.java b/src/main/java/org/wordinator/xml2docx/generator/DocxGenerator.java index 2f3f2d6..eeb0ed4 100644 --- a/src/main/java/org/wordinator/xml2docx/generator/DocxGenerator.java +++ b/src/main/java/org/wordinator/xml2docx/generator/DocxGenerator.java @@ -46,6 +46,7 @@ import org.apache.poi.xwpf.usermodel.XWPFNum; import org.apache.poi.xwpf.usermodel.XWPFNumbering; import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFRelation; import org.apache.poi.xwpf.usermodel.XWPFRun; import org.apache.poi.xwpf.usermodel.XWPFStyle; import org.apache.poi.xwpf.usermodel.XWPFStyles; @@ -1756,7 +1757,7 @@ private void makeFootnote(XWPFParagraph para, XmlObject xml) throws DocxGenerati } } - noteCursor.dispose(); + noteCursor.close(); } @@ -2243,27 +2244,29 @@ private void makeHyperlink(XWPFParagraph para, XmlCursor cursor) throws DocxGene // Convention in simple WP XML is fragment identifiers are to bookmark IDs, // while everything else is a URI to an external resource. + // If the hyperlink is to a bookmark (just a fragment ID) then we we create a hyperlink with + // an anchor, otherwise we create an external hyperlink to a URI. + CTHyperlink hyperlink = para.getCTP().addNewHyperlink(); // Set the appropriate target: if (href.startsWith("#")) { - // Just a fragment ID, must be to a bookmark + // Just a fragment ID, must be to a bookmark, set @anchor attribute. String bookmarkName = href.substring(1); hyperlink.setAnchor(bookmarkName); } else { - // Create a relationship that targets the href and use the - // relationship's ID on the hyperlink - // It's not yet clear from the POI API how to create a new relationship for - // use by an external hyperlink. - // throw new NotImplementedException("Links to external resources not yet implemented."); + // Create an external hyperlink. This creates the necessary relationship. + // Not using the createHyperlinkRun() of paragraph because it doesn't handle the + // runs within the as we need. Set the @rId attribute. + String rId = para.getDocument().getPackagePart().addExternalRelationship(href, XWPFRelation.HYPERLINK.getRelation()).getId(); + hyperlink.setId(rId); } cursor.push(); XWPFHyperlinkRun hyperlinkRun = makeHyperlinkRun(hyperlink, cursor, para); cursor.pop(); para.addRun(hyperlinkRun); - } /** diff --git a/src/test/java/org/wordinator/xml2docx/TestDocxGenerator.java b/src/test/java/org/wordinator/xml2docx/TestDocxGenerator.java index c21a579..930bd3c 100644 --- a/src/test/java/org/wordinator/xml2docx/TestDocxGenerator.java +++ b/src/test/java/org/wordinator/xml2docx/TestDocxGenerator.java @@ -251,7 +251,7 @@ public void testHyperlinkHandling() throws Exception { XWPFParagraph p = iterator.next(); assertNotNull("Expected a paragraph", p); assertEquals("Issue 65: Data following hyperlink is dropped", p.getText()); - // Normal footnote with generated ref + // Hyperlink to a bookmark (internal link): p = iterator.next(); Iterator runIterator = p.getIRuns().iterator(); assertTrue("Expected runs", runIterator.hasNext()); @@ -259,9 +259,26 @@ public void testHyperlinkHandling() throws Exception { assertEquals("First run in para before the hyperlink", ((XWPFRun)run).getText(0)); run = runIterator.next(); // Should be the hyperlink assertTrue("Expected a XWPFHyperlinkRun", run instanceof XWPFHyperlinkRun); - assertTrue("Expected fun following the hyperlink", runIterator.hasNext()); - run = runIterator.next(); // Should be the hyperlink + assertTrue("Expected run following the hyperlink", runIterator.hasNext()); + run = runIterator.next(); assertEquals("Run after the hyperlink.", ((XWPFRun)run).getText(0)); + // Now look for external link: + p = iterator.next(); + assertNotNull("Expected another paragraph after internal hyperlink", p); + p = iterator.next(); + assertNotNull("Expected another paragraph after internal hyperlink", p); + p = iterator.next(); // Should be paragraph with hyperlink + assertNotNull("Expected another paragraph after internal hyperlink", p); + runIterator = p.getIRuns().iterator(); + assertTrue("Expected runs", runIterator.hasNext()); + run = runIterator.next(); + assertTrue("First run in para before the hyperlink", ((XWPFRun)run).getText(0).startsWith("External hyperlink:")); + run = runIterator.next(); // Should be the hyperlink + assertTrue("Expected a XWPFHyperlinkRun", run instanceof XWPFHyperlinkRun); + XWPFHyperlinkRun linkRun = (XWPFHyperlinkRun)run; + String rId = linkRun.getHyperlinkId(); + assertNotNull("Expected and rId", rId); + } @Test diff --git a/src/test/resources/docx/Manual-and-Styled-Lists.docx b/src/test/resources/docx/Manual-and-Styled-Lists.docx index 174601f..84e2693 100644 Binary files a/src/test/resources/docx/Manual-and-Styled-Lists.docx and b/src/test/resources/docx/Manual-and-Styled-Lists.docx differ diff --git a/src/test/resources/simplewp/simplewpml-issue-65.swpx b/src/test/resources/simplewp/simplewpml-issue-65.swpx index 8f11ee5..2ef6fc7 100644 --- a/src/test/resources/simplewp/simplewpml-issue-65.swpx +++ b/src/test/resources/simplewp/simplewpml-issue-65.swpx @@ -64,5 +64,7 @@ 1996; 24: 1-11.

- +

External hyperlink: Wordinator + After hyperlink para with external hyperlink

+ diff --git a/version.properties b/version.properties index 2e8b9a7..1bf5794 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -version=1.1.3 +version=1.2.0