var p1 = document.Paragraphs.Add(ref o);
p1.Range.InsertParagraphAfter();
Now I want to grab the paragraph that was just created using InsertParagraphAfter() and modify it. How can I access it?
InsertParagraphAfter is supposed to extend the current selection to include the new paragraph. So if you start by creating an empty selection at the end of the existing paragraph, the current selection should be set to the new paragraph after calling InsertParagraphAfter.
Note that I have not tested the following code (I have not even tried compiling it), so I may be way off.
var p1 = document.Paragraphs.Add(ref o);
// Set the selection to the end of the paragraph.
document.Range(p1.Range.End, p1.Range.End).Select();
p1.Range.InsertParagraphAfter();
// InsertParagraphAfter should expand the active selection to include
// the newly inserted paragraph.
var newParagraph = document.Application.Selection;
You can accomplish this by adding a new paragraph relatively to the first paragraph:
Paragraph p1 = document.Paragraphs.Add(System.Reflection.Missing.Value);
p1.Range.Text = "Foo";
p1.Range.InsertParagraphAfter();
// Add new paragraph relative to first paragraph
Paragraph p2 = document.Paragraphs.Add(p1.Range);
p2.Range.Text = "Bar";
p2.Range.InsertParagraphAfter();
// Add new paragraph relative to the second paragraph
Paragraph p3 = document.Paragraphs.Add(p2.Range);
p3.Range.Text = "Baz";
I know this is way old but could not resist.
Here is the working solution (rng is one paragraph's range):
rng.InsertParagraphAfter()
If rng.Paragraphs(1).Next IsNot Nothing Then
rng.Paragraphs(1).Next.Style = ActiveDocument.Styles(WdBuiltinStyle.wdStyleNormal)
End If
Related
I am generating a Word document with Open XML. The document includes a table, and some of the cells contain a JSON string.
Before inserting the JSON string I beautify it with the following lines:
Newtonsoft.Json.Linq.JToken parsedJson =
Newtonsoft.Json.Linq.JToken.JToken.Parse(unformattedJsonString);
string formattedJsonString = parsedJson.ToString(Formatting.Indented);
For my OpenXML code I started with the "Reflect Code" from the OpenXML SDK 2.5. It looks like this:
TableCell tableCell2 = new TableCell();
TableCellProperties tableCellProperties2 = new TableCellProperties();
TableCellWidth tableCellWidth2 = new TableCellWidth() { Width = "8640", Type = TableWidthUnitValues.Dxa };
Shading shading2 = new Shading() { Val = ShadingPatternValues.Clear, Color = "auto", Fill = "auto" };
tableCellProperties2.Append(tableCellWidth2);
tableCellProperties2.Append(shading2);
Paragraph paragraph2 = new Paragraph() { RsidParagraphAddition = "00006AF4", RsidParagraphProperties = "00006AF4", RsidRunAdditionDefault = "00006AF4" };
ParagraphProperties paragraphProperties2 = new ParagraphProperties();
SpacingBetweenLines spacingBetweenLines2 = new SpacingBetweenLines() { After = "0" };
paragraphProperties2.Append(spacingBetweenLines2);
Run run2 = new Run();
Text textSecondColumn = new Text();
textSecondColumn.Text = formattedJsonString; // see the snippet above
run2.Append(textSecondColumn);
paragraph2.Append(paragraphProperties2);
paragraph2.Append(run2);
tableCell2.Append(tableCellProperties2);
tableCell2.Append(paragraph2);
tableRow.Append(tablePropertyExceptions);
tableRow.Append(tableCell1);
tableRow.Append(tableCell2);
So here's the problem - even though the string "formattedJsonString" is definitely beautified, all the line breaks and indents are lost in the final document.
I used to do this in Word Interop and it worked fine. Any idea for how to handle this with Open XML?
By the way - The SDK will not open a document.xml that has formatted/beautified Json text.
Amendment: It just occurred to me that the formatting adds line breaks - and that perhaps each line needs to be a separate paragraph. Gonna try that and will amend this post again if needed.
You're on the right track. You do need to convert the line breaks, but I don't think each line needs to be a separate paragraph (although I suppose you could do it that way if you wanted to). I would instead just create a Run for each line, inserting a Break element before the Text element in each one except the first. Also be sure to set the Space property on each Text element to SpaceProcessingModeValues.Preserve to ensure the leading whitespace isn't lost.
Add all the Runs to the Paragraph inside the TableCell and that should do it.
I think this is what you are looking for:
static void AddJsonToParagraph(string json, Paragraph paragraph)
{
string[] lines = json.Split(Environment.NewLine);
bool isFirstLine = true;
foreach (var line in lines)
{
Run run = new Run();
if (!isFirstLine)
{
run.Append(new Break());
}
isFirstLine = false;
Text text = new Text
{
Space = SpaceProcessingModeValues.Preserve,
Text = line
};
run.Append(text);
paragraph.Append(run);
}
}
In your code, replace the line that appends run2 to paragraph2 with this:
AddJsonToParagraph(formattedJsonString, paragraph2);
I'm trying to design a table that has 3 additional tables in the last cell. Like this.
I've managed to get the first nested table into row 4, but my second nested table is going into cell(1,1) of the first table.
var wordApplication = new Word.Application();
wordApplication.Visible = true;
var wordDocument = wordApplication.Documents.Add();
var docRange = wordDocument.Range();
docRange.Tables.Add(docRange, 4, 1);
var mainTable = wordDocument.Tables[1];
mainTable.set_Style("Table Grid");
mainTable.Borders.Enable = 0;
mainTable.PreferredWidthType = Word.WdPreferredWidthType.wdPreferredWidthPercent;
mainTable.PreferredWidth = 100;
docRange.Collapse(Word.WdCollapseDirection.wdCollapseStart);
var phoneRange = mainTable.Cell(4, 1).Range;
phoneRange.Collapse(Word.WdCollapseDirection.wdCollapseStart);
phoneRange.Tables.Add(phoneRange, 3, 2);
var phoneTable = mainTable.Cell(4, 1).Tables[1];
phoneTable.set_Style("Table Grid");
phoneTable.Borders.Enable = 0;
phoneTable.AutoFitBehavior(Word.WdAutoFitBehavior.wdAutoFitContent);
phoneTable.Rows.RelativeHorizontalPosition = Word.WdRelativeHorizontalPosition.wdRelativeHorizontalPositionMargin;
phoneRange.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
I've tried collapsing the range, adding in a paragraph then collapsing the range again. No luck. I found this post and many similar ones, but I must be missing something.
Thanks for your time.
It usually helps in situation like these to add a line in your code: phoneRange.Select(); and having code execution end with that. Take a look at where the Range actually is. Now you can test using the keyboard where the Range needs to be in order to insert the next table successfully.
Since you say phoneRange selects outside the third row, rather than working with phoneRange try setting a new Range object to phoneTable.Range then collapse it to its end-point.
Using OpenXML in C#, we need to:
Find a specific string of text on a Word document (this text will always exist in a table cell)
Get the formatting of the text and the table that the text exists in.
Create a new table with the same text and table formatting while pulling in text values for the cell from a nested List
This is the code that I currently have and the places I am not sure how do:
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(fileWordFile, true))
{
MainDocumentPart mainPart = wordDoc.MainDocumentPart;
Body body = mainPart.Document.Body;
IEnumerable paragraphs = body.Elements<Paragraph>();
Paragraph targetParagraph = null;
//Comment 1: Loop through paragraphs and search for a specific string of text in word document
foreach (Paragraph paragraph in paragraphs) {
if(paragraph.Elements<Run>().Any()) {
Run run = paragraph.Elements<Run>().First();
if(run.Elements<Text>().Any()) {
Text text = run.Elements<Text>().First();
if (text.Text.Equals("MY SEARCH STRING")) {
targetParagraph = paragraph;
// Comment 2: How can I get the formatting of the table that contains this text??
}
}
}
}
//Comment 3: Create table with same formatting as where the text was found
Table table1 = new Table();
TableProperties tableProperties1 = new TableProperties();
//Comment 4: How can I set these properties to be the same as the one found at "Comment 2"??
wordDoc.Close();
wordDoc.Dispose();
}
If you're looking for text elements that are inside a table cell, you can use a LINQ query to get there quickly without needing to use a heap of nested loops.
// Find the first text element matching the search string
// where the text is inside a table cell.
var textElement = body.Descendants<Text>()
.FirstOrDefault(t => t.Text == searchString &&
t.Ancestors<TableCell>().Any());
Once you have your match, the easiest way to duplicate the containing table with all its formatting and contents is simply to clone it.
if (textElement != null)
{
// get the table containing the matched text element and clone it
Table table = textElement.Ancestors<Table>().First();
Table tableCopy = (Table)table.CloneNode(deep: true);
// do stuff with copied table (see below)
}
After that, you can add things to the corresponding cell of the copied table. It's not entirely clear what you meant by "pulling in text values for the cell from a nested List" (what list? nested where?), so I'll just show a contrived example. (This code would replace the "do stuff" comment in the code above.)
// find the table cell containing the search string in the copied table
var targetCell = tableCopy.Descendants<Text>()
.First(t => t.InnerText == searchString)
.Ancestors<TableCell>()
.First();
// get the properties from the first paragraph in the target cell (so we can copy them)
var paraProps = targetCell.Descendants<ParagraphProperties>().First();
// now add new stuff to the target cell
List<string> stuffToAdd = new List<string> { "foo", "bar", "baz", "quux" };
foreach (string item in stuffToAdd)
{
// for each item, clone the paragraph properties, then add a new paragraph
var propsCopy = (ParagraphProperties)paraProps.CloneNode(deep: true);
targetCell.AppendChild(new Paragraph(propsCopy, new Run(new Text(item))));
}
Lastly, you need to add the copied table to the document somewhere or you won't see it. You don't say in your question where you would want this to appear, so I'll just put it at the end of the document. You can use methods like InsertAfter, InsertAt, InsertBefore, etc. to insert the table relative to other elements.
body.AppendChild(tableCopy);
Hope this helps.
I noticed when using MigraDoc that if I add a paragraph with any of the heading styles (e.g., "Heading1"), an entry is automatically placed in the document outline. My question is, how can I add entries in the document outline without showing the text in the document? Here is an example of my code:
var document = new Document();
var section = document.AddSection();
// The following line adds an entry to the document outline, but it also
// adds a line of text to the current section. How can I add an
// entry to the document outline without adding any text to the page?
var paragraph = section.AddParagraph("TOC Level 1", "Heading1");
I used a hack: added white text on white ground with a font size of 0.001 or so to get outlines that are actually invisible to the user.
For a perfect solution, mix PDFsharp and MigraDoc code. The hack works for me and is much easier to implement.
I realized after reading ThomasH's answer that I am already mixing PDFSharp and MigraDoc code. Since I am utilizing a PdfDocumentRenderer, I was able to add a custom outline to the PdfDocument property of that renderer. Here is an example of what I ended up doing to create a custom outline:
var document = new Document();
// Populate the MigraDoc document here
...
// Render the document
var renderer = new PdfDocumentRenderer(false, PdfFontEmbedding.Always)
{
Document = document
};
renderer.RenderDocument();
// Create the custom outline
var pdfSharpDoc = renderer.PdfDocument;
var rootEntry = pdfSharpDoc.Outlines.Add(
"Level 1 Header", pdfSharpDoc.Pages[0]);
rootEntry.Outlines.Add("Level 2 Header", pdfSharpDoc.Pages[1]);
// Etc.
// Save the document
pdfSharpDoc.Save(outputStream);
I've got a method that is slightly less hacked. Here's the basic method:
1) Add a bookmark, save into a list that bookmark field object and the name of the outline entry. Do not set a paragraph .OutlineLevel (or set as bodytext)
// Defined previously
List<dynamic> Bookmarks = new List<dynamic>();
// In your bookmarking method, P is a Paragraph already created somewhere
Bookmarks.Add(new { Bookmark = P.AddBookmark("C1"), Name = "Chapter 1", Depth = 0 });
2) At the end of your Migradoc layout, before rendering, prepare the pages
pdfwriter.PrepareRenderPages();
3) Build a dictionary of the Bookmark's parent's parent (This will be a paragraph) and pages (pages will be initialized to -1)
var Pages = Bookmarks.Select(x=> ((BookmarkField)x).Bookmark.Parent.Parent).ToDictionary(x=>x, x=>-1);
4) Now fill in those pages by iterating through the objects on each page, finding the match
for (int i = 0; i < pdfwriter.PageCount; i++)
foreach (var s in pdfwriter.DocumentRenderer.GetDocumentObjectsFromPage(i).Where(x=> Pages.ContainsKey(x))
Pages[s] = i-1;
5) You've now got a dictionary of Bookmark's parent's parents to page numbers, with this you can add your outlines directly into the PDFSharp document. This also iterates down the depth-tree, so you can have nested outlines
foreach(dynamic d in Bookmarks)
{
var o = pdfwriter.PdfDocument.Outlines;
for(int i=0;i<d.Depth;i++)
o = o.Last().Outlines;
BookmarkField BK = d.Bookmark;
int PageNumber = Pages[BK.Parent.Parent];
o.Add(d.Name, pdfwriter.PdfDocument.Pages[PageNumber], true, PdfOutlineStyle.Regular);
}
I have a word document with a table on the first page. It is the first table on the document and only one of the first page. I need to set the "Indent from left" to 0" on this table. I'm already appending some text to the document:
using (WordprocessingDocument oDocument = WordprocessingDocument.Open(_ms, true))
{
//Set paragraph, run, and runproperties objects.
Paragraph para = oDocument.MainDocumentPart.Document.Descendants<Paragraph>().First();
Run run = para.AppendChild(new Run());
RunProperties runPro = new RunProperties();
Run lineBreak = new Run(new Break());
//Set the text color and text value
Color color = new Color() { Val = "FFFFFF" };
Text text1 = new Text();
text1.Text = "text";
//Add the text to the body
runPro.Append(color);
run.Append(runPro);
run.Append(text1, lineBreak);
//Close the handle
oDocument.Close();
}
I've read a bit about the TableIndentation class, but haven't found any examples of it being used. Does anybody have experience with this?
Thanks
Have you tried using the Open XML SDK Productivity Tool (note that there are multiple downloads on this page) to inspect a document that has what you need? If not, do so. It will answer your question.
You can specify the table indentation in the table properties class like this.
TableProperties tPr = new TableProperties();
tPr.TableIndentation = new TableIndentation() { Type = indentationType, Width = indentationWidth };
use this and provide width in DXA(1"=1440 something)
props.TableIndentation = new TableIndentation() { Type = TableWidthUnitValues.Dxa, Width=-997 };