为什么在此示例中附加AutoFilter会破坏我的excel文件?

时间:2014-08-20 06:52:11

标签: c# .net excel openxml openxml-sdk

您好我使用以下方法应用AutoFilter:

public static void ApplyAutofilter(string fileName, string sheetName, string reference)
        {
            using (SpreadsheetDocument document = SpreadsheetDocument.Open(fileName, true))
            {
                IEnumerable<Sheet> sheets = document.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>().Where(s => s.Name == sheetName);
                var arrSheets = sheets as Sheet[] ?? sheets.ToArray();

                string relationshipId = arrSheets.First().Id.Value;
                var worksheetPart = (WorksheetPart)document.WorkbookPart.GetPartById(relationshipId);

                var autoFilter = new AutoFilter() { Reference = reference };
                worksheetPart.Worksheet.Append(autoFilter);

                worksheetPart.Worksheet.Save();
            }
        }

我有这个简单的Excel表格:

enter image description here

当我将此方法称为:

ApplyAutofilter(@".\Test.xlsx", "Foo", "A0:A200");

ApplyAutofilter(@".\Test.xlsx", "Foo", "A1:A200");

我尝试在以下位置打开后,未应用过滤器并且文件已损坏:

enter image description here

问题:您是否知道如何将此自动加载工具应用于Excel工作表而不会破坏它?

2 个答案:

答案 0 :(得分:8)

这是因为在哪里您正在编写XML中的autoFilter元素。我打开了一个文件,我已经运行了你的代码并在Open XML Productivity Tool中打开了它。这表明错误是

Error message: Unexpected Child Element

here查看ECMA-376标准Worksheet的XML如下所示:

<xsd:complexType name="CT_Worksheet">
  <xsd:sequence>
    <xsd:element name="sheetPr" type="CT_SheetPr" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="dimension" type="CT_SheetDimension" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="sheetViews" type="CT_SheetViews" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="sheetFormatPr" type="CT_SheetFormatPr" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="cols" type="CT_Cols" minOccurs="0" maxOccurs="unbounded"/>
    <xsd:element name="sheetData" type="CT_SheetData" minOccurs="1" maxOccurs="1"/>
    <xsd:element name="sheetCalcPr" type="CT_SheetCalcPr" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="sheetProtection" type="CT_SheetProtection" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="protectedRanges" type="CT_ProtectedRanges" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="scenarios" type="CT_Scenarios" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="autoFilter" type="CT_AutoFilter" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="sortState" type="CT_SortState" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="dataConsolidate" type="CT_DataConsolidate" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="customSheetViews" type="CT_CustomSheetViews" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="mergeCells" type="CT_MergeCells" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="phoneticPr" type="CT_PhoneticPr" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="conditionalFormatting" type="CT_ConditionalFormatting" minOccurs="0" maxOccurs="unbounded"/>
    <xsd:element name="dataValidations" type="CT_DataValidations" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="hyperlinks" type="CT_Hyperlinks" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="printOptions" type="CT_PrintOptions" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="pageMargins" type="CT_PageMargins" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="pageSetup" type="CT_PageSetup" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="headerFooter" type="CT_HeaderFooter" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="rowBreaks" type="CT_PageBreak" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="colBreaks" type="CT_PageBreak" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="customProperties" type="CT_CustomProperties" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="cellWatches" type="CT_CellWatches" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="ignoredErrors" type="CT_IgnoredErrors" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="smartTags" type="CT_SmartTags" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="drawing" type="CT_Drawing" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="drawingHF" type="CT_DrawingHF" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="picture" type="CT_SheetBackgroundPicture" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="oleObjects" type="CT_OleObjects" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="controls" type="CT_Controls" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="webPublishItems" type="CT_WebPublishItems" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="tableParts" type="CT_TableParts" minOccurs="0" maxOccurs="1"/>
    <xsd:element name="extLst" type="CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
  </xsd:sequence>
</xsd:complexType>

请注意,它是sequence,因此项目的顺序很重要。在上面的示例文件中,在autoFilter元素之后添加了pageMargins元素,这与上述模式相反。

更改代码以将autoFilter元素写入正确的位置可修复代码。我不确定这是否是最有效的方式,但这应该有效。它本质上是向后工作workbook的子元素,直到它找到autoFilter之后的第一个元素。一旦发现它使用InsertAfter方法将autoFilter插入正确的位置:

public static void ApplyAutofilter(string fileName, string sheetName, string reference)
{
    using (SpreadsheetDocument document = SpreadsheetDocument.Open(fileName, true))
    {
        IEnumerable<Sheet> sheets = document.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>().Where(s => s.Name == sheetName);
        var arrSheets = sheets as Sheet[] ?? sheets.ToArray();

        string relationshipId = arrSheets.First().Id.Value;
        var worksheetPart = (WorksheetPart)document.WorkbookPart.GetPartById(relationshipId);

        var autoFilter = new AutoFilter() { Reference = reference };
        OpenXmlElement preceedingElement = GetPreceedingElement(worksheetPart);
        worksheetPart.Worksheet.InsertAfter(autoFilter, preceedingElement);

        worksheetPart.Worksheet.Save();
    }
}

public static OpenXmlElement GetPreceedingElement(WorksheetPart worksheetPart)
{
    List<Type> elements = new List<Type>()
    {
        typeof(Scenarios),
        typeof(ProtectedRanges),
        typeof(SheetProtection),
        typeof(SheetCalculationProperties),
        typeof(SheetData)
    };

    OpenXmlElement preceedingElement = null;
    foreach (var item in worksheetPart.Worksheet.ChildElements.Reverse())
    {
        if (elements.Contains(item.GetType()))
        {
            preceedingElement = item;
            break;
        }
    }

    return preceedingElement;
}

答案 1 :(得分:0)

好!这可行!它帮助我解决了我的问题。使用以下内容有所不同

OpenXmlElement preceedingElement = GetPreceedingElement(worksheetPart);
worksheetPart.Worksheet.InsertAfter(autoFilter, preceedingElement);