Change Sheet tab color of excel file using Open XML - openxml

I want to change the sheet tab color of an excel Xlsx document. I am using the following code but it does not set the sheet color. I get object reference exception when I set the sheet tab color.
public static string filepath = #"C:\Test\Book1.xlsx";
private static void ChangeSheetcolor()
{
try
{
using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(filepath, false))
{
WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart;
IEnumerable<Sheet> sheets = spreadSheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>();
//my code
WorksheetPart worksheetPart =
GetWorksheetPartByName(spreadSheetDocument, "Sheet1");
if (worksheetPart != null)
{
// worksheetPart.Worksheet.SheetProperties.TabColor.Rgb = DocumentFormat.OpenXml.HexBinaryValue.FromString("Red");
worksheetPart.Worksheet.SheetProperties.TabColor.Rgb = DocumentFormat.OpenXml.HexBinaryValue.FromString("#CCCCCC");
// Save the worksheet.
worksheetPart.Worksheet.Save();
}
}
}
catch (Exception ex)
{
}
}
private static WorksheetPart
GetWorksheetPartByName(SpreadsheetDocument document,
string sheetName)
{
IEnumerable<Sheet> sheets =
document.WorkbookPart.Workbook.GetFirstChild<Sheets>().
Elements<Sheet>().Where(s => s.Name == sheetName);
if (sheets.Count() == 0)
{
//does not exist
return null;
}
string relationshipId = sheets.First().Id.Value;
WorksheetPart worksheetPart = (WorksheetPart)
document.WorkbookPart.GetPartById(relationshipId);
return worksheetPart;
}
How to change the sheet tab color using Open XML dlls?

You seem to have 3 issues as far as I can tell. Firstly, the second parameter to the Open method you are calling denotes whether or not the file is editable. You need to pass true here if you wish to edit the file otherwise you'll hit the following exception upon saving the worksheet:
System.IO.IOException: Cannot get stream with FileMode.Create, FileMode.CreateNew, FileMode.Truncate, FileMode.Append when access is FileAccess.Read.
Secondly, SheetProperties or TabColor could be null. In the XSD they are defined as
<xsd:element name="sheetPr" type="CT_SheetPr" minOccurs="0" maxOccurs="1"/>
and
<xsd:element name="tabColor" type="CT_Color" minOccurs="0" maxOccurs="1"/>
Note both have minOccurs="0" so either could be null. If they are, you can just create new instances and assign them to the Worksheet.
Finally, the Rgb property is arguably badly named as the documentation states (emphasis mine):
Standard Alpha Red Green Blue color value (ARGB).
The possible values for this attribute are defined by the ST_UnsignedIntHex simple type
(ยง18.18.86).
18.18.86 goes on to say (again, emphasis mine):
This simple type's contents have a length of exactly 8 hexadecimal digit(s).
Assuming you want a solid color you can set the alpha channel to FF. You also shouldn't include the leading # so for a solid red for example you should use "FFFF0000". More details on ARGB values can be found on this question.
Putting that all together your code becomes something like this:
public static string filepath = #"C:\Test\Book1.xlsx";
private static void ChangeSheetcolor()
{
try
{
using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(filepath, false))
{
WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart;
IEnumerable<Sheet> sheets = spreadSheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>();
//my code
WorksheetPart worksheetPart =
GetWorksheetPartByName(spreadSheetDocument, "Sheet1");
if (worksheetPart != null)
{
//create the SheetProperties if it doesn't exist
if (worksheetPart.Worksheet.SheetProperties == null)
worksheetPart.Worksheet.SheetProperties = new SheetProperties();
//create the TabColor if it doesn't exist
if (worksheetPart.Worksheet.SheetProperties.TabColor == null)
worksheetPart.Worksheet.SheetProperties.TabColor = new TabColor();
//this will set the tab color to Red
//note the value is an ARGB not an RGB
worksheetPart.Worksheet.SheetProperties.TabColor.Rgb = DocumentFormat.OpenXml.HexBinaryValue.FromString("FFFF0000");
// Save the worksheet.
worksheetPart.Worksheet.Save();
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}

Related

Extracting the values of elements (XML) which is in an array and put it in the message property in Camel

As you can see the Availability Flag, BeamId is getting repeated. How do I traverse and set the property for Availability Flag1 and so on, so that I can later fetch it with velocity template?
Payload:<ns2:TransportFeasibilityResponse>
<ns2:Parameters>
<ns2:AvailabilityFlag>true</ns2:AvailabilityFlag>
<ns2:SatellitedID>H1B</ns2:SatellitedID>
<ns2:BeamID>675</ns2:BeamID>
<ns2:TransportName>Earth</ns2:TransportName>
</ns2:FeasibilityParameters>
<ns2:Parameters>
<ns2:AvailabilityFlag>true</ns2:AvailabilityFlag>
<ns2:SatellitedID>J34</ns2:SatellitedID>
<ns2:BeamID>111</ns2:BeamID>
<ns2:TransportName>Jupiter</ns2:TransportName>
</ns2:Parameters>
</ns2:TransportFeasibilityResponse>
</ns2:TransportFeasibilityResponseMsg>
Code: (Its not complete)
public static HashMap<String,String> extractNameValueToProperties(String msgBody, selectedKeyList, namelist) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setExpandEntityReferences(false);
factory.setNamespaceAware(true);
Document doc = null;
try{
DocumentBuilder builder = factory.newDocumentBuilder();
doc = builder.parse(new InputSource(new StringReader(msgBody)));
} catch(Exception ex) {
Exception actException = new Exception( "Exception while extracting tagvalues", ex);
throw actException;
}
HashMap<String,String> tagNameValueMap = new HashMap<String,String>();
NodeList nodeList = doc.getElementsByTagName("*");
// Trying to enter the TransportFeasibilityResponse element
for (int i = 0; i < nodeList.getLength(); i++) {
Node indNode = nodeList.item(i);
if (indNode.indexOf(String name)>-1);
//checking for Availability flag and similar namelist
dataKey = indNode.getTextContent();
message.setProperty(selectedKeyList[k], dataKey);
k++;
j++;
else
{
continue;
}
}
}
Here,
I am setting these values in my route:
<setProperty propertyName="namelist">
<constant>AvailabilityFlag,SatellitedID,BeamID</constant>
</setProperty>
<setProperty propertyName="selectedKeyList">
<constant>AvailabilityFlag1,SatellitedID1,BeamID1,AvailabilityFlag2,SatellitedID2,BeamID2 </constant>
</setProperty>
<bean beanType="com.gdg.dgdgdg.javacodename" method="extractNameValueToProperties"/>
Question: Please tell me how I can parse through the repeating elements and assign it to the property?
Thanks
I'm not sure if I understand your question correctly, but I think you could use the Splitter pattern to split your xml per Parameters tag and process each other separately and aggregate it later.
Take for example this input:
<TransportFeasibilityResponse>
<Parameters>
<AvailabilityFlag>true</AvailabilityFlag>
<SatellitedID>H1B</SatellitedID>
<BeamID>675</BeamID>
<TransportName>Earth</TransportName>
</Parameters>
<Parameters>
<AvailabilityFlag>true</AvailabilityFlag>
<SatellitedID>J34</SatellitedID>
<BeamID>111</BeamID>
<TransportName>Jupiter</TransportName>
</Parameters>
</TransportFeasibilityResponse>
A route to process this input could be something like this:
from("direct:start")
.split(xpath("/TransportFeasibilityResponse/Parameters"), new AggregationStrategy() {
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
List<String> beamIDs = null;
if (oldExchange == null) { // first
beamIDs = new ArrayList<String>();
} else {
beamIDs = oldExchange.getIn().getBody(List.class);
}
beamIDs.add(newExchange.getIn().getBody(String.class));
newExchange.getIn().setBody(beamIDs);
return newExchange;
}
})
.setBody(xpath("/Parameters/BeamID/text()"))
.end()
.log("The final body: ${body}");
First, we split the input per Parameters tag, and then extract the BeamID from it. After that, the AggregationStrategy aggregates each message into one, grouping by BeamID.
The final message should have the a body like this:
675,111
The data I put in the body just for an example, but you could set anywhere you want into the Exchange you are manipulating inside the AggregationStrategy implementation.

LibTiff.net isn't setting the Subject tag

I'm trying to mimic some production code to generate Tiffs with a subject for testing purposes (IE in windows, right click, go to properties and the details tab there is a subject). We place some text we need to reference later in the subject field. The field we use is 0x9c9f which as far as I can find is (Subject tag used by Windows, encoded in UCS2)
Here's the code I'm using to generate the tag
public static void TagExtender(Tiff tif)
{
TiffFieldInfo[] tiffFieldInfo =
{
new TiffFieldInfo(TIFFTAG_SUBJECT, 256, 256, TiffType.BYTE, FieldBit.Custom, true, false, "XPSubject"),
};
tif.MergeFieldInfo(tiffFieldInfo, tiffFieldInfo.Length);
//if (m_parentExtender != null)
// m_parentExtender(tif);
}
public static void GenerateTiff(string filename, int pages = 1, bool encrypt = false, string tag = null)
{
// Register the custom tag handler
if (m_parentExtender == null)
{
Tiff.TiffExtendProc extender = TagExtender;
m_parentExtender = Tiff.SetTagExtender(extender);
}
// Open the output image
using (Tiff output = Tiff.Open(filename, "w"))
{
//...other code to generate tiff
if (tag != null)
{
byte[] bytes = UnicodeStr2HexStr(tag);
output.SetField(TIFFTAG_SUBJECT, bytes.Length-1, bytes);
}
// Code to actually write the image ....
output.WriteDirectory();
}
output.Close();
}
Basically, the tag (code wise) appears to be in the tiff but the windows properties dialog never shows it. Anything special needed to get this in place?
You are passing a bytecount, but set the passCount flag to false.
If you want to pass the count, use these lines at their correct positions:
// Your FieldInfo
new TiffFieldInfo((TiffTag)40095, -1, -1, TiffType.BYTE, FieldBit.Custom, true, true, "XPSubject")
// Your Input
byte[] test = Encoding.Unicode.GetBytes("Test3");
tiff.SetField((TiffTag)40095, test.Length, test);

Remove Content controls after adding text using open xml

By the help of some very kind community members here I managed to programatically create a function to replace text inside content controls in a Word document using open xml. After the document is generated it removes the formatting of the text after I replace the text.
Any ideas on how I can still keep the formatting in word and remove the content control tags ?
This is my code:
using (var wordDoc = WordprocessingDocument.Open(mem, true))
{
var mainPart = wordDoc.MainDocumentPart;
ReplaceTags(mainPart, "FirstName", _firstName);
ReplaceTags(mainPart, "LastName", _lastName);
ReplaceTags(mainPart, "WorkPhoe", _workPhone);
ReplaceTags(mainPart, "JobTitle", _jobTitle);
mainPart.Document.Save();
SaveFile(mem);
}
private static void ReplaceTags(MainDocumentPart mainPart, string tagName, string tagValue)
{
//grab all the tag fields
IEnumerable<SdtBlock> tagFields = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == tagName);
foreach (var field in tagFields)
{
//remove all paragraphs from the content block
field.SdtContentBlock.RemoveAllChildren<Paragraph>();
//create a new paragraph containing a run and a text element
Paragraph newParagraph = new Paragraph();
Run newRun = new Run();
Text newText = new Text(tagValue);
newRun.Append(newText);
newParagraph.Append(newRun);
//add the new paragraph to the content block
field.SdtContentBlock.Append(newParagraph);
}
}
Keeping the style is a tricky problem as there could be more than one style applied to the text you are trying to replace. What should you do in that scenario?
Assuming a simple case of one style (but potentially over many Paragraphs, Runs and Texts) you could keep the first Text element you come across per SdtBlock and place your required value in that element then delete any further Text elements from the SdtBlock. The formatting from the first Text element will then be maintained. Obviously you can apply this theory to any of the Text blocks; you don't have to necessarily use the first. The following code should show what I mean:
private static void ReplaceTags(MainDocumentPart mainPart, string tagName, string tagValue)
{
IEnumerable<SdtBlock> tagFields = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == tagName);
foreach (var field in tagFields)
{
IEnumerable<Text> texts = field.SdtContentBlock.Descendants<Text>();
for (int i = 0; i < texts.Count(); i++)
{
Text text = texts.ElementAt(i);
if (i == 0)
{
text.Text = tagValue;
}
else
{
text.Remove();
}
}
}
}

How to UnProtect Sheet using OpenXML code?

I have created one Sheet, which i could protect using OpenXml code.
But now there is requirement to read this excel file.
I am getting all the values as NULL because it is protected.
( I haven't placed any password yet in the code to protect the sheet, there is only one sheet in the excel file.)
I have got below code from my Search to unprotect the worksheet.
workSheet.RemoveAllChildren<SheetProtection>();
But, this is not working. I am still getting the null values while reading this protected sheet.
using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(FilePath, false))
{
WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart;
IEnumerable<Sheet> sheets = spreadSheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>();
//if ((sheets.Count() != 2) && (sheets.First().Name.Value != "StudentNomination") && (sheets.Last().Name.Value != "Sheet2"))
//{
// throw new Exception("Please Upload the correct Nomination file, for example you can download the Nomination Template file first.!!");
//}
string relationshipId = sheets.First().Id.Value;
WorksheetPart worksheetPart = (WorksheetPart)spreadSheetDocument.WorkbookPart.GetPartById(relationshipId);
Worksheet workSheet = worksheetPart.Worksheet;
workSheet.RemoveAllChildren<SheetProtection>();
SheetData sheetData = workSheet.GetFirstChild<SheetData>();
IEnumerable<Row> rows = sheetData.Descendants<Row>();
Can anyone Please help me on this?
Your code for removing protection is correct. But before that you have to open the Excel file in Edit Mode. The second argument in SpreadSheetDocument.Open should be set to true.
Also Regardless of protection, you should be able to read a cell's value. See the below code. In order to test this you would have to create a excel file and fill the cells A1,B1 and C1 with numbers.
using System.Linq;
using System.Collections.Generic;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
class Test
{
static void Main()
{
string filePath = #"E:\test.xlsx";
using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(filePath, true))
{
WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart;
IEnumerable<Sheet> sheets = spreadSheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>();
string relationshipId = sheets.First().Id.Value;
WorksheetPart worksheetPart = (WorksheetPart)spreadSheetDocument.WorkbookPart.GetPartById(relationshipId);
Worksheet workSheet = worksheetPart.Worksheet;
var dataBeforeProtection = workSheet.Descendants<Row>().First().Descendants<Cell>().First().CellValue.InnerText;
workSheet.RemoveAllChildren<SheetProtection>();
var dataAfterProtection = workSheet.Descendants<Row>().First().Descendants<Cell>().First().CellValue.InnerText;
workSheet.Save();
}
}
}

Syncfusion DocIO -- how to insert image (local file) at bookmark using BookmarksNavigator

I have been using Syncfusion DocIO for generating MS Word documents from my .net applications (winforms). So far I have dealt with plain text and it is fairly straightforward to insert text in a word document template where bookmarks serve as reference points for text insertion.
I am navigating the bookmarks using BookmarksNavigator.MoveToBookmark() . Now I need to insert an image at a bookmark but I am at a loss at how to go about it.
Please help...
Thanks.
Specifically for adding it to a bookmark :
//Move to the specified bookmark
bk.MoveToBookmark(bookmark);
//Insert the picture into the specified bookmark location
bk.DeleteBookmarkContent(true);
// we assume the text is a full pathname for an image file
// get the image file
System.Drawing.Image image = System.Drawing.Image.FromFile(sText);
IWParagraph paragraph = new WParagraph(document);
paragraph.AppendPicture(image);
bk.InsertParagraph(paragraph);
private System.Drawing.Image LoadSignature(string sFileName)
{
string sImagePath = sFileName;
System.Drawing.Image image = System.Drawing.Image.FromFile(sImagePath);
return image;
}
private void MergeSignature(WordDocument doc, string sFile, string sBalise)
{
System.Drawing.Image iSignature = LoadSignature(sFile);
WordDocument ImgDoc = new WordDocument();
ImgDoc.AddSection();
ImgDoc.Sections[0].AddParagraph().AppendPicture(iSignature);
if (iSignature != null)
{
TextSelection ts = null ;
Regex pattern = new Regex(sBalise);
ts = doc.Find(pattern);
if (ts != null)
{
doc.ReplaceFirst = true;
doc.Replace(pattern, ImgDoc, false);
}
}
iSignature.Dispose();
}
See here: https://help.syncfusion.com/file-formats/docio/working-with-mailmerge
1) You should create docx file with name "Template.docx". This file will use as template.
In your docx file create Field of type MergeField.
2) Create MergeFiled with name Image:Krishna
3)
using Syncfusion.DocIO.DLS;
using System.Drawing;
public class Source
{
public Image Krishna { get; set; } = Image.FromFile(#"C:\1.png");
}
and generating code:
public static void Generate()
{
WordDocument doc = new WordDocument("Template.docx");
Source data = new Source();
var dataTable = new MailMergeDataTable("", new Source[] { data });
doc.MailMerge.ExecuteGroup(dataTable);
doc.Save("result.docx");
doc.Close();
}