I am using the PdfSharp reference library to attempt to add functionality to my program that adds metadata tags. I am able to successfully add metadata tags to a document, but I am having an issue with updating the tags on existing custom properties. Whenever I attempt to use my method to update the custom properties, I receive the following exception:
"'System.Collections.Generic.KeyValuePair' does not contain a definition for 'Name'."
Could you guys tell me if I am coding the if statement in the foreach loop below to correctly loop through all of the custom elements in the PDF document to see if it exists and needs to be updated? Thanks.
public void AddMetaDataPDF(string property, string propertyValue, string
path)
{
PdfDocument document = PdfReader.Open(path);
bool propertyFound = false;
try {
dynamic properties = document.Info.Elements;
foreach(dynamic p in properties)
{
//Check to see if the property exists. If it does, update
value.
if(string.Equals(p.Name, property,
StringComparison.InvariantCultureIgnoreCase))
{
document.Info.Elements.SetValue("/" + property, new
PdfString(propertyValue));
}
}
// the property doesn't exist so add it
if(!propertyFound)
{
document.Info.Elements.Add(new KeyValuePair<String, PdfItem>
("/"+ property, new PdfString(propertyValue)));
}
}
catch (Exception ex)
{
MessageBox.Show(path + "\n" + ex.Message);
document.Close();
}
finally
{
if(document != null)
{
document.Save(path);
document.Close();
}
}
}
I didn't try your code but a common issue when working with this library is that you need to add a slash before the name of the property for it to be found. The code below will make the trick.
PdfDocument document = PdfReader.Open(path);
var properties = document.Info.Elements;
if (properties.ContainsKey("/" + propertyName))
{
properties.SetValue("/" + propertyName, new PdfString(propertyValue));
}
else
{
properties.Add(new KeyValuePair<String, PdfItem>("/" + propertyName, new PdfString(propertyValue)));
}
document.Save(path);
document.Close();
Also the PDF file shouldn't be write protected. Otherwise you need to use a tool for unlocking the file before calling PdfSharp.
Related
I add dicom files using the AddFile(dicomFile,name) method but the number of frames tag does not appear.
var sourcePath = Path.Combine(tempDirectory, "DICOM", $"PATIENT{i + 1}", $"STUDY{j + 1}", $"SERIES{k + 1}", $"SUBSERIES{l + 1}");
var dicomDir = new DicomDirectory { AutoValidate = false };
foreach (var file in new DirectoryInfo(tempDirectory).GetFiles("*.*", SearchOption.AllDirectories))
{
try
{
var dicomFile = DicomFile.Open(file.FullName);
if (dicomFile != null)
{
var referenceField = file.FullName.Replace(tempDirectory, string.Empty).Trim('\\');
dicomDir.AddFile(dicomFile, referenceField);
}
}
catch (Exception ex)
{
Log.Error(ex, ex.Message);
}
}
var dicomDirPath = Path.Combine(tempDirectory, "DICOMDIR");
dicomDir.Save(dicomDirPath);
resultDirectories.Add(dicomDirPath);
I also tried the addorupdate method but it doesn't work.
I use the fo-dicom library 4.0.7
When building a DICOMDIR with fo-dicom by iterative calling AddFile for each file, then you will get a DICOMDIR with all the required DicomTags. But of course there are a lot of tags that are optional and you can add them yourself.
The method AddFile returns an instance of type DicomDirectoryEntry, which gives you a reference to the patient record entry, the study record entry, the series record entry and the instance record entry. There you can add as many additional optional data that you wish. In your case it would look like
[...]
if (dicomFile != null)
{
var referenceField = file.FullName.Replace(tempDirectory, string.Empty).Trim('\\');
var entries = dicomDir.AddFile(dicomFile, referenceField);
// now you can add some additional data.
// but before adding values, make sure that those values are available
// in your original DicomFile to avoid NullReferenceExceptions.
if (dicomFile.Dataset.Contains(DicomTag.NumberOfFrames))
{
entries.InstanceRecord.AddOrUpdate(DicomTag.NumberOfFrames, dicomFile.Dataset.GetSingleValue<int>(DicomTag.NumberOfFrames));
}
}
im am trying to rename fields in itext. I can successfully rename textfields, but i am getting errors renaming checkboxfields. Here is the code so far:
try {
pdfReader = new PdfReader(s_pdfread);
} catch (Exception ex) {
Console.WriteLine("Can't create Filestream from " + s_pdfread);
Console.WriteLine(ex.Message);
ShowHelp();
Environment.Exit(-1);
}
List <renameJob> jobsToDo = new List<renameJob> ();
try {
using (FileStream fs = new FileStream(s_pdfwrite, FileMode.Create)) {
PdfStamper stamper = new PdfStamper(pdfReader, fs);
AcroFields fields = stamper.AcroFields;
foreach (var field in stamper.AcroFields.Fields) {
Console.WriteLine("fieldKeySRC: " + field.Key);
string newFieldname = field.Key.Replace(s_searchFor, s_replaceWith);
newFieldname = newFieldname.Replace(".", "");
// special name for checkbox
/*switch (stamper.AcroFields.GetFieldType(field.Key)) {
case AcroFields.FIELD_TYPE_CHECKBOX:
newFieldname = newFieldname + ".0";
break;
} planned for future. In finaly version i need to add this .0 */
if (field.Key != newFieldname) {
renameJob job = new renameJob();
job.oldName = field.Key;
job.newName = newFieldname;
jobsToDo.Add(job);
}
}
foreach (renameJob job in jobsToDo) {
if (fields.RenameField(job.oldName, job.newName) == true) {
Console.WriteLine("renameField: " + job.oldName + " to " + job.newName);
}
else {
Console.WriteLine("!!! failed: renameField: " + job.oldName + " to " + job.newName);
}
}
stamper.Close();
why does this fail for checkboxes and work fine for textboxes.... ?
As mkl explains, the "." in a field name is NOT part of the name, it's there to indicate a hierarchy.
Suppose you have a parent field named "parent" with two kids "kid1" and "kid2". In this case, the fully qualified names of the kid fields are "parent.kid1" and "parent.kid2".
With iText, you can rename fields, but you can't change the hierarchy. For instance: you can change the fully qualified name "mybox" into "hisbox", you can change the fully qualified name "parent.kid1" into "parent.child1" and "parent.kid2" into "parent.child2".
IT DOESN'T MATTER if these fields are text fields, checkbox fields, or any other type of field!
In other words: your question is wrong! The problem you have, is that you're not trying to rename a field. Instead you're trying to change "parent.kid1" into "parentkid1" and "parent.kid2" into "parentkid2". That's not the same as renaming! That's removing the hierarchy, changing a parent and two kids into two parentless kids! You can't use the rename function to do this.
If you really want to throw away the hierarchy, you need to add two new fields "parentkid1" and "parentkid2" that copy the properties of "parent.kid1" and "parent.kid2". Once you have these copies, you need to remove "parent", "kid1" and "kid2".
That's much more work. I'd advise against it, as it's an error-prone operation.
I would like to create a function with PDFSharp in order to merge some pdf's.
Here is my code
public class PDF_Merge
{
static string [] strTabPdfFiles;
public static string SetPdfToMerge(string strPdfFilesInput)
{
strTabPdfFiles = strPdfFilesInput.Split(';');
return "O";
}
public static string MergeToPdf(string strPdfFilesOutput)
{
try
{
PdfDocument objDocumentFinal = new PdfDocument(strPdfFilesOutput);
foreach (string strDoc in strTabPdfFiles)
{
PdfDocument objDocument = PdfReader.Open(strDoc, PdfDocumentOpenMode.Import);
foreach (PdfPage page in objDocument.Pages)
{
objDocumentFinal.AddPage(page);
}
objDocument.Close();----------> Exception : File cannot be modified
}
objDocumentFinal.Close();
objDocumentFinal.Save(strPdfFilesOutput);
}
catch (Exception ex)
{
return ex.Message;
}
return "O";
}
}
My problem is that on the objDocument.Close() call, i have an exception : "The document cannot be modified".
Anyone could help me about that ?
Great thanks for this lib,
Best regards,
Nixeus
PdfDocument.Close should only be called for documents created with a filename or a stream. Close will then automatically save the contents to the PDF file. You must not call Save in this case.
With the sample code in the question, Close must not be called for objDocument because it was not modified and cannot be saved.
It's OK to call Close for objDocumentFinal to save the changes. You should not call Save for objDocumentFinal because this will only save the changes again.
A PDF file opened with PdfDocumentOpenMode.Import is for import only and cannot be modified.
Try PdfDocumentOpenMode.Modify instead.
Look at the Concatenate Documents sample:
http://www.pdfsharp.net/wiki/ConcatenateDocuments-sample.ashx
I know I am late to the party, but I encountered this issue today.
The close method is trying to save the document, and thus the requirement for .Modify. In this case you don't need objDocument.Close() at all. You can optionally (and probably should?) call objDocument.Dispose().
I've written a small utility that allows me to change a simple AppSetting for another application's App.config file, and then save the changes:
//save a backup copy first.
var cfg = ConfigurationManager.OpenExeConfiguration(pathToExeFile);
cfg.SaveAs(cfg.FilePath + "." + DateTime.Now.ToFileTime() + ".bak");
//reopen the original config again and update it.
cfg = ConfigurationManager.OpenExeConfiguration(pathToExeFile);
var setting = cfg.AppSettings.Settings[keyName];
setting.Value = newValue;
//save the changed configuration.
cfg.Save(ConfigurationSaveMode.Full);
This works well, except for one side effect. The newly saved .config file loses all the original XML comments, but only within the AppSettings area. Is it possible to to retain XML comments from the original configuration file AppSettings area?
Here's a pastebin of the full source if you'd like to quickly compile and run it.
I jumped into Reflector.Net and looked at the decompiled source for this class. The short answer is no, it will not retain the comments. The way Microsoft wrote the class is to generate an XML document from the properties on the configuration class. Since the comments don't show up in the configuration class, they don't make it back into the XML.
And what makes this worse is that Microsoft sealed all of these classes so you can't derive a new class and insert your own implementation. Your only option is to move the comments outside of the AppSettings section or use XmlDocument or XDocument classes to parse the config files instead.
Sorry. This is an edge case that Microsoft just didn't plan for.
Here is a sample function that you could use to save the comments. It allows you to edit one key/value pair at a time. I've also added some stuff to format the file nicely based on the way I commonly use the files (You could easily remove that if you want). I hope this might help someone else in the future.
public static bool setConfigValue(Configuration config, string key, string val, out string errorMsg) {
try {
errorMsg = null;
string filename = config.FilePath;
//Load the config file as an XDocument
XDocument document = XDocument.Load(filename, LoadOptions.PreserveWhitespace);
if(document.Root == null) {
errorMsg = "Document was null for XDocument load.";
return false;
}
XElement appSettings = document.Root.Element("appSettings");
if(appSettings == null) {
appSettings = new XElement("appSettings");
document.Root.Add(appSettings);
}
XElement appSetting = appSettings.Elements("add").FirstOrDefault(x => x.Attribute("key").Value == key);
if (appSetting == null) {
//Create the new appSetting
appSettings.Add(new XElement("add", new XAttribute("key", key), new XAttribute("value", val)));
}
else {
//Update the current appSetting
appSetting.Attribute("value").Value = val;
}
//Format the appSetting section
XNode lastElement = null;
foreach(var elm in appSettings.DescendantNodes()) {
if(elm.NodeType == System.Xml.XmlNodeType.Text) {
if(lastElement?.NodeType == System.Xml.XmlNodeType.Element && elm.NextNode?.NodeType == System.Xml.XmlNodeType.Comment) {
//Any time the last node was an element and the next is a comment add two new lines.
((XText)elm).Value = "\n\n\t\t";
}
else {
((XText)elm).Value = "\n\t\t";
}
}
lastElement = elm;
}
//Make sure the end tag for appSettings is on a new line.
var lastNode = appSettings.DescendantNodes().Last();
if (lastNode.NodeType == System.Xml.XmlNodeType.Text) {
((XText)lastNode).Value = "\n\t";
}
else {
appSettings.Add(new XText("\n\t"));
}
//Save the changes to the config file.
document.Save(filename, SaveOptions.DisableFormatting);
return true;
}
catch (Exception ex) {
errorMsg = "There was an exception while trying to update the config value for '" + key + "' with value '" + val + "' : " + ex.ToString();
return false;
}
}
If comments are critical, it might just be that your only option is to read & save the file manually (via XmlDocument or the new Linq-related API). If however those comments are not critical, I would either let them go or maybe consider embedding them as (albeit redundant) data elements.
I've been trying to create a library to replace the MergeFields on a Word 2003 document, everything works fine, except that I lose the style applied to the field when I replace it, is there a way to keep it?
This is the code I'm using to replace the fields:
private void FillFields2003(string template, Dictionary<string, string> values)
{
object missing = Missing.Value;
var application = new ApplicationClass();
var document = new Microsoft.Office.Interop.Word.Document();
try
{
// Open the file
foreach (Field mergeField in document.Fields)
{
if (mergeField.Type == WdFieldType.wdFieldMergeField)
{
string fieldText = mergeField.Code.Text;
string fieldName = Extensions.GetFieldName(fieldText);
if (values.ContainsKey(fieldName))
{
mergeField.Select();
application.Selection.TypeText(values[fieldName]);
}
}
}
document.Save();
}
finally
{
// Release resources
}
}
I tried using the CopyFormat and PasteFormat methods in the selection, also using the get_style and set_style but to no exent.
Instead of using TypeText over the top of your selection use the the Result property of the Field:
if (values.ContainsKey(fieldName))
{
mergeField.Result = (values[fieldName]);
}
This will ensure any formatting in the field is retained.