none
OpenXML: Finding and Manipulating Embedded Charts in a Word Doc RRS feed

  • Question

  • I'm very close on this.

    I'm trying to create a function that will locate and update an embeded chart within a Word document.  Ultimately, the document will have sevral charts.

    So far, my function can iterate through the document and find a chart (currently there is only one) and plug in some dummy info.  It works, however when the document is opened, I get a pop-up complaining that the document is corrupt.  I OK past that and a second pop-up asks me if I'd like to recover the document.  I OK that and the document then opens and the chart is updated, all as if nothing were wrong.

    So it winds up OK, but my users are not going to be happy with the "corrupt file" messages.  When they're not happy, no one is happy.

    Also, I'm looking for a methodology to find and update a specific chart.  Currently I iterate through the document, and if I had to I could live with that but I'd like to implement something a little less fragile.

    I was looking at perhaps using the "Title" property on the chart and attempting to have the SDK search for that.  I'm a relative newbie to this SDK and can't seem to locate an example of that (or *ANYTHING* for that matter that attempts to find a specific chart in a document).

    Here is my code so far.  It's just a prototype, so it is maufacturing data for the cells and allthough it is iterating through the document, there is no method yet to target a specific chart:

    	private void updateChart( WordprocessingDocument doc )
    	{
    		string rid = null;
    
    		Stream stream = null;
    
    
    		foreach ( Paragraph p in doc.MainDocumentPart.Document.Body.Elements<Paragraph>( ) )
    		{
    			foreach ( Drawing d in p.Descendants<Drawing>( ) )
    			{
    				foreach ( ChartReference cr in d.Descendants<ChartReference>( ) )
    				{
    					rid = cr.Id.Value;
    
    					ChartPart cp = ( ChartPart )doc.MainDocumentPart.Parts.Where( pt => pt.RelationshipId == rid ).FirstOrDefault( ).OpenXmlPart;
    
    					foreach ( ExternalData ed in cp.ChartSpace.Elements< ExternalData >( ) )
    					{
    						string externalDataRID = ed.Id.Value;
    						EmbeddedPackagePart epp = ( EmbeddedPackagePart )cp.Parts.Where( pt => pt.RelationshipId == ed.Id ).FirstOrDefault( ).OpenXmlPart;
    
    						using ( Stream str = epp.GetStream( ) )
    						{
    							using ( MemoryStream ms = new MemoryStream( ) )
    							{
    								CopyStream( str, ms );
    								using ( SpreadsheetDocument spreadsheetDoc = SpreadsheetDocument.Open( ms, true ) )
    								{
    									ss.Sheet ws = ( ss.Sheet )spreadsheetDoc.WorkbookPart.Workbook.Sheets.FirstOrDefault( );
    
    									string sheetId = ws.Id;
    
    									WorksheetPart wsp = ( WorksheetPart )spreadsheetDoc.WorkbookPart.Parts.Where( pt => pt.RelationshipId == sheetId ).FirstOrDefault( ).OpenXmlPart;
    									ss.SheetData sd = wsp.Worksheet.Elements<ss.SheetData>( ).FirstOrDefault( );
    
    									int ctr = 0;
    									foreach ( ss.Row row in sd.Elements< ss.Row >( ) )
    									{
    										if ( ctr > 0 && ctr <= 6 )
    										{
    											ss.CellValue cv0 = row.Elements<ss.Cell>( ).ElementAt( 0 ).Elements<ss.CellValue>( ).FirstOrDefault( );
    											cv0.Text = _monthsInRange[ ctr - 1 ].startDate.ToString( "MMM-yyyy" );
    
    											ss.CellValue cv1 = row.Elements<ss.Cell>( ).ElementAt( 1 ).Elements<ss.CellValue>( ).FirstOrDefault( );
    											cv1.Text = ( ctr * 10 ).ToString( );
    
    											ss.CellValue cv2 = row.Elements<ss.Cell>( ).ElementAt( 2 ).Elements<ss.CellValue>( ).FirstOrDefault( );
    											cv2.Text = ( ctr * 10 ).ToString( );
    
    											ss.CellValue cv3 = row.Elements<ss.Cell>( ).ElementAt( 3 ).Elements<ss.CellValue>( ).FirstOrDefault( );
    											cv3.Text = ( ctr * 10 ).ToString( );
    
    											ss.CellValue cv4 = row.Elements<ss.Cell>( ).ElementAt( 4 ).Elements<ss.CellValue>( ).FirstOrDefault( );
    											cv4.Text = ( ctr * 10 ).ToString( );
    
    											ss.CellValue cv5 = row.Elements<ss.Cell>( ).ElementAt( 5 ).Elements<ss.CellValue>( ).FirstOrDefault( );
    											cv5.Text = ( ctr * 10 ).ToString( );
    										}
    
    										++ctr;
    									}
    								}
    
    								using ( Stream s = epp.GetStream( ) )
    								{
    									ms.WriteTo( s );
    								}
    							}
    						}
    
    
    
    						Chart chart = cp.ChartSpace.Elements<Chart>( ).First( );
    						Bar3DChart bc = chart.Descendants<DocumentFormat.OpenXml.Drawing.Charts.Bar3DChart>( ).FirstOrDefault( );
    
    						if ( bc != null )
    						{
    							foreach( BarChartSeries bcs in bc.Elements<BarChartSeries>( ) )
    							{
    								CategoryAxisData cad = bcs.Descendants<CategoryAxisData>( ).FirstOrDefault( );
    
    								StringReference sr = cad.Descendants<StringReference>( ).FirstOrDefault( );
    								StringCache sc = sr.Descendants<StringCache>( ).First( );
    								int ctr = 0;
    								foreach ( StringPoint sp in sc.Descendants<StringPoint>( ) )
    								{
    									NumericValue nv = sp.Descendants<NumericValue>( ).First( );
    									nv.Text = _monthsInRange[ ctr ].startDate.ToString( "MMM-yyyy" );
    									++ctr;
    								}
    
    
    								foreach ( Values values in bcs.Descendants<Values>( ) )
    								{
    									NumberingCache nc = values.Descendants<NumberingCache>( ).First( );
    
    									NumericValue nv1 = nc.Elements<NumericPoint>( ).ElementAt( 0 ).Elements<NumericValue>( ).FirstOrDefault( );
    									nv1.Text = "10";
    
    									NumericValue nv2 = nc.Elements<NumericPoint>( ).ElementAt( 1 ).Elements<NumericValue>( ).FirstOrDefault( );
    									nv2.Text = "20";
    
    									NumericValue nv3 = nc.Elements<NumericPoint>( ).ElementAt( 2 ).Elements<NumericValue>( ).FirstOrDefault( );
    									nv3.Text = "30";
    
    									NumericValue nv4 = nc.Elements<NumericPoint>( ).ElementAt( 3 ).Elements<NumericValue>( ).FirstOrDefault( );
    									nv4.Text = "40";
    
    									NumericValue nv5 = nc.Elements<NumericPoint>( ).ElementAt( 4 ).Elements<NumericValue>( ).FirstOrDefault( );
    									nv5.Text = "50";
    
    									NumericValue nv6 = nc.Elements<NumericPoint>( ).ElementAt( 5 ).Elements<NumericValue>( ).FirstOrDefault( );
    									nv6.Text = "60";
    								}
    							}
    						}
    					}
    				}
    			}
    		}
    	}

    The embedded chart is based off a 5 column (well actually 6 - the first column is row headers) x 6 row spreadsheet.

    Any help would be much appreciated.

    JP


    JP





    Wednesday, August 28, 2013 9:55 PM

Answers

  • JP,

    Your document is corrupt because you did something wrong. I think the easiest way to find the offensive piece of code/xml is to compare the original and the generated document (use Open XML power tools for VS to surf through the documents).

    You can also check the commercial document generation (mail merge) toolkit that does this right.

    Friday, August 30, 2013 1:20 PM

All replies

  • Hi,

    Thank you for posting in the MSDN Forum.

    I'm trying to involve some senior engineers into this issue and it will take some time.

    Your patience will be greatly appreciated.

    Sorry for any inconvenience and have a nice day!

    Best regards,

    Fei


    <THE CONTENT IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED>
    Thanks
    MSDN Community Support

    Please remember to "Mark as Answer" the responses that resolved your issue. It is a common way to recognize those who have helped you, and makes it easier for other visitors to find the resolution later.

    Thursday, August 29, 2013 1:26 PM
    Moderator
  • JP,

    Your document is corrupt because you did something wrong. I think the easiest way to find the offensive piece of code/xml is to compare the original and the generated document (use Open XML power tools for VS to surf through the documents).

    You can also check the commercial document generation (mail merge) toolkit that does this right.

    Friday, August 30, 2013 1:20 PM
  • Thanks Fei.

    I'm past the corrupt document issue, it was related to code and not the document (I'll post the working code).

    Currently, I'm getting the chars based on index:

    Stream stream = this._wpDoc.MainDocumentPart.ChartParts.ElementAt( chartIndex ).EmbeddedPackagePart.GetStream( );

    This works, but the indexes on the charts change randomly anytime I make a change to the document I'm reading in as my base and save it.  So if I add a new chart (or make any other change), I spend the next hour learning the new indexes for my 9 charts, commenting them all out and uncommenting one at a time.

    There *HAS* to be a better, more immutable way for find a chart in a word document.


    JP

    Saturday, September 21, 2013 3:55 PM
  • The corrupt document issue was code related.

    I've built an OpenXMLChartManipulator class that works, but is less than ideal as it finds chart by an index.  Unfortunately, for a doc that contains multiple charts (mine has nine), every time I make a change to the base document (like add a new chart or really any change), it randomly reindexes all of the charts.  I then have to comment out all the chart updates in my code and uncomment them one at a time so I can learn the new index sequence.

    Also, it currently only supports 3D Bar Charts and Line Charts.  It should be relatively simple to expand the functionality to other chart types.

    Still, it's better than nothing.

    If anyone knows how to get at a specific chart in a document, I'd *LOVE* to change this.

    Here is the class:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.IO;
    using System.Text;
    using System.Xml.XPath;
    using System.IO.Packaging;
    using System.Reflection;
    using DocumentFormat.OpenXml;
    using DocumentFormat.OpenXml.Drawing.Charts;
    using DocumentFormat.OpenXml.Packaging;
    using DocumentFormat.OpenXml.Spreadsheet;
    using DocumentFormat.OpenXml.Wordprocessing;
    
    
    /// <summary>
    /// Summary description for OpenXMLChartManipulator
    /// </summary>
    public class OpenXMLChartManipulator
    {
    	public OpenXMLChartManipulator()
    	{
    		//
    		// TODO: Add constructor logic here
    		//
    	}
    
    	public enum UpdateType
    	{
    		Cell,
    		Series,
    		Category
    	}
    
    	public enum ChartType
    	{
    		Bar3D,
    		Line
    	}
    
    	#region Private Fields
    
    	/// <summary>
    	/// Contains the word processing document
    	/// </summary>
    	private WordprocessingDocument _wpDoc;
    
    	public WordprocessingDocument WordProcessingDoc
    	{
    		get { return _wpDoc; }
    		set { _wpDoc = value; }
    	}
    
    	/// <summary>
    	/// Contains the main document part
    	/// </summary>
    	private MainDocumentPart _mainDocPart;
    
    	#endregion
    
    	#region Public Methods
    
    	#region OpenTheDocuemnt
    
    	/// <summary>
    	/// Open an Word XML document 
    	/// </summary>
    	/// <param name="docname">name of the document to be opened</param>
    	public void OpenTheDocuemnt( string docname = "" )
    	{
    		// open the word docx
    		if ( docname.Length > 0 )
    		{
    			_wpDoc = WordprocessingDocument.Open( docname, true );
    		}
    
    		// get the Main Document part
    		_mainDocPart = _wpDoc.MainDocumentPart;
    	}
    
    	#endregion
    
    	#region CloseTheDocument
    	/// <summary>
    	/// Close the document
    	/// </summary>
    	public void CloseTheDocument( )
    	{
    		_wpDoc.Close( );
    	}
    
    	#endregion
    
    	#region UpdateChart
    
    	/// <summary>
    	/// Updates the Chart in a word docuemnt.
    	/// </summary>
    	/// <param name="chartIndex">Zero-based index of which chart in the document is to be acted upon</param>
    	/// <param name="cellColumn">Corresponds to the Column that needs to be modified</param>
    	/// <param name="intRow">Corresponds to the Row that needs to be modified</param>
    	/// <param name="newValue">New value of the pointed cell</param>
    	/// <param name="axisValue">Is the new value an Axis Value?</param>
    	public void UpdateChart( int chartIndex, ChartType chartType, string cellColumn, uint intRow, string newValue, UpdateType updateType )
    	{
    		// Gets the Chart stream
    		Stream stream = this._wpDoc.MainDocumentPart.ChartParts.ElementAt( chartIndex ).EmbeddedPackagePart.GetStream( );
    //		Stream stream = this._wpDoc.MainDocumentPart.ChartParts.First( ).EmbeddedPackagePart.GetStream( );
    
    		// Open the internal spreadsheet doc for the chart
    		using ( SpreadsheetDocument wordSSDoc = SpreadsheetDocument.Open( stream, true ) )
    		{
    			// Navigate to the sheet where the chart data is located
    			WorkbookPart workBookPart = wordSSDoc.WorkbookPart;
    			Sheet theSheet = workBookPart.Workbook.Descendants<Sheet>( ).
    			  Where( s => s.Name == "Sheet1" ).FirstOrDefault( );
    			if ( theSheet != null )
    			{
    				Worksheet ws = ( ( WorksheetPart )workBookPart.GetPartById( theSheet.Id ) ).Worksheet;
    
    				// Get the cell which needs to be updated
    				Cell theCell = InsertCellInWorksheet( cellColumn, intRow, ws );
    
    				// Update the cell value
    				theCell.CellValue = new CellValue( newValue );
    				if ( updateType == UpdateType.Series || updateType == UpdateType.Category )
    				{
    					// We are updating the Series or Category Label
    					theCell.DataType = new EnumValue<CellValues>( CellValues.String );
    				}
    				else
    				{
    					// We are updating a numeric chart value
    					theCell.DataType = new EnumValue<CellValues>( CellValues.Number );
    				}
    
    				switch ( chartType )
    				{
    					case ChartType.Bar3D :
    						// Either one of these methods work. It is just to illustrate the different elements that the OpenXML goes through
    						this.Modify3DBarChartDetailed( chartIndex, cellColumn, intRow, newValue, updateType );
    						//this.ModifyChartSimplified( cellColumn, intRow, newValue, updateType );
    						break;
    
    					case ChartType.Line :
    						this.ModifyLineChartDetailed( chartIndex, cellColumn, intRow, newValue, updateType );
    						break;
    				}
    				
    				ws.Save( );
    			}
    		}
    	}
    
    	#endregion
    
    	#endregion
    
    	#region Private Methods
    
    	#region InsertCellInWorksheet
    	/// <summary>
    	/// Gets the cell where the updated value is going to be placed
    	/// </summary>
    	/// <param name="columnName">Corresponds to the Column that needs to be modified</param>
    	/// <param name="rowIndex">Corresponds to the Row that needs to be modified</param>
    	/// <param name="worksheet">WorkSheet to update</param>
    	/// <returns> The cell where the new value is going to get updated to</returns>
    	private static Cell InsertCellInWorksheet( string columnName, uint rowIndex, Worksheet worksheet )
    	{
    		SheetData sheetData = worksheet.GetFirstChild<SheetData>( );
    		string cellReference = columnName + rowIndex;
    		Row row;
    		if ( sheetData.Elements<Row>( ).Where( r => r.RowIndex == rowIndex ).Count( ) != 0 )
    		{
    			row = sheetData.Elements<Row>( ).Where( r => r.RowIndex == rowIndex ).First( );
    		}
    		else
    		{
    			row = new Row( ) { RowIndex = rowIndex };
    			sheetData.Append( row );
    		}
    
    		if ( row.Elements<Cell>( ).Where( c => c.CellReference.Value == columnName + rowIndex ).Count( ) > 0 )
    		{
    			return row.Elements<Cell>( ).Where( c => c.CellReference.Value == cellReference ).First( );
    		}
    		else
    		{
    			Cell refCell = null;
    			foreach ( Cell cell in row.Elements<Cell>( ) )
    			{
    				if ( string.Compare( cell.CellReference.Value, cellReference, true ) > 0 )
    				{
    					refCell = cell;
    					break;
    				}
    			}
    
    			Cell newCell = new Cell( ) { CellReference = cellReference };
    			row.InsertBefore( newCell, refCell );
    			worksheet.Save( );
    			return newCell;
    		}
    	}
    	#endregion
    
    	#region Modify3DBarChartDetailed
    	/// <summary>
    	/// Updates the Cached Chart data in the Word document file
    	/// </summary>
    	/// <param name="cellColumn">Corresponds to the Column that needs to be modified</param>
    	/// <param name="intRow">Corresponds to the Row that needs to be modified</param>
    	/// <param name="cellValue">New value of the pointed cell</param>
    	/// <param name="axisValue">Is the new value an Axis Value?</param>
    	private void Modify3DBarChartDetailed( int chartIndex, string cellColumn, uint intRow, string cellValue, UpdateType updateType )
    	{
    		try
    		{
    			ChartPart c_p = this._mainDocPart.ChartParts.ElementAt( chartIndex );
    //			ChartPart c_p = this._mainDocPart.ChartParts.FirstOrDefault( );
    			Chart chart = c_p.ChartSpace.Descendants<Chart>( ).FirstOrDefault( );
    			PlotArea p_c = chart.PlotArea;
    			Bar3DChart b3d = p_c.Descendants<Bar3DChart>( ).FirstOrDefault( );
    			BarChartSeries bs1 = b3d.Descendants<BarChartSeries>( ).Where( s => string.Compare( s.InnerText, "Sheet1!$" + cellColumn + "$1", true ) > 0 ).First( );
    
    			if ( updateType == UpdateType.Series)
    			{
    				CategoryAxisData v1 = bs1.Descendants<CategoryAxisData>( ).FirstOrDefault( );
    				StringReference sr = v1.Descendants<StringReference>( ).First( );
    				StringCache sc = sr.Descendants<StringCache>( ).First( );
    				StringPoint sp = sc.Descendants<StringPoint>( ).First( );
    				NumericValue nv = sp.Descendants<NumericValue>( ).First( );
    				nv.Text = cellValue;
    			}
    			else if ( updateType == UpdateType.Category )
    			{
    				CategoryAxisData v1 = bs1.Descendants<CategoryAxisData>( ).FirstOrDefault( );
    				StringReference sr = v1.Descendants<StringReference>( ).FirstOrDefault( );
    				StringCache sc = sr.Descendants<StringCache>( ).First( );
    				StringPoint sp = sc.Descendants<StringPoint>( ).ElementAt( ( int )intRow - 2 );
    				NumericValue nv = sp.Descendants<NumericValue>( ).First( );
    				nv.Text = cellValue;
    			}
    			else
    			{
    				DocumentFormat.OpenXml.Drawing.Charts.Values v1 = bs1.Descendants<DocumentFormat.OpenXml.Drawing.Charts.Values>( ).FirstOrDefault( );
    				NumberReference nr = v1.Descendants<NumberReference>( ).First( );
    				NumberingCache nc = nr.Descendants<NumberingCache>( ).First( );
    				NumericPoint np = nc.Descendants<NumericPoint>( ).ElementAt( ( int )intRow - 2 );
    				NumericValue nv = np.Descendants<NumericValue>( ).First( );
    				nv.Text = cellValue;
    			}
    		}
    		catch
    		{
    			// Chart Element is not in a recognizable format. Most likely the defined Chart is incorrect. Ignore the chart creation.
    			return;
    		}
    	}
    	#endregion
    
    
    	#region ModifyLineChartDetailed
    	/// <summary>
    	/// Updates the Cached Chart data in the Word document file
    	/// </summary>
    	/// <param name="cellColumn">Corresponds to the Column that needs to be modified</param>
    	/// <param name="intRow">Corresponds to the Row that needs to be modified</param>
    	/// <param name="cellValue">New value of the pointed cell</param>
    	/// <param name="axisValue">Is the new value an Axis Value?</param>
    	private void ModifyLineChartDetailed( int chartIndex, string cellColumn, uint intRow, string cellValue, UpdateType updateType )
    	{
    		try
    		{
    			ChartPart c_p = this._mainDocPart.ChartParts.ElementAt( chartIndex );
    			//			ChartPart c_p = this._mainDocPart.ChartParts.FirstOrDefault( );
    			Chart chart = c_p.ChartSpace.Descendants<Chart>( ).FirstOrDefault( );
    			PlotArea p_c = chart.PlotArea;
    			LineChart lc = p_c.Descendants<LineChart>( ).FirstOrDefault( );
    			LineChartSeries lcs = lc.Descendants<LineChartSeries>( ).Where( s => string.Compare( s.InnerText, "Sheet1!$" + cellColumn + "$1", true ) > 0 ).First( );
    
    			if ( updateType == UpdateType.Series )
    			{
    				CategoryAxisData v1 = lcs.Descendants<CategoryAxisData>( ).FirstOrDefault( );
    				StringReference sr = v1.Descendants<StringReference>( ).First( );
    				StringCache sc = sr.Descendants<StringCache>( ).First( );
    				StringPoint sp = sc.Descendants<StringPoint>( ).First( );
    				NumericValue nv = sp.Descendants<NumericValue>( ).First( );
    				nv.Text = cellValue;
    			}
    			else if ( updateType == UpdateType.Category )
    			{
    				CategoryAxisData v1 = lcs.Descendants<CategoryAxisData>( ).FirstOrDefault( );
    				StringReference sr = v1.Descendants<StringReference>( ).FirstOrDefault( );
    				StringCache sc = sr.Descendants<StringCache>( ).First( );
    				StringPoint sp = sc.Descendants<StringPoint>( ).ElementAt( ( int )intRow - 2 );
    				NumericValue nv = sp.Descendants<NumericValue>( ).First( );
    				nv.Text = cellValue;
    			}
    			else
    			{
    				DocumentFormat.OpenXml.Drawing.Charts.Values v1 = lcs.Descendants<DocumentFormat.OpenXml.Drawing.Charts.Values>( ).FirstOrDefault( );
    				NumberReference nr = v1.Descendants<NumberReference>( ).First( );
    				NumberingCache nc = nr.Descendants<NumberingCache>( ).First( );
    				NumericPoint np = nc.Descendants<NumericPoint>( ).ElementAt( ( int )intRow - 2 );
    				NumericValue nv = np.Descendants<NumericValue>( ).First( );
    				nv.Text = cellValue;
    			}
    		}
    		catch
    		{
    			// Chart Element is not in a recognizable format. Most likely the defined Chart is incorrect. Ignore the chart creation.
    			return;
    		}
    	}
    	#endregion
    
    	#region ModifyChartSimplified
    	/// <summary>
    	/// Updates the Cached Chart data in the Word document file
    	/// </summary>
    	/// <param name="cellColumn">Corresponds to the Column that needs to be modified</param>
    	/// <param name="intRow">Corresponds to the Row that needs to be modified</param>
    	/// <param name="cellValue">New value of the pointed cell</param>
    	/// <param name="axisValue">Is the new value an Axis Value?</param>
    	private void ModifyChartSimplified( string cellColumn, uint intRow, string cellValue, bool axisValue )
    	{
    		try
    		{
    			ChartPart c_p = this._mainDocPart.ChartParts.FirstOrDefault( );
    			BarChartSeries bs1 = c_p.ChartSpace.Descendants<BarChartSeries>( ).Where( s => string.Compare( s.InnerText, "Sheet1!$" + cellColumn + "$1", true ) > 0 ).First( );
    			if ( axisValue )
    			{
    				NumericValue nv1 = bs1.Descendants<NumericValue>( ).First( );
    				nv1.Text = cellValue;
    			}
    			else
    			{
    				// 
    				DocumentFormat.OpenXml.Drawing.Charts.Values v1 = bs1.Descendants<DocumentFormat.OpenXml.Drawing.Charts.Values>( ).FirstOrDefault( );
    				NumericPoint np = v1.Descendants<NumericPoint>( ).ElementAt( ( int )intRow - 2 );
    				NumericValue nv = np.Descendants<NumericValue>( ).First( );
    				nv.Text = cellValue;
    			}
    		}
    		catch
    		{
    			// Chart Element is not in a recognizable format. Most likely the defined Chart is incorrect. Ignore the chart creation.
    			return;
    		}
    	}
    	#endregion
    
    	#endregion
    
    }


    JP


    Saturday, September 21, 2013 4:06 PM
  • Any Luck with this? the script looks good i am looking at doing something similar, really like how things are layed out so far.
    Saturday, November 23, 2013 10:21 PM
  • I am also in similar situation but I have around 60 charts in a single document and I cannot rely on index because index is changing randomly.

    I would really like to solve this problem by identifying each chart with unique Title but I have no luck yet. I am new to OpenXML SDK and Object map so just exploring more, however if anyone has solution for this then I would greatly appreciate it.

    Friday, April 11, 2014 8:49 PM