dotnet / Open-XML-SDK

Open XML SDK by Microsoft
https://www.nuget.org/packages/DocumentFormat.OpenXml/
MIT License
4.02k stars 544 forks source link

EndnotesPart.AddHyperlinkRelationship not generating proper endnotes.xml.rels #1778

Open bhargavgaglani07 opened 1 month ago

bhargavgaglani07 commented 1 month ago

Describe the bug Adding an endnote having hyperlink into a document having no existing relationship for an endnote part is not generating proper endnotes.xml.rels file due to which generated document cannot be open using MS word or any other related application.

Screenshots

image image

To Reproduce Kindly run attached console app to reproduce the issue. EndnotesWithURLIssue.zip

Observed behavior If same code is executed (EndnotesPart.AddHyperlinkRelationship) for document already having an existing relationship present for endnotepart then generated document can be opened.

Expected behavior EndnotesPart.AddHyperlinkRelationship should create a proper endnotes.xml.rels if not present.

Desktop (please complete the following information):

Debug Info Adding below code before adding hyperlink relationship fixes the issue.

if(!document.Package.PartExists(new Uri("/word/_rels/endnotes.xml.rels", UriKind.Relative)))
{
    var part = document.Package.CreatePart(new Uri("/word/_rels/endnotes.xml.rels", UriKind.Relative), "application/vnd.openxmlformats-package.relationships+xml");
    part.Package.Flush();
}
mikeebowen commented 1 week ago

Hi @bhargavgaglani07,

I believe you see this issue because if there are no <endnotes/> element in the endnotes part if there are no existing endnotes. So, you need to add an <endnotes/> element before adding an endnote. I modified your code to do this and using the code below it will add the endnote and the relationship correctly. Also, FYI you don't need to call .Save() in a using statement. The document will save and dispose when the using closes.

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using W = DocumentFormat.OpenXml.Wordprocessing;

string dir = @"C:\source\tmp\";

if (!Directory.Exists(dir))
{
    Directory.CreateDirectory(dir);
}

string docxPath = @$"{dir}bbb - Copy.docx";
var outputPath = $"{dir}{DateTime.Now.Ticks}.docx";

Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"Processing file {Path.GetFileName(docxPath)}");

using (var memoryStream = File.Open(docxPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
using (var destinationStream = File.Create(outputPath))
using (var document = WordprocessingDocument.Open(memoryStream, true))
{

    Console.WriteLine($"Adding endnote having url");
    MainDocumentPart mainDocumentPart = document.MainDocumentPart ?? document.AddMainDocumentPart();
    EndnotesPart endNotesPart = mainDocumentPart.EndnotesPart ?? mainDocumentPart.AddNewPart<EndnotesPart>();

    if (endNotesPart.Endnotes is null)
    {
        endNotesPart.Endnotes = new Endnotes();
    }

    long endNoteId = GenerateEndnoteWithHyperlink(endNotesPart);

    mainDocumentPart.Document?.Body?.ChildElements.OfType<Paragraph>().LastOrDefault()?.InsertAfterSelf(GenerateParagraph(endNoteId));

    memoryStream.Seek(0, SeekOrigin.Begin);
    memoryStream.CopyTo(destinationStream);
}

try
{
    using (var document = WordprocessingDocument.Open(outputPath, true))
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine($"Could open generated document ({outputPath})");
        Console.ForegroundColor = ConsoleColor.Yellow;
    }
}
catch (Exception ex)
{
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine($"Could not open generated document ({outputPath}) due to error {ex.Message}");
    Console.ForegroundColor = ConsoleColor.Yellow;
}

Console.WriteLine();

Console.WriteLine("Hit enter to exit");
Console.ReadLine();

Paragraph GenerateParagraph(long endnoteId)
{
    Paragraph paragraph1 = new Paragraph();

    ParagraphProperties paragraphProperties1 = new ParagraphProperties();

    ParagraphMarkRunProperties paragraphMarkRunProperties1 = new ParagraphMarkRunProperties();
    Languages languages1 = new Languages() { Val = "en-IN" };

    paragraphMarkRunProperties1.Append(languages1);

    paragraphProperties1.Append(paragraphMarkRunProperties1);

    W.Run run1 = new W.Run();

    W.RunProperties runProperties1 = new W.RunProperties();
    Languages languages2 = new Languages() { Val = "en-IN" };

    runProperties1.Append(languages2);
    W.Text text1 = new W.Text();
    text1.Text = "Adding paragraph having endnote containing hyperlink.";

    run1.Append(runProperties1);
    run1.Append(text1);

    W.Run run2 = new W.Run();

    W.RunProperties runProperties2 = new W.RunProperties();
    RunStyle runStyle1 = new RunStyle() { Val = "EndnoteReference" };
    Languages languages3 = new Languages() { Val = "en-IN" };

    runProperties2.Append(runStyle1);
    runProperties2.Append(languages3);
    EndnoteReference endnoteReference1 = new EndnoteReference() { Id = endnoteId };

    run2.Append(runProperties2);
    run2.Append(endnoteReference1);

    paragraph1.Append(paragraphProperties1);
    paragraph1.Append(run1);
    paragraph1.Append(run2);
    return paragraph1;
}

long GenerateEndnoteWithHyperlink(EndnotesPart endnotesPart)
{
    long nextId = endnotesPart.Endnotes.ChildElements.OfType<Endnote>().Max(x => x.Id?.Value) + 1 ?? 1;

    Endnote endnote1 = new Endnote() { Id = nextId };

    Paragraph paragraph1 = new Paragraph();

    ParagraphProperties paragraphProperties1 = new ParagraphProperties();
    ParagraphStyleId paragraphStyleId1 = new ParagraphStyleId() { Val = "EndnoteText" };

    ParagraphMarkRunProperties paragraphMarkRunProperties1 = new ParagraphMarkRunProperties();
    Languages languages1 = new Languages() { Val = "en-IN" };

    paragraphMarkRunProperties1.Append(languages1);

    paragraphProperties1.Append(paragraphStyleId1);
    paragraphProperties1.Append(paragraphMarkRunProperties1);

    W.Run run1 = new W.Run();

    W.RunProperties runProperties1 = new W.RunProperties();
    RunStyle runStyle1 = new RunStyle() { Val = "EndnoteReference" };

    runProperties1.Append(runStyle1);
    EndnoteReferenceMark endnoteReferenceMark1 = new EndnoteReferenceMark();

    run1.Append(runProperties1);
    run1.Append(endnoteReferenceMark1);

    W.Run run2 = new W.Run();
    W.Text text1 = new W.Text() { Space = SpaceProcessingModeValues.Preserve };
    text1.Text = " ";

    run2.Append(text1);

    var relId = endnotesPart.AddHyperlinkRelationship(new Uri("https://www.dummyurlfromcode.com"), true).Id;

    W.Hyperlink hyperlink1 = new W.Hyperlink() { History = true, Id = relId };

    W.Run run3 = new W.Run() { RsidRunProperties = "005B5038" };

    W.RunProperties runProperties2 = new W.RunProperties();
    RunStyle runStyle2 = new RunStyle() { Val = "Hyperlink" };
    Languages languages2 = new Languages() { Val = "en-IN" };

    runProperties2.Append(runStyle2);
    runProperties2.Append(languages2);
    W.Text text2 = new W.Text();
    text2.Text = "https://www.dummyurlfromcode.com";

    run3.Append(runProperties2);
    run3.Append(text2);

    hyperlink1.Append(run3);

    paragraph1.Append(paragraphProperties1);
    paragraph1.Append(run1);
    paragraph1.Append(run2);
    paragraph1.Append(hyperlink1);

    endnote1.Append(paragraph1);
    endnotesPart.Endnotes.Append(endnote1);
    return endnote1.Id?.Value ?? throw new InvalidOperationException("Failed to generate a valid endnote id");
}
bhargavgaglani07 commented 1 week ago

Hi @mikeebowen,

Thank you for your response, but I would like to draw your attention to part where endnotes is already available in document and it also has 3 endnote. Check below for details.

Image

Here issue is that relationship part for XML is not getting generated by calling endnotesPart.AddHyperlinkRelationship(new Uri("https://www.dummyurlfromcode.com", UriKind.Absolute), true).

Endnote will get added but while opening the document it will fail as related hyperlink part is missing.

Refer below screenshot of running shared console application.

Image

Let me know if I am missing something or in case you need more information. Also note that, I tried adding below shared code but it will not get executed as document already have endnotes node along with multiple endnote.

if (endNotesPart.Endnotes is null)
{
    endNotesPart.Endnotes = new Endnotes();
}

Thanks, Bhargav

mikeebowen commented 6 days ago

@bhargavgaglani07, Can you share your code? It works for me to add an endnote to an existing endnotes. The null check is there in case the document does not already have endnotes.

bhargavgaglani07 commented 1 day ago

Hi @mikeebowen,

Sure, please find attached console application, running the same will reproduce issue. Kindly note that here issue is not with adding endnote (no null reference exception is observed). Endnote gets added but when document is being opened at that time issue is observed due to missing reference part.

EndnotesWithURLIssue.zip

Let me know if you need more information.

Thanks Bhargav