i am new to OpenXML and i am about to pull my hair on this issue. Help would really appreciated.
Overview is that i am trying to fill the word document content template thru asp.net.
I was easily able to populate the fields using CustomXML, BUT THE document i am trying to fill is also mapped with SharePoint document library. So when i upload the document in SharePoint library it will auto populate the columns from the content controls on Word document. Now using custom XML is ruining that setting. And using OpenXML to fill data is not working when the controls are mapped to SharePoint.
Please help with sample code or the right direction.
This is exactly what we did in our project:) lucky you.
first you need to create event receiver for that document library. and you need to implement ItemUpdated and ItemAdded. see
http://www.dotnetcurry.com/ShowArticle.aspx?ID=649
http://blogs.msdn.com/b/brianwilson/archive/2007/03/05/part-1-event-handlers-everything-you-need-to-know-about-microsoft-office-sharepoint-portal-server-moss-event-handlers.aspx
//code for event receiver.. This will give you name of content control and its values
Dictionary<string, string> results = new Dictionary<string, string>();
using (Stream stream = file.OpenBinaryStream(SPOpenBinaryOptions.SkipVirusScan)) {
using (WordprocessingDocument doc = WordprocessingDocument.Open(stream, true)) {
var contentControls = doc.MainDocumentPart
.GetXDocument()
.Descendants(w + "sdt");
foreach ( var contentControl in contentControls )
{
string key = (string)contentControl.Descendants(w + "sdtPr").Elements(w + "alias").Attributes(w + "val").FirstOrDefault();
string val = GetTextFromContentControl(contentControl);
results[key] = val;
}
}
static string GetTextFromContentControl(XElement contentControlNode) {
return contentControlNode.Descendants(w + "p")
.Select
(
p => p.Elements()
.Where(z => z.Name == r || z.Name == ins || z.Name == br)
.Descendants()
.Where(z => z.Name == w + "t" || z.Name == w + "br")
.StringConcatenate(element => (string)element + (element.Name == w + "br" ? Environment.NewLine : "")) + Environment.NewLine
).StringConcatenate();
}
Related
I'm currently working on modifying a Tree View control (Telerik MVC Extensions) for a customer request. Their request is a simple one: if an item within the tree has an Attachment, add a paperclip beside the node to identify it.
I have so far been able to do so but, found a small hiccup with this. I can add the image to certain nodes that have an Attachment, however, all nodes that don't should have no image (by that, I mean they should appear normal within the tree). Instead though, I find that the tree places a blank the size of the paperclip image.
Is there a way to dynamically turn off this blank (aka not add an Image Url if unnecessary)? Below is my code where I'm executing this process (is done on the expansion method of the tree due that only the bottom level shows the Attachments).
Navigation Controller
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GetNextTreeViewLevel(TreeViewItem node)
{
...
//If bottom layer, then execute the following
var data = _TreeRepo.GetProcessesByParcel(int.Parse(values[1]), cntTreeList);
nodes = from item in data
select new TreeViewItem
{
Text = item.strProcess,
Value = "PR" + "," + item.cntProcess.ToString(),
LoadOnDemand = false,
Enabled = true,
Selected = SelectedSearchResult.ToString().Length > 0
&& SelectedSearchResult.ToString().Split('~').Length > 3
&& decimal.Parse(SelectedSearchResult.ToString()
.Split('~')
.Last()
.Substring(2)) == item.cntProcess
ImageUrl = item.ysnHasAttachment.HasValue && item.ysnHasAttachment.Value == 1
? #"/Content/NewImages/attachment.png"
: string.Empty
};
return new JsonResult { Data = nodes };
}
Screen shots of what it looks like without/with code for Image Url:
I at long last came up with a solution to this issue. The problem was how I was getting my data added to the nodes. The original logic was doing a Linq query after fetching the data to get an IEnumerable object.
Because of that, every node was trying to add an image (even if there was none). Hence the weird looking space. Below is how I reworked this logic to correctly get my data.
var processNodes = new List<TreeViewItem>();
var data = _TreeRepo.GetProcessesByParcel(int.Parse(values[1]), cntTreeList);
foreach (var item in data)
{
#region Process has at least one Attachment
if (item.ysnHasAttachment.HasValue && item.ysnHasAttachment.Value == 1)
processNodes.Add(new TreeViewItem
{
Text = item.strProcess,
Value = "PR" + "," + item.cntProcess.ToString(),
LoadOnDemand = false,
Enabled = true,
Selected = SelectedSearchResult.ToString().Length > 0
&& SelectedSearchResult.ToString().Split('~').Length > 3
&& decimal.Parse(SelectedSearchResult.ToString()
.Split('~')
.Last()
.Substring(2)) == item.cntProcess,
ImageUrl = "/Content/NewImages/smallAttachment.png"
});
#endregion
#region Process has no Attachments
else
processNodes.Add(new TreeViewItem
{
Text = item.strProcess,
Value = "PR" + "," + item.cntProcess.ToString(),
LoadOnDemand = false,
Enabled = true,
Selected = SelectedSearchResult.ToString().Length > 0
&& SelectedSearchResult.ToString().Split('~').Length > 3
&& decimal.Parse(SelectedSearchResult.ToString()
.Split('~')
.Last()
.Substring(2)) == item.cntProcess
}
#endregion
}
nodes = processNodes;
At this point, you can now return the nodes. Those that should have had an Attachment icon will, and those that shouldn't won't. Funny how 4 months later, you can come up with something off the cuff.
I have created an xPages custom control based on Dynamic View Panel. I then added 2 comboboxes filled with various values (States, Departments) and an editbox field and a Search button. I then coded the follow to return the search string onto a computed "Search in view results" for the panel.
var tmpArray = new Array("");
var cTerms = 0;
if(viewScope.categoryText1 != null) {
if ( viewScope.categoryText1.trim() != "") {
tmpArray[cTerms++] = "(FIELD State CONTAINS \"" + viewScope.categoryText1 + "\")";
}
}
if(viewScope.categoryText2 != null ){
if ( viewScope.categoryText2.trim() != "") {
tmpArray[cTerms++] = "(FIELD Department = \"" + viewScope.categoryText2 + "\")";
}
}
if(viewScope.searchString != null ) {
if ( viewScope.searchString != "") {
tmpArray[cTerms++] = "( \"" + viewScope.searchString + "\")";
}
}
qstring = tmpArray.join(" AND ").trim();
viewScope.queryString = qstring; // this just displays the query
return qstring // this is what sets the search property
The search works for the editbox field values but not for the strings generated by the comboboxes: 'FIELD State CONTAINS "some state"' or 'FIELD Department = "some deptname"'. These search strings return an empty view.
The Column names match the underlying Notesview (both programmatically and column title).
I think this might have something to do with what are the column names surfaced by the Dynamic View Panel but I'm not sure.
Full text search looks in document fields for search strings, not in column values.
So, make sure fields State and Department contain the strings you are looking for.
Do you use aliases? Maybe you save abbreviation for State in document only but user can select State's full name for search...
I have a view in which I search for products. I'm for example looking for product 1234.
The problem is their also exist products called 1234A and 1234 C etc. When I look with the code mentioned below I get all the items from product 1234 but also from 1234A and 1234 C etc.
It has to be limited to items from product 1234 only
Search code (under Data / Search in view results):
var tmpArray = new Array("");
var cTerms = 0;
if (sessionScope.SelectedProduct != null & sessionScope.SelectedProduct != "") {
tmpArray[cTerms++] = "(FIELD spareProduct = \"" + sessionScope.SelectedProduct +
"\")";
}
if (sessionScope.Development != null & sessionScope.Development != "") {
tmpArray[cTerms++] = "(FIELD spareStatus = \"*" + sessionScope.Development +
"*\")";
}
qstring = tmpArray.join(" AND ").trim();
return qstring
I used the suggestion from Frantisek :
I made a view with a combined column . (combined with the different "keys" I search for)
Then instead of using data / search , I used data/keys with exact keymatch. In this key I combined the searched items.
Since I had a field in wich I had sometimes at the end a character "°" , and it seems that this character doesn't work with a lookup , I took it out of my view and searched item with #Word(FIELDNAME; "°" ; 1).
As Frantisek suggested I could have used #ReplaceSubstring( field; "°"; "" ) also.
I have a Sharepoint (2007) list with some items in it. When I click on one of these items, it will open an Excel (2003) file with a lot of macros. I need to get the ID of this (Sharepoint) item and send it to a cell of my Excel file... Then a macro will be executed and get all the data we need for this ID.
How can I send the item's ID to my Excel file ?
Any idea ?
Thanks
I once write a DataTable into an new excel file. So you can go ahead and change the function parameter from DataTable to SPList/SPLisItem, and write to an existing file (my current implementation writes to a new Excel file everytime, I execute this function). Also, make sure you add references for the Excel (COM) objects for e.g. Microsoft Excel 12.0 Object Library etc. If you need more help let me know.
public void excelgenerate(DataSet ds)
{
Microsoft.Office.Interop.Excel.Application oAppln;
//declaring work book
Microsoft.Office.Interop.Excel.Workbook oWorkBook;
//declaring worksheet
Microsoft.Office.Interop.Excel.Worksheet oWorkSheet;
oAppln = new Microsoft.Office.Interop.Excel.Application();
oWorkBook = (Microsoft.Office.Interop.Excel.Workbook)(oAppln.Workbooks.Add(true));
Microsoft.Office.Interop.Excel.Range wRange;
foreach (DataTable table in ds.Tables)
{
oWorkSheet = (Microsoft.Office.Interop.Excel.Worksheet)(oWorkBook.Worksheets.Add(Type.Missing, Type.Missing, Type.Missing, Type.Missing));
oWorkSheet.Name = table.TableName;
oWorkSheet.Activate();
DataRow dr = table.Rows[0];
string path = dr["Path"].ToString();
if (path.Length > 0)
{
string[] mylist = path.Split('\\');
var features = Array.FindLastIndex(mylist, str => str.Equals("Features"));
string stringmine = "Type ---> " + mylist[4]
+ "/" + mylist[5]
+ " Project Name ---> " + mylist[6]
+ " Feature Name ---> " + mylist[features + 1];
oWorkSheet.Cells[1, 1] = stringmine;
Microsoft.Office.Interop.Excel.Range colrange = oWorkSheet.get_Range(oWorkSheet.Cells[1, 1], oWorkSheet.Cells[1, 8]);
colrange.Merge(true);
}
int ColumnIndex = 0;
foreach (DataColumn col in table.Columns)
{
ColumnIndex++;
oWorkSheet.Cells[2, ColumnIndex] = col.ColumnName;
wRange = (Microsoft.Office.Interop.Excel.Range)oWorkSheet.Cells[2, ColumnIndex];
wRange.Font.Bold = true;
}
int rowIndex = 1;
foreach (DataRow row in table.Rows)
{
rowIndex++;
ColumnIndex = 0;
foreach (DataColumn col in table.Columns)
{
ColumnIndex++;
oWorkSheet.Cells[rowIndex + 1, ColumnIndex] = row[col.ColumnName].ToString();
}
}
oWorkSheet.Columns.AutoFit();
oWorkSheet.Rows.AutoFit();
}
string fileName = System.Guid.NewGuid().ToString().Replace("-", "") + ".xls";
Console.WriteLine("Number of sheets written : " + oWorkBook.Worksheets.Count);
oWorkBook.SaveAs(fileName, Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookNormal, null, null, false, false, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlShared, false, false, null, null, null);
oWorkBook.Close(null, null, null);
oAppln.Quit();
}
For executing the Macro using C# ASP.NET and SharePoint, I would recommend you using this article
Hope it will answer your question!
Unless there a reason why you cannot link the SharePoint list data directly into a worksheet and bring your macros into that spreadsheet I think the steps below will get you what you need. It seems too simple...there must be a reason this does not work for what you're trying to do. In any case, here are the steps to make this work:
1) Make sure the SharePoint list actually has an indexed column that enforces unique values. You can check this by looking at the document library settings. Look to make sure there is an index column under the columns listing. If there is not one, you can create one by selecting the "create new column" action, select your data type and make sure that you select the radio button that says "enforce unique values".
2) Export the library to excel using the "export to excel" options in the library's main page menu. This will establish a data link by default and store an excel query file at a default location on your machine that you can discover by going to the data tab and selecting "connections".
3) Copy the macro into the spreadsheet that is linked to your data source and adjust the references in your macro to extract the information you need from the SharePoint list.
Hope this helps.
I need to read data from a single worksheet in an Excel 2007 workbook using the Open XML SDK 2.0. I have spent a lot of time searching for basic guidelines to doing this, but I have only found help on creating spreadsheets.
How do I iterate rows in a worksheet and then iterate the cells in each row, using this SDK?
The other answer seemed more like a meta-answer. I have been struggling with this since using LINQ does work with separated document parts. The following code includes a wrapper function to get the value from a Cell, resolving any possible string lookups.
public void ExcelDocTest()
{
Debug.WriteLine("Running through sheet.");
int rowsComplete = 0;
using (SpreadsheetDocument spreadsheetDocument =
SpreadsheetDocument.Open(#"path\to\Spreadsheet.xlsx", false))
{
WorkbookPart workBookPart = spreadsheetDocument.WorkbookPart;
foreach (Sheet s in workBookPart.Workbook.Descendants<Sheet>())
{
WorksheetPart wsPart = workBookPart.GetPartById(s.Id) as WorksheetPart;
Debug.WriteLine("Worksheet {1}:{2} - id({0}) {3}", s.Id, s.SheetId, s.Name,
wsPart == null ? "NOT FOUND!" : "found.");
if (wsPart == null)
{
continue;
}
Row[] rows = wsPart.Worksheet.Descendants<Row>().ToArray();
//assumes the first row contains column names
foreach (Row row in wsPart.Worksheet.Descendants<Row>())
{
rowsComplete++;
bool emptyRow = true;
List<object> rowData = new List<object>();
string value;
foreach (Cell c in row.Elements<Cell>())
{
value = GetCellValue(c);
emptyRow = emptyRow && string.IsNullOrWhiteSpace(value);
rowData.Add(value);
}
Debug.WriteLine("Row {0}: {1}", row,
emptyRow ? "EMPTY!" : string.Join(", ", rowData));
}
}
}
Debug.WriteLine("Done, processed {0} rows.", rowsComplete);
}
public static string GetCellValue(Cell cell)
{
if (cell == null)
return null;
if (cell.DataType == null)
return cell.InnerText;
string value = cell.InnerText;
switch (cell.DataType.Value)
{
case CellValues.SharedString:
// For shared strings, look up the value in the shared strings table.
// Get worksheet from cell
OpenXmlElement parent = cell.Parent;
while (parent.Parent != null && parent.Parent != parent
&& string.Compare(parent.LocalName, "worksheet", true) != 0)
{
parent = parent.Parent;
}
if (string.Compare(parent.LocalName, "worksheet", true) != 0)
{
throw new Exception("Unable to find parent worksheet.");
}
Worksheet ws = parent as Worksheet;
SpreadsheetDocument ssDoc = ws.WorksheetPart.OpenXmlPackage as SpreadsheetDocument;
SharedStringTablePart sstPart = ssDoc.WorkbookPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
// lookup value in shared string table
if (sstPart != null && sstPart.SharedStringTable != null)
{
value = sstPart.SharedStringTable.ElementAt(int.Parse(value)).InnerText;
}
break;
//this case within a case is copied from msdn.
case CellValues.Boolean:
switch (value)
{
case "0":
value = "FALSE";
break;
default:
value = "TRUE";
break;
}
break;
}
return value;
}
Edit: Thanks #Nitin-Jadhav for the correction to GetCellValue().
The way I do this is with Linq. There are lots of sample around on this subject from using the SDK to just going with pure Open XML (no SDK). Take a look at:
Office Open XML Formats: Retrieving
Excel 2007 Cell Values (uses pure
OpenXML, not SDK, but the concepts
are really close)
Using LINQ to Query Tables in Excel
2007 (uses Open XML SDK, assumes
ListObject)
Reading Data from SpreadsheetML
(probably best "overall introduction"
article)