在预先存在的电子表格中定义 OpenXml 样式
Define OpenXml Styles in pre-existing spreadsheet
我正在使用 OpenXml SDK 生成传播sheet。我目前使用大约 5 种不同的单元格格式,我在样式 sheet 中定义了这些格式。这工作正常,假设我正在创建一个全新的文档。
样式 sheet 看起来像这样:
<?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>
我想支持第二种情况,在这种情况下,我的程序将新作品sheet添加到预先存在的传播sheet。
这可能是:
- 由我的程序创建的传播sheet
- 由我的程序创建的传播sheet,但在 Excel
中修改
- 由Excel
创建的传播sheet
在现有跨页中创建样式的最佳策略是什么sheet?
理想情况下,我想检测所需的样式是否已经存在,如果存在则重用它们。仅根据其定义来检测样式将是混乱的代码。如果我可以为我定义的 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 中编辑传播 sheet,则定义会更改,其中一些会被删除。
目前,这看起来不是一个实用的解决方案。
有什么方法可以为 cellXfs 元素中的 xf 节点指定名称?那会让生活更轻松。
另一种方法是在我每次生成作品时定义新样式(以及字体、填充和边框)sheet。反复做,每次都会使文件变大。
最好的策略是什么?
我有一个策略,当然不完美,但足以满足我的目的。
我决定
- 用一组固定的属性来描述我使用的样式
- 定义一个函数来查找与这些属性匹配的样式
- 如果找不到,则创建一个新样式
作为最低要求,我希望函数至少找到它自己生成的样式。
如果我的应用程序导出到同一个 excel 文件 100 次,我不想生成相同的样式 100 次,每次都使 excel 文件变大一点。
我指定的属性集不完整。例如,我没有指定字体系列。如果有一种样式可以匹配我的所有属性,但使用不同的字体,我仍然会使用这种样式。这对我来说没问题,但在其他应用程序中可能是不可接受的。
由于历史原因,我的代码在 VB 中,但它可以很容易地移植到 C#。
这是我的代码的略微简化版本。共有 19 种不同的样式,它们由函数 CreateStyleSheet() 初始化。如果样式 sheet 根本不存在,则会在检查各个样式之前创建它。
查找或创建样式的主要函数是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
我正在使用 OpenXml SDK 生成传播sheet。我目前使用大约 5 种不同的单元格格式,我在样式 sheet 中定义了这些格式。这工作正常,假设我正在创建一个全新的文档。
样式 sheet 看起来像这样:
<?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>
我想支持第二种情况,在这种情况下,我的程序将新作品sheet添加到预先存在的传播sheet。
这可能是:
- 由我的程序创建的传播sheet
- 由我的程序创建的传播sheet,但在 Excel 中修改
- 由Excel 创建的传播sheet
在现有跨页中创建样式的最佳策略是什么sheet?
理想情况下,我想检测所需的样式是否已经存在,如果存在则重用它们。仅根据其定义来检测样式将是混乱的代码。如果我可以为我定义的 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 中编辑传播 sheet,则定义会更改,其中一些会被删除。
目前,这看起来不是一个实用的解决方案。
有什么方法可以为 cellXfs 元素中的 xf 节点指定名称?那会让生活更轻松。
另一种方法是在我每次生成作品时定义新样式(以及字体、填充和边框)sheet。反复做,每次都会使文件变大。
最好的策略是什么?
我有一个策略,当然不完美,但足以满足我的目的。
我决定
- 用一组固定的属性来描述我使用的样式
- 定义一个函数来查找与这些属性匹配的样式
- 如果找不到,则创建一个新样式
作为最低要求,我希望函数至少找到它自己生成的样式。
如果我的应用程序导出到同一个 excel 文件 100 次,我不想生成相同的样式 100 次,每次都使 excel 文件变大一点。
我指定的属性集不完整。例如,我没有指定字体系列。如果有一种样式可以匹配我的所有属性,但使用不同的字体,我仍然会使用这种样式。这对我来说没问题,但在其他应用程序中可能是不可接受的。
由于历史原因,我的代码在 VB 中,但它可以很容易地移植到 C#。
这是我的代码的略微简化版本。共有 19 种不同的样式,它们由函数 CreateStyleSheet() 初始化。如果样式 sheet 根本不存在,则会在检查各个样式之前创建它。
查找或创建样式的主要函数是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