EndnotesPart.AddHyperlinkRelationship not generating proper endnotes.xml.rels
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
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):
- OS: windows 10
- Office version: 16.0.17628.20188
- .NET Target: 4.8
- DocumentFormat.OpenXml Version: 2.5.0, 3.1.0
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();
}
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");
}
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.
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.
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
@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.
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.
Let me know if you need more information.
Thanks Bhargav
@mikeebowen can you take a look at this again with the latest changes I've pushed in?
Hi @bhargavgaglani07, your repro uses the OpenXML SDK 2.5.0, which is out of support. The sample I posted uses 3+. Please reformat your code to use 3+ and see if the issue persists.
Hi @mikeebowen ,
It is reproducible with v3.1.1 as well. Please find attached sample console application.
EndnotesWithURLIssue_v3.1.1.zip
Thanks, Bhargav
Hi @bhargavgaglani07,
There is an issue somewhere in your code. I refactored to use using statements instead of Close and Dispose and the endnote with hyperlink and its relationship are correctly added when targeting v3.1.1.
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using W = DocumentFormat.OpenXml.Wordprocessing;
foreach (var path in Directory.GetFiles(@"C:\source\tmp\bar"))
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"Processing file {Path.GetFileName(path)}");
if (!Directory.Exists(@"C:\source\tmp\bar\foo"))
{
Directory.CreateDirectory(@"C:\source\tmp\bar\foo");
}
var outputPath = $@"C:\source\tmp\bar\foo\{DateTime.Now.Ticks}.docx";
var documentPayload = File.ReadAllBytes(path);
using (var memoryStream = new MemoryStream())
{
memoryStream.Position = 0;
memoryStream.Write(documentPayload);
using (var wordprocessingDocument = WordprocessingDocument.Open(memoryStream, true))
{
if (wordprocessingDocument?.MainDocumentPart?.EndnotesPart is not null && wordprocessingDocument?.MainDocumentPart?.Document?.Body is not null)
{
Console.WriteLine("Adding endnote with url");
var endNoteId = GenerateEndnoteWithHyperlink(wordprocessingDocument.MainDocumentPart.EndnotesPart);
wordprocessingDocument.MainDocumentPart.Document.Body.AppendChild(GenerateParagraph(endNoteId));
wordprocessingDocument.MainDocumentPart.EndnotesPart.Endnotes.Save();
wordprocessingDocument.MainDocumentPart.Document.Save();
wordprocessingDocument.Save();
File.WriteAllBytes(outputPath, memoryStream.ToArray());
}
}
}
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;
}
}
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, the code from the post above works, but I was also able to make your code work by creating a new Console project and copy/pasting your code, only changing the file paths and it also adds the hyperlinks and relationships correctly. Can you test with different files? Maybe the .docx you're using is malformed somehow.
Here is the working project EndnoteWithURL.zip
This has an answer and a working sample, so closing this issue.