├── .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 | ![screenshot of invoce](https://raw.githubusercontent.com/3973770/SwiftXLSX/main/Sources/Sample/screen1.png) 74 | 75 | ![screenshot of icon test](https://raw.githubusercontent.com/3973770/SwiftXLSX/main/Sources/Sample/screen2.png) 76 | 77 | ![screenshot of performance test](https://raw.githubusercontent.com/3973770/SwiftXLSX/main/Sources/Sample/screen3.png) 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 | --------------------------------------------------------------------------------