OpenXML SDK getting ActiveX controls value - c#

For one of my projects in college I had to create a test as a Word document and add some ActiveX forms in it to be completed by another person. After this I had to programmatically extract the answers from it and put a grade on the test.
I used OpenXML SDK for processing the document, but it gave me headaches because I couldn't find a way to get the ActiveX values.
So what was the solution?

After searching the Internet and a bit of sniffing on the document I have found that the ActiveX control data can be found in a document part specified by the control ID. Determining the type of control is a bit tricky because I didn't find any documentation about this. Apparently you must get the "classid" attribute from a control and try to match it to the classids you know. Below is the code for determining the values for three types of controls. The rest of the ids are marked as not known and you can match them intuitively to the ones you added in the document.
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Xml;
using System.IO;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml.Packaging;
namespace OpenXMLTest
{
class Program
{
const string textBoxId = "{8BD21D10-EC42-11CE-9E0D-00AA006002F3}";
const string radioButtonId = "{8BD21D50-EC42-11CE-9E0D-00AA006002F3}";
const string checkBoxId = "{8BD21D40-EC42-11CE-9E0D-00AA006002F3}";
static void Main(string[] args)
{
string fileName = #"C:\Users\Andy\Desktop\test_l1demo.docx";
using (WordprocessingDocument doc = WordprocessingDocument.Open(fileName, false))
{
foreach (Control control in doc.MainDocumentPart.Document.Body.Descendants())
{
Console.WriteLine();
Console.WriteLine("Control {0}:", control.Name);
Console.WriteLine("Id: {0}", control.Id);
displayControlDetails(doc, control.Id);
}
}
Console.Read();
}
private static void displayControlDetails(WordprocessingDocument doc, StringValue controlId)
{
string classId, type, value;
OpenXmlPart part = doc.MainDocumentPart.GetPartById(controlId);
OpenXmlReader reader = OpenXmlReader.Create(part.GetStream());
reader.Read();
OpenXmlElement controlDetails = reader.LoadCurrentElement();
classId = controlDetails.GetAttribute("classid", controlDetails.NamespaceUri).Value;
switch (classId)
{
case textBoxId:
type = "TextBox";
break;
case radioButtonId:
type = "Radio Button";
break;
case checkBoxId:
type = "CheckBox";
break;
default:
type = "Not known";
break;
}
value = "No value attribute"; //displays this if there is no "value" attribute found
foreach (OpenXmlElement child in controlDetails.Elements())
{
if (child.GetAttribute("name", controlDetails.NamespaceUri).Value == "Value")
{
//we've found the value typed by the user in this control
value = child.GetAttribute("value", controlDetails.NamespaceUri).Value;
}
}
reader.Close();
Console.WriteLine("Class id: {0}", classId);
Console.WriteLine("Control type: {0}", type);
Console.WriteLine("Control value: {0}", value);
}
}
}

Related

PDF clown field value editing

I am using the PDF clown library to edit a pre-existing PDF (mass autofilling them based on user input) and i'm having trouble changing the form fields (specifically the textfields) value, when attempting to run the code below i will be given this error which is highlighted on the code sample below
" System.InvalidCastException: 'Unable to cast object of type 'org.pdfclown.objects.PdfName' to type 'org.pdfclown.objects.IPdfNumber'.'"
the block of code im having trouble with is a loop that runs through each field and attempts to locate the field matching the name and then altering that fields Value to match the hardcoded string
using org.pdfclown.documents;
using org.pdfclown.documents.contents.composition;
using org.pdfclown.documents.contents.entities;
using org.pdfclown.documents.contents.fonts;
using org.pdfclown.documents.contents.xObjects;
using org.pdfclown.documents.interaction.annotations;
using org.pdfclown.documents.interaction.forms;
using org.pdfclown.documents.files;
using org.pdfclown.objects;
using org.pdfclown.files;
using files = org.pdfclown.files;
using System;
using System.Collections.Generic;
using System.Drawing;
namespace PDFedit
{
class PDFLoader
{
public static void load (string path )
{
string filepath = path;
File file = new File(filepath);
Document document = file.Document;
Form form = document.Form;
Fields fields = form.Fields;
string value = "william";
foreach (Field field in form.Fields.Values)
{
if (field.Name == "Testtext")
{
string typeName = field.GetType().name;
field.value = "data to be written into the field" // this is the line that throws
the error
Console.WriteLine(" Type: " + typeName);
Console.WriteLine(" Value: " + field.Value);
Console.WriteLine(" Data: " + field.BaseDataObject.ToString());
}
};
file.Save();
}
}
}
Apologies if i've committed any fauxpas in asking a question, this is my first post and i'm new to programming outside of a tutorial environment.

Get information from XML file in C# requested in dialog

I'm trying to parse/get the information of an XML file where I have saved the setting values.
I would like to open a dialog, where the user can select the .xml file and after that get the information and load the settings.
The XML file looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<Configuration version="1.2" createDate="2018-07-17T10:00:00">
<AutoScale>1</Autoscale>
<Threshold>2142</Threshold>
<MinAuto>14</MinAuto>
<MinMan>1</MinMan>
<MaxMan>1</MaxMan>
<BlueBackground>1</BlueBackground>
<Contour>1</Contour>
<Rotate>180</Rotate>
<Flip>Vertical</Flip>
</Configuration>
My code (in C#) looks like this:
using (var openFileDialogXML = new OpenFileDialog()){
System.IO.Stream myStream = null;
openFileDialogXML.InitialDirectory = #System.Environment.CurrentDirectory;
openFileDialogXML.Filter = "xml files (*.xml)|*.xml|All files (*.*)|*.*";
openFileDialogXML.FilterIndex = 1;
openFileDialogXML.RestoreDirectory = true;
DialogResult dr = openFileDialogXML.ShowDialog();
if (dr == System.Windows.Forms.DialogResult.OK)
{
using (XmlReader reader = XmlReader.Create(openFileDialogXML.FileName))
{
reader.MoveToContent();
var version = reader.GetAttribute("version");
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "AutoScale":
//Get AutoScale value
break;
case "Threshold":
break;
case "MinAuto":
break;
case "MinMan":
break;
case "MaxMan":
break;
}
}
}
}
I'm open to use any parser but I would like to read it element by element because it could happen that we add new settings in the future.
Can you please help me/ give me some advice about how I can reach this?
I like using Xml Linq and putting results into a dictionary so when new items are added the xml parser doesn't have to change :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication53
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
Dictionary<string, string> dict = doc.Element("Configuration").Elements()
.GroupBy(x => x.Name.LocalName, y => (string)y)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
}
I would suggest to use DataContract and load the XML into specified object. When your configuration file changes, you would need to update also the Entity.
[DataContract]
public class MyXmlClass
{
[DataMember]
public int PropertyToSerialize { get; set; }
}
You can then use DataContractSerializer as described here - https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/serialization-and-deserialization
It will be much easier for you to work with object than parsing XML manually :)
Some quick and dirty answer if you want to parse it manually:
using System.Xml;
[...]
XmlTextReader xtr = new XmlTextReader(GetResourceStream("config.xml"));
while (xtr.Read())
{
if (xtr.AttributeCount == 0)
continue;
if (xtr.LocalName == "Configuration")
{
string version = xtr.GetAttribute("version");
string date = xtr.GetAttribute("createDate");
Console.WriteLine($"version={version} - date = {date}")
}
else if (xtr.LocalName == "AutoScale")
{
string autoscale = xtr.ReadString();
Console.WriteLine($"autoscale={autoscale}")
}
[...]
}
xtr.Close();
I didn't try the code, if you need more start by looking XmlTextReader examples or documentation (stackoverflow should have plenty of them)

Add boosting to specific document types in Umbraco using lucene

I'm trying to boost specific Umbraco document types, but when viewing the score using Luke the Score 0.0035.
I'm using Lucene.net version 2.9.4.1.
I'm trying to add the boost by using the DocumentWriting event, my code is below, it does get called and i'm able to step through and make sure the correct boost line is reached:
using System;
using Examine;
using Examine.LuceneEngine;
using Examine.LuceneEngine.Providers;
using Spurs.Site.Models.Enums;
using Umbraco.Core;
namespace Spurs.Site.Infrastructure
{
public class UmbracoEvents : ApplicationEventHandler
{
public UmbracoEvents()
{
var indexer = (LuceneIndexer)ExamineManager.Instance.IndexProviderCollection["ExternalIndexer"];
indexer.DocumentWriting += Indexer_DocumentWriting;
}
private void Indexer_DocumentWriting(object sender, DocumentWritingEventArgs e)
{
string nodeTypeAlias;
var hasNodeTypeAlias = e.Fields.TryGetValue("nodeTypeAlias", out nodeTypeAlias);
if (!hasNodeTypeAlias)
return;
DocumentTypeAlias documentType = (DocumentTypeAlias)Enum.Parse(typeof(DocumentTypeAlias), nodeTypeAlias, true);
switch (documentType)
{
case DocumentTypeAlias.playerPage:
e.Document.SetBoost(1000f);
break;
case DocumentTypeAlias.fixturePage:
e.Document.SetBoost(500f);
break;
default:
e.Document.SetBoost(1f);
break;
}
}
}
}
I must be missing something, it's as if the e.Document.SetBoost isn't saved.

How do I programmatically determine which WMI property is the primary key of a class?

I need to dynamically determine which property of a WMI class is the primary key in C#.
I can manually locate this information using CIM Studio or the WMI Delphi Code Creator but I need to find all property names of a class and flag which is / are the key / keys... and I already know how to find the property names of a class.
Manual identification of the key is covered in a related answer and I'm hoping the author (I'm looking at RRUZ) might be able to fill me in on how they locate the key (or anyone else who might know).
Many thanks.
To get the key field of a WMI class, you must iterate over the qualifiers of the properties for the WMI class and then search for the qualifier called key and finally check if the value of that qualifier is true.
Try this sample
using System;
using System.Collections.Generic;
using System.Management;
using System.Text;
namespace GetWMI_Info
{
class Program
{
static string GetKeyField(string WmiCLass)
{
string key = null;
ManagementClass manClass = new ManagementClass(WmiCLass);
manClass.Options.UseAmendedQualifiers = true;
foreach (PropertyData Property in manClass.Properties)
foreach (QualifierData Qualifier in Property.Qualifiers)
if (Qualifier.Name.Equals("key") && ((System.Boolean)Qualifier.Value))
return Property.Name;
return key;
}
static void Main(string[] args)
{
try
{
Console.WriteLine(String.Format("The Key field of the WMI class {0} is {1}", "Win32_DiskPartition", GetKeyField("Win32_DiskPartition")));
Console.WriteLine(String.Format("The Key field of the WMI class {0} is {1}", "Win32_Process", GetKeyField("Win32_Process")));
}
catch (Exception e)
{
Console.WriteLine(String.Format("Exception {0} Trace {1}", e.Message, e.StackTrace));
}
Console.WriteLine("Press Enter to exit");
Console.Read();
}
}
}
For those interested I've expanded on RRUZ's answer by:
allowing the query to be run against a remote machine, and
adding support for classes with multiple primary keys (as is the case with Win32_DeviceBus).
static void Main(string[] args)
{
foreach (var key in GetPrimaryKeys(#"root\cimv2\win32_devicebus"))
{
Console.WriteLine(key);
}
}
static List<string> GetPrimaryKeys(string classPath, string computer = ".")
{
var keys = new List<string>();
var scope = new ManagementScope(string.Format(#"\\{0}\{1}", computer, System.IO.Path.GetDirectoryName(classPath)));
var path = new ManagementPath(System.IO.Path.GetFileName(classPath));
var options = new ObjectGetOptions(null, TimeSpan.MaxValue, true);
using (var mc = new ManagementClass(scope, path, options))
{
foreach (var property in mc.Properties)
{
foreach (var qualifier in property.Qualifiers)
{
if (qualifier.Name.Equals("key") && ((System.Boolean)qualifier.Value))
{
keys.Add(property.Name);
break;
}
}
}
}
return keys;
}

Get, alter and update the copy cache

I searched pretty much everywhere on google, but I didn't find anything useable. I want to know how to get the current cache and save it on a string. This string will get processed and afterwards replace the current cache.
I am talking about our ordinary copy cache (CTRL + C) on Windows.
Use the System.Windows.Forms.Clipboard.GetText()
http://msdn.microsoft.com/en-us/library/kz40084e.aspx
Example from MSDN:
// Demonstrates SetText, ContainsText, and GetText.
public String SwapClipboardHtmlText(String replacementHtmlText)
{
String returnHtmlText = null;
if (Clipboard.ContainsText(TextDataFormat.Html))
{
returnHtmlText = Clipboard.GetText(TextDataFormat.Html);
Clipboard.SetText(replacementHtmlText, TextDataFormat.Html);
}
return returnHtmlText;
}
First in your console application add reference to System.Windows.Forms.dll via the Solution Explorer window. Then you should be able to add using System.Windows.Forms.
Here is some sample code to read clipboard text from your console application (important : you need the [STAThread] attribute added to your Main as shown below; else there will be a ThreadStateException thrown)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace SampleConsole
{
class Program
{
[STAThread]
static void Main(string[] args)
{
if (Clipboard.ContainsText(TextDataFormat.Text))
{
string clipBoardText = Clipboard.GetText(TextDataFormat.Text);
Console.WriteLine("TEXT in ClipBoard : " + clipBoardText);
Console.WriteLine("Type text to replace (and press Enter key) :");
string replaceText = Console.ReadLine();
Clipboard.SetText(replaceText);
Console.WriteLine("REPLACED ClipBoard Text : " + Clipboard.GetText(TextDataFormat.Text));
}
else
{
Console.WriteLine("No text in clipboard, please type now (and press Enter key) :");
string newText = Console.ReadLine();
Clipboard.SetText(newText);
Console.WriteLine("NEW ClipBoard Text : " + Clipboard.GetText(TextDataFormat.Text));
}
Console.Read();
}
}
}

Categories