【问题标题】:Define OpenXml Styles in pre-existing spreadsheet在预先存在的电子表格中定义 OpenXml 样式
【发布时间】:2018-02-17 18:34:34
【问题描述】:

我正在使用 OpenXml SDK 生成电子表格。我目前使用在样式表中定义的大约 5 种不同的单元格格式。假设我正在创建一个全新的文档,这很好用。

样式表如下所示:

<?xml version="1.0" encoding="utf-8"?>
<x:styleSheet xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
    <x:fonts>
        <x:font>
            <x:sz val="11" />
        </x:font>
        <x:font>
            <x:sz val="18" />
        </x:font>
        <x:font>
            <x:i />
            <x:sz val="11" />
        </x:font>
    </x:fonts>
    <x:fills>
        <x:fill>
            <x:patternFill patternType="none" />
        </x:fill>
        <x:fill>
            <x:patternFill patternType="gray125" />
        </x:fill>
        <x:fill>
            <x:patternFill patternType="solid">
                <x:fgColor rgb="C0C0C0" />
            </x:patternFill>
        </x:fill>
        <x:fill>
            <x:patternFill patternType="solid">
                <x:fgColor rgb="DCDCDC" />
            </x:patternFill>
        </x:fill>
    </x:fills>
    <x:borders>
        <x:border />
        <x:border>
            <x:left style="thin" />
            <x:right style="thin" />
            <x:top />
            <x:bottom />
            <x:diagonal />
        </x:border>
    </x:borders>
    <x:cellXfs>
        <x:xf />
        <x:xf fontId="1" fillId="2" borderId="1">
            <x:alignment horizontal="left" vertical="top" wrapText="1" />
        </x:xf>
        <x:xf fontId="0" fillId="0" borderId="1">
            <x:alignment horizontal="left" vertical="top" wrapText="1" />
        </x:xf>
        <x:xf>
            <x:alignment horizontal="left" vertical="top" wrapText="1" />
        </x:xf>
        <x:xf fontId="2" fillId="0" borderId="1">
            <x:alignment horizontal="left" vertical="top" wrapText="1" />
        </x:xf>
        <x:xf fontId="1" fillId="3" borderId="1">
            <x:alignment horizontal="left" vertical="top" wrapText="1" />
        </x:xf>
    </x:cellXfs>
</x:styleSheet>

我想支持第二种情况,我的程序将新工作表添加到预先存在的电子表格中。

这可能是:

  • 我的程序创建的电子表格
  • 由我的程序创建但在 Excel 中修改的电子表格
  • 由 Excel 创建的电子表格

在现有电子表格中创建样式的最佳策略是什么?

理想情况下,我想检测所需的样式是否已经存在,如果存在则重用它们。仅根据其定义检测样式将是混乱的代码。如果我可以为我定义的 xf 元素分配一个唯一的名称,那将更加实用。

我知道我可以在 cellStyles 节点中定义命名样式,它引用 cellStyleXfs 集合中的项目,例如:

<x:cellStyleXfs>
    <x:xf />
    <x:xf fontId="1" fillId="2" borderId="1">
        <x:alignment horizontal="left" vertical="top" wrapText="1" />
    </x:xf>
    <x:xf fontId="0" fillId="0" borderId="1">
        <x:alignment horizontal="left" vertical="top" wrapText="1" />
    </x:xf>
    <x:xf>
        <x:alignment horizontal="left" vertical="top" wrapText="1" />
    </x:xf>
    <x:xf fontId="2" fillId="0" borderId="1">
        <x:alignment horizontal="left" vertical="top" wrapText="1" />
    </x:xf>
    <x:xf fontId="1" fillId="3" borderId="1">
        <x:alignment horizontal="left" vertical="top" wrapText="1" />
    </x:xf>
</x:cellStyleXfs>

<x:cellStyles>
    <x:cellStyle name="Standard"              xfId="0" />
    <x:cellStyle name="ML_Header"             xfId="1" />
    <x:cellStyle name="ML_LanguageText"       xfId="2" />
    <x:cellStyle name="ML_DefaultLeftAligned" xfId="3" />
    <x:cellStyle name="ML_CommentText"        xfId="4" />
    <x:cellStyle name="ML_SubHeader"          xfId="5" />
</x:cellStyles>

但是,我无法弄清楚 cellStyleXfs 集合和 callXfs 集合中的项目之间的关系。

此外,Excel 的行为就像它拥有 cellStyles 和 cellStyleXfs 节点一样。如果我在 excel 中编辑电子表格,则会更改定义并删除其中的一些。

目前,这看起来不像是一个实用的解决方案。

有什么方法可以为 cellXfs 元素中的 xf 节点分配名称?那会让生活更轻松。

另一种方法是在每次生成工作表时定义新样式(以及字体、填充和边框)。重复执行,每次都会使文件变大。

最好的策略是什么?

【问题讨论】:

    标签: .net excel openxml openxml-sdk


    【解决方案1】:

    我的策略当然不完美,但对我的目的来说已经足够了。

    我决定了

    • 用一组固定的属性来描述我使用的样式
    • 定义一个查找与这些属性匹配的样式的函数
    • 如果找不到,则创建一个新样式

    作为最低要求,我希望该函数至少能找到它自己生成的样式。

    如果我的应用程序导出到同一个 excel 文件 100 次,我不想生成 100 次相同的样式,使 excel 文件每次都变大一点。

    我指定的属性集不完整。例如,我没有指定字体系列。如果有一种样式与我的所有属性相匹配,但使用了不同的字体,我仍然会使用这种样式。这对我来说没问题,但在另一个应用程序中可能无法接受。

    由于历史原因,我的代码是用 VB 编写的,但可以很容易地移植到 C#。

    这是我的代码的略微简化的版本。有 19 种不同的样式,由函数 CreateStyleSheet() 初始化。如果样式表根本不存在,则在检查各个样式之前创建它。

    查找或创建样式的主要函数是 FindCellFormat。它的 15 个参数定义了它检查的集合或属性。

    对于 8 种样式,有一个辅助函数 FindCellFormatForStatus,但这并不是特别相关。

    Imports DocumentFormat.OpenXml
    Imports DocumentFormat.OpenXml.Packaging
    Imports DocumentFormat.OpenXml.Spreadsheet
    
    Friend Class clsOpenXmlUtil
    
      'CellFormat IDs
      Public Property CellFormat_ColumnHeader                  As Integer
      Public Property CellFormat_LanguageText                  As Integer
      Public Property CellFormat_DefaultLeftAligned            As Integer
      Public Property CellFormat_CommentText                   As Integer
      Public Property CellFormat_ColumnSubHeader               As Integer
      Public Property CellFormat_ColumnHeaderSmall             As Integer
    
      Public Property CellFormat_Location_UI                   As Integer
      Public Property CellFormat_Location_Code                 As Integer
      Public Property CellFormat_Location_Resource             As Integer
      Public Property CellFormat_MultipleUsageTrue             As Integer
    
      Public Property CellFormat_ComponentHeader               As Integer
    
      Public Property CellFormat_Status_Unknown                As Integer
      Public Property CellFormat_Status_OriginalText           As Integer
      Public Property CellFormat_Status_Edited                 As Integer
      Public Property CellFormat_Status_GlobalDatabase         As Integer
      Public Property CellFormat_Status_ExcelImport            As Integer
      Public Property CellFormat_Status_OnlineTranslation      As Integer
      Public Property CellFormat_Status_ImportedLocalization   As Integer
      Public Property CellFormat_Status_OutOfDate              As Integer
    
      Public Sub CreateStyleSheet ( ExlBookPart As WorkbookPart )
    
        If ExlBookPart.GetPartsCountOfType(Of WorkbookStylesPart) = 0 Then
    
          Dim ss                    = New Stylesheet()
          Dim fontCollection        = New Fonts()
          Dim fillCollection        = New Fills()
          Dim borderCollection      = New Borders()
          Dim formatCollection      = New CellFormats()
    
          'This block only defines the default styles.
          'The styles which we actually use are defined in a second step.
    
          fontCollection.Append   ( New Font        With { .FontSize       = New FontSize    With { .Val         = 11 } } )
    
          fillCollection.Append   ( New Fill        With { .PatternFill    = New PatternFill With { .PatternType = PatternValues.None } } )
          fillCollection.Append   ( New Fill        With { .PatternFill    = New PatternFill With { .PatternType = PatternValues.Gray125 } } )
    
          borderCollection.Append ( New Border() )
    
          formatCollection.Append ( New CellFormat() )
    
          ss.Append ( fontCollection )
          ss.Append ( fillCollection )
          ss.Append ( borderCollection )
          ss.Append ( formatCollection )
    
          Dim ExlStyleSheetPart = ExlBookPart.AddNewPart(Of WorkbookStylesPart)
          ExlStyleSheetPart.Stylesheet = ss
    
        End If
    
        Dim ExlStyleSheet As Stylesheet = ExlBookPart.WorkbookStylesPart.Stylesheet
    
        'Check for cell formats with specific attributes. Create the cell formats if necessary.
        CellFormat_ColumnHeader       = FindCellFormat ( ExlStyleSheet, 18, False, False, "C0C0C0", "", BorderStyleValues.Thin, BorderStyleValues.Thin, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, HorizontalAlignmentValues.Left, VerticalAlignmentValues.Top, True, False )
        CellFormat_LanguageText       = FindCellFormat ( ExlStyleSheet, 11, False, False, "",       "", BorderStyleValues.Thin, BorderStyleValues.Thin, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, HorizontalAlignmentValues.Left, VerticalAlignmentValues.Top, True, True )
        CellFormat_DefaultLeftAligned = FindCellFormat ( ExlStyleSheet, 11, False, False, "",       "", BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, HorizontalAlignmentValues.Left, VerticalAlignmentValues.Top, True, False )
        CellFormat_CommentText        = FindCellFormat ( ExlStyleSheet, 11, True,  False, "",       "", BorderStyleValues.Thin, BorderStyleValues.Thin, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, HorizontalAlignmentValues.Left, VerticalAlignmentValues.Top, True, True )
        CellFormat_ColumnSubHeader    = FindCellFormat ( ExlStyleSheet, 18, False, False, "DCDCDC", "", BorderStyleValues.Thin, BorderStyleValues.Thin, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, HorizontalAlignmentValues.Left, VerticalAlignmentValues.Top, True, False )
        CellFormat_ColumnHeaderSmall  = FindCellFormat ( ExlStyleSheet, 11, False, False, "C0C0C0", "", BorderStyleValues.Thin, BorderStyleValues.Thin, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, HorizontalAlignmentValues.Left, VerticalAlignmentValues.Top, True, False )
    
        CellFormat_Location_UI        = FindCellFormat ( ExlStyleSheet, 11, False, False, "CCFFCC", "", BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, HorizontalAlignmentValues.Left, VerticalAlignmentValues.Top, True, False )
        CellFormat_Location_Code      = FindCellFormat ( ExlStyleSheet, 11, False, False, "FFFF99", "", BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, HorizontalAlignmentValues.Left, VerticalAlignmentValues.Top, True, False )
        CellFormat_Location_Resource  = FindCellFormat ( ExlStyleSheet, 11, False, False, "CC99FF", "", BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, HorizontalAlignmentValues.Left, VerticalAlignmentValues.Top, True, False )
        CellFormat_MultipleUsageTrue  = FindCellFormat ( ExlStyleSheet, 11, False, False, "CCFFFF", "", BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.None, HorizontalAlignmentValues.Left, VerticalAlignmentValues.Top, True, False )
    
        CellFormat_ComponentHeader    = FindCellFormat ( ExlStyleSheet, 18, False, False, "FFFF99", "", BorderStyleValues.None, BorderStyleValues.None, BorderStyleValues.Thin, BorderStyleValues.Thin, BorderStyleValues.None, HorizontalAlignmentValues.Left, VerticalAlignmentValues.Top, True, False )
    
        CellFormat_Status_Unknown              = FindCellFormatForStatus ( ExlStyleSheet, GeneralSettings.ExcelBackground_Unknown,              GeneralSettings.ExcelForeground_Unknown              )
        CellFormat_Status_OriginalText         = FindCellFormatForStatus ( ExlStyleSheet, GeneralSettings.ExcelBackground_OriginalText,         GeneralSettings.ExcelForeground_OriginalText         )
        CellFormat_Status_Edited               = FindCellFormatForStatus ( ExlStyleSheet, GeneralSettings.ExcelBackground_Edited,               GeneralSettings.ExcelForeground_Edited               )
        CellFormat_Status_GlobalDatabase       = FindCellFormatForStatus ( ExlStyleSheet, GeneralSettings.ExcelBackground_GlobalDatabase,       GeneralSettings.ExcelForeground_GlobalDatabase       )
        CellFormat_Status_ExcelImport          = FindCellFormatForStatus ( ExlStyleSheet, GeneralSettings.ExcelBackground_ExcelImport,          GeneralSettings.ExcelForeground_ExcelImport          )
        CellFormat_Status_OnlineTranslation    = FindCellFormatForStatus ( ExlStyleSheet, GeneralSettings.ExcelBackground_OnlineTranslation,    GeneralSettings.ExcelForeground_OnlineTranslation    )
        CellFormat_Status_ImportedLocalization = FindCellFormatForStatus ( ExlStyleSheet, GeneralSettings.ExcelBackground_ImportedLocalization, GeneralSettings.ExcelForeground_ImportedLocalization )
        CellFormat_Status_OutOfDate            = FindCellFormatForStatus ( ExlStyleSheet, GeneralSettings.ExcelBackground_OutOfDate,            GeneralSettings.ExcelForeground_OutOfDate            )
    
        'So far as I can tell, this is not necessary.
        'ExlStyleSheet.Save()
    
      End Sub
    
      Public Function FindCellFormatForStatus ( ExlStyleSheet        As Stylesheet,
                                                FillColour           As System.Drawing.Color,
                                                FontColour           As System.Drawing.Color ) As Integer
    
        Dim FillColourString = $"{FillColour.R:X2}{FillColour.G:X2}{FillColour.B:X2}"
        Dim FontColourString = $"{FontColour.R:X2}{FontColour.G:X2}{FontColour.B:X2}"
    
        Return FindCellFormat ( ExlStyleSheet,
                                11,
                                False,
                                False,
                                FillColourString,
                                FontColourString,
                                BorderStyleValues.Thin,
                                BorderStyleValues.Thin,
                                BorderStyleValues.None,
                                BorderStyleValues.None,
                                BorderStyleValues.None,
                                HorizontalAlignmentValues.Left,
                                VerticalAlignmentValues.Top,
                                True,
                                True)
    
      End Function
    
      Public Function FindCellFormat ( ExlStyleSheet        As Stylesheet,
                                       FontSize             As Integer,
                                       FontItalic           As Boolean,
                                       FontBold             As Boolean,
                                       FillColour           As String,
                                       FontColour           As String,
                                       LeftBorderStyle      As BorderStyleValues,
                                       RightBorderStyle     As BorderStyleValues,
                                       TopBorderStyle       As BorderStyleValues,
                                       BottomBorderStyle    As BorderStyleValues,
                                       DiagonalBorderStyle  As BorderStyleValues,
                                       HorizontalAlignment  As HorizontalAlignmentValues,
                                       VerticalAlignment    As VerticalAlignmentValues,
                                       WrapText             As Boolean,
                                       Unlocked             As Boolean ) As Integer
    
        Dim formatCollection  As CellFormats = ExlStyleSheet.GetFirstChild(Of CellFormats)
        Dim fontCollection    As Fonts       = ExlStyleSheet.GetFirstChild(Of Fonts)
        Dim fillCollection    As Fills       = ExlStyleSheet.GetFirstChild(Of Fills)
        Dim borderCollection  As Borders     = ExlStyleSheet.GetFirstChild(Of Borders)
    
        Dim FormatId          As Integer = -1
    
        If      formatCollection IsNot Nothing _
        AndAlso fontCollection   IsNot Nothing _
        AndAlso fillCollection   IsNot Nothing _
        AndAlso borderCollection IsNot Nothing _
        Then
    
          Dim Count As Integer = formatCollection.Elements.Count
          For i As Integer = 0 To Count-1
    
            Try
    
              Dim format      As CellFormat = formatCollection.ElementAt(i)
    
              'I have decided to skip cell formats without related Font, Fill or Border objects.
              If format.FontId   Is Nothing Then Continue For
              If format.FillId   Is Nothing Then Continue For
              If format.BorderId Is Nothing Then Continue For
    
              'These should now succeed.
              Dim Font   As Font   = fontCollection.ElementAt(format.FontId.Value)
              Dim Fill   As Fill   = fillCollection.ElementAt(format.FillId.Value)
              Dim Border As Border = borderCollection.ElementAt(format.BorderId.Value)
    
              'Read some values from the format, where we must check for null.
    
              'As far as I can tell, the italic is usually indicated by the presence of an Italic object.
              'Italic.Value is usually null. If Italic.Value is not null, then I guess we have to consider its value.
              'The same applies to Bold.
              Dim IsItalic              = Font.Italic IsNot Nothing
              Dim IsBold                = Font.Bold   IsNot Nothing
              Dim IsFontSize            = If ( Font?.FontSize.Val.Value, -1 )
    
              If IsItalic AndAlso Font.Italic.Val IsNot Nothing Then
                IsItalic = Font.Italic.Val.Value
              End If
    
              If IsBold AndAlso Font.Bold.Val IsNot Nothing Then
                IsBold = Font.Bold.Val.Value
              End If
    
              Dim IsLeftBorderStyle     = if ( Border.LeftBorder?.Style     Is Nothing, BorderStyleValues.None, Border.LeftBorder.Style.Value     )
              Dim IsRightBorderStyle    = if ( Border.RightBorder?.Style    Is Nothing, BorderStyleValues.None, Border.RightBorder.Style.Value    )
              Dim IsTopBorderStyle      = if ( Border.TopBorder?.Style      Is Nothing, BorderStyleValues.None, Border.TopBorder.Style.Value      )
              Dim IsBottomBorderStyle   = if ( Border.BottomBorder?.Style   Is Nothing, BorderStyleValues.None, Border.BottomBorder.Style.Value   )
              Dim IsDiagonalBorderStyle = if ( Border.DiagonalBorder?.Style Is Nothing, BorderStyleValues.None, Border.DiagonalBorder.Style.Value )
    
              Dim IsFillColour = ""
              If Fill.PatternFill.PatternType.Value = PatternValues.Solid Then
                'I believe that I have seen both 8 and 6 character values.
                'Tentatively only compare 6 characters
                IsFillColour = If ( Fill.PatternFill?.ForegroundColor?.Rgb?.Value, "" )
                If IsFillColour.Length = 8 Then
                  IsFillColour = IsFillColour.Substring(2)
                End If
              End If
    
              Dim IsFontColour = If ( Font.Color?.Rgb?.Value, "" )
              If IsFontColour.Length = 8 Then
                IsFontColour = IsFontColour.Substring(2)
              End If
    
              'Unlocked is only meaningful if protection is applies to the worksheet.
              'In this case, all cells are locked unless they are specifically unlocked.
              'As far as I can tell this requires the attribute applyProtection="1" and the child node <protection locked="0">.
              'See also the YouTube video https://www.youtube.com/watch?time_continue=20&v=KN2Q0vWMd8k
              Dim IsUnlocked = if ( format.ApplyProtection?.Value, False ) AndAlso Not If ( format.Protection?.Locked?.Value, True )
    
              'Always check for null
              Dim IsHorizontalAlignment  = If ( format.Alignment?.Horizontal?.Value, HorizontalAlignmentValues.General )
              Dim IsVerticalAlignment    = If ( format.Alignment?.Vertical?.Value,   VerticalAlignmentValues.Bottom )
              Dim IsWrapText             = If ( format.Alignment?.WrapText?.Value,   False )
    
              'Finally start comparing stuff
              If Font.FontSize.Val.Value           <> FontSize            Then Continue For
    
              If IsItalic                          <> FontItalic          Then Continue For
              If IsBold                            <> FontBold            Then Continue For
    
              If IsFillColour                      <> FillColour          Then Continue For
              If IsFontColour                      <> FontColour          Then Continue For
    
              If IsLeftBorderStyle                 <> LeftBorderStyle     Then Continue For
              If IsRightBorderStyle                <> RightBorderStyle    Then Continue For
              If IsTopBorderStyle                  <> TopBorderStyle      Then Continue For
              If IsBottomBorderStyle               <> BottomBorderStyle   Then Continue For
              If IsDiagonalBorderStyle             <> DiagonalBorderStyle Then Continue For
    
              If IsHorizontalAlignment             <> HorizontalAlignment Then Continue For
              If IsVerticalAlignment               <> VerticalAlignment   Then Continue For
              If IsWrapText                        <> WrapText            Then Continue For
    
              If IsUnlocked                        <> Unlocked            Then Continue For
    
              'If we get this far, we will consider it to be a match.
              FormatId = i
              Exit For
    
            Catch ex As Exception
              'We'll take that as a no shall we?
              Continue For
            End Try
    
          Next
    
          If FormatId = -1 Then
    
            'We did not find the format, so create a new one.
            'The new IDs will be the collection size BEFORE adding a new element.
            Dim FontId   = fontCollection.Elements.Count
            Dim FillId   = fillCollection.Elements.Count
            Dim BorderId = borderCollection.Elements.Count
            FormatId     = formatCollection.Elements.Count
    
            Dim TextColour  As Color = Nothing
            If FontColour.HasContent() Then
              TextColour = New Color With { .Rgb = New HexBinaryValue ( FontColour ) }
            End If
    
            fontCollection.Append   ( New Font   With { .FontSize       = New FontSize       With { .Val = FontSize   },
                                                        .Italic         = New Italic         With { .Val = FontItalic },
                                                        .Bold           = New Bold           With { .Val = FontBold   },
                                                        .Color          = TextColour } )
    
            borderCollection.Append ( New Border With { .LeftBorder     = New LeftBorder     With { .Style = LeftBorderStyle     },
                                                        .RightBorder    = New RightBorder    With { .Style = RightBorderStyle    },
                                                        .TopBorder      = New TopBorder      With { .Style = TopBorderStyle      },
                                                        .BottomBorder   = New BottomBorder   With { .Style = BottomBorderStyle   },
                                                        .DiagonalBorder = New DiagonalBorder With { .Style = DiagonalBorderStyle } } )
    
            If String.IsNullOrEmpty(FillColour) Then
              'Use default fill
              FillId = 0
            Else
              Dim fgColour = New ForegroundColor With { .Rgb = New HexBinaryValue ( FillColour ) }
              fillCollection.Append ( New Fill With { .PatternFill = New PatternFill ( fgColour ) With { .PatternType = PatternValues.Solid } } )
            End If
    
            Dim align  = New Alignment With { .Horizontal = HorizontalAlignment, .Vertical = VerticalAlignment, .WrapText = WrapText }
            Dim format = New CellFormat ( align ) With { .FontId=FontId, .FillId=FillId, .BorderId=BorderId }
            formatCollection.Append ( format )
    
            If Unlocked Then
              format.ApplyProtection = True
              format.Protection = New Protection With { .Locked = False }
            End If
    
          End If
    
        End If
    
        Return FormatId
    
      End Function
    
    End Class
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多