I am trying to loop through a Dataset, creating a page per item using Aspose.Words Mail-Merge functionality. The below code is looping through a Dataset - and passing some values to the Mail-Merge Execute function.
var blankDocument = new Document();
var pageDocument = new Document(sFilename);
...
foreach (DataRow row in ds.Tables[0].Rows){
var sBarCode = row["BarCode"].ToString();
var imageFilePath = HttpContext.Current.Server.MapPath("\\_temp\\") + sBarCode + ".png";
var tempDoc = (Document)pageDocument.Clone(true);
var fieldNames = new string[] { "Test", "Barcode" };
var fieldData = new object[] { imageFilePath, imageFilePath };
tempDoc.MailMerge.Execute(fieldNames, fieldData);
blankDocument.AppendDocument(tempDoc, ImportFormatMode.KeepSourceFormatting);
}
var stream = new MemoryStream();
blankDocument.Save(stream, SaveFormat.Docx);
// I then output this stream using headers,
// to cause the browser to download the document.
The mail merge item { MERGEFIELD Test } gets the correct data from the Dataset. However the actual image displays page 1's image on all pages using:
{ INCLUDEPICTURE "{MERGEFIELD Barcode }" \* MERGEFORMAT \d }
Say this is my data for the "Barcode" field:
c:\img1.png
c:\img2.png
c:\img3.png
Page one of this document, displays c:\img1.png in text for the "Test" field. And the image that is show, is img1.png.
However Page 2 shows c:\img2.png as the text, but displays img1.png as the actual image.
Does anyone have any insight on this?
Edit: It seems as this is more of a Word issue. When I toggle between Alt+F9 modes inside Word, the image actually displays c:\img1.png as the source. So that would be why it is being displayed on every page.
I've simplified it to:
{ INCLUDEPICTURE "{MERGEFIELD Barcode }" \d }
Also, added test data for this field inside Word's Mailings Recipient List. When I preview, it doesn't pull in the data, changing the image. So, this is the root problem.
I know this is old question. But still I would like to answer it.
Using Aspose.Words it is very easy to insert images upon executing mail merge. To achieve this you should simply use mergefield with a special name, like Image:MyImageFieldName.
https://docs.aspose.com/words/net/insert-checkboxes-html-or-images-during-mail-merge/#how-to-insert-images-from-a-database
Also, it is not required to loop through rows in your dataset and execute mail merge for each row. Simply pass whole data into MailMerge.Execute method and Aspose.Words will duplicate template for each record in the data.
Here is a simple example of such template
After executing mail merge using the following code:
// Create dummy data.
DataTable dt = new DataTable();
dt.Columns.Add("FirstName");
dt.Columns.Add("LastName");
dt.Columns.Add("MyImage");
dt.Rows.Add("John", "Smith", #"C:\Temp\1.png");
dt.Rows.Add("Jane", "Smith", #"C:\Temp\2.png");
// Open template, execute mail merge and save the result.
Document doc = new Document(#"C:\Temp\in.docx");
doc.MailMerge.Execute(dt);
doc.Save(#"C:\Temp\out.docx");
The result will look like the following:
Disclosure: I work at Aspose.Words team.
If this was Word doing the output, (not sure about Aspose), there would be two possible problems here.
INCLUDEPICTURE expects backslashes to be doubled up, e.g. "c\\img2.png", or (somewhat less reliable) to use forward slashes, or Mac ":" separators on that platform. It may be OK if the data comes in via a field result as you are doing here, though.
INCLUDEPICTURE results have not updated automatically "by design" since Microsoft modified a bunch of field behaviors for security reasons about 10 years ago. If you are merging to an output document, you can probably work around that by using the following nested fields:
{ INCLUDEPICTURE { IF TRUE "{ MERGEFIELD Barcode }" } }
or to remove the fields in the result document,
{ IF { INCLUDEPICTURE { IF TRUE "{ MERGEFIELD Barcode }" } } {
INCLUDEPICTURE { IF TRUE "{ MERGEFIELD Barcode }" } } }
All the { } need to be inserted with Ctrl+F9 in the usual way.
(Don't ask me where this use of "TRUE" is documented - as far as I know, it is not.)
Related
I'm working on a simple document merge. Wanted to find some strings and replace it with another string (Outside table).
So here is the issue, when i try to use TextReplacer.SearchAndReplace after accessing table using var table = wordDoc.MainDocumentPart.Document.Body.Elements<DocumentFormat.OpenXml.Wordprocessing.Table>(); then SearchAndReplace is not working. Don't know what is the issue.
eg code:
private static async Task MergeDoc(WordprocessingDocument wordDoc) {
var table = wordDoc.MainDocumentPart.Document.Body.Elements<DocumentFormat.OpenXml.Wordprocessing.Table>();
TextReplacer.SearchAndReplace(wordDoc, "string to replace", "value", true);
}
If I remove the table variable which actually a reference to the table from word document, then SearchAndReplace is working
I'm trying to parse the main (last in the dom tree)
<table>
in this website: "https://aips.um.si/PredmetiBP5/Main.asp?Mode=prg&Zavod=77&Jezik=&Nac=1&Nivo=P&Prg=1571&Let=1"
Im using the Htmlagilitypack and writing code in C# on a wpf application in visual studio 17.
Right now im using this code:
iso = Encoding.GetEncoding("windows-1250");
web = new HtmlWeb()
{
AutoDetectEncoding = false,
OverrideEncoding = iso,
};
//http = https://aips.um.si/PredmetiBP5/Main.asp?Mode=prg&Zavod=77&Jezik=&Nac=1&Nivo=P&Prg=1571&Let=1
string http = formatLetnikLink(l.Attributes["onclick"].Value).ToString();
var htmlProgDoc = web.Load(http);
string s = htmlProgDoc.ParsedText;
htmlprogDoc.ParsedText correctly includes all the rows
that are supposed to be in the last table
(I had this for debugging, just incase the watch window was broken or something... idk...)
I tried to first get all the tables on the tables on the website. And realized that there are 6
<table></table>
tags on it, even tho you visualy see only one. After debuggign for a couple of hours, i realized that the last main table, is the last
<table>
in the dom tree, and that the parser parsing fully all the
<tr>
tags that the table has. This is the problem, I need all the tr tags.
var tables = htmlProgDoc.DocumentNode.SelectNodes("//table");
There are 6 times
<table></table>
tags, as expected, and everyone of them is fully parsed, including all their rows and columns, except the last one, in the last one it only parses the first two rows and then the parser apears to append a
</table>
by its self, I also tried using the direct xpath selector, copy-ed from firefox:
"/html/body/div/center[2]/font/font/font/table", instead of "//table"
which found the correct table, but the table also contained only the first 2 rows
var theTableINeed = tables.Last();
//contains the correct table which I need, but with only the first two rows
The Html on that page is malformed. One possible workaround is stripping the code for last table and parse it as a document.
var client = new WebClient();
string html = client.DownloadString(url);
int lastTableOpen = html.LastIndexOf("<table");
int lastTableClose = html.LastIndexOf("</table");
string lastTable = html.Substring(lastTableOpen, lastTableClose - lastTableOpen + 8);
Then use HtmlAgilityPack:
var table = new HtmlDocument();
table.LoadHtml(lastTable);
foreach (var row in table.DocumentNode.SelectNodes("//table//tr"))
{
Console.WriteLine(row.ToString());
}
But I don't know if there are problems in the table itself.
whilst trying to work on something else, i stumbled across JSON.NET, and have a quick question regarding the results.
I have a XML Field in sql, which i return in a data reader, I then run this through the following:
XmlDocument doc = new XmlDocument();
doc.LoadXml(rdr.GetString(0));
en.Add(JsonConvert.SerializeXmlNode(doc));
en is a List as there could be many rows returns. the JSON that is created is as follows with real data modified but the structure intact:
"{\"Entity\":{\"#xmlns:xsd\":\"http://www.w3.org/2001/XMLSchema\",\"#xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\",\"AKA\":{\"string\":[\"Name 1\",\"Name 2\"]},\"Countries\":{\"string\":[\"UK\",\"US\"]},\"IdentNumbers\":{\"string\":[\"Date Set 2\",\"Data Set 1\",\"Data Set 3\",\"Data Set 4\"]},\"PercentageMatch\":\"94\"}}"
So if there were 3 entries then msg.d would contain three values as can be seen from FireBug output below
How do i loop through this information on the client side, and present it in a table?
EDIT
So for the table layout. Any single item needs to have a heading and its associated value, for any items that have one or more value, then i need the table to have a single heading with each item on a new line. Something similiar to this:
Heading 1
Single Item Value
Heading 2
First Item Value \n
Second Item Value
Heading 2
Single Item Value
EDIT
Ok, kind of getting to where I want it. i've produced this:
success: function (msg) {
var resultHtml = "";
$.each(msg.d, function (i, entity) {
//now entity will contain one row of data - you could access the following objects :
//entity.AKA is an array with which you could loop with
resultHtml += '<label><b>Countries:</b></label>';
resultHtml += '<text>' + entity.Countries + '</text>';
resultHtml += '<label><b>Ident:</b></label>';
resultHtml += '<text>' + entity.IdentNumbers + '</text>';
//etc
});
Which produces the output of heading in bold with the value underneath. What I know need to work out, is how to only show one instance at a time, and have pages to move through :-) Any Idea?
using $.each, maybe? Here's the syntax :
$.each(msg.d, function(i, entity) {
//now entity will contain one row of data - you could access the following objects :
//entity.AKA is an array with which you could loop with
//entity.Countries
//entity.IdentNumbers
//etc
});
Then you could construct that table in your each loop. If you give me more info on how you'd want to set up your table (the format), we could help you on that.
Here's a fiddle for you. Resize the output window and check the table : http://jsfiddle.net/hungerpain/9KBDg/
This one is probably a little stupid, but I really need it. I have document with 5 tables each table has a heading. heading is a regular text with no special styling, nothing. I need to extract data from those tables + plus header.
Currently, using MS interop I was able to iterate through each cell of each table using something like this:
app.Tables[1].Cell(2, 2).Range.Text;
But now I'm struggling on trying to figure out how to get the text right above the table.
Here's a screenshot:
For the first table I need to get "I NEED THIS TEXT" and for secnd table i need to get: "And this one also please"
So, basically I need last paragraph before each table. Any suggestions on how to do this?
Mellamokb in his answer gave me a hint and a good example of how to search in paragraphs. While implementing his solution I came across function "Previous" that does exactly what we need. Here's how to use it:
wd.Tables[1].Cell(1, 1).Range.Previous(WdUnits.wdParagraph, 2).Text;
Previous accepts two parameters. First - Unit you want to find from this list: http://msdn.microsoft.com/en-us/library/microsoft.office.interop.word.wdunits.aspx
and second parameter is how many units you want to count back. In my case 2 worked. It looked like it should be because it is right before the table, but with one, I got strange special character: ♀ which looks like female indicator.
You might try something along the lines of this. I compare the paragraphs to the first cell of the table, and when there's a match, grab the previous paragraph as the table header. Of course this only works if the first cell of the table contains a unique paragraph that would not be found in another place in the document:
var tIndex = 1;
var tCount = oDoc.Tables.Count;
var tblData = oDoc.Tables[tIndex].Cell(1, 1).Range.Text;
var pCount = oDoc.Paragraphs.Count;
var prevPara = "";
for (var i = 1; i <= pCount; i++) {
var para = oDoc.Paragraphs[i];
var paraData = para.Range.Text;
if (paraData == tblData) {
// this paragraph is at the beginning of the table, so grab previous paragraph
Console.WriteLine("Header: " + prevPara);
tIndex++;
if (tIndex <= tCount)
tblData = oDoc.Tables[tIndex].Cell(1, 1).Range.Text;
else
break;
}
prevPara = paraData;
}
Sample Output:
Header: I NEED THIS TEXT
Header: AND THIS ONE also please
Recently, my question here was answered. Now that I've got my XML all parsed and looking pretty, I've got another question about my application I've been banging my head against a wall over the past few day(s).
The XML is used to automatically add Artist names to a listbox. What I want to do is provide links to Amazon searches from these artists. In the following function, the XML is parsed and the artist name is then added to the list. I need to somehow put a hyperlink on this artist name. Does anybody know how this would be possible?
EDIT: I am missing the connection between steps 2 and 3 in the answer that has been provided. Also, I do not understand how number 3 works at all. I must admit I'm a neophyte at Silverlight programming. From my understanding, you do the binding in the XAML page. How can this be done for listbox items that have not even been created yet?
Additionally, I realized something that the Amazon URLs use + signs where spaces are in artist names. I edited the code to reflect that. Please understand that having the hyperlink as text under each artist name is not what I'm going after. ;)
public void DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null || e.Error.Message.IndexOf("NotFound") == -1)
{
ArtistsList.Items.Clear();
uname.Text = "Try Another One!";
XDocument doc = XDocument.Parse(e.Result);
var topArtists = from results in doc.Descendants("artist")
select results.Element("name").Value.ToString();
foreach (string artist in topArtists)
{
ArtistsList.Items.Add(artist);
string amazonPlus = artist.Replace(" ", "+");
string amazonURL = "http://www.amazon.ca/s/ref=nb_ss_gw?url=search-alias%3Daps&field-keywords=" + amazonPlus + "&x=0&y=0";
ArtistsList.Items.Add(amazonURL);
}
}
}
EDIT 2 Is there anybody who can clarify the answer provided?
1) Create an Artist object with a Name and Amazon Url Property
2) When you parse the XML, create a collection of items using LINQ.
var topArtists = from result in doc.Descendants("artists")
select new Artist
{
Name = result.Element("name").Value,
Amazon = new Uri(string.format("http://amazon.com/artist={0}", result.Element("name").Value), UriKind.Absolute),
};
ArtistList.ItemsSource = topArtists;
3) I would then use a data template to bind the Name to a TextBlock
Text or HyperlinkButton Content and the Amazon property to the
HyperlinkButton.NavigateUrl.