Addin Link [URL] to an Image in Excel cell using open xml - c#

I'm trying to use AddExternalRelationship method, but it doesn't work,
simply I want to add Link to File [my file in this case is an image on a server] using openXML
using (var newDoc = SpreadsheetDocument.Open(xlsDestFilePath, true)) {
var run = new DocumentFormat.OpenXml.Spreadsheet.Run();
var picture = new DocumentFormat.OpenXml.Spreadsheet.Picture();
var shape = new DocumentFormat.OpenXml.Vml.Shape() {
Id = "_x0000_i1025",
Style = "width:453.5pt;height:270.8pt"
};
run.Append(picture);
newDoc.AddHyperlinkRelationship(
new Uri("URL GOES HERE", System.UriKind.Absolute), true);
}

Use AddHyperlinkRelationship
It is clearly stated in the msdn documentation to not use AddExternalRelationship
Refer:
https://msdn.microsoft.com/en-us/library/office/cc562653.aspx
public HyperlinkRelationship AddHyperlinkRelationship(
Uri hyperlinkUri,
bool isExternal,
string id
)

Related

How to generate .resx file dynamically in C# in human readable format

I was trying to generate .resx file using ResourceWriter but file generated is not in readable format
string filePath = #"C:\Users\Desktop\Resources.resx";
ResourceWriter writer = new ResourceWriter(filePath);
writer.AddResource("Hello", "World");
With this code I achieved this.
But I want resource file to be in this format.
How can I achieve this dynamically.
I tried this piece of code, after seeing Markus's answer but no luck.
public void CreateResx()
{
var resxWriter = new ResXResourceWriter(filePath);
resxWriter.AddResource(new ResXDataNode("Hello", "World"));
var dataNode = new ResXDataNode("Hello", "People")
{
Comment = "dummy"
};
resxWriter.AddResource(dataNode);
}
It's unfortunately not that straight forward.
You need to add individual ResxDataNodes to the file.
I use the following code to generate my RESX files:
public static byte[] CreateResxContent(Query query)
{
using (var resxStream = new MemoryStream())
{
var resxWriter = new ResXResourceWriter(resxStream);
if (query.Elements.Count > 0)
{
foreach (var element in query.Elements)
{
var dataNode =
new ResXDataNode(element.Entries[TextIdIndex], element.Entries[TranslationIndex])
{
Comment = element.Entries[DescriptionIndex]
};
resxWriter.AddResource(dataNode);
}
}
resxWriter.Close();
return resxStream.ToArray();
}
}
So in your case changing
writer.AddResource("Hello", "World");
to
writer.AddResource(new ResXDataNode("Hello", "World"));
might already do the trick.

Copying hyperlinks from a cell to another with NPOI

I am trying to copy certain data from a sheet to another, but some cells are simple strings and some are hyperlinks.
If I use StringCellValue on the strings one it's ok, but I haven't found a method to copy the hyperlinks from the original sheet into the new one that I am constructing.
For the construction of the new sheet and for data copying I am using NPOI.
//UPDATE
I have added the code to insert the hyperlinks but when I run the program it shows the following exception: Object reference not set to an instance of an object.
Here is my code:
using (FileStream fs = new FileStream(#"C:\Users\File.xlsx", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
Console.WriteLine("Time to wait....");
templateWorkbook = new XSSFWorkbook(fs);
}
row.GetCell(6).SetCellValue(sheettoget.GetRow(1).GetCell(13).StringCellValue);
var sourceLink = sheettoget.GetRow(1).GetCell(13).Hyperlink;
if(sourceLink != null)
{
Console.WriteLine("Inserting first Juice session...");
var targetLink = new XSSFHyperlink(sourceLink.Type);
targetLink.Address = sourceLink.Address;
}
row.GetCell(6).Hyperlink = targetLink;
row.GetCell(6).CellStyle = sheettoget.GetRow(1).GetCell(13).CellStyle;
You can copy a hyperlink like this, where sourceCell is the cell you are copying from, and targetCell is the cell you are copying to:
targetCell.SetCellValue(sourceCell.StringCellValue);
var sourceLink = sourceCell.Hyperlink;
if (sourceLink != null)
{
var targetLink = new XSSFHyperlink(sourceLink.Type);
targetLink.Address = sourceLink.Address;
targetCell.Hyperlink = targetLink;
// also copy the cell style to ensure the copied link still looks like a link
targetCell.CellStyle = sourceCell.CellStyle;
}

Add picture in header to a .docx using novacode DocX

Header header_default = doc.Headers.first;
Paragraph p1 = header_default.InsertParagraph();
I've tried to add a picture in my header in a word file. I tried it with:
p1.AppendPicture(picture);
And also with a table:
Table t1 = header_default.InsertTable(10,2);
t1.Pictures.Add(picture);
Well the big problem is that the code never run to this place it's always crashing by inserting a paragraph to the header:
Paragraph p1 = header_default.InsertParagraph();
or
Table t1 = header_default.InsertTable(10,2);
Error: System.NullReferenceException
I'm new in .net and docx library hope someone can help me with the prblem
This is how I do it, notice I use Doc.Headers.odd rather than first
Doc.AddHeaders();
var headerDefault = Doc.Headers.odd;
var headlineFormat = GetTopHeadlineFormat();
var logo = System.Drawing.Image.FromFile(AppSettings.MulalleyLogoSmall);
using (var ms = new MemoryStream())
{
logo.Save(ms, logo.RawFormat);
ms.Seek(0, SeekOrigin.Begin);
var img = Doc.AddImage(ms);
var pic1 = img.CreatePicture();
var p = headerDefault.InsertParagraph();
p.InsertPicture(pic1);
p.InsertParagraphBeforeSelf(Doc.InsertParagraph());
}

How to edit bookmarks in a Word template using DocumentFormat.OpenXml and save it as a new PDF file?

I'm really having trouble in editing bookmarks in a Word template using Document.Format.OpenXML and then saving it to a new PDF file.
I cannot use Microsoft.Word.Interop as it gives a COM error on the server.
My code is this:
public static void CreateWordDoc(string templatePath, string destinationPath, Dictionary<string, dynamic> dictionary)
{
byte[] byteArray = File.ReadAllBytes(templatePath);
using (MemoryStream stream = new MemoryStream())
{
stream.Write(byteArray, 0, (int)byteArray.Length);
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(stream, true))
{
var bookmarks = (from bm in wordDoc.MainDocumentPart.Document.Body.Descendants<BookmarkStart>()
select bm).ToList();
foreach (BookmarkStart mark in bookmarks)
{
if (mark.Name != "Table" && mark.Name != "_GoBack")
{
UpdateBookmark(dictionary, mark);//Not doing anything
}
else if (mark.Name != "Table")
{
// CreateTable(dictionary, wordDoc, mark);
}
}
File.WriteAllBytes("D:\\RohitDocs\\newfile_rohitsingh.docx", stream.ToArray());
wordDoc.Close();
}
// Save the file with the new name
}
}
private static void UpdateBookmark(Dictionary<string, dynamic> dictionary, BookmarkStart mark)
{
string name = mark.Name;
string value = dictionary[name];
Run run = new Run(new Text(value));
RunProperties props = new RunProperties();
props.AppendChild(new FontSize() { Val = "20" });
run.RunProperties = props;
var paragraph = new DocumentFormat.OpenXml.Wordprocessing.Paragraph(run);
mark.Parent.InsertAfterSelf(paragraph);
paragraph.PreviousSibling().Remove();
mark.Remove();
}
I was trying to replace bookmarks with my text but the UpdateBookmark method doesn't work. I'm writing stream and saving it because I thought if bookmarks are replaced then I can save it to another file.
I think you want to make sure that when you reference mark.Parent that you are getting the correct instance that you are expecting.
Once you get a reference to the correct Paragraph element where your content should go, use the following code to add/swap the run.
// assuming you have a reference to a paragraph called "p"
p.AppendChild<Run>(new Run(new Text(content)) { RunProperties = props });
// and here is some code to remove a run
p.RemoveChild<Run>(run);
To answers the second part of your question, when I did a similar project a few years ago we used iTextSharp to create PDFs from Docx. It worked very well and the API was easy to grok. We even added password encryption and embedded watermarks to the PDFs.

how can I put a content in a mergefield in docx

I'm developing a web application with asp.net and I have a file called Template.docx that works like a template to generate other reports. Inside this Template.docx I have some MergeFields (Title, CustomerName, Content, Footer, etc) to replace for some dynamic content in C#.
I would like to know, how can I put a content in a mergefield in docx ?
I don't know if MergeFields is the right way to do this or if there is another way. If you can suggest me, I appreciate!
PS: I have openxml referenced in my web application.
Edits:
private MemoryStream LoadFileIntoStream(string fileName)
{
MemoryStream memoryStream = new MemoryStream();
using (FileStream fileStream = File.OpenRead(fileName))
{
memoryStream.SetLength(fileStream.Length);
fileStream.Read(memoryStream.GetBuffer(), 0, (int) fileStream.Length);
memoryStream.Flush();
fileStream.Close();
}
return memoryStream;
}
public MemoryStream GenerateWord()
{
string templateDoc = "C:\\temp\\template.docx";
string reportFileName = "C:\\temp\\result.docx";
var reportStream = LoadFileIntoStream(templateDoc);
// Copy a new file name from template file
//File.Copy(templateDoc, reportFileName, true);
// Open the new Package
Package pkg = Package.Open(reportStream, FileMode.Open, FileAccess.ReadWrite);
// Specify the URI of the part to be read
Uri uri = new Uri("/word/document.xml", UriKind.Relative);
PackagePart part = pkg.GetPart(uri);
XmlDocument xmlMainXMLDoc = new XmlDocument();
xmlMainXMLDoc.Load(part.GetStream(FileMode.Open, FileAccess.Read));
// replace some keys inside xml (it will come from database, it's just a test)
xmlMainXMLDoc.InnerXml = xmlMainXMLDoc.InnerXml.Replace("field_customer", "My Customer Name");
xmlMainXMLDoc.InnerXml = xmlMainXMLDoc.InnerXml.Replace("field_title", "Report of Documents");
xmlMainXMLDoc.InnerXml = xmlMainXMLDoc.InnerXml.Replace("field_content", "Content of Document");
// Open the stream to write document
StreamWriter partWrt = new StreamWriter(part.GetStream(FileMode.Open, FileAccess.Write));
//doc.Save(partWrt);
xmlMainXMLDoc.Save(partWrt);
partWrt.Flush();
partWrt.Close();
reportStream.Flush();
pkg.Close();
return reportStream;
}
PS: When I convert MemoryStream to a file, I got a corrupted file. Thanks!
I know this is an old post, but I could not get the accepted answer to work for me. The project linked would not even compile (which someone has already commented in that link). Also, it seems to use other Nuget packages like WPFToolkit.
So I'm adding my answer here in case someone finds it useful. This only uses the OpenXML SDK 2.5 and also the WindowsBase v4. This works on MS Word 2010 and later.
string sourceFile = #"C:\Template.docx";
string targetFile = #"C:\Result.docx";
File.Copy(sourceFile, targetFile, true);
using (WordprocessingDocument document = WordprocessingDocument.Open(targetFile, true))
{
// If your sourceFile is a different type (e.g., .DOTX), you will need to change the target type like so:
document.ChangeDocumentType(WordprocessingDocumentType.Document);
// Get the MainPart of the document
MainDocumentPart mainPart = document.MainDocumentPart;
var mergeFields = mainPart.RootElement.Descendants<FieldCode>();
var mergeFieldName = "SenderFullName";
var replacementText = "John Smith";
ReplaceMergeFieldWithText(mergeFields, mergeFieldName, replacementText);
// Save the document
mainPart.Document.Save();
}
private void ReplaceMergeFieldWithText(IEnumerable<FieldCode> fields, string mergeFieldName, string replacementText)
{
var field = fields
.Where(f => f.InnerText.Contains(mergeFieldName))
.FirstOrDefault();
if (field != null)
{
// Get the Run that contains our FieldCode
// Then get the parent container of this Run
Run rFldCode = (Run)field.Parent;
// Get the three (3) other Runs that make up our merge field
Run rBegin = rFldCode.PreviousSibling<Run>();
Run rSep = rFldCode.NextSibling<Run>();
Run rText = rSep.NextSibling<Run>();
Run rEnd = rText.NextSibling<Run>();
// Get the Run that holds the Text element for our merge field
// Get the Text element and replace the text content
Text t = rText.GetFirstChild<Text>();
t.Text = replacementText;
// Remove all the four (4) Runs for our merge field
rFldCode.Remove();
rBegin.Remove();
rSep.Remove();
rEnd.Remove();
}
}
What the code above does is basically this:
Identify the 4 Runs that make up the merge field named "SenderFullName".
Identify the Run that contains the Text element for our merge field.
Remove the 4 Runs.
Update the text property of the Text element for our merge field.
UPDATE
For anyone interested, here is a simple static class I used to help me with replacing merge fields.
Frank Fajardo's answer was 99% of the way there for me, but it is important to note that MERGEFIELDS can be SimpleFields or FieldCodes.
In the case of SimpleFields, the text runs displayed to the user in the document are children of the SimpleField.
In the case of FieldCodes, the text runs shown to the user are between the runs containing FieldChars with the Separate and the End FieldCharValues. Occasionally, several text containing runs exist between the Separate and End Elements.
The code below deals with these problems. Further details of how to get all the MERGEFIELDS from the document, including the header and footer is available in a GitHub repository at https://github.com/mcshaz/SimPlanner/blob/master/SP.DTOs/Utilities/OpenXmlExtensions.cs
private static Run CreateSimpleTextRun(string text)
{
Run returnVar = new Run();
RunProperties runProp = new RunProperties();
runProp.Append(new NoProof());
returnVar.Append(runProp);
returnVar.Append(new Text() { Text = text });
return returnVar;
}
private static void InsertMergeFieldText(OpenXmlElement field, string replacementText)
{
var sf = field as SimpleField;
if (sf != null)
{
var textChildren = sf.Descendants<Text>();
textChildren.First().Text = replacementText;
foreach (var others in textChildren.Skip(1))
{
others.Remove();
}
}
else
{
var runs = GetAssociatedRuns((FieldCode)field);
var rEnd = runs[runs.Count - 1];
foreach (var r in runs
.SkipWhile(r => !r.ContainsCharType(FieldCharValues.Separate))
.Skip(1)
.TakeWhile(r=>r!= rEnd))
{
r.Remove();
}
rEnd.InsertBeforeSelf(CreateSimpleTextRun(replacementText));
}
}
private static IList<Run> GetAssociatedRuns(FieldCode fieldCode)
{
Run rFieldCode = (Run)fieldCode.Parent;
Run rBegin = rFieldCode.PreviousSibling<Run>();
Run rCurrent = rFieldCode.NextSibling<Run>();
var runs = new List<Run>(new[] { rBegin, rCurrent });
while (!rCurrent.ContainsCharType(FieldCharValues.End))
{
rCurrent = rCurrent.NextSibling<Run>();
runs.Add(rCurrent);
};
return runs;
}
private static bool ContainsCharType(this Run run, FieldCharValues fieldCharType)
{
var fc = run.GetFirstChild<FieldChar>();
return fc == null
? false
: fc.FieldCharType.Value == fieldCharType;
}
You could try http://www.codeproject.com/KB/office/Fill_Mergefields.aspx which uses the Open XML SDK to do this.

Categories

Resources