Open-XML saving word document produces corrupted file - c#

Although I'm new to the Open-XML world, I've already encountered some troubles/problems using it. The most of them were easily solved but I can't get around this one:
public class ReportDocument : IDisposable
{
private MemoryStream stream;
private WordprocessingDocument document;
private MainDocumentPart mainPart;
public byte[] DocumentData
{
get
{
this.document.ChangeDocumentType(WordprocessingDocumentType.MacroEnabledDocument);
byte[] documentData = this.stream.ToArray();
return documentData;
}
}
public ReportDocument()
{
byte[] template = DocumentTemplates.SingleReportTemplate;
this.stream = new MemoryStream();
stream.Write(template, 0, template.Length);
this.document = WordprocessingDocument.Open(stream, true);
this.mainPart = document.MainDocumentPart;
}
public void SetReport(Report report)
{
Body body = mainPart.Document.Body;
var placeholder = body.Descendants<SdtBlock>();
this.SetPlaceholderTextValue(placeholder, "Company", WebApplication.Service.Properties.Settings.Default.CompanyName);
this.SetPlaceholderTextValue(placeholder, "Title", String.Format("Status Report for {0} to {1}", report.StartDate.ToShortDateString(),
report.ReportingInterval.EndDate.ToShortDateString()));
//this.SetPlaceholderTextValue(placeholder, "Subtitle", String.Format("for {0}", report.ReportingInterval.Project.Name));
this.SetPlaceholderTextValue(placeholder, "Author", report.TeamMember.User.Username);
this.SetPlaceholderTextValue(placeholder, "Date", String.Format("for {0}", DateTime.Today.ToShortDateString()));
}
private void SetPlaceholderTextValue(IEnumerable<SdtBlock> sdts, string alias, string value)
{
SdtContentBlock contentBlock = this.GetContentBlock(sdts, alias);
Text text = contentBlock.Descendants<Text>().First();
text.Text = value;
}
private SdtContentBlock GetContentBlock(IEnumerable<SdtBlock> sdts, string alias)
{
return sdts.First(sdt => sdt.Descendants<SdtAlias>().First().Val.Value == alias).SdtContentBlock;
}
public void Dispose()
{
this.document.Close();
}
}
So i create a new document, based on a template it gains through the memory stream and want to write it back to a memory stream when the changes are made.
The big problem is, when i save the resulting byte array the data docx file is corrupted:
The document.xml in .\word is called document2.xml
The document.xml.rels in .\word_rels is called document2.xml.rels and it contains
I hope some of you can provide good solutions for it.
MFG SakeSushiBig

Change your DocumentData property to this and I think it should work. The important thing is to close the document before you read the memorystream.
public byte[] DocumentData
{
get
{
this.document.ChangeDocumentType(WordprocessingDocumentType.MacroEnabledDocument);
this.document.MainDocumentPart.Document.Save();
this.document.Close();
byte[] documentData = this.stream.ToArray();
return documentData;
}
}

Related

Better way of writing json files in C# and validate the code

i have a lass like this
public class Params
{
public string FirstName;
public string SecondName;
public string Path;
public long Count;
public double TotalSize;
public long Time;
public bool HasError;
public Params()
{
}
public Params(string firstName, string secondName, string path, long count, double totalSize, long time, bool hasError)
{
FirstName = firstName;
SecondName = secondName;
Path = path;
Count = count;
TotalSize = totalSize;
Time = time;
HasError = hasError;
}
}
I have the json class like this:
public static class FileWriterJson
{
public static void WriteToJsonFile<T>(string filePath, T objectToWrite, bool append = true) where T : new()
{
TextWriter writer = null;
try
{
var contentsToWriteToFile = JsonConvert.SerializeObject(objectToWrite);
writer = new StreamWriter(filePath, append);
writer.Write(contentsToWriteToFile);
}
finally
{
if (writer != null)
writer.Close();
}
}
public static T ReadFromJsonFile<T>(string filePath) where T : new()
{
TextReader reader = null;
try
{
reader = new StreamReader(filePath);
var fileContents = reader.ReadToEnd();
return JsonConvert.DeserializeObject<T>(fileContents);
}
finally
{
if (reader != null)
reader.Close();
}
}
}
The main program is like this
var Params1 = new Params("Test", "TestSecondName", "Mypath",7, 65.0, 0, false);
FileWriterJson.WriteToJsonFile<Params>("C:\\Users\\myuser\\bin\\Debug\\test1.json", Params1);
FileWriterJson.WriteToJsonFile<Params>("C:\\Users\\myuser\\bin\\Debug\\test1.json", Params1);
This is mine test1.json:
{"FirstName":"Test","SecondName":"TestSecondName","Path":"Mypath","Count":7,"TotalSize":65.0,"Time":0,"HasError":false}{"FirstName":"Test","SecondName":"TestSecondName","Path":"Mypath","Count":7,"TotalSize":65.0,"Time":0,"HasError":false}
As you can see i have two json objects written in the file.
What i need to do is:
void ReadAllObjects(){
//read the json object from the file
// count the json objects - suppose there are two objects
for (int i=0;i<2;i++){
//do some processing with the first object
// if processing is successfull delete the object (i don't know how to delete the particular json object from file)
} }
but when i read like this
var abc =FileWriterJson.ReadFromJsonFile<Params>(
"C:\\Users\\myuser\\bin\\Debug\\test1.json");
i get the following error:
"Additional text encountered after finished reading JSON content: {.
Path '', line 1, position 155."
Then i used the following code to read the JSON file
public static IEnumerable<T> FromDelimitedJson<T>(TextReader reader, JsonSerializerSettings settings = null)
{
using (var jsonReader = new JsonTextReader(reader) { CloseInput = false, SupportMultipleContent = true })
{
var serializer = JsonSerializer.CreateDefault(settings);
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.Comment)
continue;
yield return serializer.Deserialize<T>(jsonReader);
}
}
}
}
Which worked fine for me.
Now i need following suggestion:
1> when i put my test1.json data in https://jsonlint.com/ it says Error:
Parse error on line 9:
..."HasError": false} { "FirstName": "Tes
----------------------^
Expecting 'EOF', '}', ',', ']', got '{'
should i write into file in some other way.
2>Is there any better of doing this.
You are writing each object out individually to the file.
But what you are creating is not a valid JSON file, just a text file with individual JSON objects.
To make it valid JSON, then you need to put the objects into an array or list and then save this to the file.
var Params1 = new Params("Test", "TestFirstName", "Mypath",7, 65.0, 0, false);
var Params2 = new Params("Test 2", "TestSecondName", "Mypath",17, 165.0, 10, false);
List<Params> paramsList = new List<Params>();
paramsList .Add(Params1);
paramsList .Add(Params2);
FileWriterJson.WriteToJsonFile<List<Params>>("C:\\Users\\myuser\\bin\\Debug\\test1.json", paramsList);
FileWriterJson.WriteToJsonFile("C:\Users\myuser\bin\Debug\test1.json", Params1);
Then you should be able to read it in OK. Don't forget to read in a List<Params>

OpenXml image relationship doesn't exist

I'm an intern at a large company who got tasked with working on a previous intern's project that apparently worked at some point, but now it's broken. What the program does, is it takes a bunch of text and images out of a document and inserts them into a template document. The problem is, about half of the images aren't forming relationships and I'm getting the red X "Image cannot be displayed" empty box. I've been doing some digging with the productivity tool, and I found out that there are a couple duplicate IDs, as well as quite a few non-existent relationships, although looking at his code I'm not sure what might be causing that. Here are his 2 methods for copying images:
internal static void CopyImages(OpenXmlElement oldTable, OpenXmlElement newTable,
WordprocessingDocument testData, WordprocessingDocument testReport)
{
List<Blip> sourceBlips = DocumentHelper.GetAllBlips(oldTable);
List<Blip> targetBlips = DocumentHelper.GetAllBlips(newTable);
foreach (Blip sourceBlip in sourceBlips)
{
foreach (Blip targetBlip in targetBlips)
{
if (targetBlip.Embed.Value == sourceBlip.Embed.Value)
{
if (testData.MainDocumentPart.GetPartById(sourceBlip.Embed.Value) is ImagePart imagePart)
{
ImagePart newImagePart = testReport.MainDocumentPart.AddPart(imagePart);
targetBlip.Embed.Value = testReport.MainDocumentPart.GetIdOfPart(newImagePart);
break;
}
}
}
}
}
internal static void CopyEmbeddedVisioImages(OpenXmlElement oldTable, OpenXmlElement newTable,
WordprocessingDocument testData, WordprocessingDocument testReport)
{
List<EmbeddedObject> sourceObjects = oldTable.Descendants<EmbeddedObject>().ToList();
List<EmbeddedObject> targetObjects = newTable.Descendants<EmbeddedObject>().ToList();
foreach (EmbeddedObject targetobj in targetObjects)
{
foreach (EmbeddedObject sourceObj in sourceObjects)
{
if (testData.MainDocumentPart.GetPartById(sourceObj.Descendants<ImageData>()
.FirstOrDefault().RelationshipId) is ImagePart oldImagePart)
{
ImagePart newImagePart = testReport.MainDocumentPart.AddPart(oldImagePart);
targetobj.Descendants<ImageData>().FirstOrDefault().RelationshipId =
testReport.MainDocumentPart.GetIdOfPart(newImagePart);
}
if (testData.MainDocumentPart.GetPartById(sourceObj.Descendants<OleObject>()
.FirstOrDefault().Id) is OpenXmlPart openXmlPart)
{
EmbeddedObjectPart newEmbeddedObj = (EmbeddedObjectPart)testReport.MainDocumentPart.AddPart(openXmlPart);
targetobj.Descendants<OleObject>().FirstOrDefault().Id =
testReport.MainDocumentPart.GetIdOfPart(newEmbeddedObj);
}
}
}
}
I've tried calling Save() and Close() on the documents. I even tried calling Dispose(). using(WordprocessingDocument foo = WordprocessingDocument.Open(bar, false){} doesn't seem to help either. I'm not too worried about the duplicate IDs for now, but I have no idea why only some of the relationships are being formed while others are not. This is a massive project so navigating through some of it can be pretty tricky.
Edit: It's probably also worth mentioning that the images stop forming relationships at a certain point. It isn't random. About 2/3 of the way down none of the images work.
Here's the updated set of methods
internal static void CopyImages(OpenXmlElement oldTable, OpenXmlElement newTable,
WordprocessingDocument testData, WordprocessingDocument testReport)
{
List<Blip> sourceBlips = DocumentHelper.GetAllBlips(oldTable);
List<Blip> targetBlips = DocumentHelper.GetAllBlips(newTable);
foreach (Blip sourceBlip in sourceBlips)
{
foreach (Blip targetBlip in targetBlips)
{
if (targetBlip.Embed.Value == sourceBlip.Embed.Value)
{
if (testData.MainDocumentPart.GetPartById(sourceBlip.Embed.Value) is ImagePart imagePart)
{
//ImagePart newImagePart = testReport.MainDocumentPart.AddPart(imagePart);
ImagePart newImagePart = testReport.MainDocumentPart.AddImagePart(imagePart.ContentType);
newImagePart.FeedData(imagePart.GetStream(FileMode.Open, FileAccess.Read));
targetBlip.Embed.Value = testReport.MainDocumentPart.GetIdOfPart(newImagePart);
break;
}
}
}
}
}
internal static void CopyEmbeddedVisioImages(OpenXmlElement oldTable, OpenXmlElement newTable,
WordprocessingDocument testData, WordprocessingDocument testReport)
{
List<EmbeddedObject> sourceObjects = oldTable.Descendants<EmbeddedObject>().ToList();
List<EmbeddedObject> targetObjects = newTable.Descendants<EmbeddedObject>().ToList();
foreach (EmbeddedObject targetobj in targetObjects)
{
foreach (EmbeddedObject sourceObj in sourceObjects)
{
if (testData.MainDocumentPart.GetPartById(sourceObj.Descendants<ImageData>()
.FirstOrDefault().RelationshipId) is ImagePart oldImagePart)
{
//ImagePart newImagePart = testReport.MainDocumentPart.AddPart(oldImagePart);
ImagePart newImagePart = testReport.MainDocumentPart.AddImagePart(oldImagePart.ContentType);
newImagePart.FeedData(oldImagePart.GetStream(FileMode.Open, FileAccess.Read));
targetobj.Descendants<ImageData>().FirstOrDefault().RelationshipId =
testReport.MainDocumentPart.GetIdOfPart(newImagePart);
}
if (testData.MainDocumentPart.GetPartById(sourceObj.Descendants<OleObject>()
.FirstOrDefault().Id) is OpenXmlPart openXmlPart)
{
EmbeddedObjectPart newEmbeddedObj = (EmbeddedObjectPart)testReport.MainDocumentPart.AddPart(openXmlPart);
targetobj.Descendants<OleObject>().FirstOrDefault().Id =
testReport.MainDocumentPart.GetIdOfPart(newEmbeddedObj);
}
}
}
}
Here's an update on my findings.
There are 25 total blips in the entire document.
targetBlip.Embed.Value != sourceBlip.Embed.Value in most cases or maybe it's something else?
Elements containing pictures are cloned from source doc and then saved into target doc.
All elements are being read. Tables containing pictures with broken relationships exist and are filled with other content, so it's not like it's missing those elements.
The duplicate IDs are due to the target document containing a couple images to begin with, so when I copy over the other images, some of those IDs are duplicated. This isn't my concern for now.
Images from a source document can't be added as-is into a target document;
an image has a unique id/number within its parent document and this one might conflict with the target document if one already exists with that same id.
Replace the following line
ImagePart newImagePart = testReport.MainDocumentPart.AddPart(imagePart);
with the one below. Here a whole new image file gets embedded and gets a new id assigned.
ImagePart newImagePart = testReport.MainDocumentPart.AddImagePart(oldImagePart.ContentType);
newImagePart.FeedData(oldImagePart.GetStream(FileMode.Open, FileAccess.Read));
It's important that the ids in the target document are unique.
I share some (old(er)) code fragments about how I handled to merge images from one document into another. (This is a fragment of a more complete/complex implementation where duplicate images are being detected and prevented from being inserted more than once.)
It starts by iterating over all Drawings in the source document and building a list of these together with their original id as in this source document. Then all images get inserted into the target document; while doing so the new id as in the target document gets mapped to each item.
Each drawing in the source document gets updated with the id as in the target document; the list contains both orginal source and new target ids. (This sounds bizarre, but for me at that moment only this gave the expected result.)
Only after the image merge has completed, the content (paragraphs and tables) get merged into the target document, which consists of adding clones of these items.
public class DocumentMerger
{
private readonly WordprocessingDocument _targetDocument;
public DocumentMerger(WordprocessingDocument targetDocument)
{
this._targetDocument = targetDocument;
}
public void Merge(WordprocessingDocument sourceDocument)
{
ImagesMerger imagesMerger = new ImagesMerger(this._targetDocument);
this._imagesMerger.Merge(sourceDocument);
// Merge the content; paragraphs and tables.
this._targetDocumentPart.Document.Save();
}
}
public class ImageInfo
{
private String _id;
private ImagePart _image;
private readonly String _originalId;
private ImageInfo(ImagePart image, String id)
{
this._id = id;
this._image = image;
this._originalId = id;
}
public String Id
{
get { return this._id; }
}
public ImagePart Image
{
get { return this._image; }
}
public String OriginalId
{
get { return this._originalId; }
}
public static ImageInfo Create(MainDocumentPart documentPart, ImagePart image)
{
String id = documentPart.GetIdOfPart(image);
ImageInfo r = new ImageInfo(image, id);
return r;
}
public void Reparent(MainDocumentPart documentPart)
{
ImagePart newImage = documentPart.AddImagePart(this._image.ContentType);
newImage.FeedData(this._image.GetStream(FileMode.Open, FileAccess.Read));
String newId = documentPart.GetIdOfPart(newImage);
this._id = newId;
this._image = newImage;
}
}
public class ImagesMerger
{
private readonly IList<ImageInfo> _imageInfosOfTheTargetDocument = new List<ImageInfo>();
private readonly MainDocumentPart _targetDocumentPart;
public ImagesMerger(WordprocessingDocument targetDocument)
{
this._targetDocumentPart = targetDocument.MainDocumentPart;
}
public void Merge(WordprocessingDocument sourceDocument)
{
MainDocumentPart sourceDocumentPart = sourceDocument.MainDocumentPart;
IList<ImageInfo> imageInfosOfTheSourceDocument = this.getImageInfos(sourceDocumentPart);
if (0 == imageInfosOfTheSourceDocument.Count) { return; }
this.addTheImagesToTheTargetDocument(imageInfosOfTheSourceDocument);
this.rereferenceTheImagesToTheirCorrespondingImageParts(sourceDocumentPart, imageInfosOfTheSourceDocument);
}
private void addTheImagesToTheTargetDocument(IList<ImageInfo> imageInfosOfTheSourceDocument)
{
for (Int32 i = 0, j = imageInfosOfTheSourceDocument.Count; i < j; i++)
{
imageInfoOfTheSourceDocument.Reparent(this._targetDocumentPart);
this._imageInfosOfTheTargetDocument.Add(imageInfoOfTheSourceDocument);
}
}
private IList<ImageInfo> getImageInfos(MainDocumentPart documentPart)
{
List<ImageInfo> r = new List<ImageInfo>();
foreach (ImagePart image in documentPart.ImageParts)
{
ImageInfo imageInfo = ImageInfo.Create(documentPart, image);
r.Add(imageInfo);
}
return r;
}
private void rereferenceTheImagesToTheirCorrespondingImageParts(MainDocumentPart sourceDocumentPart, IList<ImageInfo> imageInfosOfTheSourceDocument)
{
IEnumerable<Drawing> images = sourceDocumentPart.Document.Body.Descendants<Drawing>();
foreach (Drawing image in images)
{
Blip blip = image.Inline.Graphic.GraphicData.Descendants<Blip>().FirstOrDefault();
String originalId = blip.Embed.Value;
ImageInfo imageInfo = imageInfosOfTheSourceDocument.FirstOrDefault(o => o.OriginalId._Equals(originalId));
blip.Embed.Value = imageInfo.Id;
}
}
}

ITextSharp SetVisibleSignature not working as expected

So I have been working with the Java implementation of IText, but now I'm pretty much writing a port of our signing process to C#, and I've hit a snag. So when im signing my document using the SetVisibleSignature(rect, page, name) overload, it signs the document as expected(as long as the signature field does not exist), but when i use the overload SetVisibleSignature(name) to sign an existing field, it does not actually sign the document. Am I doing something stupid perhaps and missing something?
Thank you for any help.
Updated Code
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.security;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using BouncyCastle = Org.BouncyCastle;
public class DocumentSigner : IDocumentSigner
{
private const string _datetimeFormat = "dd/MM/yyyy hh:mm:ss";
private readonly IDateTimeProvider _dateTimeProvider;
private readonly ISettingManager _settingManager;
public DocumentSigner(IDateTimeProvider dateTimeProvider, ISettingManager settingManager)
{
Guard.ArgumentNotNull(dateTimeProvider, "dateTimeProvider");
Guard.ArgumentNotNull(settingManager, "settingManager");
_dateTimeProvider = dateTimeProvider;
_settingManager = settingManager;
}
public byte[] Sign(Certificate certificate, SigningInformation information, List<SigningBlock> signingBlocks, List<MemberItemSignature> signatureImages, byte[] document, bool certify)
{
document = AddMetaData(information, document);
document = AddSignatureFields(information, signingBlocks, document);
return SignDocument(certificate, information, signingBlocks, signatureImages, document, certify);
}
private byte[] AddMetaData(SigningInformation information, byte[] document)
{
if (information.CustomProperties != null && information.CustomProperties.Any())
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true))
{
Dictionary<string, string> currentProperties = reader.Info;
foreach (string key in information.CustomProperties.Keys)
{
if (currentProperties.ContainsKey(key))
{
currentProperties[key] = information.CustomProperties[key];
}
else
{
currentProperties.Add(key, information.CustomProperties[key]);
}
}
stamper.MoreInfo = currentProperties;
}
}
return outputStream.ToArray();
}
}
return document;
}
private byte[] AddSignatureFields(SigningInformation information, List<SigningBlock> signingBlocks, byte[] document)
{
for (int i = 0; i < signingBlocks.Count; i++)
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true))
{
CreateSignatureField(reader, stamper, signingBlocks[i]);
}
}
document = outputStream.ToArray();
}
}
return document;
}
private PdfSignatureAppearance CreatePdfAppearance(PdfStamper stamper, SigningInformation information, bool certify)
{
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Location = information.Location;
appearance.Reason = information.Purpose;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
CreatePdfAppearanceCertifyDocument(appearance, certify);
return appearance;
}
private void CreatePdfAppearanceCertifyDocument(PdfSignatureAppearance appearance, bool certify)
{
if (certify)
{
appearance.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING;
}
else
{
appearance.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED;
}
}
private PdfStamper CreatePdfStamper(PdfReader reader, MemoryStream outputStream, byte[] document)
{
return PdfStamper.CreateSignature(reader, outputStream, '\0', null, true);
}
private void CreateSignatureField(PdfReader reader, PdfStamper stamper, SigningBlock signingBlock)
{
if (signingBlock == null)
{
return;
}
if (!DoesSignatureFieldExist(reader, signingBlock.Name))
{
PdfFormField signatureField = PdfFormField.CreateSignature(stamper.Writer);
signatureField.SetWidget(new Rectangle(signingBlock.X, signingBlock.Y, signingBlock.X + signingBlock.Width, signingBlock.Y + signingBlock.Height), null);
signatureField.Flags = PdfAnnotation.FLAGS_PRINT;
signatureField.FieldName = signingBlock.Name;
signatureField.Page = signingBlock.Page;
stamper.AddAnnotation(signatureField, signingBlock.Page);
}
}
private bool DoesSignatureFieldExist(PdfReader reader, string signatureFieldName)
{
if (String.IsNullOrWhiteSpace(signatureFieldName))
{
return false;
}
return reader.AcroFields.DoesSignatureFieldExist(signatureFieldName);
}
private byte[] GetSignatureImage(List<MemberItemSignature> signatureImages, string signingBlockName)
{
MemberItemSignature signature = signingBlockName.Contains("Initial") ? signatureImages.Where(image => image.Use == SignatureUses.Initial).FirstOrDefault() : signatureImages.Where(image => image.Use == SignatureUses.Signature).FirstOrDefault();
if (signature != null)
{
return signature.Image;
}
else
{
return null;
}
}
private byte[] SignDocument(Certificate certificate, SigningInformation information, List<SigningBlock> signingBlocks, List<MemberItemSignature> signatureImages, byte[] document, bool certify)
{
for (int i = 0; i < signingBlocks.Count; i++)
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = CreatePdfStamper(reader, outputStream, document))
{
SigningBlock signingBlock = signingBlocks[i];
PdfSignatureAppearance appearance = CreatePdfAppearance(stamper, information, certify && i == 0);
SignDocumentSigningBlock(certificate, information, signingBlock, appearance, stamper, GetSignatureImage(signatureImages, signingBlock.Name));
}
}
document = outputStream.ToArray();
}
}
return document;
}
private void SignDocumentSigningBlock(Certificate certificate, SigningInformation information, SigningBlock block, PdfSignatureAppearance appearance, PdfStamper stamper, byte[] signatureImage)
{
X509Certificate2 x509Certificate = new X509Certificate2(certificate.Bytes, certificate.Password, X509KeyStorageFlags.Exportable);
appearance.SetVisibleSignature(block.Name);
SignDocumentSigningBlockWithImage(signatureImage, appearance);
SignDocumentSigningBlockWithText(appearance, x509Certificate);
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)x509Certificate.PrivateKey)
{
IExternalSignature externalSignature = new PrivateKeySignature(DotNetUtilities.GetRsaKeyPair(rsa).Private, _settingManager["DocumentSigningEncryptionHashAlgorithm"]);
MakeSignature.SignDetached(appearance, externalSignature, new BouncyCastle::X509.X509Certificate[] { DotNetUtilities.FromX509Certificate(x509Certificate) }, null, null, new TSAClientBouncyCastle(_settingManager["DocumentSigningTimestampingServiceAddress"]), Int32.Parse(_settingManager["DocumentSigningEstimatedTimestampSize"]), CryptoStandard.CMS);
}
}
private void SignDocumentSigningBlockWithImage(byte[] signatureImage, PdfSignatureAppearance appearance)
{
if (signatureImage != null && signatureImage.Length > 0)
{
Image signatureImageInstance = Image.GetInstance(signatureImage);
appearance.Image = signatureImageInstance;
appearance.SignatureGraphic = signatureImageInstance;
}
}
private void SignDocumentSigningBlockWithText(PdfSignatureAppearance appearance, X509Certificate2 x509Certificate)
{
if (x509Certificate == null)
{
return;
}
appearance.Layer2Text = SignDocumentSigningBlockWithTextBuildText(x509Certificate);
appearance.Layer2Font = new Font(Font.FontFamily.COURIER, 7.0f, Font.NORMAL, BaseColor.LIGHT_GRAY);
appearance.Acro6Layers = true;
}
private string SignDocumentSigningBlockWithTextBuildText(X509Certificate2 x509Certificate)
{
Dictionary<string, string> fields = SignDocumentSigningBlockWithTextBuildTextIssuerFields(x509Certificate.IssuerName.Name);
string organization = fields.Keys.Contains("O") ? fields["O"] : String.Empty;
string commonName = fields.Keys.Contains("CN") ? fields["CN"] : String.Empty;
string signDate = _dateTimeProvider.Now.ToString(_datetimeFormat);
string expirationDate = x509Certificate.NotAfter.ToString(_datetimeFormat);
return "Digitally signed by " + organization + "\nSignee: " + commonName + "\nSign date: " + signDate + "\n" + "Expiration date: " + expirationDate;
}
private Dictionary<string, string> SignDocumentSigningBlockWithTextBuildTextIssuerFields(string issuer)
{
Dictionary<string, string> fields = new Dictionary<string, string>();
string[] issuerFields = issuer.Split(',');
foreach (string field in issuerFields)
{
string[] fieldSplit = field.Split('=');
string key = fieldSplit[0].Trim();
string value = fieldSplit[1].Trim();
if (!fields.Keys.Contains(key))
{
fields.Add(key, value);
}
else
{
fields[key] = value;
}
}
return fields;
}
}
Values
_settingManager["DocumentSigningEncryptionHashAlgorithm"] = "SHA-256";
_settingManager["DocumentSigningTimestampingServiceAddress"] = "http://services.globaltrustfinder.com/adss/tsa";
_settingManager["DocumentSigningEstimatedTimestampSize"] = 104000;
The code provided by the OP references and accesses multiple objects of unknown classes. To make it runnable, therefore, it had to be cut down to be self-contained.
The cut-down version fortunately could still be used to reproduce and analyze the issue, cf. the post scriptum. Any statement from here on is based on the behavior of this cut-down version.
The issue observed by the OP could be reproduced using iTextSharp 5.5.7 (and analogously using iText 5.5.7), and also very interestingly it could not be reproduced using version 5.5.6 of either library. As I'm more into Java, I looked into the changes in iText. They had been ported to iTextSharp in a very faithful fashion.
Indeed, this issue is a regression, signing pre-existing empty signature fields in append mode is broken in iText(Sharp) 5.5.7.
Between 5.5.6 and 5.5.7 a change has been made to PdfSignatureAppearance.preClose. If signing an existing signature field, the code used to manipulate the first widget of the signature field in question (af.getFieldItem(name).getWidget(0)), now it works on the associated merged dictionary (af.getFieldItem(name).getMerged(0)).
Unfortunately, while the former was an object actually existing in the original PDF and, therefore, calling writer.markUsed for it marked its changed contents for writing to the incremental update section, the latter does not correspond to an object in the original PDF (it is a virtual aggregation of multiple objects), so calling writer.markUsed for it does not mark the changes to be written as incremental update anymore.
So, while the actual signature value still is written to the file, it is not connected to the designated signature field anymore.
The change has been done to fix the method behavior.
Before this preClosed worked incorrectly because it retrieved field dictionary as widget annotation. It's incorrect in case when field and widget dicts are not merged. In case they were merged, everything worked as expected. The latter is the most possible case for digital signature fields, but it isn't obligated according to spec.
(DEV-1448)
This is correct, in case of separate field and widget dictionaries certain changes have to be made to the field, not the widget. Merely the implementation does not work as desired in append mode.
PS: This is the cut-down version of the OP's code:
public class DocumentSigner
{
private const string _datetimeFormat = "dd/MM/yyyy hh:mm:ss";
public byte[] Sign(ICollection<X509Certificate> chain, ICipherParameters pk, string signingBlock, byte[] document, bool certify, String pattern = null)
{
document = AddMetaData(document);
if (pattern != null)
File.WriteAllBytes(String.Format(pattern, "1"), document);
document = AddSignatureFields(signingBlock, document);
if (pattern != null)
File.WriteAllBytes(String.Format(pattern, "2"), document);
return SignDocument(chain, pk, signingBlock, document, certify);
}
private byte[] AddMetaData(byte[] document)
{
return document;
}
private byte[] AddSignatureFields(string signingBlock, byte[] document)
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true))
{
CreateSignatureField(reader, stamper, signingBlock);
}
}
document = outputStream.ToArray();
}
return document;
}
private PdfSignatureAppearance CreatePdfAppearance(PdfStamper stamper, bool certify)
{
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Location = "information.Location";
appearance.Reason = "information.Purpose";
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
CreatePdfAppearanceCertifyDocument(appearance, certify);
return appearance;
}
private void CreatePdfAppearanceCertifyDocument(PdfSignatureAppearance appearance, bool certify)
{
if (certify)
{
appearance.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING;
}
else
{
appearance.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED;
}
}
private PdfStamper CreatePdfStamper(PdfReader reader, MemoryStream outputStream, byte[] document)
{
return PdfStamper.CreateSignature(reader, outputStream, '\0', null, true);
}
private void CreateSignatureField(PdfReader reader, PdfStamper stamper, string signingBlock)
{
if (signingBlock == null)
{
return;
}
if (!DoesSignatureFieldExist(reader, signingBlock))
{
PdfFormField signatureField = PdfFormField.CreateSignature(stamper.Writer);
signatureField.SetWidget(new Rectangle(100, 100, 200, 200), null);
signatureField.Flags = PdfAnnotation.FLAGS_PRINT;
signatureField.FieldName = signingBlock;
signatureField.Page = 1;
stamper.AddAnnotation(signatureField, 1);
}
}
private bool DoesSignatureFieldExist(PdfReader reader, string signatureFieldName)
{
if (String.IsNullOrWhiteSpace(signatureFieldName))
{
return false;
}
return reader.AcroFields.DoesSignatureFieldExist(signatureFieldName);
}
private byte[] GetSignatureImage(string signingBlockName)
{
return null;
}
private byte[] SignDocument(ICollection<X509Certificate> chain, ICipherParameters pk, string signingBlock, byte[] document, bool certify)
{
using (MemoryStream outputStream = new MemoryStream())
{
using (PdfReader reader = new PdfReader(document))
{
using (PdfStamper stamper = CreatePdfStamper(reader, outputStream, document))
{
PdfSignatureAppearance appearance = CreatePdfAppearance(stamper, certify);
SignDocumentSigningBlock(chain, pk, signingBlock, appearance, stamper, GetSignatureImage(signingBlock));
}
}
document = outputStream.ToArray();
}
return document;
}
private void SignDocumentSigningBlock(ICollection<X509Certificate> chain, ICipherParameters pk, string block, PdfSignatureAppearance appearance, PdfStamper stamper, byte[] signatureImage)
{
appearance.SetVisibleSignature(block);
SignDocumentSigningBlockWithImage(signatureImage, appearance);
SignDocumentSigningBlockWithText(appearance, chain.First());
IExternalSignature externalSignature = new PrivateKeySignature(pk, "SHA-256");
MakeSignature.SignDetached(appearance, externalSignature, chain, null, null, new TSAClientBouncyCastle("http://services.globaltrustfinder.com/adss/tsa"), 104000, CryptoStandard.CMS);
}
private void SignDocumentSigningBlockWithImage(byte[] signatureImage, PdfSignatureAppearance appearance)
{
if (signatureImage != null && signatureImage.Length > 0)
{
Image signatureImageInstance = Image.GetInstance(signatureImage);
appearance.Image = signatureImageInstance;
appearance.SignatureGraphic = signatureImageInstance;
}
}
private void SignDocumentSigningBlockWithText(PdfSignatureAppearance appearance, X509Certificate x509Certificate)
{
if (x509Certificate == null)
{
return;
}
appearance.Layer2Text = SignDocumentSigningBlockWithTextBuildText(x509Certificate);
appearance.Layer2Font = new Font(Font.FontFamily.COURIER, 7.0f, Font.NORMAL, BaseColor.LIGHT_GRAY);
appearance.Acro6Layers = true;
}
private string SignDocumentSigningBlockWithTextBuildText(X509Certificate x509Certificate)
{
Dictionary<string, string> fields = SignDocumentSigningBlockWithTextBuildTextIssuerFields(x509Certificate.IssuerDN.ToString());
string organization = fields.Keys.Contains("O") ? fields["O"] : String.Empty;
string commonName = fields.Keys.Contains("CN") ? fields["CN"] : String.Empty;
string signDate = System.DateTime.Now.ToString(_datetimeFormat);
string expirationDate = x509Certificate.NotAfter.ToString();
return "Digitally signed by " + organization + "\nSignee: " + commonName + "\nSign date: " + signDate + "\n" + "Expiration date: " + expirationDate;
}
private Dictionary<string, string> SignDocumentSigningBlockWithTextBuildTextIssuerFields(string issuer)
{
Dictionary<string, string> fields = new Dictionary<string, string>();
string[] issuerFields = issuer.Split(',');
foreach (string field in issuerFields)
{
string[] fieldSplit = field.Split('=');
string key = fieldSplit[0].Trim();
string value = fieldSplit[1].Trim();
if (!fields.Keys.Contains(key))
{
fields.Add(key, value);
}
else
{
fields[key] = value;
}
}
return fields;
}
}
PPS: The Java/iText tests have been done using the signTest_2_user2699460 unit test in ComplexSignatureFields.java which works on test-2-user2699460.pdf, an intermediary output of the C# code above.
PPPS: Meanwhile the changes resulting in the regression have been rolled back:
Returned the use of .getWidget method instead of .getMerged since the case, when signature field dictionary and dictionary its widget annotation are not merged, is rather uncommon if can be encountered at all. Moreover the use of merged dictionary instead of widget requires more efforts since .getMerged method returns not actually the dictionary obtained by merging signature field dict and widget annotation dict, but also AcroForm dict.
(DEV-1579)
Thus, the regression most likely will be resolved in version 5.5.8

AppendAllText producing invalid JSON

I have a JSON file containing:
[{
"title":"Colors",
"text":"1. White 2. Blue 3. Red 4. Yellow 5. Green"
}]
If I use
string json = JsonConvert.SerializeObject(favecolors, Formatting.Indented);
var jsonFile= Server.MapPath("~/App_Data/favecolors.json");
System.IO.File.AppendAllText(#jsonFile, ","+json);
I can append a JSON object to the file resulting in:
[{
"title":"Colors",
"text":"1. White 2. Blue 3. Red 4. Yellow 5. Green"
}],{
"title":"Colors",
"text":"1. White 2. Blue 3. Red 4. Yellow 5. Green"
}
which isn't valid JSON because the right square bracket is in the wrong place. Can anybody help?
If the format of the json of your file is going to be always the same, in that case a json array with at least one node [{},{}] you could do that
var jsonFile = Server.MapPath("~/App_Data/favecolors.json");
FileStream fs = new FileStream(#jsonFile, FileMode.Open, FileAccess.ReadWrite);
fs.SetLength(fs.Length - 1); // Remove the last symbol ']'
fs.Close();
string json = JsonConvert.SerializeObject(favecolors, Formatting.Indented);
System.IO.File.AppendAllText(#jsonFile, "," + json + "]");
It is not the most elegant solution but it should make that you want to do.
Note: this solution is very tricky, be sure that the content of your file ends with the ']' symbol, otherwise you would rather do the following: 1) Read the file into a var, 2) concat the new json (fitting it properly), 3) write the file with the merged json.
You could try something more generic, instead of adding/removing the ending bracket. Hope the following example fits your needs.
[JsonObject]
public class FavoriteColor
{
public FavoriteColor()
{}
public FavoriteColor(string title, string text)
{
Title = title;
Text = text;
}
[JsonProperty(PropertyName = "title")]
public string Title { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// Append new objects to your file
private async Task Append()
{
// Read file content and deserialize
string content = await ReadAsync("json.txt");
var colors = new List<FavoriteColor>();
if (!string.IsNullOrWhiteSpace(content))
colors.AddRange(JsonConvert.DeserializeObject<List<FavoriteColor>>(content));
// Add your new favorite color!
var fav = new FavoriteColor("new", "new color");
colors.Add(fav);
// Writo back to file
await WriteAsync("json.txt", JsonConvert.SerializeObject(colors));
}
// Async read
private async Task<string> ReadAsync(string file)
{
if (!File.Exists(file))
return null;
string content;
using (var fileStream = File.OpenRead(file))
{
byte[] buffer = new byte[fileStream.Length];
await fileStream.ReadAsync(buffer, 0, (int)fileStream.Length);
content = Encoding.UTF8.GetString(buffer);
}
return content;
}
// Async write
private async Task WriteAsync(string file, string content)
{
using (var fileStream = File.OpenWrite(file))
{
byte[] buffer = (new UTF8Encoding()).GetBytes(content);
await fileStream.WriteAsync(buffer, 0, buffer.Length);
}
}
}

iterate through and infoPath form

I have a infopath form that may contain multiple attachments: By using a group of repeating elements, the user can click an “click add item” option he will be able to upload more attachments.
In Sharepoint I am using a workflow to extract the attachments and put them in a separate list. So far I manage only to extract the first one and the workflow finishes successfully.
can I put a loop or something to iterate trough the form?
I attach the code below:
public sealed partial class FileCopyFeature : SharePointSequentialWorkflowActivity
{
public FileCopyFeature()
{
InitializeComponent();
}
public Guid workflowId = default(System.Guid);
public Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties workflowProperties = new Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties();
private void CopyFile(object sender, EventArgs e)
{
// Retrieve the file associated with the item
// on which the workflow has been instantiated
SPFile file = workflowProperties.Item.File;
if (file == null)
return;
// Get the binary data of the file
byte[] xmlFormData = null;
xmlFormData = file.OpenBinary();
// Load the data into an XPathDocument object
XPathDocument ipForm = null;
if (xmlFormData != null)
{
using (MemoryStream ms = new MemoryStream(xmlFormData))
{
ipForm = new XPathDocument(ms);
ms.Close();
}
}
if (ipForm == null)
return;
// Create an XPathNavigator object to navigate the XML
XPathNavigator ipFormNav = ipForm.CreateNavigator();
ipFormNav.MoveToFollowing(XPathNodeType.Element);
XmlNamespaceManager nsManager =
new XmlNamespaceManager(new NameTable());
foreach (KeyValuePair<string, string> ns
in ipFormNav.GetNamespacesInScope(XmlNamespaceScope.All))
{
if (ns.Key == String.Empty)
{
nsManager.AddNamespace("def", ns.Value);
}
else
{
nsManager.AddNamespace(ns.Key, ns.Value);
}
}
do
{
XPathNavigator nodeNav = ipFormNav.SelectSingleNode("//my:field2", nsManager);
// Retrieve the value of the attachment in the InfoPath form
//XPathNavigator nodeNav = ipFormNav.SelectSingleNode(
//"//my:field2", nsManager);
string ipFieldValue = string.Empty;
if (nodeNav != null)
{
ipFieldValue = nodeNav.Value;
// Decode the InfoPath file attachment
InfoPathAttachmentDecoder dec =
new InfoPathAttachmentDecoder(ipFieldValue);
string fileName = dec.Filename;
byte[] data = dec.DecodedAttachment;
// Add the file to a document library
using (SPWeb web = workflowProperties.Web)
{
SPFolder docLib = web.Folders["Doc"];
docLib.Files.Add(fileName, data);
docLib.Update();
// workflowProperties.Item.CopyTo(data + "/Doc/" + fileName);
}
}
}
while (ipFormNav.MoveToNext());
}
}
/// <summary>
/// Decodes a file attachment and saves it to a specified path.
/// </summary>
public class InfoPathAttachmentDecoder
{
private const int SP1Header_Size = 20;
private const int FIXED_HEADER = 16;
private int fileSize;
private int attachmentNameLength;
private string attachmentName;
private byte[] decodedAttachment;
/// <summary>
/// Accepts the Base64 encoded string
/// that is the attachment.
/// </summary>
public InfoPathAttachmentDecoder(string theBase64EncodedString)
{
byte[] theData = Convert.FromBase64String(theBase64EncodedString);
using (MemoryStream ms = new MemoryStream(theData))
{
BinaryReader theReader = new BinaryReader(ms);
DecodeAttachment(theReader);
}
}
private void DecodeAttachment(BinaryReader theReader)
{
//Position the reader to get the file size.
byte[] headerData = new byte[FIXED_HEADER];
headerData = theReader.ReadBytes(headerData.Length);
fileSize = (int)theReader.ReadUInt32();
attachmentNameLength = (int)theReader.ReadUInt32() * 2;
byte[] fileNameBytes = theReader.ReadBytes(attachmentNameLength);
//InfoPath uses UTF8 encoding.
Encoding enc = Encoding.Unicode;
attachmentName = enc.GetString(fileNameBytes, 0, attachmentNameLength - 2);
decodedAttachment = theReader.ReadBytes(fileSize);
}
public void SaveAttachment(string saveLocation)
{
string fullFileName = saveLocation;
if (!fullFileName.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
fullFileName += Path.DirectorySeparatorChar;
}
fullFileName += attachmentName;
if (File.Exists(fullFileName))
File.Delete(fullFileName);
FileStream fs = new FileStream(fullFileName, FileMode.CreateNew);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(decodedAttachment);
bw.Close();
fs.Close();
}
public string Filename
{
get { return attachmentName; }
}
public byte[] DecodedAttachment
{
get { return decodedAttachment; }
}
It appears that your issue has to do with your use of MoveToNext. Per the documentation, this function moves to the next sibling, and does not navigate to children elements. Your code appears to go to the first element it finds (presumably my:myFields), looks for the first child named my:field2 (it only pulls the first one since you are using SelectSingleNode, and then goes to the next sibling of my:myFields (not the next sibling of my:field2). One way to fix this might be to replace your current do-while loop with a call to SelectNodes like the following and then iterate over nodeList.
XmlNodeList nodelist = ipFormNav.SelectNodes("//my:field2", nsManager);

Categories

Resources