Removing / clearing comments in Excel with EPPlus - epplus

I am having some problems working with comments in Excel docs using EPPlus. I can add comments and remove them, however sometimes it isn't successful.
I am using the comments to display validation messages from the result of processing the Excel file, and when this file gets re-processed, I want to clear any existing comments before running the validation again.
My current method of clearing the comments is this:
_sheet.Cells.Style.Fill.PatternType = ExcelFillStyle.None;
while (_sheet.Comments.Count > 0)
{
// Note: not using _sheet.Comments.RemoveAt(0) since this can throw [Exception: Key does not exist] OfficeOpenXml.RangeCollection.Delete(UInt64 key)
_sheet.Comments.RemoveAt(_sheet.Comments.Count - 1);
}
Which seems to clear them fine. But when I try to add a comment as below:
const string author = "...";
cell.Style.Fill.PatternType = ExcelFillStyle.Solid;
cell.Style.Fill.BackgroundColor.SetColor(Color.LemonChiffon);
if (cell.Comment == null)
{
cell.AddComment(message, author);
}
else
{
cell.Comment.Text = message;
cell.Comment.Author = author;
}
where cell is an ExcelRange instance for a specific cell, I get the below stack trace:
System.NullReferenceException : Object reference not set to an instance of an object.
at OfficeOpenXml.Drawing.Vml.ExcelVmlDrawingCommentCollection.AddDrawing(ExcelRangeBase cell)
at OfficeOpenXml.Drawing.Vml.ExcelVmlDrawingCommentCollection.Add(ExcelRangeBase cell)
at OfficeOpenXml.ExcelComment..ctor(XmlNamespaceManager ns, XmlNode commentTopNode, ExcelRangeBase cell)
at OfficeOpenXml.ExcelCommentCollection.Add(ExcelRangeBase cell, String Text, String author)
at OfficeOpenXml.ExcelRangeBase.Set_Comment(Object value, Int32 row, Int32 col)
at OfficeOpenXml.ExcelRangeBase.SetSingle(_setValue valueMethod, Object value)
at OfficeOpenXml.ExcelRangeBase.AddComment(String Text, String Author)
Can anyone help me with this? What am I doing wrong?
Thanks,
James

I contacted Jan Kallman regarding this, and it turned out to be a bug with the VML drawings not being sorted, and was fixed in the patch / commit 5027f556c029 of the library.

Related

Using mailitem.PrintOut() to print a single page?

I'm working on a simple Outlook 2016/2019 VSTO plugin.
When an email is selected and a ribbon button is pressed, it needs to print just the first page of the email to the default printer. mailitem.PrintOut(); works, but will print the whole email. Is there a way to specify the first page only?
var m = e.Control.Context as Inspector;
var mailitem = m.CurrentItem as MailItem;
if (mailitem != null)
{
mailitem.PrintOut();
}
Update: See my answer for the code I used to get this working.
The Outlook object model doesn't provide any property or method for that. You need to parse the message body on your own and use .net mechanisms for printing this piece on your own.
Note, you may try using the Word object model for printing the message bodies (a specific range of pages). The Document.PrintOut method prints all or part of the specified document. Optional parameters allow specifying the page range.
The Outlook object model provides three main ways for working with item bodies:
Body - a string representing the clear-text body of the Outlook item.
HTMLBody - a string representing the HTML body of the specified item.
Word editor - the Microsoft Word Document Object Model of the message being displayed. The WordEditor property of the Inspector class returns an instance of the Document class from the Word object model which you can use to deal with the message body.
You can read more about all these ways in the Chapter 17: Working with Item Bodies.
As #Eugene said, there's no way to specify a single page using mailItem.PrintOut.
I've finally managed to find a way to do this. I save the document as a .doc file in the temp directory, then using Microsoft.Office.Interop.Word to setup the page margins / size and then send the current page to the printer. Hopefully this helps someone as I couldn't find any working examples for c#!
private void btnPrintOnePage_Click(object sender, RibbonControlEventArgs e)
{
string randFile = Path.GetTempPath() + "POP_" + RandomString(35) + ".doc";
var m = e.Control.Context as Inspector;
var mailitem = m.CurrentItem as MailItem;
if (mailitem != null)
{
mailitem.SaveAs(randFile, OlSaveAsType.olDoc);
Word.Application ap = new Word.Application();
Word.Document document = ap.Documents.Open(randFile);
document.PageSetup.PaperSize = Word.WdPaperSize.wdPaperA4;
document.PageSetup.TopMargin = 25;
document.PageSetup.RightMargin = 25;
document.PageSetup.BottomMargin = 25;
document.PageSetup.LeftMargin = 25;
Word.WdPrintOutRange printRange = Word.WdPrintOutRange.wdPrintCurrentPage;
document.PrintOut(false,null,printRange);
document.Close(false, false, false);
File.Delete(randFile);
}
}
public static string RandomString(int length)
{
Random random = new Random();
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}

Javafx Hyperlink parameters on action

Thank you for reading my question and apologies for the noobness
I am writing my first JavaFX application in which I have an array of hyperlinks which have latitude longitude (e.g. "42N 7E") in the text value of the hyperlink which is being updated every second from another Thread and updates the hyperlink text in the Main Thread. (This works fine)
public static void setPosLatLong(String posLatLong, int SID) {
Main.posLatLong[SID].setText(posLatLongValue);
}
I am trying to use the value in the hyperlink text when clicking on the hyperlink to dynamically change the destination URL with the latest latlong values... but I get the error 'local variables referenced from a lambda expression must be final or effectively final'
int SID = 'id of the hyperlink corresponding to a machine'
posLatLong[SID] = new Hyperlink();
posLatLong[SID].setOnAction((ActionEvent event) -> {
getHostServices().showDocument("http://maps.google.com/maps?z=17&q=" + posLatLong[SID].getText());
});
I have tried all kinds of ways to get around this but I am shamefully stuck. If anyone could point me in the right direction so that the last updated value in the hyperlink array is passed as a parameter when opening the browser it would be greatly appreciated.
I think I managed to find a solution myself so I'll post it in case it could be useful to someone
posLatLong[i].setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
String eventLatLong = "";
Object source = event.getSource();
if (source instanceof Hyperlink) {
Hyperlink link = (Hyperlink) source;
eventLatLong = link.getText();
}
getHostServices().showDocument("http://maps.google.com/maps?z=17&q=" + eventLatLong );
}
});
Tada !

SharePoint 2010: Error Mapping to Picture Hyperlink with SPMetal

Whenever I have a column of type hyperlink with the format set for pictures, I get an error whenever there is actually a value in that column.
The exception it throws is "Specified cast is not valid".
My thought is that the problem is either here (the FieldType being set to Url):
[Microsoft.SharePoint.Linq.ColumnAttribute(Name = "FOO", Storage = "FOO_", FieldType = "Url")]
public string FOO
{
get
{
return this._FOO;
}
set
{
if ((value != this._FOO))
{
this.OnPropertyChanging("FOO", this._FOO);
this._FOO = value;
this.OnPropertyChanged("FOO");
}
}
}
Or here (it being cast to a string):
private string _FOO;
But I'd have no idea what the proper values for either of those fields should be.
Any help would be greatly appreciated.
It works whenever this field does not have data in it and I JUST used SPMetal to generate the class, so I'll get the two most obvious questions out of the way.
Link to the answer:
https://mgreasly.wordpress.com/2012/06/25/spmetal-and-workflow-associations/
Turns out it is a known bug when mapping lists that have associated workflows.
SPMetal assigns it as a nullable integer when it's supposed to be an Object, hence the cast error.
Workaround: manually edit the mappings to make the type it returns an object or ignore the column by using a parameter map.

iTextSharp: Convert PdfObject to PdfStream

I am attempting to pull some font streams out of a pdf file (legality is not an issue, as my company has paid for the rights to display these documents in their original manner - and this requires a conversion which requires the extraction of the fonts).
Now, I had been using MUTool - but it also extracts the images in the pdf as well with no method for bypassing them and some of these contain 10s of thousands of images. So, I took to the web for answers and have come to the following solution:
I get all of the fonts into a font dictionary and then I attempt to convert them into PdfStreams (for flatedecode and then writing to files) using the following code:
PdfDictionary tg = (PdfDictionary)PdfReader.GetPdfObject((PdfObject)cItem.pObj);
PdfName type = (PdfName)PdfReader.GetPdfObject(tg.Get(PdfName.SUBTYPE));
try
{
int xrefIdx = ((PRIndirectReference)((PdfObject)cItem.pObj)).Number;
PdfObject pdfObj = (PdfObject)reader.GetPdfObject(xrefIdx);
PdfStream str = (PdfStream)(pdfObj);
byte[] bytes = PdfReader.GetStreamBytesRaw((PRStream)str);
}
catch { }
But, when I get to PdfStream str = (PdfStream)(pdfObj); I get the error below:
Unable to cast object of type 'iTextSharp.text.pdf.PdfDictionary'
to type 'iTextSharp.text.pdf.PdfStream'.
Now, I know that PdfDictionary derives from (extends) PdfObject so I am uncertain as to what I am doing incorrectly here. Someone please help - I either need advice on patching this code, or if entirely incorrect, either code to extract the stream properly or direction to a place with said code.
Thank you.
EDIT
My revised code is here:
public static void GetStreams(PdfReader pdf)
{
int page_count = pdf.NumberOfPages;
for (int i = 1; i <= page_count; i++)
{
PdfDictionary pg = pdf.GetPageN(i);
PdfDictionary fObj = (PdfDictionary)PdfReader.GetPdfObject(res.Get(PdfName.FONT));
if (fObj != null)
{
foreach (PdfName name in fObj.Keys)
{
PdfObject obj = fObj.Get(name);
if (obj.IsIndirect())
{
PdfDictionary tg = (PdfDictionary)PdfReader.GetPdfObject(obj);
PdfName type = (PdfName)PdfReader.GetPdfObject(tg.Get(PdfName.SUBTYPE));
int xrefIdx = ((PRIndirectReference)obj).Number;
PdfObject pdfObj = pdf.GetPdfObject(xrefIdx);
if (pdfObj == null && pdfObj.IsStream())
{
PdfStream str = (PdfStream)(pdfObj);
byte[] bytes = PdfReader.GetStreamBytesRaw((PRStream)str);
}
}
}
}
}
}
However, I am still receiving the same error - so I am assuming that this is an incorrect method of retrieving font streams. The same document has had fonts extracted using muTool successfully - so I know the problem is me and not the pdf.
There are at least two things wrong in your code:
You cast an object to a stream without performing this check: if (pdfObj == null && pdfObj.isStream()) { // cast to stream } As you get the error message that you're trying to cast a dictionary to a stream, I'm 99% sure that the second part of the check will return false whereas pdfObj.isDictionary() probably returns true.
You try extracting a stream from PdfReader and you're trying to cast that object to a PdfStream instead of to a PRStream. PdfStream is the object we use to create PDFs, PRStream is the object used when we inspect PDFs using PdfReader.
You should fix this problem first.
Now for your general question. If you read ISO-32000-1, you'll discover that a font is defined using a font dictionary. If the font is embedded (fully or partly), the font dictionary will refer to a stream. This stream can contain the full font information, but most of the times, you'll only get a subset of the glyphs (because that's best practice when creating a PDF).
Take a look at the example ListFontFiles from my book "iText in Action" to get a first impression of how fonts are organized inside a PDF. You'll need to combine this example with ISO-32000-1 to find more info about the difference between FONTFILE, FONTFILE2 and FONTFILE3.
I've also written an example that replaces an unembedded font with a font file: EmbedFontPostFacto. This example serves as an introduction to explain how difficult font replacement is.
Please go to http://tinyurl.com/iiacsCH16 if you need the C# version of the book samples.

Creating new smartform data using Ektron ContentTypes

Ektron 8.0.1 SP1
I am using SmartForms and Content Types to read (and hopefully write) data. I can read data but now I am attempting to write a new record similar to the following.
ContentTypeManager<member> contentTypeManager = new ContentTypeManager<member>();
ContentType<member> newmem = new ContentType<member>();
newmem.SmartForm.details.field1 = "Chuck"; // This line throws 'Object reference not set to an instance of an object.' error
newmem.SmartForm.details.field2 = "Norris";
contentTypeManager.Update(newmem);
I get the error "Object reference not set to an instance of an object." for that first assignment line. What am I missing?
I am having trouble finding good documentation on ContentTypes for 8.0.1 now that the Ektron website has been redesigned.
Thx.
Thanks for clarifying, to ADD content to a folder that has a smartform assigned to it, the basic code block should get you started: (Note: the Html attribute of the content is simply the xml matched to the schema you created)
Ektron.Cms.Framework.Content.ContentManager cmanager = new Cms.Framework.Content.ContentManager();
Ektron.Cms.ContentData cdata = new ContentData();
cdata.FolderId = 0;
cdata.XmlConfiguration.Id = 0; //SMARTFORM ID HERE
cdata.Html = "<root><field1>field1 value</field1><field2>field2 value</field2></root>";
cmanager.Add(cdata);
You could update ContentTypes.cs to include an Add method. Just copy the Update method and change contentManager.Update to contentManager.Add.
public void Add(ContentType<T> contentType)
{
Initialize();
contentType.Content.Html = Ektron.Cms.EkXml.Serialize(typeof(T), contentType.SmartForm);
contentManager.Add(contentType.Content);
}
Unfortunately, contentManager.Add returns void. Ideally it should return the new content ID.