我正在尝试使用openxml来生成自动excel文件。我面临的一个问题是使用excel的开放式xml对象模型来容纳我的对象模型。我必须明白我为工作表附加子元素的顺序很重要。
例如:
workSheet.Append(sheetViews);
workSheet.Append(columns);
workSheet.Append(sheetData);
workSheet.Append(mergeCells);
workSheet.Append(drawing);
上述排序不会产生任何错误。
但是以下内容:
workSheet.Append(sheetViews);
workSheet.Append(columns);
workSheet.Append(sheetData);
workSheet.Append(drawing);
workSheet.Append(mergeCells);
给出错误
所以这不允许我随时创建一个绘图对象并将其附加到工作表中。这迫使我在使用它们之前创建这些元素。
有人能告诉我,我是否正确理解了这个问题?因为我相信我们应该能够打开任何excel文件,必要时为工作表创建一个新的子元素并附加它。但现在这可能会打破这些元素应该被附加的顺序。
感谢。
答案 0 :(得分:8)
根据Standard ECMA-376 Office Open XML File Formats,CT_Worksheet
具有所需的序列:
以下内容崩溃的原因:
workSheet.Append(sheetViews);
workSheet.Append(columns);
workSheet.Append(sheetData);
workSheet.Append(drawing);
workSheet.Append(mergeCells);
是因为您之前有drawing
mergeCells
。只要您在 mergeCells
之后追加drawing
,您的代码就可以正常运行。
注意:您可以在ECMA-376 3rd edition Part 1 (.zip) - >中找到完整的XSD。 OfficeOpenXML-XMLSchema-Strict - > sml.xsd。
答案 1 :(得分:1)
我发现对于父对象都有一个Property定义的所有“Singleton”子节点(例如Worksheet.sheetViews),使用singleton属性并将新对象赋值给它而不是使用“Append”这会导致类本身确保订单正确。
workSheet.Append(sheetViews);
workSheet.Append(columns);
workSheet.Append(sheetData); // bad idea(though it does work if the order is good)
workSheet.Append(drawing);
workSheet.Append(mergeCells);
更正确的格式......
workSheet.sheetViews=sheetViews; // order doesn't matter.
workSheet.columns=columns;
...
答案 2 :(得分:1)
对于那些像我一样通过 Google 到达这里的人,下面的函数解决了插入子元素后的排序问题:
public static T ReorderChildren<T>(T element) where T : OpenXmlElement
{
Dictionary<Type, int> childOrderHashTable = element.GetType()
.GetCustomAttributes()
.Where(x => x is ChildElementInfoAttribute)
.Select( (x, idx) => new KeyValuePair<Type, int>(((ChildElementInfoAttribute)x).ElementType, idx))
.ToDictionary(x => x.Key, x => x.Value);
List<OpenXmlElement> reorderedChildren = element.ChildElements
.OrderBy(x => childOrderHashTable[x.GetType()])
.ToList();
element.RemoveAllChildren();
element.Append(reorderedChildren);
return element;
}
DocumentFormat.OpenXml
库中生成的类型具有可用于反映来自 OOXML 架构的元数据的自定义属性。此解决方案依赖于 System.Reflection
和 System.Linq
(即速度不是很快),但无需对字符串列表进行硬编码以正确排序特定类型的子元素。
我在对 ValidationErrorInfo.Node
属性进行验证后使用此函数,并通过引用清理新创建的元素。这样我就不会在整个文档中递归地应用这个方法。
答案 3 :(得分:0)
作为Joe Masilotti already explained,订单在架构中定义。
不幸的是,OpenXML库无法确保基础XML模式所要求的序列化XML中子元素的正确顺序。如果订单不正确,应用程序可能无法成功解析XML。
以下是我在代码中使用的通用解决方案:
private T GetOrCreateWorksheetChildCollection<T>(Spreadsheet.Worksheet worksheet)
where T : OpenXmlCompositeElement, new()
{
T collection = worksheet.GetFirstChild<T>();
if (collection == null)
{
collection = new T();
if (!worksheet.HasChildren)
{
worksheet.AppendChild(collection);
}
else
{
// compute the positions of all child elements (existing + new collection)
List<int> schemaPositions = worksheet.ChildElements
.Select(e => _childElementNames.IndexOf(e.LocalName)).ToList();
int collectionSchemaPos = _childElementNames.IndexOf(collection.LocalName);
schemaPositions.Add(collectionSchemaPos);
schemaPositions = schemaPositions.OrderBy(i => i).ToList();
// now get the index where the position of the new child is
int index = schemaPositions.IndexOf(collectionSchemaPos);
// this is the index to insert the new element
worksheet.InsertAt(collection, index);
}
}
return collection;
}
// names and order of possible child elements according to the openXML schema
private static readonly List<string> _childElementNames = new List<string>() {
"sheetPr", "dimension", "sheetViews", "sheetFormatPr", "cols", "sheetData",
"sheetCalcPr", "sheetProtection", "protectedRanges", "scenarios", "autoFilter",
"sortState", "dataConsolidate", "customSheetViews", "mergeCells", "phoneticPr",
"conditionalFormatting", "dataValidations", "hyperlinks", "printOptions",
"pageMargins", "pageSetup", "headerFooter", "rowBreaks", "colBreaks",
"customProperties", "cellWatches", "ignoredErrors", "smartTags", "drawing",
"drawingHF", "picture", "oleObjects", "controls", "webPublishItems", "tableParts",
"extLst"
};
该方法始终将新的子元素插入到正确的位置,以确保生成的文档有效。
答案 4 :(得分:0)
helb的答案很美-谢谢您,helb。
它有一个轻微的缺点,即不测试子元素的顺序是否已经存在问题。下面的微小修改可确保在添加新元素时没有先前存在的问题(您仍然需要他的_childElementNames
,这是无价的),并且效率更高:
private static int getChildElementOrderIndex(OpenXmlElement collection)
{
int orderIndex = _childElementNames.IndexOf(collection.LocalName);
if( orderIndex < 0)
throw new InvalidOperationException($"Internal: worksheet part {collection.LocalName} not found");
return orderIndex;
}
private static T GetOrCreateWorksheetChildCollection<T>(Worksheet worksheet) where T : OpenXmlCompositeElement, new()
{
T collection = worksheet.GetFirstChild<T>();
if (collection == null)
{
collection = new T();
if (!worksheet.HasChildren)
{
worksheet.AppendChild(collection);
}
else
{
int collectionSchemaPos = getChildElementOrderIndex(collection);
int insertPos = 0;
int lastOrderNum = -1;
for(int i=0; i<worksheet.ChildElements.Count; ++i)
{
int thisOrderNum = getChildElementOrderIndex(worksheet.ChildElements[i]);
if(thisOrderNum<=lastOrderNum)
throw new InvalidOperationException($"Internal: worksheet parts {_childElementNames[lastOrderNum]} and {_childElementNames[thisOrderNum]} out of order");
lastOrderNum = thisOrderNum;
if( thisOrderNum < collectionSchemaPos )
++insertPos;
}
// this is the index to insert the new element
worksheet.InsertAt(collection, insertPos);
}
}
return collection;
}