How do I unlock a content control using the OpenXML SDK in a Word 2010 document? - ms-word

I am manipulating a Word 2010 document on the server-side and some of the content controls in the document have the following Locking properties checked
Content control cannot be deleted
Contents cannot be edited
Can anyone advise set these Locking options to false or remove then altogether using the OpenXML SDK?

The openxml SDK provides the Lock class and the LockingValues enumeration
for programmatically setting the options:
Content control cannot be deleted and
Contents cannot be edited
So, to set those two options to "false" (LockingValues.Unlocked),
search for all SdtElement elements in the document and set the Val property to
LockingValues.Unlocked.
The code below shows an example:
static void UnlockAllSdtContentElements()
{
using (WordprocessingDocument wordDoc =
WordprocessingDocument.Open(#"c:\temp\myword.docx", true))
{
IEnumerable<SdtElement> elements =
wordDoc.MainDocumentPart.Document.Descendants<SdtElement>();
foreach (SdtElement elem in elements)
{
if (elem.SdtProperties != null)
{
Lock l = elem.SdtProperties.ChildElements.First<Lock>();
if (l == null)
{
continue;
}
if (l.Val == LockingValues.SdtContentLocked)
{
Console.Out.WriteLine("Unlock content element...");
l.Val = LockingValues.Unlocked;
}
}
}
}
}
static void Main(string[] args)
{
UnlockAllSdtContentElements();
}

Just for the ones who copy this code, keep in mind that if there is no Locks associated to the content control, then there won't be a Lock property associated to it, so when the code executes the following instruction, it will return a exception since there is no element found:
Lock l = elem.SdtProperties.ChildElements.First<Lock>();
The way to fix this is do the FirstOrDefault instead of First.

Related

store data persistent for learn app with unity

I'm currently working on a language learn app with unity. I want to implement that when you guessed a work (e.g. a number) incorrect, you need to guess the word again in the next iteration. I thought of a way that you store for each word in every play mode a value between +10 to -10 and when an item has a big negative number the word occurrence more often than if it has a big positiv number.
My Problem is that I don't know how to store the data properly. PlayerPrefs are too inconvenient for this problem, and I don't know how to modify a JSON file properly.
Currently, I store the data for the items in a class.
Maybe you could have a structure like:
Numbers
write
zero: -5
one: +3
match
zero: +5
one: +4
Alphabet
write:
A: -10
match:
A: +5
Does anyone have an idea how to solve this problem?
One of the best JSON serialization libraries is Newtonsoft.Json.
You can use your class and serialize an object to JSON object, and then save it as a string to file.
public static string Serialize(object obj)
{
var settings = new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
};
return JsonConvert.SerializeObject(obj, settings);
}
After that you can save it to file in the Application.persistentDataPath directory.
var text = Serialize(data);
var tmpFilePath = Path.Combine(Application.persistentDataPath, "filename");
Directory.CreateDirectory(Path.GetDirectoryName(tmpFilePath));
if (File.Exists(tmpFilePath))
{
File.Delete(tmpFilePath);
}
File.WriteAllText(tmpFilePath, text);
After that you can read the file at any time using File.ReadAllText and deserialize it to an object.
public static T Deserialize<T>(string text)
{
var settings = new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
};
try
{
var result = JsonConvert.DeserializeObject<T>(text, settings);
return result ?? default;
}
catch (Exception e)
{
Debug.Log(e);
}
return default;
}
T result = default;
try
{
if (File.Exists(path))
{
var text = File.ReadAllText(path);
result = Deserialize<T>(text);
}
}
catch (Exception e)
{
Debug.LogException(e);
}
return result;
Unfortunately, there is no easy way to store data persistently between play sessions in Unity. PlayerPrefs and creating your own JSON file are the simplest ways of doing this.
The good news is that JSON files are quite easy to make and modify, thanks to the builtin JSONUtility Unity provides.
If you make a separate class or struct to hold your scores, give that clas a [Serializable] tag and keep a reference to that in your current setup (which is probably a MonoBehaviour).
You can use the File class (specifically File.CreateText() and File.OpenText()) to write to/read from a file. If you do this every time a value changes, you should end up with persistent saved data across multiple play sessions.

How to determine if something was copied or cut to the clipboard

in my #execute method I am able to get the selection out of the clipboard / LocalSelectionTransfer. But I have no idea how to react on that based on how the user has put the content to the clipboard.
I have to decide whether I duplicate or not the content.
This is what I have:
#Execute
public void execute(#Named(IServiceConstants.ACTIVE_SHELL) Shell shell, #Named(IServiceConstants.ACTIVE_PART) MPart activePart) {
Clipboard clipboard = new Clipboard(shell.getDisplay());
TransferData[] transferDatas = clipboard.getAvailableTypes();
boolean weCanUseIt= false;
for(int i=0; i<transferDatas.length; i++) {
if(LocalSelectionTransfer.getTransfer().isSupportedType(transferDatas[i])) {
weCanUseIt = true;
break;
}
}
if (weCanUseIt) {
#SuppressWarnings("unchecked")
List<Object> objects = ((StructuredSelection)LocalSelectionTransfer.getTransfer().getSelection()).toList();
for(Object o: objects) {
System.out.println(o.getClass());
}
}
}
any Ideas???
You only get something in the clipboard using LocalSelectionTransfer if you code a part in your RCP to use this transfer type for a Copy operation. It provides a way to transfer the selection directly.
This transfer type will not be used if something is copied to the clipboard any other way (in this case it might be something like TextTransfer or FileTransfer).
So you will only be using LocalSelectionTransfer to deal with a selection from another part in which case you presumably know how to deal with the objects.
If you are trying to do Copy and Cut then you should do the Cut in the source viewer - but this will remove the selection so you can't use LocalSelectionTransfer for that. Use a transfer such as FileTransfer or TextTransfer which doesn't rely on the current selection.

How to edit pasted content using the Open XML SDK

I have a custom template in which I'd like to control (as best I can) the types of content that can exist in a document. To that end, I disable controls, and I also intercept pastes to remove some of those content types, e.g. charts. I am aware that this content can also be drag-and-dropped, so I also check for it later, but I'd prefer to stop or warn the user as soon as possible.
I have tried a few strategies:
RTF manipulation
Open XML manipulation
RTF manipulation is so far working fairly well, but I'd really prefer to use Open XML as I expect it to be more useful in the future. I just can't get it working.
Open XML Manipulation
The wonderfully-undocumented (as far as I can tell) "Embed Source" appears to contain a compound document object, which I can use to modify the copied content using the Open XML SDK. But I have been unable to put the modified content back into an object that lets it be pasted correctly.
The modification part seems to work fine. I can see, if I save the modified content to a temporary .docx file, that the changes are being made correctly. It's the return to the clipboard that seems to be giving me trouble.
I have tried assigning just the Embed Source object back to the clipboard (so that the other types such as RTF get wiped out), and in this case nothing at all gets pasted. I've also tried re-assigning the Embed Source object back to the clipboard's data object, so that the remaining data types are still there (but with mismatched content, probably), which results in an empty embedded document getting pasted.
Here's a sample of what I'm doing with Open XML:
using OpenMcdf;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
...
object dataObj = Forms.Clipboard.GetDataObject();
object embedSrcObj = dateObj.GetData("Embed Source");
if (embedSrcObj is Stream)
{
// read it with OpenMCDF
Stream stream = embedSrcObj as Stream;
CompoundFile cf = new CompoundFile(stream);
CFStream cfs = cf.RootStorage.GetStream("package");
byte[] bytes = cfs.GetData();
string savedDoc = Path.GetTempFileName() + ".docx";
File.WriteAllBytes(savedDoc, bytes);
// And then use the OpenXML SDK to read/edit the document:
using (WordprocessingDocument openDoc = WordprocessingDocument.Open(savedDoc, true))
{
OpenXmlElement body = openDoc.MainDocumentPart.RootElement.ChildElements[0];
foreach (OpenXmlElement ele in body.ChildElements)
{
if (ele is Paragraph)
{
Paragraph para = (Paragraph)ele;
if (para.ParagraphProperties != null && para.ParagraphProperties.ParagraphStyleId != null)
{
string styleName = para.ParagraphProperties.ParagraphStyleId.Val;
Run run = para.LastChild as Run; // I know I'm assuming things here but it's sufficient for a test case
run.RunProperties = new RunProperties();
run.RunProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("test"));
}
}
// etc.
}
openDoc.MainDocumentPart.Document.Save(); // I think this is redundant in later versions than what I'm using
}
// repackage the document
bytes = File.ReadAllBytes(savedDoc);
cf.RootStorage.Delete("Package");
cfs = cf.RootStorage.AddStream("Package");
cfs.Append(bytes);
MemoryStream ms = new MemoryStream();
cf.Save(ms);
ms.Position = 0;
dataObj.SetData("Embed Source", ms);
// or,
// Clipboard.SetData("Embed Source", ms);
}
Question
What am I doing wrong? Is this just a bad/unworkable approach?

Converting programmatically created openXML word documents to HTML errors with nulls

I'm creating WordProcessingDocuments using openxml (which works fine and the produced word doc is exactly what I want), now I'm trying to convert these newly created docs to HTML using the openxml Powertools. I'm new to this so I'm hoping thats it's something stupid that I'm missing but was hoping someone could point me in the right direction with these nullable errors I'm receiving.
This is the exact error...
System.NullReferenceException: Object reference not set to an instance of an object.
at OpenXmlPowerTools.HtmlConverter.ConvertToHtmlTransform(WordprocessingDocument wordDoc, HtmlConverterSettings settings, XNode node, Func2 imageHandler)
at OpenXmlPowerTools.HtmlConverter.<>c__DisplayClass37.<ConvertToHtmlTransform>b__1d(XElement e)
at System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext()
at System.Xml.Linq.XContainer.AddContentSkipNotify(Object content)
at System.Xml.Linq.XElement..ctor(XName name, Object content)
at OpenXmlPowerTools.HtmlConverter.ConvertToHtmlTransform(WordprocessingDocument wordDoc, HtmlConverterSettings settings, XNode node, Func2 imageHandler)
at OpenXmlPowerTools.HtmlConverter.<>c__DisplayClass37.<ConvertToHtmlTransform>b__1c(XElement e)
at System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext()
at System.Xml.Linq.XContainer.AddContentSkipNotify(Object content)
at System.Xml.Linq.XContainer.AddContentSkipNotify(Object content)
at System.Xml.Linq.XElement..ctor(XName name, Object[] content)
at OpenXmlPowerTools.HtmlConverter.ConvertToHtmlTransform(WordprocessingDocument wordDoc, HtmlConverterSettings settings, XNode node, Func`2 imageHandler)
I'm using the exact same code you can find on Eric Whites blog.
public static void PrintHTML(string file)
{
byte[] byteArray = File.ReadAllBytes(file);
using (MemoryStream memoryStream = new MemoryStream())
{
memoryStream.Write(byteArray, 0, byteArray.Length);
using (WordprocessingDocument doc =
WordprocessingDocument.Open(memoryStream, true))
{
HtmlConverterSettings settings = new HtmlConverterSettings()
{
//PageTitle = "some title"
};
XElement html = HtmlConverter.ConvertToHtml(doc, settings);
File.WriteAllText(#"C:\\Temp\Test.html", html.ToStringNewLineOnAttributes());
}
}
}
I know the code works because if i pass it a normal worddoc that I haven't created it works fine and converts to html fine. If i create a word doc using openxml then manually copy the contents into a new word file, save it, then pass it through the conversion code, that will work as well. So I'm thinking it must be something to do with the way I'm createing the word doc in openxml initially. Maybe im not adding a part to the file that is required.
Using the openxml sdk I have compared a working and non working file and they appear to have the same components/parts.
From the errors I've posted does anyone have any ideas of where the problem could be, ie, what is null? I can post the creation code for the word doc but it's quite extensive and it might just confuse people more.
I finally got to the bottom of this. I had to dig out the source code for the HtmlConverter in the openxmlpower tools, after some debuging I found that this line in the code was erroring...
line 371
styleId = (string)wordDoc.MainDocumentPart.StyleDefinitionsPart
.GetXDocument().Root.Elements(W.style)
.Where(e => (string)e.Attribute(W.type) == "paragraph" &&
(string)e.Attribute(W._default) == "1")
.FirstOrDefault().Attributes(W.styleId).FirstOrDefault();
basically in my debugging the
(string)e.Attribute(W._default)
was returning as True or False
so i changed the following line
.Where(e => (string)e.Attribute(W.type) == "paragraph" &&
(string)e.Attribute(W._default) == "1")
to
.Where(e => (string)e.Attribute(W.type) == "paragraph" && (
(string)e.Attribute(W._default) == "1" || (string)e.Attribute(W._default) == "true"))
and now works as expected
Had the same issue where I was saving a reportbuilder report to OpenWordXML and could not convert the bytes to html.
Had to add the following line of code for it to work correctly with version 2.8.1.0
private static IEnumerable<XElement> ParaStyleParaPropsStack(XDocument stylesXDoc,
string paraStyleName, XElement para)
{
if (stylesXDoc == null)
yield break;
var localParaStyleName = paraStyleName;
while (localParaStyleName != null)
{
XElement paraStyle = stylesXDoc.Root.Elements(W.style).FirstOrDefault(s
=>
**s.Attribute(W.type) != null &&**
s.Attribute(W.type).Value == "paragraph" &&
s.Attribute(W.styleId).Value == localParaStyleName);
s.Attribute(W.type) != null && // the liner that was added

VSTO How to identify parts (like a table) in a word document?

I have multiple tables in a word template and need to change each of it in an other way. Also it is possible that one or the other will be deleted or inserted so I can't say I take the 5th and that's always the same one.
The identification has to be saved so I can't use the .ID value.
What way is there to identify a specific table with VSTO? Preferable one which can also be set in the document without VSTO.
I found a way to do it:
Mark the table in word and add a bookmark to it. You have to choose a unique name so you can also identify the table. The identification can be done with a method like the following:
public Word.Bookmark GetBookmark(String bookmarkName)
{
// Find bookmark
Word.Bookmark bookmark = null;
foreach (Word.Bookmark curBookmark in Globals.ThisDocument.Bookmarks)
{
if (curBookmark.Name.Equals(bookmarkName))
{
bookmark = curBookmark;
break;
}
}
return bookmark;
}
I ran into similar problem. To solve the problem I set title of the table in word tamplate(Right click table->Table Properties...->Alt text->Title) and search all the table in word document for the title. Below is the code I used to search the table.
public static Table getTable(Document doc, String title){
int totalTables = doc.Tables.Count;
Microsoft.Office.Interop.Word.Table ret = null;
for (int i = 1; i <= totalTables; i++){
if (title.Equals(doc.Tables[i].Title, StringComparison.OrdinalIgnoreCase)){
ret = doc.Tables[i];
break;
}
}
return ret;
}
Is it possible to use alternative text for the tables as identifier?
Use the below code to retrieve them (code copied from here)
Word.Application wdApp = Application;
Word.Document wdDoc = wdApp.ActiveDocument;
Word.Table wdTable = wdDoc.Tables[1];
MessageBox.Show(wdTable.Title + "\n" + wdTable.Descr);