ExcelSoftArtisans - Bug on Save method with open Stream - Cannot accessed closed file - officewriter

I have the following issue:
I’m doing an export of an ASP.Net GridView directly to an excel file.
I’m setting an image as a header in this method:
private static void insertPageHeaderFooter(ExcelInterfaceSoftArtisans excel,DateTime generatedDateTime)
{
StringBuilder builderFooterLeft = new StringBuilder();
builderFooterLeft.Append("&08Comfone AG Tel: +41 31 341 10 10");
builderFooterLeft.Append("\r");
builderFooterLeft.Append("&08Nussbaumstrasse 25 Fax: +41 31 341 10 11");
builderFooterLeft.Append("\r");
builderFooterLeft.Append("&08CH-3000 Bern 22 www.comfone.com");
StringBuilder builderFooterRight = new StringBuilder();
String sDateTime = generatedDateTime.ToString(CultureInfoHandler.ShortDateShortTimeFormat);
builderFooterRight.Append("&08&F");
builderFooterRight.Append("\r");
builderFooterRight.Append(string.Format("&08 {0}", sDateTime));
builderFooterRight.Append("\r"); //new line
builderFooterRight.Append("&08Page &P of &N");
excel.SetHeader("&G", 0.6, HeaderFooterSection.Section.Left, false);
excel.SetFooter(builderFooterLeft.ToString(), HeaderFooterSection.Section.Left, false);
excel.SetFooter(builderFooterRight.ToString(), HeaderFooterSection.Section.Right, false);
}
protected void SetHeader(string sText, double dImageSizeFactor, HeaderFooterSection.Section section, Worksheet sheet)
{
string headerAbsolutePath = HttpContext.Current.Server.MapPath("~/Resources/Mandates/CHECF.png");
Stream imageStream = new FileStream(headerAbsolutePath, FileMode.Open, FileAccess.Read);
Size imageSize = Image.FromStream(imageStream).Size;
imageStream = new FileStream(headerAbsolutePath, FileMode.Open, FileAccess.Read);
HeaderFooterSection header = sheet.PageSetup.GetHeader(section);
header.SetContent(sText, imageStream);
header.SetContent(sText);
header.GetPicture().Height = (int)(imageSize.Height * dImageSizeFactor);
header.GetPicture().Width = (int)(imageSize.Width * dImageSizeFactor);
imageStream.Close();
}
As you can see on the last line, I close the Stream.
Now, I want to save my excel file this way:
HttpContext.Current.Response.ClearContent();
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ClearHeaders();
excel.SaveWorkbook(sFileName, HttpContext.Current.Response, false);
/// <summary>
/// Saves the current Workbook under the given filename
/// </summary>
/// <param name="filename"></param>
/// <param name="response"></param>
/// <param name="openInBrowser"></param>
public void SaveWorkbook(string filename, HttpResponse response, bool openInBrowser)
{
if (book != null)
{
application.Save(book, response, filename, openInBrowser);
}
}
but when I close the stream in the SetHeader method, I get the following error:
Error Message: Cannot access a closed file.
Stack Trace: at System.IO.__Error.FileNotOpen()
at System.IO.FileStream.Seek(Int64 offset, SeekOrigin origin)
and when I don’t close the stream in the SetHeader method, the file is correctly saved.
Are you aware of this bug? How is it possible that I need to have an open stream in order to save an excel file? What can I do to fix that?
I’m attaching you the whole classes I’m using, so you can identify the problem better.
Thanks for your quick answer and solution on this problem.

[Disclaimer: I am the product owner of OfficeWriter]
This issue has been confirmed as a bug and has been submitted to the OfficeWriter development team.
In the meantime, I would recommend Sam's suggested workaround of using the overload that takes the image's file path instead of the image stream.
Here is a generic code snippet for how to insert an image into the header of an Excel file using the file path overload of HeaderFooterSection.SetContent():
//Open the workbook and get a handle on the worksheet that will contain the
//image in the header
ExcelApplication xla = new ExcelApplication();
Workbook wb = xla.Open(Page.MapPath("\\template.xlsx"));
Worksheet ws = wb.Worksheets["Sheet1"];
//Set the header
HeaderFooterSection header = ws.PageSetup.GetHeader(HeaderFooterSection.Section.Left);
header.SetContent("&G", Page.MapPath("\\images\\image1.png"));
Please see our documentation for additional reference on the SetContent() overloads and using ExcelWriter to format headers and footers in workbooks.

This issue has been addressed in the recent 8.5.1 release of OfficeWriter. See the change log.

Related

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?

Signature defined. Must be closed in PdfSignatureAppearance

I am getting the below error when signing a pdf. The error is
“Signature defined. Must be closed in PdfSignatureAppearance.”
I am able to sign the pdf for the first time. It creates a pdf file in output folder with the signature in the first page. So far the code works fine.
Now when I give the recently generated file as input to sign in a second page I get the error “Signature defined. Must be closed in PdfSignatureAppearance.”
I am getting the error in the below line
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(300, 40, 530, 120), pageNo, "Icsi-Vendor");
Please find the code below
if (File.Exists(fName))
{
PdfReader.unethicalreading = true;
using (PdfReader pdfReader = new PdfReader(fName))
{
//file name
fName = fName.Substring(fName.LastIndexOf("\\") + 1);
outputFile = outputFolder + fName + ".pdf";
if (!File.Exists(outputFile))
{
using (FileStream fout = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite))
{
using (PdfStamper stamper = PdfStamper.CreateSignature(pdfReader, fout, '\0'))
{
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
string imagePath = txtImage.Text;
iTextSharp.text.Image signatureFieldImage = iTextSharp.text.Image.GetInstance(imagePath);
appearance.SignatureGraphic = signatureFieldImage;
signatureFieldImage.SetAbsolutePosition(250, 50);
stamper.GetOverContent(pageNo).AddImage(signatureFieldImage);
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(300, 40, 530, 120), pageNo, "Icsi-Vendor");
appearance.Reason = txtReason.Text;
IExternalSignature es = new PrivateKeySignature(pk, "SHA-256");
MakeSignature.SignDetached(appearance, es, new X509Certificate[] { pk12.GetCertificate(alias).Certificate }, null, null, null, 0, CryptoStandard.CMS);
stamper.Close();
}
}
}
}
this.Invoke(new BarDelegate(UpdateBar), fName);
}
Can some one help me please and let me know in case more details are required.
There are multiple issues in the OP's code:
The correct Close call
When applying signatures, one must not close the stamper object itself but instead the signature appearance object. And if one uses helper methods like MakeSignature.SignDetached, one does not even have to code that closing because SignDetached implicitly already closes the appearance in its last line.
Thus, please
remove stamper.Close() and
don't put PdfStamper stamper = PdfStamper.CreateSignature(pdfReader, fout, '\0') into a using directive as this causes a call of the stamper's Dispose method which in turn calls Close.
Usually you are not hurt by those lines because after the implicit appearance close in MakeSignature.SignDetached, further close calls are ignored.
If you don't get that far, though, e.g. due to some error situation before, such close calls cause the error you observe, in your case the close call caused by the using directive.
The issue in SetVisibleSignature
You are getting the error in
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(300, 40, 530, 120), pageNo, "Icsi-Vendor");
Unfortunately the actual error occurring in this line is replaced by the error caused by the Close call during the Dispose call due to the using directive.
Considering the message code:
/**
* Sets the signature to be visible. It creates a new visible signature field.
* #param pageRect the position and dimension of the field in the page
* #param page the page to place the field. The fist page is 1
* #param fieldName the field name or <CODE>null</CODE> to generate automatically a new field name
*/
virtual public void SetVisibleSignature(Rectangle pageRect, int page, String fieldName) {
if (fieldName != null) {
if (fieldName.IndexOf('.') >= 0)
throw new ArgumentException(MessageLocalization.GetComposedMessage("field.names.cannot.contain.a.dot"));
AcroFields af = writer.GetAcroFields();
AcroFields.Item item = af.GetFieldItem(fieldName);
if (item != null)
throw new ArgumentException(MessageLocalization.GetComposedMessage("the.field.1.already.exists", fieldName));
this.fieldName = fieldName;
}
if (page < 1 || page > writer.reader.NumberOfPages)
throw new ArgumentException(MessageLocalization.GetComposedMessage("invalid.page.number.1", page));
this.pageRect = new Rectangle(pageRect);
this.pageRect.Normalize();
rect = new Rectangle(this.pageRect.Width, this.pageRect.Height);
this.page = page;
}
the obvious causes would be
the field name containing a dot,
the named field already existing in the document, or
an invalid page number.
As you describe your situation as
I am able to sign the pdf for the first time. It creates a pdf file in output folder with the signature in the first page. So far the code works fine. Now when I give the recently generated file as input to sign in a second page I get the error
I assume the second item to be the most probable cause: If you want to add multiple signatures to the same document, their field names must differ.
Append mode
As you indicate that you apply multiple signatures to the same file, you must use the append mode. If you don't, you'll invalidate the earlier signatures:
PdfStamper stamper = PdfStamper.CreateSignature(pdfReader, fout, '\0', true);
Cf. that CreateSignature method overload comment
/**
* Applies a digital signature to a document, possibly as a new revision, making
* possible multiple signatures. The returned PdfStamper
* can be used normally as the signature is only applied when closing.
* <p>
... (outdated Java example code) ...
* #param reader the original document
* #param os the output stream or <CODE>null</CODE> to keep the document in the temporary file
* #param pdfVersion the new pdf version or '\0' to keep the same version as the original
* document
* #param tempFile location of the temporary file. If it's a directory a temporary file will be created there.
* If it's a file it will be used directly. The file will be deleted on exit unless <CODE>os</CODE> is null.
* In that case the document can be retrieved directly from the temporary file. If it's <CODE>null</CODE>
* no temporary file will be created and memory will be used
* #param append if <CODE>true</CODE> the signature and all the other content will be added as a
* new revision thus not invalidating existing signatures
* #return a <CODE>PdfStamper</CODE>
* #throws DocumentException on error
* #throws IOException on error
*/
public static PdfStamper CreateSignature(PdfReader reader, Stream os, char pdfVersion, string tempFile, bool append)

Upload Servlet with custom file keys

I have built a Server that you can upload files to and download, using Eclipse, servlet and jsp, it's all very new to me. (more info).
Currently the upload system works with the file's name. I want to programmatically assign each file a random key. And with that key the user can download the file. That means saving the data in a config file or something like : test.txt(file) fdjrke432(filekey). And when the user inputs the filekey the servlet will pass the file for download.
I have tried using a random string generator and renameTo(), for this. But it doesn't work the first time, only when I upload the same file again does it work. And this system is flawed, the user will receive the file "fdjrke432" instead of test.txt, their content is the same but you can see the problem.
Any thoughts, suggestions or solutions for my problem?
Well Sebek, I'm glad you asked!! This is quite an interesting one, there is no MAGIC way to do this. The answer is indeed to rename the file you uploaded. But I suggest adding the random string before the name of the file; like : fdjrke432test.txt.
Try this:
filekey= RenameRandom();
File renamedUploadFile = new File(uploadFolder + File.separator+ filekey+ fileName);
item.write(renamedUploadFile);
//remember to give the user the filekey
with
public String RenameRandom()
{
final int LENGTH = 8;
StringBuffer sb = new StringBuffer();
for (int x = 0; x < LENGTH; x++)
{
sb.append((char)((int)(Math.random()*26)+97));
}
System.out.println(sb.toString());
return sb.toString();
}
To delete or download the file from the server you will need to locate it, the user will input the key, you just need to search the upload folder for a file that begins with that key:
filekey= request.getParameter("filekey");
File f = new File(getServletContext().getRealPath("") + File.separator+"data");
File[] matchingFiles = f.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(filekey);
}
});
String newfilename = matchingFiles[0].getName();
// now delete or download newfilename

jasper reports with HTML Format

Am using jasper reports library with GWT application.
The reports is generated well with CSV format but with HTML format it generate the HTML page with icons of missing picture.
I know that jasper using transparent image called "PX", this image not found.
How can i solve this problem?
Thanks in Advance
If you don't have images to show then you can do this:
JasperPrint jasperPrint = JasperFillManager.fillReport(path, parameters, con);
JRHtmlExporter htmlExporter = new JRHtmlExporter();
response.setContentType("text/html");
request.getSession().setAttribute(ImageServlet.DEFAULT_JASPER_PRINT_SESSION_ATTRIBUTE, jasperPrint);
htmlExporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
htmlExporter.setParameter(JRExporterParameter.OUTPUT_WRITER, out);
htmlExporter.setParameter(JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN, false);
htmlExporter.exportReport();
The important line is this one:
htmlExporter.setParameter(JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN, false);
That will make all the "px" images disappear.
Try passing in your image as a parameter to the report so you won't have to worry about image paths.
You can set the type of the parameter as a BufferedImage or whatever image class suits.
My solution was to use data URIs. This isn't very elegant since it bloats the size of the HTML and doesn't work in IE prior to IE8, but it does allow you to not bother worrying about creating files out of the image attachments Jasper sends you either.
If you're going to implement this, you want to add this argument to your request:
<argument name="IMAGES_URI"><![CDATA[data:]]></argument>
Then you need to parse the report HTML that JasperServer sends back:
foreach ($attachments as $name => $attachment) {
// Cut off the cid: portion of the name.
$name = substr($name, 4);
// Replace any image URIs with a data: uri.
if (strtolower(substr($name, 0, 4)) !== 'uuid' && strtolower($name) !== 'report') {
if (strtoupper(substr($attachment, 0, 3)) === 'GIF') {
// It's a GIF.
$report = str_replace("data:$name", 'data:image/gif;base64,' . base64_encode($attachment), $report);
} elseif (/* more file type tests */) {
// and so on...
}
}
}
For large images, it's best to do as Gordon suggested and pass in a parameter specifying the URL of a file that is permanently stored on the server. This method is more of a failsafe for gracefully handling any unexpected images JasperServer tries throwing at you.
I'm a bit late to this discussion but this is what I've been using. The key is to pass the imagesMap to both the session attribute and exporter parameter, and to set the IMAGES_URI exporter parameter.
private void exportReportAsHtml(HttpServletRequest request, HttpServletResponse response, JasperPrint jasperPrint) throws IOException, JRException {
response.setContentType("text/html");
request.getSession().setAttribute(ImageServlet.DEFAULT_JASPER_PRINT_SESSION_ATTRIBUTE, jasperPrint);
Map imagesMap = new HashMap();
request.getSession().setAttribute("IMAGES_MAP", imagesMap);
JRHtmlExporter exporter = new JRHtmlExporter();
exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
exporter.setParameter(JRExporterParameter.OUTPUT_WRITER, response.getWriter());
exporter.setParameter(JRHtmlExporterParameter.IMAGES_MAP, imagesMap);
exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI, "image?image=");
exporter.exportReport();
}

Sending an email attachment in memory using OpenXML

I've got an Excel file that's built using OpenXML 2 and I want to send it as an email attachment. e.g.
System.IO.MemoryStream stream = new System.IO.MemoryStream();
SpreadsheetDocument package = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook))
AddParts(package); //created using document reflector
Saving the spreadsheet to a temp file using
stream.WriteTo(new System.IO.FileStream(#"c:\test.xlsx", System.IO.FileMode.Create));
works fine. But trying to send the stream directly as an email attachment fails - just get an empty file attached to the email when I do
System.Net.Mail.Attachment file = new System.Net.Mail.Attachment(stream, "MobileBill.xlsx", "application/vnd.ms-excel");
Anbody know how to do this?
Ok, I got this working, though through some effort. To create the stream:
MemoryStream stream = new MemoryStream();
using (SpreadsheetDocument package = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook))
{
Excel.CreateSpreadsheet(package, Excel_Methods.CreateSpotQuoteOut(), true);
}
stream.Seek(0, SeekOrigin.Begin);
System.Net.Mail.Attachment attach = new System.Net.Mail.Attachment(stream, "spreadsheet.xlsx");
attach.ContentDisposition.CreationDate = DateTime.Now;
attach.ContentDisposition.ModificationDate = DateTime.Now;
attach.ContentDisposition.Inline = false;
attach.ContentDisposition.Size = stream.Length;
attach.ContentType.MediaType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Also, I found that mine were not being sent right after I created them, and the reason for that is "standalone=yes" was not being added to the xml declaration of all the pages, so in my AddParts function, after adding the parts, I passed them into this function:
private static void AddXMLStandalone(OpenXmlPart part)
{
System.IO.StreamWriter writer = new System.IO.StreamWriter(part.GetStream());
XmlDocument doc = new XmlDocument();
doc.Load(part.GetStream());
doc.InnerXml = doc.InnerXml.Substring(doc.InnerXml.IndexOf("?>") + 2);
doc.InnerXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" + doc.InnerXml;
part.GetStream().SetLength(doc.InnerXml.Length);
doc.Save(writer);
writer.Flush();
writer.Close();
}
Good luck!
do this:
System.Net.Mail.Attachment file = new System.Net.Mail.Attachment(new MemoryStream(stream.ToArray()), "MobileBill.xlsx", "application/vnd.ms-excel");
Apparently the memory stream doesn't get flushed or something
For your "content unreadable" problem, make sure to Save() your Workbooks and Worksheets and enclose your SpreadsheetDocument in a using statement to ensure all packages and zipped streams are flushed, closed and so on.
System.IO.MemoryStream stream = new System.IO.MemoryStream();
using (SpreadsheetDocument package = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook)))
{
AddParts(package);
//Save if AddParts hasn't done it
}
System.Net.Mail.Attachment file = ...
Thinking out load: could it be, that the Attachment class expects to read from the current possition in the provided stream? If this is the case, you would probably have to "seek" back to the beginning of the stream, before feeding it to the Attachment constructor:
AddParts(package); //created using document reflector
stream.Seek(0, SeekOrigin.Begin);
System.Net.Mail.Attachment file = new System.Net.Mail.Attachment(stream, "MobileBill.xlsx", "application/vnd.ms-excel");