How can I set the "Print Titles" property of a spreadsheet with OpenXML, to have a row show at the top of every printed page?
The property is directly saved in the SpreadsheetPrintingParts object, however, this doesn't appear to be fully integrated into OpenXML as of yet, and requires passing a base64 string in to the variable. (see here) The content of this string appears to be tied to the machine the file is opened on, which didn't work for my implementation - I wasn't able to create a non-corrupt file through SpreadsheetPrintingParts.FeedData().
Instead, I found this post which stated giving the row the defined name "Print_Titles" has the same effect. I was then able to create a defined name through OpenXML with the following code:
public void SetPrintTitleRows(int startRowIndex, int? endRowIndex = null)
{
var localSheetId = _localsheetId++; //LocalSheetIds are 0-indexed.
var definedName = new DefinedName
{
Name = "_xlnm.Print_Titles",
LocalSheetId = localSheetId,
Text = String.Format("\'{0}\'!${1}:${2}", _sheetName, startRowIndex, endRowIndex ?? startRowIndex)
};
if (_workbookPart.Workbook.DefinedNames == null)
{
var definedNamesCol = new DefinedNames();
_workbookPart.Workbook.Append(definedNamesCol);
}
_workbookPart.Workbook.DefinedNames.Append(definedName);
}
Points to note:
DefinedName.LocalSheetId is zero-indexed, as opposed to Sheet.Id which is 1-indexed
DefinedNames is once per workbook, but can contain multiple DefinedName objects for different sheets.
Related
I'm helping out with an open-source project. In it, we are getting a feed (open using Chrome) using var feed = new SyndicationFeed(...){...}; that contains different article titles along with their names.
After that I am able to iterate through the feed using foreach(var item in feed.Items){} and in there I can get the Title & Name string using item.Title.Text & item.Authors[0].Name.
The problem is that these properties are read-only, but I want to be able to add some text to the end of the Title property. So, the only way to update the Title (since it's of type TextSyndicationContent) seems to be by using item.Title.WriteTo()
How I can use the WriteTo to update the value of the existing Title without creating a new RSS Feed? Also, how can I get the outerElementName & outerElementNamespace?
I have added a screenshot of what the function requires me to put in.
Is the outerElementName "title" and outerElementNamespace "group? If so, what can I put in as XMLWriter to update the value of title?
The Title property can be set on the item.
Create a new instance of TextSyndicationContent and copy over the title with the additional text.
//...
string newTitleText = item.Title.Text + "Some extra text content";
if (!string.IsNullOrWhiteSpace(item.Title.Type)) {
string type = item.Title.Type == "text" ? "Plaintext" : item.Title.Type;
TextSyndicationContentKind textKind = (TextSyndicationContentKind)
Enum.Parse(typeof(TextSyndicationContentKind), type, ignoreCase: true);
item.Title = new TextSyndicationContent(newTitleText, textKind);
} else {
item.Title = new TextSyndicationContent(newTitleText);
}
//...
i am trying to add some attributes on my datasets and it is working fine, but i have one problem when writing the value of the attributes, i need them to be a string, not a INT nor float or whatever, every tutorial i find on the internet uses a int value for those attributes..
I've tried a lot of different things and none of them works well and the code is becoming very ugly, this is my code:
foreach (BasicVariable bv in pf.basicVariableList.bvList)
{
H5DataSetId dataSetId = H5D.create(bvGroupId, bv.bvTag, typeId, spaceId);
bv.atribList.Add(new Atribbs("Eng. Unit", "Temperatura"));
bv.atribList.Add(new Atribbs("Atrib2", "Teste"));
foreach(Atribbs a in bv.atribList )
{
H5DataTypeId auxDT = H5T.copy(H5T.H5Type.C_S1);
H5T.setSize(auxDT, H5T.getSize(H5T.H5Type.C_S1));
const int auxData = 2;
long[] auxDim = new long[1];
auxDim[0] = 1;
H5DataSpaceId auxDS = H5S.create_simple(1, auxDim);
var attr_id = H5A.create(dataSetId, a.attName, auxDT, auxDS);
byte[] bytes = Encoding.UTF8.GetBytes(a.valueStr);
var atrData = new string[] { Encoding.Default.GetString(bytes)};
H5A.write<string>(attr_id, auxDT, new H5Array<string>(atrData));
H5A.close(attr_id);
}
H5D.close(dataSetId);
}
H5F.close(fileId);
The attributes i've created are "Eng. Unit" and "Atrib2" and that is working fine, the problem is thet the "Eng. Unit" should have the "Temperatura" value and "Atrib2" should have "Teste" as it's value, this is what i get on the hdfl viewer
enter image description here
enter image description here
Maybe you want to have a look at HDFql, which greatly simplifies how HDF5 files are handled. Here goes a solution using HDFql in C# (assume that the HDF5 file is named test.h5 and the dataset is named dset):
HDFql.Execute("USE FILE test.h5"); // use (i.e. open) HDF5 file "test.h5"
HDFql.Execute("CREATE ATTRIBUTE \"dset/Eng. Unit\" AS VARCHAR VALUES(Temperatura)"); // create attribute "Eng. Unit" in dataset "dset" with an initial value of "Temperatura"
HDFql.Execute("CREATE ATTRIBUTE dset/Atrib2 AS VARCHAR VALUES(Teste)"); // create attribute "Atrib2" in dataset "dset" with an initial value of "Teste"
Using OpenXML in C#, we need to:
Find a specific string of text on a Word document (this text will always exist in a table cell)
Get the formatting of the text and the table that the text exists in.
Create a new table with the same text and table formatting while pulling in text values for the cell from a nested List
This is the code that I currently have and the places I am not sure how do:
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(fileWordFile, true))
{
MainDocumentPart mainPart = wordDoc.MainDocumentPart;
Body body = mainPart.Document.Body;
IEnumerable paragraphs = body.Elements<Paragraph>();
Paragraph targetParagraph = null;
//Comment 1: Loop through paragraphs and search for a specific string of text in word document
foreach (Paragraph paragraph in paragraphs) {
if(paragraph.Elements<Run>().Any()) {
Run run = paragraph.Elements<Run>().First();
if(run.Elements<Text>().Any()) {
Text text = run.Elements<Text>().First();
if (text.Text.Equals("MY SEARCH STRING")) {
targetParagraph = paragraph;
// Comment 2: How can I get the formatting of the table that contains this text??
}
}
}
}
//Comment 3: Create table with same formatting as where the text was found
Table table1 = new Table();
TableProperties tableProperties1 = new TableProperties();
//Comment 4: How can I set these properties to be the same as the one found at "Comment 2"??
wordDoc.Close();
wordDoc.Dispose();
}
If you're looking for text elements that are inside a table cell, you can use a LINQ query to get there quickly without needing to use a heap of nested loops.
// Find the first text element matching the search string
// where the text is inside a table cell.
var textElement = body.Descendants<Text>()
.FirstOrDefault(t => t.Text == searchString &&
t.Ancestors<TableCell>().Any());
Once you have your match, the easiest way to duplicate the containing table with all its formatting and contents is simply to clone it.
if (textElement != null)
{
// get the table containing the matched text element and clone it
Table table = textElement.Ancestors<Table>().First();
Table tableCopy = (Table)table.CloneNode(deep: true);
// do stuff with copied table (see below)
}
After that, you can add things to the corresponding cell of the copied table. It's not entirely clear what you meant by "pulling in text values for the cell from a nested List" (what list? nested where?), so I'll just show a contrived example. (This code would replace the "do stuff" comment in the code above.)
// find the table cell containing the search string in the copied table
var targetCell = tableCopy.Descendants<Text>()
.First(t => t.InnerText == searchString)
.Ancestors<TableCell>()
.First();
// get the properties from the first paragraph in the target cell (so we can copy them)
var paraProps = targetCell.Descendants<ParagraphProperties>().First();
// now add new stuff to the target cell
List<string> stuffToAdd = new List<string> { "foo", "bar", "baz", "quux" };
foreach (string item in stuffToAdd)
{
// for each item, clone the paragraph properties, then add a new paragraph
var propsCopy = (ParagraphProperties)paraProps.CloneNode(deep: true);
targetCell.AppendChild(new Paragraph(propsCopy, new Run(new Text(item))));
}
Lastly, you need to add the copied table to the document somewhere or you won't see it. You don't say in your question where you would want this to appear, so I'll just put it at the end of the document. You can use methods like InsertAfter, InsertAt, InsertBefore, etc. to insert the table relative to other elements.
body.AppendChild(tableCopy);
Hope this helps.
My end aim is to programatically set Excel's "Print Titles" value of Page Setup for all worksheets in my document.
Initially I tried looking at using the SpreadsheetPrintingParts object (based on this question) - however, that requires generating a base 64 string, which seemingly has to come from an exisiting file. (I am generating my spreadsheet from scratch.)
This post then taught me that I could set "Print_Titles" as a defined name on the row I needed instead. I've been attempting to do that programatically, but this seems to corrupt all my files.
My code:
var definedNamesCol = new DefinedNames(); //Create the collection
var definedName = new DefinedName() { Name = "_xlnm.Print_Titles", Text = "\'SheetName\'!$2:$2", LocalSheetId = (UInt32) (_nextSheetId - 1) }; // Create a new range
definedNamesCol.Append(definedName); // Add it to the collection
_workbookPart.Workbook.Append(definedNamesCol);
I've also looked with the OpenXML productivity tool which suggests: (Essentially identical)
DefinedNames definedNames1 = new DefinedNames();
DefinedName definedName1 = new DefinedName(){ Name = "_xlnm.Print_Titles", LocalSheetId = (UInt32Value)0U };
definedName1.Text = "\'SheetName\'!$2:$2";
definedNames1.Append(definedName1)
I've also tried setting the Xlm property on DefinedName but the file then opens with an error that it contains Macro's in a Macro-Free file, which isn't what I think I want to do.
A (simplified) version of what I'm generating in workbook.xml:
<?xml version="1.0" encoding="utf-8"?>
<x:workbook xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<x:sheets>
<x:sheet name="ABBEY" sheetId="1" r:id="R2f5447238bc94fa4" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" />
</x:sheets>
<x:definedNames>
<x:definedName name="_xlnm.Print_Titles" localSheetId="0">'SheetName'!$2:$2</x:definedName>
</x:definedNames>
</x:workbook>
Is there a better way to approach the problem? Or is my intention right, and it's a misunderstanding of the method somewhere else?
The above code was within a CreateWorksheet method, so was being called for each sheet. In the resulting workbook.xml file, this was then creating multiple definedNames objects, when there should only be a single definedNames object containing multiple definedNames.
I resolved the problem using this code:
var definedName = new DefinedName() { Name = "_xlnm.Print_Titles", Text = "\'Sheet Name\'!$2:$2", LocalSheetId = (UInt32) (_nextSheetId - 1) }; // Create a new range
if (_workbookPart.Workbook.DefinedNames == null)
{
var definedNamesCol = new DefinedNames();
_workbookPart.Workbook.Append(definedNamesCol);
}
_workbookPart.Workbook.DefinedNames.Append(definedName); // Add it to the collection
I am processing an .xlsm file and need to know how to use a list on another sheet for data validation using openXML and C#.
To start, I have a .xlsm file with two empty sheets and macros in it. In my program I open the file, Create the column header on Sheet1 then create the validation list on sheet2. So, after I run my program Sheet1 "A1" contains the text "Color" and Sheet2 "A1:A4" contains "Blue","Green","Red","Yellow". I get this far just fine.
I would like to make it so there is a dropdown list in all cells of column "A" on sheet1 that contains each of the 4 colors and enforces them as the only input. In Microsoft Excel this is done by going to the "Data" tab, selecting "Data Validation" selecting "List" and highlighting the cells you want to use. I need to make this association programmatically.
The (Desired) XML that Microsoft Excel creates if I do it manually is this:
<extLst>
<ext uri="{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main">
<x14:dataValidations count="1" xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main">
<x14:dataValidation type="list" allowBlank="1" showInputMessage="1" showErrorMessage="1">
<x14:formula1>
<xm:f>'Validation Data'!$A$1:$A$4</xm:f>
</x14:formula1>
<xm:sqref>A1:A1048576</xm:sqref>
</x14:dataValidation>
</x14:dataValidations>
</ext>
</extLst>
The following method and results is something I tried. It may give a better Idea of what I'm trying to do.
Here, I pass in "'Sheet2'!$A$1:$A$4" as the "validationListCells" parameter. This represents the cells in "Sheet2" that, in this example, would contain the color names "Red", "Green"...etc.
I pass in "A2:A1048576" as the "cellsToValidate" parameter. This represents all cells of Sheet1 column "A", on which I want to enforce validation.
I pass "Sheet1" as the worksheetName parameter.
private void InsertValidation(String worksheetName, String validationListCells, String cellsToValidate)
{
DataValidations dataValidations1 = new DataValidations() { Count = (UInt32Value)1U };
DataValidation dataValidation1 = new DataValidation()
{
Formula1 = new Formula1(validationListCells),
Type = DataValidationValues.List,
ShowInputMessage = true,
ShowErrorMessage = true,
SequenceOfReferences = new ListValue<StringValue>() { InnerText = cellsToValidate }
};
dataValidations1.Append(dataValidation1);
using (SpreadsheetDocument spreadSheet = SpreadsheetDocument.Open(_documentPath, true))
{
WorksheetPart worksheetPart = GetWorksheetPartByName(spreadSheet, worksheetName);
worksheetPart.Worksheet.Append(dataValidations1);
worksheetPart.Worksheet.Save();
}
}
It results in this XML in Sheet1.xml. Which causes an error in Excel.
<x:dataValidations count="1">
<x:dataValidation type="list" showInputMessage="1" showErrorMessage="1" sqref="A2: A1048576">
<x:formula1>'Sheet2'!$A$1:$A$5</x:formula1>
</x:dataValidation>
</x:dataValidations>
It looks like I may be on the right track since it is beginning to resemble the xml created by Excel, but I'm completely new to openXML and I'm finding little about this topic on the net.
Thanks in advance!
For anyone else in need of this..the code below worked for me.
I put in there user3251089's variable names.
In general, when I try to programmatically create an excel "feature" I manually make a really basic excel that has in it that feature (delete extra sheets too). Then I reflect the code and try to make it prettier.
hope it serves to someone!
using Excel = DocumentFormat.OpenXml.Office.Excel;
using X14 = DocumentFormat.OpenXml.Office2010.Excel;
.....
Worksheet worksheet = worksheetPart.Worksheet;
WorksheetExtensionList worksheetExtensionList = new WorksheetExtensionList();
WorksheetExtension worksheetExtension = new WorksheetExtension() { Uri = "{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}" };
worksheetExtension.AddNamespaceDeclaration("x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main");
X14.DataValidations dataValidations = new X14.DataValidations() { Count = (UInt32Value)3U };
dataValidations.AddNamespaceDeclaration("xm", "http://schemas.microsoft.com/office/excel/2006/main");
//sites validation
dataValidations.Append(new X14.DataValidation()
{
Type = DataValidationValues.List,
AllowBlank = true,
ShowInputMessage = true,
ShowErrorMessage = true,
DataValidationForumla1 = new X14.DataValidationForumla1() { Formula = new Excel.Formula(validationListCells) },
ReferenceSequence = new Excel.ReferenceSequence(cellsToValidate)
});
worksheetExtension.Append(dataValidations);
worksheetExtensionList.Append(worksheetExtension);
worksheet.Append(worksheetExtensionList);
worksheet.Save();