Issue while adding/updating TOC in MS Word using OpenXML - ms-word

I have a requirement to add/update TOC - Table of Contents in MS document using OpenXML. I am facing challenges to achieve the same. I am using MS Office 2016.
I have tried all the options from this post:
How to generate Table Of Contents using OpenXML SDK 2.0?
Also gone through Eric White videos.
I am trying to use UpdateField option and able to add empty TOC following the sample code from the above link.
However when I open the document I am not getting a pop-up dialog which will ask to update the TOC.
Here is the sample code:
var sdtBlock = new SdtBlock();
sdtBlock.InnerXml = GetTOC(); //TOC Xml
document.MainDocumentPart.Document.Body.AppendChild(sdtBlock);
DocumentFormat.OpenXml.Wordprocessing.SimpleField f;
f = new SimpleField();
f.Instruction = "sdtContent";
f.Dirty = true;
document.MainDocumentPart.Document.Body.Append(f);
var setting = document.MainDocumentPart.DocumentSettingsPart;
if (setting != null)
{
document.MainDocumentPart.DocumentSettingsPart.Settings.Append(new UpdateFieldsOnOpen() { Val = new DocumentFormat.OpenXml.OnOffValue(true)});
document.MainDocumentPart.DocumentSettingsPart.Settings.Save();
}
It is displaying default message, whereas I have valid entries (Headings).
Is it due to MS Office 2016? UpdateField Pop-up is not coming?
I don't want to go with below options:
Word Automation - Due to Microsoft.Office.Interop.Word
Word Automation Services - Require Sharepoint for this
Adding Macro - As it is asking to save the document every time I open it.
Also let me know if there is any better option to create/update TOC.
Your answer/comment is really very helpful.

Related

How to properly close Word documents after Documents.Open

I have the following code for a C# console app. It parses a Word document for textboxes and inserts the same text into the document at the textbox anchor point with markup. This is so I can convert to Markdown using pandoc, including textbox content which is not available due to https://github.com/jgm/pandoc/issues/3086. I can then replace my custom markup with markdown after conversion.
The console app is called in a PowerShell loop for all documents in a target list.
When I first run the Powershell script, all documents are opened and saved (with a new name) without error. But the next time I run it, I get an occasional popup error:
The last time you opened '' it caused a serious error. Do you still want to open it?
I can get through this by selecting yes on every popup, but this requires intervention and is tedious and slow. I want to know why this code results in this problem?
string path = args[0];
Console.WriteLine($"Parsing {path}");
Application word = new Application();
Document doc = word.Documents.Open(path);
try
{
foreach (Shape shp in doc.Shapes)
{
if (shp.TextFrame.HasText != 0)
{
string text = shp.TextFrame.TextRange.Text;
int page = shp.Anchor.Information[WdInformation.wdActiveEndPageNumber];
string summary = Regex.Replace(text, #"\r\n?|\n", " ");
Console.WriteLine($"++++textbox++++ Page {page}: {summary.Substring(0, Math.Min(summary.Length, 40))}");
string newtext = #$"{Environment.NewLine}TEXTBOX START%%%{text}%%%TEXTBOX END{Environment.NewLine}";
var range = shp.Anchor;
range.InsertBefore(Environment.NewLine);
range.Collapse();
range.Text = newtext;
range.set_Style(WdBuiltinStyle.wdStyleNormal);
}
}
string newFile = Path.GetFullPath(path) + ".notb.docx";
doc.SaveAs2(newFile);
}
finally
{
doc.Close();
word.Quit();
}
The console app is called in a PowerShell loop for all documents in a target list.
You can automate Word from your PowerShell script directly without involving any other dependencies. At least that will allow you to keep a single Word instance without creating each time a new Word Application instance for each document:
Application word = new Application();
Document doc = word.Documents.Open(path);
In the loop you could just open documents for processing and then closing them. It should improve the overall performance of your solution.
When you are done processing a document you need to close it by using the Close method which closes the specified document.
Also when a new Word Application instance is created, don't forget to close it as well by calling the Quit method which quits Microsoft Word and optionally saves or routes the open documents.
Application.Quit SaveChanges:=wdSaveChanges, OriginalFormat:=wdWordDocument

The PDFViewer is not showing the content while downloading is possible

I am trying to use a PDFViewer in a SAPUI5 application, something like the following sample app.
When I try to use this component in Google chrome it will not load the data, however it is possible to download the PDF itself and it shows the url works and file is available.
If I open it in Firefox or IE it works!
I discussed the problem here with SAP OpenUI5 team. Finally we understood the problem is not the UI5 library and the problem is inside of our ABAP implementation that provides download link for the PDF file from SAP Document Management System (SAP DMS).
We finally found the solution and discovered why the pdf that we try to show from our SAP DMS is downloadable while it is not shown in the pdf viewer inside the modern browsers like Chrome or Firefox.
The source for the solution can find here.
The below two changes are different from normal implementation that can be found in most of the tutorials in the internet:
The header value must be changed to Inline;filename= instead of outline;filename.
Call the method /IWBEP/IF_MGW_CONV_SRV_RUNTIME=>Set_header to set the header.
Finally we have the following ABAP code in SAP systems for downloading files form document management system (SAP DMS).
"Logic for Download the files from Document Managmenet System
DATA: ls_lheader TYPE ihttpnvp,
ls_stream TYPE ty_s_media_resource,
ls_entity TYPE zgw_odata_document_file.
CONSTANTS: lc_headername TYPE string VALUE 'Content-Disposition',
lc_headervalue1 TYPE string VALUE 'inline; filename="',
lc_headervalue2 TYPE string VALUE '";'.
* "Get the name of the Entity
DATA(lv_entity_name) = io_tech_request_context->get_entity_type_name( ).
CASE lv_entity_name.
WHEN 'DocumentFile'.
DATA(lo_document_file) = NEW zcl_gw_odata_document_file( ).
lo_document_file->read_stream(
EXPORTING
it_key_tab = it_key_tab
IMPORTING
es_stream = ls_entity ).
ls_lheader-name = lc_headername.
ls_entity-file_name = escape( val = ls_entity-file_name format = cl_abap_format=>e_url ).
ls_lheader-value = lc_headervalue1 && ls_entity-file_name && lc_headervalue2 .
set_header( is_header = ls_lheader ).
ls_stream-mime_type = ls_entity-mimetype.
ls_stream-value = ls_entity-binfile.
copy_data_to_ref( EXPORTING is_data = ls_stream
CHANGING cr_data = er_stream ).
WHEN OTHERS.
ENDCASE.

How to get MS Word total pages count using Open XML SDK?

I am using below code to get the page count but it is not giving actual page count(PFA). What is the better way to get the total pages count?
var pageCount = doc.ExtendedFilePropertiesPart.Properties.Pages.Text.Trim();
Note: We cannot use the Office Primary Interop Assemblies in my Azure web app service
Thanks in advance.
In theory, the following property can return that information from the Word Open XML file, using the Open XML SDK:
int pageCount = (int) document.ExtendedFilePropertiesPart.Properties.Pages.Text;
In practice, however, this isn't reliable. It might work, but then again, it might not - it all depends on 1) What Word managed to save in the file before it was closed and 2) what kind of editing may have been done on the closed file.
The only sure way to get a page number or a page count is to open a document in the Word application interface. Page count and number of pages is calculated dynamically, during editing, by Word. When a document is closed, this information is static and not necessarily what it will be when the document is open or printed.
See also https://github.com/OfficeDev/Open-XML-SDK/issues/22 for confirmation.
This code worked for me. It adds "Page X of Y" to the document.
para = new Paragraph(new Run(
new Text() { Text = "Page ", Space = SpaceProcessingModeValues.Preserve },
new SimpleField() { Instruction = "PAGE" },
new Text() { Text = " of ", Space = SpaceProcessingModeValues.Preserve },
new SimpleField() { Instruction = "NUMPAGES \\*MERGEFORMAT" }));

Get Word's window handle to use with WPF windows

I have a number of WPF dialogs in my Word Add-In. For one of them (and only one, strangely), it is sometimes not focused when opened. I believe I need to set its parent.
I know there is a way to set the owner of a WPF window to a HWND, but is there any way to get a HWND in Word 2010? I found this HWND property but it is Word 2013 and later only. Is there any other way to get Word's HWND, other than using GetForegroundWindow() which does not guarantee the handle for the window I actually want (or any other similar kludge)?
I found something helpful in Get specific window handle using Office interop. But all those answers are based on getting the handle for a window you're newly creating. I modified it somewhat to get an existing window, and stuffed it into a utility method.
doc is the current document.
using System.Windows.Interop;
using System.Diagnostics;
public void SetOwner(System.Windows.Window pd)
{
var wordProcs = Process.GetProcessesByName("winword").ToList();
// in read-only mode, this would be e.g. "1.docx [Read-Only] - Microsoft Word"
var procs = wordProcs.Where(x =>
x.MainWindowTitle.StartsWith(Path.GetFileName(doc.FullName))
&&
x.MainWindowTitle.EndsWith("- Microsoft Word"));
if (procs.Count() >= 1)
{
// would prefer Word 2013's Window.HWND property for this
var handle = procs.First().MainWindowHandle;
WindowInteropHelper wih = new WindowInteropHelper(pd);
wih.Owner = handle;
}
}
Unfortunately it doesn't seem to be possible to account for multiple windows with the same document name (in different folders), because the number of processes is never greater than 1. But I think that's an acceptable limitation.

How to find Window by variable title using TestStack.White framework?

I am using TestStack.White framework to automate opening new document in MS Word 2013.
I am opening Microsoft Word application with:
Application application = Application.Launch("winword.exe");
After that, I am trying to get the window by partial title:
Window window = application.GetWindow("Word", InitializeOption.NoCache);
But it throws an exception saying that there is no such window.
Window title is: Document1 - Word
The question is: How to get a window by partial title taking into consideration that the title is changing every time: "Document2 - Word", "Document3 - Word", etc.
Also tried *Word but looks like this func does not support wildcards
If I invoke:
List windows = application.GetWindows();
after launching an application, windows list is empty.
Thanks in advance,
Ostap
You can use EnumWindows to find all the open windows.
Within that callback you'll get a window handle which you can then us with GetWindowTextLength and GetWindowText
This will let you decide what window handle is to the window you want. From there you can use GetWindowThreadProcessId to retrieve the process ID for the word document.
And finally with that you can create a TestStack White application using the Application.Start()
It looks like opening window takes some noticeable time. GUI testing frameworks often have functions like Wait() to make sure the window is already created/visible/enabled. I'm not an expert in Teststack.White. Probably this document may help: http://teststackwhite.readthedocs.io/en/latest/AdvancedTopics/Waiting/
public static Window GetWindowBySubstring(this Application app, string titleSubString)
{
return app.GetWindows().FirstOrDefault(w => w.Title.Contains(titleSubString));
}