Programmatically change/add thumbnail of pptx powerpoint. With Openxml sdk? - c#

I use openxml sdk 2.5 in combination with the power tools by Eric White. I've managed to create dynamic pptx presentations using template files. (In C#)
Unfortunately the thumbnail gets lost during the process. Is there any way to (re)create the thumbnail of a pptx-file using openxml or power tools? I successfully wrote some code that changes an existing thumbnail with an image. But when there is is no thumbnail it gives me a System.NullReferenceException. Here is the code:
using OpenXmlPowerTools;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DocumentFormat.OpenXml.Packaging;
namespace ConsoleApplication1
{
class AddThumbnail_
{
public static void ReplaceThumbnail(ThumbnailPart thumbnailPart, string newThumbnail)
{
using (
FileStream imgStream = new FileStream(newThumbnail, FileMode.Open, FileAccess.Read))
{
thumbnailPart.FeedData(imgStream);
}
}
static void Main(string[] args)
{
var templatePresentation = "Modified.pptx";
var outputPresentation = "Modified.pptx";
var baPresentation = File.ReadAllBytes(templatePresentation);
var pmlMainPresentation = new PmlDocument("Main.pptx", baPresentation);
OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(pmlMainPresentation);
PresentationDocument document = streamDoc.GetPresentationDocument();
var thumbNailPart = document.ThumbnailPart;
ReplaceThumbnail(thumbNailPart, #"C:\Path\to\image\image.jpg");
document.SaveAs(outputPresentation);
}
}
}
EDIT:
I realize this question has been asked before (How to generate thumbnail image for a PPTX file in C#?) and the answer is "enable preview screenshot when saving the presentation" but this would mean I'd have to open every pptx and manually set this flag. I would appreciate a C# solution.
Thank you in advance!

If the thumbnail has never existed then the ThumbnailPart won't necessarily exist in the document and so the thumbNailPart variable in your code will be null. In that scenario, as well as setting the image for the ThumbnailPart you need to add the part itself.
Normally when using the OpenXml SDK you would call the AddPart method passing in a new ThumbnailPart but for some reason the ThumbnailPart constructor is protected internal and thus not accessible to you. Instead, there is an AddThumbnailPart method on the PresentationDocument which will create a new ThumbnailPart. The AddThumbnailPart method takes either a string for the content type or a ThumbnailPartType enum member.
Adding the following to your code should fix your issue:
if (document.ThumbnailPart == null)
document.AddThumbnailPart(ThumbnailPartType.Jpeg);
var thumbNailPart = document.ThumbnailPart;

Related

How to Modify Dicom Tags Using fo-dicom

I am creating a console application that will modify dicom tags. I will load up a single dicom file and update the PatientID tag.
I can not seem to to get anything to modify. I am able to read tags, but updating/adding does not seem to work for me. Previously I have used the DICOM ToolKit on powershell and it is very straight forward and easy, but I want to start developing in c# and so far I am failing.
using System;
using System.IO;
using SpiromicsImporterPrep.FileMethods;
using Dicom;
namespace SpiromicsImporterPrep
{
class Program
{
static void Main(string[] args)
{
string filename = #"Z:\SPIROMICS\Human_Scans\Dispatch_Received\NO_BACKUP_DONE_HERE\MIFAR\FORCE\JH114062-FU4\119755500\Non_Con_FRC__0.75__Qr40__5_7094\IM001139";
var file = DicomFile.Open(filename, readOption: FileReadOption.ReadAll);
var dicomDataset = file.Dataset;
dicomDataset.AddOrUpdate(DicomTag.PatientID, "TEST-PATIENT");
}
}
}
I expect after running the code when I look at the Dicom Header tags for this file with ImageJ or and other dicom reader that the value for the PatientID tag will be "TEST-PATIENT" The code runs with no errors but nothing seems to be updated or changed when I look at the dicom header.
you should invoke DicomFile.Save() Method.
string[] files = System.IO.Directory.GetFiles(#"D:\AcquiredImages\20191107\1.2.826.0.1.3680043.2.461.11107149.3266627937\1.2.276.0.7230010.3.1.3.3632557514.6848.1573106796.739");
foreach (var item in files)
{
DicomFile dicomFile = DicomFile.Open(item,FileReadOption.ReadAll);
dicomFile.Dataset.AddOrUpdate<string>(DicomTag.PatientName, "abc");
dicomFile.Save(item);
}
FileReadOption.ReadAll is required.

Unable to cast object of type 'iTextSharp.text.pdf.PdfArray' to type 'iTextSharp.text.pdf.PRStream'

I have to replace number "14-1" into "10-2". I am using following iText code but getting following type cast error. Can any one help me by modifying the program and remove the casting issue:
I have many PDF's where i have to replace the numbers at same location. I also need to understand it logically to how to do this:
using System;
using System.IO;
using System.Text;
using iTextSharp.text.io;
using iTextSharp.text.pdf;
using System.Windows.Forms;
namespace iText5
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public const string src = #"D:\test1\A.pdf";
public const string dest = #"D:\test1\ENV1.pdf";
private void button1_Click(object sender, EventArgs e)
{
FileInfo file = new FileInfo(dest);
file.Directory.Create();
manipulatePdf(src, dest);
}
public void manipulatePdf(String src, String dest)
{
PdfReader reader = new PdfReader(src);
PdfDictionary dict = reader.GetPageN(1);
PdfObject obj = dict.GetDirectObject(PdfName.CONTENTS);
PRStream stream = (PRStream)obj;
byte[] data = PdfReader.GetStreamBytes(stream);
string xyz = Encoding.UTF8.GetString(data);
byte[] newBytes = Encoding.UTF8.GetBytes(xyz.Replace("14-1", "10-2"));
stream.SetData(newBytes);
PdfStamper stamper = new PdfStamper(reader, new FileStream(dest, FileMode.Create));
stamper.Close();
reader.Close();
}
}
}
This is a problem:
PdfDictionary dict = reader.GetPageN(1);
PdfObject obj = dict.GetDirectObject(PdfName.CONTENTS);
PRStream stream = (PRStream)obj;
First you get a page dictionary. That page dictionary has a /Contents entry. If you read the PDF standard (ISO 32000), then you see that the value of the /Contents entry can be either a stream, or an array. You assume that it's always a stream. In some cases, your code will work, but in cases where the value of the /Contents entry is an array of references to a series of streams, you will get a class cast error (for the obvious reason that an array of streams is not the same as a stream).
I think that you want to do something like this:
byte[] data = reader.GetPageContent(i);
string xyz = PdfEncodings.ConvertToString(data, PdfObject.TEXT_PDFDOCENCODING);
string abc = xyz.Replace("14-1", "10-2");
reader.SetPageContent(i, PdfEncodings.ConvertToBytes(abc, PdfObject.TEXT_PDFDOCENCODING));
However, that's a very bad idea, because of the reasons explained in the answers to these questions:
Replace the text in pdf document using itextSharp
PDF text replace not working
Is there any API in C# or .net to edit pdf documents?
Using ContentByteUtils for raw PDF manipulation
...
You are making the assumption that you will find a literal string with value "14-1" in the content. That might be true for simple PDF documents, but in many cases the appearance of "14-1" on a page (that you can read with your eyes) doesn't mean the string "14-1" is present as such in the content (that you extract with GetPageContent). That string could be part of an XObject, or the syntax to render "14-1" could be constructed in such a way that xyz.Replace("14-1", "10-2") won't change xyz in any way.
Bottom line: PDF is not a format for editing. A page in a PDF file consists of content that is added at absolute positions. The content on a page doesn't reflow if you change it (e.g. the existing content won't move to the next line or to the next page if you add extra content). Instead of editing a PDF document, you should edit the source that was used to create the document, and then create a new PDF from that source.
Important: you are using an old version of iText. We abandoned the name iTextSharp more than two years ago in favor of iText for .NET. The current version of iText is iText 7.1.2; see Nuget: https://www.nuget.org/packages/itext7/
Many people think that iText 5.5.13 is the latest version. That assumption is wrong. iText 5 has been discontinued and is no longer supported. The recent 5.5.x versions are maintenance releases for paying customers who can't migrate to iText 7 right away.

Unable to use fileInfo and fileStream in PCL project for Xamarin Project

I'm currently facing an issue with using fileInfo and fileStream related codes in my Xamarin PCL Project as Visual Studio will give an error saying that the type or namespace name 'FileInfo' could not be found.
I have already included the using System; and using system.IO statements in the code but it is still not working.
using System;
using System.IO;
using IPShare.Models;
using IPShare.ViewModels;
using Xamarin.Forms;
using System.Net;
using System.Resources;
using System.Text;
using System.Threading.Tasks;
using PCLStorage;
using System.Net.Http;
namespace IPShare.Views
{
public partial class ItemsPage : ContentPage
{
ItemsViewModel viewModel;
private async void OnButtonClicked(object sender, EventArgs e)
{
// Load file meta data with FileInfo
FileInfo fileInfo = new FileInfo(path);
// The byte[] to save the data in
byte[] data = new byte[fileInfo.Length];
// Load a filestream and put its content into the byte[]
using (FileStream fs = fileInfo.OpenRead())
{
fs.Read(data, 0, data.Length);
}
// Delete the temporary file
fileInfo.Delete();
}
}
}
As I'm new to xamarin and C# any help would be greatly appreciated.
The the forum thread linked to below says that FileInfo class is not available in PCL's (It's dated 2013, so this may be outdated).
How to use System.IO.File or similar for cross platform projects in PCLs?

downloading the content of an xml file from Azure Blob Storage

In order to prevent the usual issues where I have an Xml file in a folder in one project and want to access it from other projects and have to deal with the file path issues, I want to download the Xml file contents directly from Azure blob storage where it resides now.
Not sure how to accomplish that although I see many examples of how to download images into streams, not sure how that works for Xml.
I am currently using the following ( which works until I move the Xml file)
public class MenuLoader
{
//var rootpath = HttpContext.Current.Server.MapPath("~");
private static readonly string NavMenuXmlPath = Path.Combine(ServicesHelpers.GetClassLibraryRootPath(),
#"ServicesDataFiles\MRNavigationMenu.xml");
);
//load the menus, based on the users role into the AppCache
public static void LoadMenus(IPrincipal principal)
{
var navXml = new NavigationMenusFromXml(NavMenuXmlPath);
var nmim = new NavigationMenuItemManager(navXml);
AppCache.Menus = nmim.Load(principal);
}
}
I want to eliminate all the bs associated with path combining and just download the xml from the file on Azure, i.e. replacing the string
#"ServicesDataFiles\MRNavigationMenu.xml"
with
"https://batlgroupimages.blob.core.windows.net:443/files/MRNavigationMenu.xml"
Naturally, that wouldn't work but there must be someway to load that xml into a file variable for use with the method.
Note: That is a publicly accessible file on azure for testing.
Use a memory stream. Remember to set position to zero before attempting to read.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.IO;
using System.Xml;
namespace ConsoleApplication1
{
class Program
{
const string URL = "https://batlgroupimages.blob.core.windows.net/files/MRNavigationMenu.xml";
static void Main(string[] args)
{
MemoryStream stream = new MemoryStream();
XmlWriter writer = XmlWriter.Create(stream);
XmlDocument doc = new XmlDocument();
doc.Load(URL);
writer.WriteRaw(doc.OuterXml.ToString());
writer.Flush();
}
}
}

Different results when signing same data with same keys in DSA cryptoservice provider

I am trying to build a small program that can discover whether file/files has been accessed or modified using DSA Signing algorithm.
I will give you this small example code to describe the issue.
Suppose that we have a file c:\Temporary\Temp.txt.
We have in our program 2 buttons. When we click the first button we create a digital signature
on the name of the file and the last access time on it. We also export the parameters and save them. On the second button click we recreate the signature on the same file using the parameters that we exported earlier and compare the new signature with the previous.
The problem here is that the program (always) gives me a result that the file has been accessed!!!!
I used the debugger to find the problem and saw that all variable values are identical between the 2 button_click events and that the difference occurs in the signing process.
Would you please tell me where the problem is?
Here is the code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.Security.Cryptography;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
string filename = #"C:\Temporary\Temp.txt";
DSAParameters parameters;
byte[] data_to_sign, signature;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void Sign_button_Click(object sender, EventArgs e)
{
FileInfo f_info = new FileInfo(filename);
string file_information = f_info.FullName + f_info.LastAccessTime;
UnicodeEncoding byteEncoder = new UnicodeEncoding();
data_to_sign = byteEncoder.GetBytes(file_information);
DSACryptoServiceProvider dsaprovider = new DSACryptoServiceProvider();
parameters = dsaprovider.ExportParameters(true);
signature = dsaprovider.SignData(data_to_sign);
label1.Text = " Signature generated";
}
private void Verify_button_Click(object sender, EventArgs e)
{
FileInfo f_info = new FileInfo(filename);
string file_information = f_info.FullName + f_info.LastAccessTime;
UnicodeEncoding byteEncoder = new UnicodeEncoding();
data_to_sign = byteEncoder.GetBytes(file_information);
DSACryptoServiceProvider dsaprovider2 = new DSACryptoServiceProvider();
dsaprovider2.ImportParameters(parameters);
byte [] signature2 = dsaprovider2.SignData(data_to_sign);
if (signature == signature2)
label1.Text = "The file hasn't been accessed";
else
label1.Text = "Opp... The file has been accessed";
}
}
}
You are comparing two byte arrays using the == operator. You are comparing them by reference and this is always false for two different byte array instances, even if their contents are the same. You need to compare their contents instead.
Use the various code pieces from this question to actually compare the contents.
Instead of
if (signature == signature2)
you can do something like:
if (signature.SequenceEqual(signature2))
If you have System.Linq in your using declarations.
You completely missuse DSA. DSA is a signature algorithm, that you need to verify using a digital signature verification algorithm. In fact DSA is designed to produce a different signature on every use, because it depends on random numbers to generate it.
What you want here is an Hash algorithm, like SHA-256 or Keccak.
You're using
string file_information = f_info.FullName + f_info.LastAccessTime;
as the basis for the deciion, which means you rely on the fact that the file name and last access time did not change.
You don't know what the .NET framework does internally when
creating an object of type FileInfo. Maybe that already accesses the
file. Or it accesses the file when using the FullName property.
You should also call Refresh() when accessing the properties of the same file again. Otherwise it may use cached information.
The LastAccessTime is not very reliable. For performance reasons,
Windows 7 does not update that flag. Read "Windows
Internals 6" for details.
Why are you using DSA at all in this
example? You could simply compare the file_information strings
directly. Signing or not would not make the difference.
Like
Sebastian said, compare byte[] for equality correctly

Categories