├── .github
└── FUNDING.yml
├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ ├── xcbaselines
│ └── SwiftXLSXTests.xcbaseline
│ │ ├── 2623BAEF-EDC4-4341-A1D6-67842741C220.plist
│ │ └── Info.plist
│ └── xcschemes
│ └── SwiftXLSX.xcscheme
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── Sample
│ ├── example.xlsx
│ ├── screen1.png
│ ├── screen2.png
│ └── screen3.png
└── SwiftXLSX
│ ├── SwiftXlsx.swift
│ ├── XCell.swift
│ ├── XFontName.swift
│ ├── XImage.swift
│ ├── XSheet.swift
│ ├── XWorkBook.swift
│ ├── Xtools.swift
│ └── icons.xcassets
│ ├── Contents.json
│ ├── blue.imageset
│ ├── Contents.json
│ └── waving.png
│ ├── green.imageset
│ ├── Contents.json
│ └── flag.png
│ ├── pin.imageset
│ ├── Contents.json
│ └── pin.png
│ ├── swiftxlsxlogo.imageset
│ ├── Contents.json
│ ├── swiftxlsxlogo-1.png
│ ├── swiftxlsxlogo-2.png
│ └── swiftxlsxlogo.png
│ └── yellow.imageset
│ ├── Contents.json
│ └── goal.png
└── Tests
└── SwiftXLSXTests
└── SwiftXLSXTests.swift
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [3973770]
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcbaselines/SwiftXLSXTests.xcbaseline/2623BAEF-EDC4-4341-A1D6-67842741C220.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | classNames
6 |
7 | SwiftXLSXTests
8 |
9 | testPerformance()
10 |
11 | com.apple.XCTPerformanceMetric_WallClockTime
12 |
13 | baselineAverage
14 | 0.601210
15 | baselineIntegrationDisplayName
16 | Local Baseline
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcbaselines/SwiftXLSXTests.xcbaseline/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | runDestinationsByUUID
6 |
7 | 2623BAEF-EDC4-4341-A1D6-67842741C220
8 |
9 | localComputer
10 |
11 | busSpeedInMHz
12 | 0
13 | cpuCount
14 | 1
15 | cpuKind
16 | Apple M1
17 | cpuSpeedInMHz
18 | 0
19 | logicalCPUCoresPerPackage
20 | 8
21 | modelCode
22 | MacBookAir10,1
23 | physicalCPUCoresPerPackage
24 | 8
25 | platformIdentifier
26 | com.apple.platform.macosx
27 |
28 | targetArchitecture
29 | arm64
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/SwiftXLSX.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
67 |
68 |
74 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "ZipArchive",
6 | "repositoryURL": "https://github.com/ZipArchive/ZipArchive.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "825ff12a74a94c54e737e279604c27e6740e8a2c",
10 | "version": "2.4.2"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SwiftXLSX",
8 | platforms: [
9 | .iOS(.v10),
10 | .macOS(.v11),
11 | ],
12 | products: [
13 | .library(
14 | name: "SwiftXLSX",
15 | targets: ["SwiftXLSX"]),
16 | ],
17 | dependencies: [
18 | .package(url: "https://github.com/ZipArchive/ZipArchive.git", "2.3.0"..<"2.5.0")
19 | ],
20 | targets: [
21 | .target(
22 | name: "SwiftXLSX",
23 | dependencies: ["ZipArchive"]),
24 | .testTarget(
25 | name: "SwiftXLSXTests",
26 | dependencies: ["SwiftXLSX"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftXLSX
2 |
3 | **Excel spreadsheet (XLSX) format writer on pure SWIFT.**
4 |
5 | SwiftXLSX is a library focused creating Excel spreadsheet (XLSX) files directly on mobile devices.
6 |
7 |
8 | ## Generation example XLSX file
9 |
10 | ```swift
11 | // library generate huge XLSX file with three sheets.
12 | XWorkBook.test()
13 | ```
14 |
15 | ## NEWS
16 |
17 | - Add support inserting images into cells.
18 |
19 | ```swift
20 | //new kind value of XValue for icons/images
21 | XValue.icon(XImageCell)
22 | ```
23 |
24 | ### example of inserting image
25 |
26 | ```swift
27 | let cell = sheet.AddCell(XCoords(row: 1, col: 1))
28 | let ImageCellValue:XImageCell = XImageCell(key: XImages.append(with: logoicon!)!, size: CGSize(width: 200, height: 75))
29 | // CGSize(width: 200, height: 75) - size display image in pixel
30 | cell.value = .icon(ImageCellValue)
31 | ```
32 |
33 | - fix bug creating empty document
34 |
35 | ## Requirements
36 |
37 | **Apple Platforms**
38 |
39 | - Xcode 11.3 or later
40 | - Swift 5.5 or later
41 | - iOS 10.0
42 |
43 | ## Installation
44 |
45 | ### Swift Package Manager
46 |
47 | [Swift Package Manager](https://swift.org/package-manager/) is a tool for
48 | managing the distribution of Swift code. It’s integrated with the Swift build
49 | system to automate the process of downloading, compiling, and linking
50 | dependencies on all platforms.
51 |
52 | Once you have your Swift package set up, adding `SwiftXLSX` as a dependency is as
53 | easy as adding it to the `dependencies` value of your `Package.swift`.
54 |
55 | ```swift
56 | dependencies: [
57 | .package(url: "https://github.com/3973770/SwiftXLSX", .upToNextMajor(from: "0.1.0"))
58 | ]
59 | ```
60 |
61 | If you're using SwiftXLSX in an app built with Xcode, you can also add it as a direct
62 | dependency [using Xcode's
63 | GUI](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app).
64 |
65 | ### Dependency
66 |
67 | **SSZipArchive**
68 | ZipArchive is a simple utility class for zipping and unzipping files on iOS, macOS and tvOS.
69 | https://github.com/ZipArchive/ZipArchive.git
70 |
71 |
72 | ## Screenshots
73 | 
74 |
75 | 
76 |
77 | 
78 |
79 | ## Contributing
80 |
81 | ### Sponsorship
82 |
83 | If this library saved you any amount of time or money, please consider [sponsoring
84 | the work of its maintainer](https://github.com/sponsors/3973770).
85 | Any amount is appreciated and helps in maintaining the project.
86 |
87 |
88 | ## Example for use
89 |
90 | ```swift
91 | let book = XWorkBook()
92 |
93 | let color:[UIColor] = [.darkGray, .green, .lightGray, .orange, .systemPink, .cyan, .purple, .magenta, .blue]
94 | let colortext:[UIColor] = [.darkGray, .black, .white, .darkText, .lightText]
95 |
96 |
97 | func GetRandomFont() -> XFontName {
98 | let cases = XFontName.allCases
99 | return cases[Int.random(in: 0..>>")
369 | print("\(fileid)")
370 | ```
371 |
372 | ## License
373 |
374 | SSZipArchive is protected under the [MIT license](https://github.com/samsoffes/ssziparchive/raw/master/LICENSE) and our slightly modified version of [minizip-ng (formally minizip)](https://github.com/zlib-ng/minizip-ng) 3.0.2 is licensed under the [Zlib license](https://www.zlib.net/zlib_license.html).
375 |
376 | ## Acknowledgments
377 |
378 | * Big thanks to [ZipArchive](https://github.com/ZipArchive).
379 |
--------------------------------------------------------------------------------
/Sources/Sample/example.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3973770/SwiftXLSX/6414cb5f3827a99bfcde2a26f2ecb2c0a35b4982/Sources/Sample/example.xlsx
--------------------------------------------------------------------------------
/Sources/Sample/screen1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3973770/SwiftXLSX/6414cb5f3827a99bfcde2a26f2ecb2c0a35b4982/Sources/Sample/screen1.png
--------------------------------------------------------------------------------
/Sources/Sample/screen2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3973770/SwiftXLSX/6414cb5f3827a99bfcde2a26f2ecb2c0a35b4982/Sources/Sample/screen2.png
--------------------------------------------------------------------------------
/Sources/Sample/screen3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3973770/SwiftXLSX/6414cb5f3827a99bfcde2a26f2ecb2c0a35b4982/Sources/Sample/screen3.png
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/SwiftXlsx.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 | // Created by Kostiantyn Bohonos on 1/19/22.
16 | //
17 |
18 | #if os(macOS)
19 | import Cocoa
20 | public typealias FontClass = NSFont
21 | public typealias ColorClass = NSColor
22 | public typealias ImageClass = NSImage
23 | #else
24 | import UIKit
25 | public typealias FontClass = UIFont
26 | public typealias ColorClass = UIColor
27 | public typealias ImageClass = UIImage
28 | #endif
29 |
30 | public struct SwiftXLSX {
31 | static func test() -> Bool {
32 | XWorkBook.test()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/XCell.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 | // Created by Kostiantyn Bohonos on 1/19/22.
16 | //
17 |
18 | import Foundation
19 | #if os(macOS)
20 | import Cocoa
21 | #else
22 | import UIKit
23 | #endif
24 |
25 | // Mark: struct coordinats of cell
26 | public struct XCoords {
27 | public var row:Int = 0
28 | public var col:Int = 0
29 | private var caddr:String?
30 | public var address: String {
31 | if let addr = self.caddr {
32 | return addr
33 | }else{
34 | return "\(self.col):\(self.row)"
35 | }
36 | }
37 |
38 | init (){}
39 | public init (row:Int,col:Int){
40 | self.row = row
41 | self.col = col
42 | self.caddr = "\(self.col):\(self.row)"
43 | }
44 | }
45 |
46 | public struct XRect {
47 | public var row:Int = 0
48 | public var col:Int = 0
49 | public var width:Int = 0
50 | public var height:Int = 0
51 | public init(_ row:Int,_ col:Int,_ width:Int,_ height:Int){
52 | self.row = row
53 | self.col = col
54 | self.width = width
55 | self.height = height
56 | }
57 | init (){}
58 |
59 | public func inrect(_ coord:XCoords) -> Bool{
60 | if self.row <= coord.row, self.col <= coord.col, coord.col <= self.col+self.width-1, coord.row <= self.row+self.height-1 {
61 | if self.row == coord.row, self.col == coord.col {
62 | return false
63 | }else{
64 | return true
65 | }
66 | }else{
67 | return false
68 | }
69 | }
70 | }
71 |
72 | public struct XFont {
73 | public var Font:XFontName
74 | public var FontSize:Int
75 | public var bold:Bool
76 | public var italic:Bool
77 | public var strike:Bool
78 | public var underline:Bool
79 | public init (_ Font:XFontName,_ FontSize:Int,_ bold:Bool=false,_ italic:Bool=false,_ strike:Bool=false,_ underline:Bool=false){
80 | self.Font = Font
81 | self.FontSize = FontSize
82 | self.bold = bold
83 | self.italic = italic
84 | self.strike = strike
85 | self.underline = underline
86 | }
87 |
88 | public var getfont:FontClass?
89 | {
90 | if let font = FontClass(name: self.Font.rawValue, size: CGFloat(self.FontSize)) {
91 | return font
92 | }else{
93 | return FontClass(name: "Arial", size: CGFloat(self.FontSize))
94 | }
95 | }
96 | }
97 |
98 | public enum XValue : Equatable {
99 | case long(UInt64)
100 | case integer(Int)
101 | case text(String)
102 | case double(Double)
103 | case float(Float)
104 | case icon(XImageCell)
105 | }
106 |
107 |
108 | public enum XAligmentHorizontal:UInt64 {
109 | case left, center, right
110 |
111 | public func str() -> String {
112 | switch self {
113 | case .left:
114 | return "left"
115 | case .center:
116 | return "center"
117 | case .right:
118 | return "right"
119 | }
120 | }
121 | public func id() -> UInt64 {
122 | switch self {
123 | case .left:
124 | return 1
125 | case .center:
126 | return 2
127 | case .right:
128 | return 3
129 | }
130 | }
131 | }
132 |
133 | public enum XAligmentVertical:UInt64 {
134 | case top, center, bottom
135 |
136 | func str() -> String {
137 | switch self {
138 | case .top:
139 | return "top"
140 | case .center:
141 | return "center"
142 | case .bottom:
143 | return "bottom"
144 | }
145 | }
146 | func id() -> UInt64 {
147 | switch self {
148 | case .top:
149 | return 1
150 | case .center:
151 | return 2
152 | case .bottom:
153 | return 3
154 | }
155 | }
156 | }
157 |
158 | final public class XCell{
159 | public var coords:XCoords?
160 | public var value:XValue?
161 | public var Font:XFont?
162 | public var alignmentVertical:XAligmentVertical = .center
163 | public var alignmentHorizontal:XAligmentHorizontal = .left
164 | public var color : ColorClass = .black
165 | public var colorbackground : ColorClass = .white
166 |
167 | public var width:Int = 50
168 |
169 | public var Border:Bool = false
170 |
171 | var idFont:Int = 0
172 | var idFill:Int = 0
173 | var idStyle:Int = 0
174 | var idVal:Int?
175 | var nocalculatewidth: Bool = true
176 |
177 | public init(_ coords:XCoords){
178 | self.coords = coords
179 | self.Font = XFont(.TrebuchetMS, 10)
180 | }
181 |
182 | public func Cols(txt color: ColorClass,bg bgcolor: ColorClass){
183 | self.color = color
184 | self.colorbackground = bgcolor
185 | }
186 |
187 | public func Als(v Vertical:XAligmentVertical,h Horizontal:XAligmentHorizontal){
188 | self.alignmentVertical = Vertical
189 | self.alignmentHorizontal = Horizontal
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/XFontName.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 | // Created by Kostiantyn Bohonos on 1/19/22.
16 | //
17 |
18 |
19 | import Foundation
20 |
21 | public enum XFontName:String, CaseIterable{
22 | case AcademyEngravedLET = "AcademyEngravedLetPlain"
23 | case AlNile = "AlNile"
24 | case AmericanTypewriter = "AmericanTypewriter"
25 | case AppleColorEmoji = "AppleColorEmoji"
26 | case AppleSDGothicNeo = "AppleSDGothicNeo-Regular"
27 | case AppleSymbols = "AppleSymbols"
28 | case Arial = "ArialMT"
29 | case ArialHebrew = "ArialHebrew"
30 | case ArialRoundedMTBold = "ArialRoundedMTBold"
31 | case Avenir = "Avenir-Book"
32 | case AvenirNext = "AvenirNext-Regular"
33 | case AvenirNextCondensed = "AvenirNextCondensed-Regular"
34 | case Baskerville = "Baskerville"
35 | case Bodoni72 = "BodoniSvtyTwoITCTT-Book"
36 | case Bodoni72Oldstyle = "BodoniSvtyTwoOSITCTT-Book"
37 | case Bodoni72Smallcaps = "BodoniSvtyTwoSCITCTT-Book"
38 | case BodoniOrnaments = "BodoniOrnamentsITCTT"
39 | case BradleyHand = "BradleyHandITCTT-Bold"
40 | case ChalkboardSE = "ChalkboardSE-Regular"
41 | case Chalkduster = "Chalkduster"
42 | case Charter = "Charter-Roman"
43 | case Cochin = "Cochin"
44 | case Copperplate = "Copperplate"
45 | case CourierNew = "CourierNewPSMT"
46 | case Damascus = "Damascus"
47 | case DevanagariSangamMN = "DevanagariSangamMN"
48 | case Didot = "Didot"
49 | case DINAlternate = "DINAlternate-Bold"
50 | case DINCondensed = "DINCondensed-Bold"
51 | case EuphemiaUCAS = "EuphemiaUCAS"
52 | case Farah = "Farah"
53 | case Futura = "Futura-Medium"
54 | case Galvji = "Galvji"
55 | case GeezaPro = "GeezaPro"
56 | case Georgia = "Georgia"
57 | case GillSans = "GillSans"
58 | case GranthaSangamMN = "GranthaSangamMN-Regular"
59 | case Helvetica = "Helvetica"
60 | case HelveticaNeue = "HelveticaNeue"
61 | case HiraginoMaruGothicProN = "HiraMaruProN-W4"
62 | case HiraginoMinchoProN = "HiraMinProN-W3"
63 | case HiraginoSans = "HiraginoSans-W3"
64 | case HoeflerText = "HoeflerText-Regular"
65 | case Kailasa = "Kailasa"
66 | case Kefa = "Kefa-Regular"
67 | case KhmerSangamMN = "KhmerSangamMN"
68 | case KohinoorBangla = "KohinoorBangla-Regular"
69 | case KohinoorDevanagari = "KohinoorDevanagari-Regular"
70 | case KohinoorGujarati = "KohinoorGujarati-Regular"
71 | case KohinoorTelugu = "KohinoorTelugu-Regular"
72 | case LaoSangamMN = "LaoSangamMN"
73 | case MalayalamSangamMN = "MalayalamSangamMN"
74 | case MarkerFelt = "MarkerFelt-Thin"
75 | case Menlo = "Menlo-Regular"
76 | case Mishafi = "DiwanMishafi"
77 | case MuktaMahee = "MuktaMahee-Regular"
78 | case MyanmarSangamMN = "MyanmarSangamMN"
79 | case Noteworthy = "Noteworthy-Light"
80 | case NotoNastaliqUrdu = "NotoNastaliqUrdu"
81 | case NotoSansKannada = "NotoSansKannada-Regular"
82 | case NotoSansMyanmar = "NotoSansMyanmar-Regular"
83 | case NotoSansOriya = "NotoSansOriya"
84 | case Optima = "Optima-Regular"
85 | case Palatino = "Palatino-Roman"
86 | case Papyrus = "Papyrus"
87 | case PartyLET = "PartyLetPlain"
88 | case PingFangHK = "PingFangHK-Regular"
89 | case PingFangSC = "PingFangSC-Regular"
90 | case PingFangTC = "PingFangTC-Regular"
91 | case Rockwell = "Rockwell-Regular"
92 | case SavoyeLET = "SavoyeLetPlain"
93 | case SinhalaSangamMN = "SinhalaSangamMN"
94 | case SnellRoundhand = "SnellRoundhand"
95 | case Symbol = "Symbol"
96 | case TamilSangamMN = "TamilSangamMN"
97 | case Thonburi = "Thonburi"
98 | case TimesNewRoman = "TimesNewRomanPSMT"
99 | case TrebuchetMS = "TrebuchetMS"
100 | case Verdana = "Verdana"
101 | case ZapfDingbats = "ZapfDingbatsITC"
102 | case Zapfino = "Zapfino"
103 |
104 | func ind() -> UInt64 {
105 | switch self {
106 | case .AcademyEngravedLET:
107 | return 1
108 | case .AlNile:
109 | return 2
110 | case .AmericanTypewriter:
111 | return 3
112 | case .AppleColorEmoji:
113 | return 4
114 | case .AppleSDGothicNeo:
115 | return 5
116 | case .AppleSymbols:
117 | return 6
118 | case .Arial:
119 | return 7
120 | case .ArialHebrew:
121 | return 8
122 | case .ArialRoundedMTBold:
123 | return 9
124 | case .Avenir:
125 | return 10
126 | case .AvenirNext:
127 | return 11
128 | case .AvenirNextCondensed:
129 | return 12
130 | case .Baskerville:
131 | return 13
132 | case .Bodoni72:
133 | return 14
134 | case .Bodoni72Oldstyle:
135 | return 15
136 | case .Bodoni72Smallcaps:
137 | return 16
138 | case .BodoniOrnaments:
139 | return 17
140 | case .BradleyHand:
141 | return 18
142 | case .ChalkboardSE:
143 | return 19
144 | case .Chalkduster:
145 | return 20
146 | case .Charter:
147 | return 21
148 | case .Cochin:
149 | return 22
150 | case .Copperplate:
151 | return 23
152 | case .CourierNew:
153 | return 24
154 | case .Damascus:
155 | return 25
156 | case .DevanagariSangamMN:
157 | return 26
158 | case .Didot:
159 | return 27
160 | case .DINAlternate:
161 | return 28
162 | case .DINCondensed:
163 | return 29
164 | case .EuphemiaUCAS:
165 | return 30
166 | case .Farah:
167 | return 31
168 | case .Futura:
169 | return 32
170 | case .Galvji:
171 | return 33
172 | case .GeezaPro:
173 | return 34
174 | case .Georgia:
175 | return 35
176 | case .GillSans:
177 | return 36
178 | case .GranthaSangamMN:
179 | return 37
180 | case .Helvetica:
181 | return 38
182 | case .HelveticaNeue:
183 | return 39
184 | case .HiraginoMaruGothicProN:
185 | return 40
186 | case .HiraginoMinchoProN:
187 | return 41
188 | case .HiraginoSans:
189 | return 42
190 | case .HoeflerText:
191 | return 43
192 | case .Kailasa:
193 | return 44
194 | case .Kefa:
195 | return 45
196 | case .KhmerSangamMN:
197 | return 46
198 | case .KohinoorBangla:
199 | return 47
200 | case .KohinoorDevanagari:
201 | return 48
202 | case .KohinoorGujarati:
203 | return 49
204 | case .KohinoorTelugu:
205 | return 50
206 | case .LaoSangamMN:
207 | return 51
208 | case .MalayalamSangamMN:
209 | return 52
210 | case .MarkerFelt:
211 | return 53
212 | case .Menlo:
213 | return 54
214 | case .Mishafi:
215 | return 55
216 | case .MuktaMahee:
217 | return 56
218 | case .MyanmarSangamMN:
219 | return 57
220 | case .Noteworthy:
221 | return 58
222 | case .NotoNastaliqUrdu:
223 | return 59
224 | case .NotoSansKannada:
225 | return 60
226 | case .NotoSansMyanmar:
227 | return 61
228 | case .NotoSansOriya:
229 | return 62
230 | case .Optima:
231 | return 63
232 | case .Palatino:
233 | return 64
234 | case .Papyrus:
235 | return 65
236 | case .PartyLET:
237 | return 66
238 | case .PingFangHK:
239 | return 67
240 | case .PingFangSC:
241 | return 68
242 | case .PingFangTC:
243 | return 69
244 | case .Rockwell:
245 | return 70
246 | case .SavoyeLET:
247 | return 71
248 | case .SinhalaSangamMN:
249 | return 72
250 | case .SnellRoundhand:
251 | return 73
252 | case .Symbol:
253 | return 74
254 | case .TamilSangamMN:
255 | return 76
256 | case .Thonburi:
257 | return 77
258 | case .TimesNewRoman:
259 | return 78
260 | case .TrebuchetMS:
261 | return 79
262 | case .Verdana:
263 | return 80
264 | case .ZapfDingbats:
265 | return 81
266 | case .Zapfino:
267 | return 82
268 | }
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/XImage.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 | // Created by Kostiantyn Bohonos on 6/15/22.
16 | //
17 |
18 | import Foundation
19 | #if os(macOS)
20 | import Cocoa
21 | #else
22 | import UIKit
23 | #endif
24 |
25 | public enum XImageType:String {
26 | case png = "png"
27 | case jpeg = "jpeg"
28 | case jpg = "jpg"
29 | }
30 |
31 | #if os(macOS)
32 | extension NSBitmapImageRep {
33 | var pngData: Data? { representation(using: .png, properties: [:]) }
34 | }
35 | extension Data {
36 | var bitmap: NSBitmapImageRep? { NSBitmapImageRep(data: self) }
37 | }
38 | extension NSImage {
39 | func pngData() -> Data? {
40 | tiffRepresentation?.bitmap?.pngData
41 | }
42 | }
43 | #endif
44 |
45 |
46 | public class XImage{
47 | var Key = ""
48 | var data:Data?
49 | var type:XImageType = .png
50 |
51 | func config(with data:Data, Key:String){
52 | self.data = data
53 | self.Key = Key
54 | }
55 |
56 | init(with data:Data, Key:String){
57 | self.config(with: data, Key: Key)
58 | }
59 |
60 |
61 |
62 | public init?(with image:ImageClass) {
63 | guard let data = image.pngData() else {return nil}
64 | self.config(with: data, Key: "\(XCS.checksum(data: data))")
65 | }
66 |
67 | public init?(with image:ImageClass, Key:String) {
68 | guard let data = image.pngData() else {return nil}
69 | self.config(with: data, Key: Key)
70 | }
71 |
72 | @discardableResult
73 | func Write(toPath path:String) -> Bool{
74 | var url = URL(fileURLWithPath: path)
75 | url = url.appendingPathComponent(self.Key)
76 | url = url.appendingPathExtension(self.type.rawValue)
77 | do {
78 | try self.data?.write(to: url, options: [.atomic])
79 | } catch {
80 | print("Error write file \(url) : \(error)")
81 | return false
82 | }
83 | return true
84 | }
85 | }
86 |
87 | public struct XImageCell {
88 | public let key: String
89 | public let size: CGSize
90 |
91 | public init(key: String, size: CGSize) {
92 | self.key = key
93 | self.size = size
94 | }
95 | }
96 |
97 | extension XImageCell:Equatable{
98 | public static func == (lhs: Self, rhs: Self) -> Bool{
99 | lhs.key == rhs.key && lhs.size.equalTo(rhs.size)
100 | }
101 | }
102 |
103 | public class XImages{
104 | public static var list:[String:XImage] = [:]
105 |
106 | public static func append(with ximg:XImage) -> String{
107 | if list[ximg.Key] == nil {
108 | list[ximg.Key] = ximg
109 | }
110 | return ximg.Key
111 | }
112 |
113 | public static func append(with image:ImageClass) -> String?{
114 | guard let ximg = XImage(with: image) else {return nil}
115 | return append(with: ximg)
116 | }
117 |
118 | public static func append(with image:ImageClass, Key:String) -> String?{
119 | if list[Key] == nil {
120 | guard let ximg = XImage(with: image,Key: Key) else {return nil}
121 | return append(with: ximg)
122 | }else{
123 | return Key
124 | }
125 | }
126 |
127 | public static func removeAll(){
128 | self.list.removeAll()
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/XSheet.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 | // Created by Kostiantyn Bohonos on 1/19/22.
16 | //
17 |
18 | import Foundation
19 |
20 | final public class XSheet{
21 |
22 | private static let ABC = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
23 | public var title:String = ""
24 | private var cells:[XCell] = []
25 | var mergecells : [String] = []
26 | var merge : [XRect] = []
27 | var RowH : [Int:Int] = [:]
28 | var ColW : [Int:Int] = [:]
29 | private var indexcells : [String:XCell] = [:]
30 | var xml : String?
31 | var fix:XCoords = XCoords()
32 | var drawingsxml:String?
33 | var drawingsxmlrels:String?
34 | var drawingsSheetrels:String?
35 |
36 | public init() {}
37 |
38 | public init(_ title:String){
39 | self.title = title
40 | }
41 |
42 | public func ForRowSetHeight(_ row:Int,_ Height:Int){
43 | self.RowH[row] = Height
44 | }
45 | public func ForColumnSetWidth(_ column:Int,_ Width:Int){
46 | self.ColW[column] = Width
47 | }
48 |
49 | public var GetMaxRowCol : (row:Int,col:Int) {
50 | var maxRC:(row: Int,col: Int) = (row:0,col:0)
51 | for Cell in self.cells {
52 | guard let cellcoords = Cell.coords else {continue}
53 | if maxRC.row < cellcoords.row {
54 | maxRC.row = cellcoords.row
55 | }
56 | if maxRC.col < cellcoords.col {
57 | maxRC.col = cellcoords.col
58 | }
59 | }
60 | return maxRC
61 | }
62 |
63 | static func EncodeNumberABC(_ num:Int) -> String {
64 | guard num >= XSheet.ABC.count else { return "\(XSheet.ABC[num])"}
65 |
66 | let whole:Int = num / XSheet.ABC.count
67 | let remain:Int = num - whole*XSheet.ABC.count
68 |
69 | return "\(XSheet.EncodeNumberABC(whole-1))\(XSheet.EncodeNumberABC(remain))"
70 | }
71 |
72 | public func MergeRect(_ rect:XRect) {
73 | let xcord = XSheet.EncodeNumberABC(rect.col-1)
74 | let ycord = "\(rect.row)"
75 | let x2cord = XSheet.EncodeNumberABC(rect.col+rect.width-2)
76 | let y2cord = "\(rect.row+rect.height-1)"
77 | self.merge.append(rect)
78 | self.mergecells.append("\(xcord)\(ycord):\(x2cord)\(y2cord)")
79 |
80 | if let maincell = self.Get(XCoords(row: rect.row, col: rect.col)) {
81 | for row in rect.row...rect.row+rect.height-1 {
82 | for col in rect.col...rect.col+rect.width-1 {
83 | if col == rect.col, row == rect.row {
84 | continue
85 | }
86 |
87 | let cord = XCoords(row: row, col: col)
88 | if self.Get(cord) == nil {
89 | let cellnew = self.AddCell(cord)
90 | cellnew.value = .text("")
91 | cellnew.Border = maincell.Border
92 | cellnew.Font = maincell.Font
93 | cellnew.color = maincell.color
94 | cellnew.colorbackground = maincell.colorbackground
95 | }
96 | }
97 | }
98 | }
99 | }
100 |
101 | /// add cell with coords to current sheet
102 | public func AddCell(_ coords:XCoords) -> XCell{
103 | if let cell = self.Get(coords) {
104 | return cell
105 | }else{
106 | let cellnew = XCell(coords)
107 | self.cells.append(cellnew)
108 | AddCellToIndex(cellnew)
109 | return cellnew
110 | }
111 | }
112 |
113 | /// append exestit cell to current sheet
114 | func append(_ newElement: XCell){
115 | self.cells.append(newElement)
116 | AddCellToIndex(newElement)
117 | }
118 |
119 | func AddCellToIndex(_ cell:XCell){
120 | if self.indexcells[cell.coords!.address] == nil {
121 | self.indexcells[cell.coords!.address] = cell
122 | }
123 | }
124 |
125 | /// build indez of all cell in sheet
126 | public func buildindex(){
127 | self.indexcells.removeAll()
128 | for cell in self.cells {
129 | self.indexcells[cell.coords!.address] = cell
130 | }
131 | }
132 |
133 | /// get cell from sheer by coords
134 | public func Get(_ coords:XCoords) -> XCell? {
135 | guard let ret = self.indexcells[coords.address] else { return nil }
136 | return ret
137 | }
138 |
139 | func GetMaxWidth(_ col:Int,_ numrows:Int) -> Int {
140 | var maxw=50;
141 | for row in 1...numrows {
142 | let cell = self.Get(XCoords(row: row, col: col))
143 | if cell != nil, !cell!.nocalculatewidth, maxw < cell!.width {
144 | maxw = cell!.width
145 | }
146 | }
147 | return maxw
148 | }
149 |
150 | }
151 |
152 | extension XSheet: Sequence {
153 | public func makeIterator() -> Array.Iterator {
154 | return cells.makeIterator()
155 | }
156 |
157 | }
158 |
159 | fileprivate extension String{
160 |
161 | func XSheetTitle() -> String
162 | {
163 | let syms:[String] = [":","\\","/","?","*","[","]"," "," "," "," "]
164 | var str = "\(self)"
165 | for sym in syms {
166 | str = str.replacingOccurrences(of: sym, with: " ", options: NSString.CompareOptions.literal, range: nil)
167 | }
168 | return str
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/XWorkBook.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 | // Created by Kostiantyn Bohonos on 1/19/22.
16 | //
17 |
18 | import Foundation
19 | #if os(macOS)
20 | import Cocoa
21 | #else
22 | import UIKit
23 | #endif
24 | import ZipArchive
25 |
26 |
27 |
28 | final public class XWorkBook{
29 | private var Sheets:[XSheet] = []
30 |
31 | // for styles
32 | private var Fonts:[UInt64:(String,Int)] = [:]
33 | private var Fills:[String] = []
34 | private var colorsid:[String] = []
35 | private var Bgcolor:[String] = []
36 | private var xfs:[UInt64:(String,Int)] = [:]
37 | private var Borders:[String] = []
38 | private var Drawings:[String] = []
39 |
40 | private var vals:[String] = []
41 | private var valss:Set = Set([])
42 | private var CHARSIZE:[UInt64:CGFloat] = [:]
43 |
44 | public init() {}
45 |
46 | var count:Int {
47 | return self.Sheets.count
48 | }
49 |
50 | func removeAll(){
51 | self.Sheets.removeAll()
52 | }
53 |
54 | func append(_ newElement: XSheet){
55 | self.Sheets.append(newElement)
56 | }
57 |
58 | /// create and return new sheet
59 | public func NewSheet(_ title:String) -> XSheet{
60 | let Sheet = XSheet(title.XSheetTitle())
61 | self.append(Sheet)
62 | return Sheet
63 | }
64 | /// clear all local data for building xlsx
65 | private func clearlocaldata(){
66 | self.Fonts.removeAll()
67 | self.colorsid.removeAll()
68 | self.Fills.removeAll()
69 | self.Borders.removeAll()
70 | self.xfs.removeAll()
71 | self.vals.removeAll()
72 | self.vals.append("")
73 | self.valss.removeAll()
74 | self.valss.insert("")
75 | self.CHARSIZE.removeAll()
76 | self.Bgcolor.removeAll()
77 | }
78 |
79 | private func findFont(_ cell:XCell){
80 | let font = cell.Font!
81 |
82 | var idval:UInt64 = cell.Font!.bold ? 1 : 0
83 | idval += cell.Font!.italic ? 2 : 0
84 | idval += cell.Font!.strike ? 3 : 0
85 | idval += cell.Font!.underline ? 15 : 0
86 | idval += UInt64(cell.Font!.FontSize) * 10
87 | idval += (UInt64(cell.Font!.Font.ind())+1) * 10000
88 |
89 |
90 | let col:ColorClass = cell.color
91 | if let hex = col.Hex {
92 | if let index = self.colorsid.firstIndex(of: hex) {
93 | idval += (UInt64(index)+1) * 1000000
94 | }else{
95 | self.colorsid.append(hex)
96 | idval += (UInt64(self.colorsid.count)+1) * 1000000
97 | }
98 | }
99 |
100 | if let (_,ind) = self.Fonts[idval] {
101 | cell.idFont = ind
102 | }else{
103 | let xml = "\(font.bold ? "" : "")\(font.italic ? "" : "")\(font.strike ? "" : "")\(font.underline ? "" : "")"
104 |
105 | cell.idFont = self.Fonts.count
106 | self.Fonts[idval] = (xml,self.Fonts.count)
107 | }
108 | }
109 |
110 | private func findFills(_ cell:XCell){
111 | let hexcolor = cell.colorbackground.Hex!
112 |
113 | if let index = self.Bgcolor.firstIndex(of: hexcolor) {
114 | cell.idFill = index
115 | }else{
116 | self.Bgcolor.append(hexcolor)
117 | let Fontxml = ""
118 |
119 | if let indexfill = self.Fills.firstIndex(of: Fontxml) {
120 | cell.idFill = indexfill
121 | }else{
122 | self.Fills.append(Fontxml)
123 | cell.idFill = self.Fills.count-1
124 | }
125 | }
126 | }
127 |
128 | private func findxf(_ cell:XCell,_ sheet:XSheet){
129 | cell.idStyle = -1
130 | var idval: UInt64 = cell.alignmentHorizontal.id()
131 | idval += cell.alignmentVertical.id() * 10
132 | idval += cell.Border ? 100 : 0
133 | idval += UInt64(cell.idFill) * 1000
134 | idval += UInt64(cell.idFont) * 1000000
135 |
136 | for merge in sheet.merge {
137 | if merge.inrect(cell.coords!){
138 | if let cellmain = sheet.Get(XCoords(row: merge.row, col: merge.col)) {
139 | cell.idStyle = cellmain.idStyle
140 | cell.idFont = cellmain.idFont
141 | cell.idFill = cellmain.idFill
142 | break
143 | }
144 | }
145 | }
146 | if cell.idStyle == -1 {
147 | if let (_,ind) = self.xfs[idval] {
148 | cell.idStyle = ind
149 | }else{
150 | let xf = ""
151 | cell.idStyle = self.xfs.count
152 | self.xfs[idval] = (xf,self.xfs.count)
153 | }
154 | }
155 | }
156 |
157 | private func findVals(_ cell:XCell){
158 | cell.idVal = nil
159 | if let val = cell.value {
160 | switch val {
161 | case .text(let strval):
162 | let key:UInt64 = cell.Font!.Font.ind()*1000000+UInt64(cell.Font!.FontSize)
163 | var sf:CGFloat = 0
164 | if let sizefont = self.CHARSIZE[key] {
165 | sf = sizefont
166 | }else{
167 | let size = cell.Font!.getfont!.Rectfor("0")
168 | self.CHARSIZE[key] = size.width
169 | sf = size.width
170 | }
171 | cell.width = Int(sf * CGFloat(strval.count) + 10)
172 |
173 | if self.valss.contains(strval) {
174 | if let indexval = self.vals.firstIndex(of: strval) {
175 | cell.idVal = indexval
176 | }
177 | }else{
178 | self.vals.append(strval)
179 | self.valss.insert(strval)
180 | cell.idVal = self.vals.count - 1
181 | }
182 | default:
183 | break
184 | }
185 | }
186 | }
187 |
188 | private func BuildMediaDrawings(){
189 | self.Drawings.removeAll()
190 | var indexsheet = 0
191 | for sheet in self {
192 | indexsheet += 1
193 | sheet.drawingsxml = nil
194 | sheet.drawingsxmlrels = nil
195 | sheet.drawingsSheetrels = nil
196 |
197 | var cellwithicon:[(xic:XImageCell,Coords:XCoords)] = []
198 | var keys:[String] = []
199 |
200 |
201 | for cell in sheet {
202 |
203 | guard let cellvalue = cell.value else {continue}
204 | switch cellvalue {
205 | case .icon(let XIC):
206 | if keys.firstIndex(of: XIC.key) == nil {
207 | keys.append(XIC.key)
208 | }
209 | cellwithicon.append((xic:XIC,Coords:cell.coords!))
210 | default:
211 | continue
212 | }
213 | }
214 |
215 | if cellwithicon.count > 0 {
216 | /// we have some cell with images
217 |
218 | let Xml:NSMutableString = NSMutableString()
219 | let Xmlrel:NSMutableString = NSMutableString()
220 | Xml.append("")
221 | Xml.append("")
222 | var index = 1
223 | for key in keys {
224 | Xml.append("")
225 | index += 1
226 | }
227 | if keys.count > 0 {
228 | Xmlrel.append("")
229 | }
230 |
231 | Xml.append("")
232 | sheet.drawingsxmlrels = String(Xml)
233 | sheet.drawingsSheetrels = String(Xmlrel)
234 | Xml.setString("")
235 | Xmlrel.setString("")
236 |
237 |
238 |
239 |
240 | Xml.append("")
241 | let str = """
242 |
243 | """
244 | Xml.append(str)
245 | for cell in cellwithicon {
246 | let index = keys.firstIndex(of: cell.xic.key)!
247 |
248 | let EMU = 9525.0
249 |
250 | let y:UInt32 = UInt32(cell.xic.size.height*EMU)
251 | let x:UInt32 = UInt32(cell.xic.size.width*EMU)
252 |
253 |
254 |
255 | let cellstr = """
256 | \(cell.Coords.col-1)0\(cell.Coords.row-1)0
257 | """
258 | Xml.append(cellstr)
259 | }
260 | Xml.append("")
261 | sheet.drawingsxml = String(Xml)
262 | Xml.setString("")
263 | }
264 | }
265 | }
266 |
267 | private func BuildStyles(){
268 | self.clearlocaldata()
269 |
270 | self.Fonts[0] = ("",0)
271 |
272 | self.Fills.append("")
273 |
274 | self.Bgcolor.append("FFFFFFFF")
275 |
276 | self.xfs[0] = ("",0)
277 |
278 | self.Borders.append("");
279 | self.Borders.append("");
280 |
281 |
282 | for sheet in self {
283 | for cell in sheet {
284 | self.findFont(cell)
285 | self.findFills(cell)
286 |
287 | self.findxf(cell , sheet)
288 | self.findVals(cell)
289 | }
290 | }
291 | }
292 |
293 | private func BuildSheet(_ sheet:XSheet){
294 | sheet.buildindex()
295 | let Xml:NSMutableString = NSMutableString()
296 | Xml.append("\n")
297 | Xml.append("")
298 |
299 | let (numrows,numcols) = sheet.GetMaxRowCol
300 |
301 | if numrows>0 {
302 | Xml.append("")
303 | }
304 |
305 | Xml.append("")
306 | if (sheet.fix.row + sheet.fix.col) == 0 {
307 | Xml.append("")
308 | }else if sheet.fix.row > 0 , sheet.fix.col == 0 {
309 | Xml.append("")
310 | Xml.append("")
311 | Xml.append("")
312 | }else if sheet.fix.row == 0 , sheet.fix.col > 0 {
313 | Xml.append("")
314 | Xml.append("")
315 | Xml.append("")
316 | }else{
317 | Xml.append("")
318 | Xml.append("")
319 | Xml.append("")
320 | }
321 | Xml.append("")
322 | Xml.append("")
323 |
324 | if numcols>0 {
325 | Xml.append("")
326 | for col:Int in 1...numcols {
327 | var wcalc:CGFloat = 50
328 | if sheet.ColW.count > 0 {
329 | let w = sheet.ColW[col]
330 | if w != nil {
331 | wcalc = CGFloat(w!)
332 | }else{
333 | wcalc = CGFloat(sheet.GetMaxWidth(col, numrows))
334 | }
335 | }else{
336 | wcalc = CGFloat(sheet.GetMaxWidth(col, numrows))
337 | }
338 | wcalc = wcalc * (0.16666016 * 1.2)//0.12499512
339 |
340 | Xml.append("")
341 | }
342 | Xml.append("")
343 | }
344 |
345 |
346 |
347 | Xml.append("")
348 | var hasimages = false
349 | if numrows == 0 {
350 | Xml.append("
")
351 | }else{
352 | for row:Int in 1...numrows {
353 | let colls:NSMutableString = NSMutableString()
354 | for col:Int in 1...numcols {
355 | if let cell = sheet.Get(XCoords(row: row, col: col)) {
356 | /// output values
357 | if let value = cell.value {
358 | switch value {
359 | case .text(_):
360 | if cell.idVal != nil {
361 | /// this is text value and insert like id
362 | colls.append("\(cell.idVal!)")
363 | }else{
364 | /// output empty cell
365 | colls.append("= 0 ? cell.idStyle : 0)\" />")
366 | }
367 | case .double(let val):
368 | colls.append("\(String(format: "%.3f", val))")
369 | case .float(let val):
370 | colls.append("\(String(format: "%.3f", val))")
371 | case.integer(let val):
372 | colls.append("\(val)")
373 | case .long(let val):
374 | colls.append("\(val)")
375 | case .icon(_):
376 | /// output empty cell
377 | hasimages = true
378 | colls.append("= 0 ? cell.idStyle : 0)\" />")
379 | }
380 | }else{
381 | colls.append("= 0 ? cell.idStyle : 0)\" />")
382 | }
383 |
384 | }else{
385 | /// no cell with coords (row,col)
386 | colls.append("")
387 | }
388 | }
389 | if let ht = sheet.RowH[row] {
390 | Xml.append("")
391 | }else{
392 | Xml.append("")
393 | }
394 | Xml.append(String(colls))
395 | colls.setString("")
396 | Xml.append("
")
397 | }
398 | }
399 | Xml.append("
")
400 | if hasimages {
401 | Xml.append("")
402 | }
403 |
404 |
405 |
406 | if sheet.mergecells.count > 0 {
407 | Xml.append("")
408 | for addr in sheet.mergecells {
409 | Xml.append("")
410 | }
411 | Xml.append("")
412 | }
413 |
414 | Xml.append("")
415 | sheet.xml = String(Xml)
416 | Xml.setString("")
417 | }
418 |
419 | private func BuildSheets() {
420 | for sheet in self {
421 | self.BuildSheet(sheet)
422 | }
423 | }
424 |
425 |
426 |
427 |
428 |
429 | /// Check exist directory and create if don't exist
430 | @discardableResult
431 | private func CheckCreateDirectory(path pathdirectory:String) -> Bool{
432 | let filemanager = FileManager.default
433 | if !filemanager.fileExists(atPath: pathdirectory) {
434 | do {
435 | try filemanager.createDirectory(atPath: pathdirectory, withIntermediateDirectories: true, attributes: nil)
436 | } catch {
437 | print("Error creating directory \(pathdirectory) : \(error.localizedDescription)")
438 | return false
439 | }
440 | }
441 | return true
442 | }
443 |
444 | /// remove file or directory
445 | @discardableResult
446 | private func RemoveFile(path pathfile:String) -> Bool{
447 | do {
448 | try FileManager.default.removeItem(atPath: pathfile)
449 | } catch {
450 | print("Error remove file \(pathfile) : \(error.localizedDescription)")
451 | return false
452 | }
453 | return true
454 | }
455 |
456 | /// write string buffer to file
457 | @discardableResult
458 | private func Write(data strData:String, tofile path:String) -> Bool{
459 | do {
460 | try strData.write(toFile: path, atomically: true, encoding: .utf8)
461 | } catch {
462 | print("Error write file \(path) : \(error.localizedDescription)")
463 | return false
464 | }
465 | return true
466 | }
467 |
468 | private var rels:String {
469 | "\n"
470 | }
471 |
472 | private var SharedStrings:String {
473 | let Xml:NSMutableString = NSMutableString()
474 | Xml.append("\n")
475 | Xml.append("")
476 | for val in vals {
477 | Xml.append("\(val.XmlPrep())")
478 | }
479 | Xml.append("")
480 | return String(Xml)
481 | }
482 |
483 | private var StyleStrings:String {
484 | let Xml:NSMutableString = NSMutableString()
485 |
486 | Xml.append("\n")
487 | Xml.append("")
488 | Xml.append("")
489 |
490 | if Fonts.isEmpty {
491 | Xml.append("")
492 | }else{
493 | Xml.append("")
494 | let ar = Fonts.values.sorted(by: { $0.1 < $1.1})
495 | for (font,_) in ar {
496 | Xml.append(font)
497 | }
498 | Xml.append("")
499 | }
500 |
501 | if Fills.isEmpty {
502 | Xml.append("")
503 | }else{
504 | Xml.append("")
505 | for fill in Fills {
506 | Xml.append(fill)
507 | }
508 | Xml.append("")
509 | }
510 |
511 | if Borders.isEmpty {
512 | Xml.append("")
513 | }else{
514 | Xml.append("")
515 | for border in Borders {
516 | Xml.append(border)
517 | }
518 | Xml.append("")
519 | }
520 |
521 | Xml.append("")
522 | Xml.append("")
523 | Xml.append("")
524 |
525 | if xfs.isEmpty {
526 | Xml.append("")
527 | }else{
528 | Xml.append("")
529 |
530 | let ar = xfs.values.sorted(by: { $0.1 < $1.1})
531 | for (xf,_) in ar {
532 | Xml.append(xf)
533 | }
534 | Xml.append("")
535 | }
536 |
537 | Xml.append("")
538 | Xml.append("")
539 | Xml.append("")
540 | Xml.append("")
541 |
542 | return String(Xml)
543 | }
544 |
545 | private var ContentTypesStrings:String {
546 | let Xml:NSMutableString = NSMutableString()
547 | Xml.append("\n")
548 | Xml.append("")
549 | Xml.append("")
550 | Xml.append("")
551 |
552 | Xml.append("")
553 | for i in 1...Sheets.count {
554 | Xml.append("")
555 | }
556 |
557 | Xml.append("")
558 | for i in 1...Sheets.count {
559 | if Sheets[i-1].drawingsSheetrels != nil {
560 | Xml.append("")
561 | }
562 | }
563 |
564 |
565 | Xml.append("")
566 | Xml.append("")
567 | Xml.append("")
568 | return String(Xml)
569 | }
570 |
571 | private var WorkBookXmlRelsStrings:String {
572 | let Xml:NSMutableString = NSMutableString()
573 | let str = """
574 |
575 |
576 |
577 |
578 |
579 | """
580 | Xml.append(str)
581 | for i in 1...Sheets.count {
582 | Xml.append("")
583 | }
584 | Xml.append("")
585 | return String(Xml)
586 | }
587 |
588 | private var WorkBookXmlStrings:String {
589 | let Xml:NSMutableString = NSMutableString()
590 |
591 | Xml.append("\n")
592 | Xml.append("")
593 | Xml.append("")
594 | Xml.append("")
595 | for i in 1...Sheets.count {
596 | Xml.append("")
597 | }
598 |
599 |
600 |
601 | Xml.append("")
602 | Xml.append("")
603 | Xml.append("")
604 | Xml.append("")
605 | return String(Xml)
606 | }
607 |
608 | private var HeadSheedXML: String {
609 | "\n"
610 | }
611 | private var HeadSheedXMLEnd: String {
612 | ""
613 | }
614 |
615 | /// prepare files for Xlsx file
616 | private func preparefiles(for filename:String) -> String{
617 | var CachePath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0]
618 |
619 | CachePath = "\(CachePath)/tmpxls"
620 | self.CheckCreateDirectory(path: CachePath)
621 |
622 | let FolderId = "folderxls\(arc4random())"
623 | let BasePath = "\(CachePath)/\(FolderId)"
624 | self.CheckCreateDirectory(path: BasePath)
625 | self.CheckCreateDirectory(path: "\(BasePath)/_rels")
626 | self.CheckCreateDirectory(path: "\(BasePath)/xl")
627 | self.CheckCreateDirectory(path: "\(BasePath)/xl/media")
628 |
629 |
630 | for (_,ximg) in XImages.list {
631 | ximg.Write(toPath: "\(BasePath)/xl/media/")
632 | }
633 |
634 |
635 |
636 | self.CheckCreateDirectory(path: "\(BasePath)/xl/_rels")
637 | self.CheckCreateDirectory(path: "\(BasePath)/xl/worksheets")
638 | self.CheckCreateDirectory(path: "\(BasePath)/xl/worksheets/_rels")
639 | self.CheckCreateDirectory(path: "\(BasePath)/xl/drawings")
640 | self.CheckCreateDirectory(path: "\(BasePath)/xl/drawings/_rels")
641 |
642 | self.CheckCreateDirectory(path: "\(BasePath)/xl/theme")
643 |
644 | let style = """
645 |
646 |
647 | """
648 | self.Write(data: style, tofile: "\(BasePath)/xl/theme/theme1.xml")
649 |
650 |
651 | self.Write(data: self.rels, tofile: "\(BasePath)/_rels/.rels")
652 | self.Write(data: self.SharedStrings, tofile: "\(BasePath)/xl/sharedStrings.xml")
653 | self.Write(data: self.StyleStrings, tofile: "\(BasePath)/xl/styles.xml")
654 | self.Write(data: self.ContentTypesStrings, tofile: "\(BasePath)/[Content_Types].xml")
655 | self.Write(data: self.WorkBookXmlRelsStrings, tofile: "\(BasePath)/xl/_rels/workbook.xml.rels")
656 | self.Write(data: self.WorkBookXmlStrings, tofile: "\(BasePath)/xl/workbook.xml")
657 |
658 | var i = 1
659 | for sheet in Sheets {
660 |
661 | self.Write(data: sheet.xml!, tofile: "\(BasePath)/xl/worksheets/sheet\(i).xml")
662 |
663 | if let drawingsxml = sheet.drawingsxml {
664 | self.Write(data: drawingsxml, tofile: "\(BasePath)/xl/drawings/drawing\(i).xml")
665 | }
666 | if let drawingsxmlrels = sheet.drawingsxmlrels {
667 | self.Write(data: drawingsxmlrels, tofile: "\(BasePath)/xl/drawings/_rels/drawing\(i).xml.rels")
668 | }
669 |
670 | var rels = self.HeadSheedXML
671 | if let drawingsSheetrels = sheet.drawingsSheetrels {
672 | rels = "\(rels)\(drawingsSheetrels)"
673 | }
674 | rels = "\(rels)\(self.HeadSheedXMLEnd)"
675 | self.Write(data: rels, tofile: "\(BasePath)/xl/worksheets/_rels/sheet\(i).xml.rels")
676 |
677 | i += 1
678 | }
679 |
680 |
681 | let filepath = "\(CachePath)/\(filename)"
682 | _ = SSZipArchive.createZipFile(atPath: filepath,
683 | withContentsOfDirectory: BasePath,
684 | keepParentDirectory: false,
685 | compressionLevel: -1,
686 | password: nil,
687 | aes: true,
688 | progressHandler: nil)
689 | self.RemoveFile(path: BasePath)
690 | return filepath
691 | }
692 |
693 |
694 | /// write xlxs file and return path
695 | public func save(_ filename:String) -> String {
696 | self.BuildStyles()
697 | self.BuildSheets()
698 | self.BuildMediaDrawings()
699 | return self.preparefiles(for: filename)
700 | }
701 |
702 | /// generate example xlsx file
703 | static public func test() -> Bool {
704 | let book = XWorkBook()
705 |
706 | let testBundle = Bundle(for: self).resourcePath!
707 | let pathBundle = testBundle.appending("/SwiftXLSX_SwiftXLSX.bundle")
708 | let BundleTest = Bundle(path: pathBundle)
709 |
710 | var icons:[ImageClass] = []
711 | for iconname in ["green","blue","yellow","pin"] {
712 | var img:ImageClass?
713 | #if os(macOS)
714 | img = BundleTest?.image(forResource: iconname)
715 | #else
716 | img = ImageClass(named: iconname, in: BundleTest, compatibleWith: nil)
717 | #endif
718 | if let img = img {
719 | icons.append(img)
720 | }
721 | }
722 | #if os(macOS)
723 | let logoicon = BundleTest?.image(forResource: "swiftxlsxlogo")
724 | #else
725 | let logoicon = ImageClass(named: "swiftxlsxlogo", in: BundleTest, compatibleWith: nil)
726 | #endif
727 |
728 |
729 | let color:[ColorClass] = [.darkGray, .green, .lightGray, .orange, .systemPink, .cyan, .purple, .magenta, .blue]
730 | var colortext:[ColorClass] = [.darkGray, .black, .white]
731 | #if os(macOS)
732 | colortext.append(contentsOf:[.textColor,.textBackgroundColor])
733 | #else
734 | colortext.append(contentsOf: [.darkText,.lightText])
735 | #endif
736 |
737 | func GetRandomFont() -> XFontName {
738 | let cases = XFontName.allCases
739 | return cases[Int.random(in: 0..>>")
1021 | print("\(fileid)")
1022 | return true
1023 | }
1024 | }
1025 |
1026 | extension XWorkBook: Sequence {
1027 | public func makeIterator() -> Array.Iterator {
1028 | return Sheets.makeIterator()
1029 | }
1030 | }
1031 |
1032 | #if os(macOS)
1033 | fileprivate extension NSFont {
1034 | /// Calculate size text for current font
1035 | func Rectfor(_ str:String)-> CGSize {
1036 | let fontAttributes = [NSAttributedString.Key.font: self]
1037 | let size = (str as NSString).size(withAttributes: fontAttributes)
1038 | return size
1039 | }
1040 | }
1041 | #else
1042 | fileprivate extension UIFont {
1043 | /// Calculate size text for current font
1044 | func Rectfor(_ str:String)-> CGSize {
1045 | let fontAttributes = [NSAttributedString.Key.font: self]
1046 | let size = (str as NSString).size(withAttributes: fontAttributes)
1047 | return size
1048 | }
1049 | }
1050 | #endif
1051 |
1052 |
1053 | fileprivate extension String{
1054 | func XmlPrep() -> String
1055 | {
1056 | let sm = NSMutableString()
1057 | for (_, char) in self.enumerated() {
1058 | switch char {
1059 | case "&":
1060 | sm.append("&")
1061 | case "\"":
1062 | sm.append(""")
1063 | case "'":
1064 | sm.append("'")
1065 | case ">":
1066 | sm.append(">")
1067 | case "<":
1068 | sm.append("<")
1069 | default:
1070 | sm.append(String(char))
1071 | }
1072 | }
1073 | return String(sm)
1074 | }
1075 |
1076 | func XSheetTitle() -> String
1077 | {
1078 | let sm = NSMutableString()
1079 | for (_, char) in self.enumerated() {
1080 | switch char {
1081 | case ":":
1082 | sm.append(" ")
1083 | case "\\":
1084 | sm.append(" ")
1085 | case "\"":
1086 | sm.append(" ")
1087 | case "?":
1088 | sm.append(" ")
1089 | case "*":
1090 | sm.append(" ")
1091 | case "[":
1092 | sm.append(" ")
1093 | case "]":
1094 | sm.append(" ")
1095 | default:
1096 | sm.append(String(char))
1097 | }
1098 | }
1099 | return String(sm)
1100 | }
1101 | }
1102 |
1103 | extension ColorClass {
1104 | static var hexdict:[UInt64:String] = [:]
1105 | /// encode color to HEX format AARRGGBB use cache for optimization
1106 | var Hex:String?{
1107 | var r: CGFloat = 0
1108 | var g: CGFloat = 0
1109 | var b: CGFloat = 0
1110 | var a: CGFloat = 0
1111 | #if os(macOS)
1112 | let rgb = self.usingColorSpace(NSColorSpace.deviceRGB) ?? ColorClass.gray
1113 | rgb.getRed(&r, green: &g, blue: &b, alpha: &a)
1114 | #else
1115 | self.getRed(&r, green: &g, blue: &b, alpha: &a)
1116 | #endif
1117 |
1118 | let ri = lroundf(Float(r) * 255)
1119 | let gi = lroundf(Float(g) * 255)
1120 | let bi = lroundf(Float(b) * 255)
1121 | let ai = lroundf(Float(a) * 255)
1122 |
1123 | let idcolor = UInt64(ai)*1000000000+UInt64(ri)*1000000+UInt64(gi)*1000+UInt64(bi)
1124 | if let hexcol = ColorClass.hexdict[idcolor] {
1125 | return hexcol
1126 | }else{
1127 | let hexcolgen = String(format: "%02lX%02lX%02lX%02lX",ai,ri,gi,bi)
1128 | ColorClass.hexdict[idcolor] = hexcolgen
1129 | return hexcolgen
1130 | }
1131 | }
1132 | }
1133 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/Xtools.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 | // Created by Kostiantyn Bohonos on 6/15/22.
16 | //
17 |
18 | import Foundation
19 |
20 | public struct XCS{
21 | private static var table: [UInt32] = {
22 | (0...255).map { i -> UInt32 in
23 | (0..<8).reduce(UInt32(i), { c, _ in
24 | (c % 2 == 0) ? (c >> 1) : (0xEDB88320 ^ (c >> 1))
25 | })
26 | }
27 | }()
28 |
29 | public static func checksum(data: Data) -> UInt32 {
30 | var byteData = [UInt8](repeating:0, count: data.count)
31 | data.copyBytes(to: &byteData, count: data.count)
32 | return checksum(bytes: byteData)
33 | }
34 |
35 | /// check sum CRC32
36 | public static func checksum(bytes: [UInt8]) -> UInt32 {
37 | return ~(bytes.reduce(~UInt32(0), { crc, byte in
38 | (crc >> 8) ^ table[(Int(crc) ^ Int(byte)) & 0xFF]
39 | }))
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/blue.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "waving.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/blue.imageset/waving.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3973770/SwiftXLSX/6414cb5f3827a99bfcde2a26f2ecb2c0a35b4982/Sources/SwiftXLSX/icons.xcassets/blue.imageset/waving.png
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/green.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "flag.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/green.imageset/flag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3973770/SwiftXLSX/6414cb5f3827a99bfcde2a26f2ecb2c0a35b4982/Sources/SwiftXLSX/icons.xcassets/green.imageset/flag.png
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/pin.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "pin.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/pin.imageset/pin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3973770/SwiftXLSX/6414cb5f3827a99bfcde2a26f2ecb2c0a35b4982/Sources/SwiftXLSX/icons.xcassets/pin.imageset/pin.png
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/swiftxlsxlogo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "swiftxlsxlogo.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "swiftxlsxlogo-1.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "swiftxlsxlogo-2.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/swiftxlsxlogo.imageset/swiftxlsxlogo-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3973770/SwiftXLSX/6414cb5f3827a99bfcde2a26f2ecb2c0a35b4982/Sources/SwiftXLSX/icons.xcassets/swiftxlsxlogo.imageset/swiftxlsxlogo-1.png
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/swiftxlsxlogo.imageset/swiftxlsxlogo-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3973770/SwiftXLSX/6414cb5f3827a99bfcde2a26f2ecb2c0a35b4982/Sources/SwiftXLSX/icons.xcassets/swiftxlsxlogo.imageset/swiftxlsxlogo-2.png
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/swiftxlsxlogo.imageset/swiftxlsxlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3973770/SwiftXLSX/6414cb5f3827a99bfcde2a26f2ecb2c0a35b4982/Sources/SwiftXLSX/icons.xcassets/swiftxlsxlogo.imageset/swiftxlsxlogo.png
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/yellow.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "goal.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/SwiftXLSX/icons.xcassets/yellow.imageset/goal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/3973770/SwiftXLSX/6414cb5f3827a99bfcde2a26f2ecb2c0a35b4982/Sources/SwiftXLSX/icons.xcassets/yellow.imageset/goal.png
--------------------------------------------------------------------------------
/Tests/SwiftXLSXTests/SwiftXLSXTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import SwiftXLSX
3 |
4 | final class SwiftXLSXTests: XCTestCase {
5 |
6 |
7 | func testExample() throws {
8 | XCTAssertEqual(SwiftXLSX.test(), true)
9 | }
10 |
11 | // func testPerformance() throws {
12 | // self.measure {
13 | // _ = SwiftXLSX.test()
14 | // }
15 | // }
16 | }
17 |
--------------------------------------------------------------------------------