diff --git a/.gitignore b/.gitignore index 05f42f4..bd53872 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated +.build/ build/ DerivedData diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..56e359e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.19.6) + +project(Fuzi LANGUAGES Swift) + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift) + +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + # LibXml2 must be built on Windows. It's preinstalled on MacOS. + add_subdirectory(Vendor) +endif() + +add_subdirectory(Sources) diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt new file mode 100644 index 0000000..79c051d --- /dev/null +++ b/Sources/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(Fuzi + Document.swift + Element.swift + Error.swift + Helpers.swift + Node.swift + NodeSet.swift + Queryable.swift) + +target_link_libraries(Fuzi PUBLIC + LibXml2) diff --git a/Sources/Document.swift b/Sources/Document.swift index 299150f..9a386d9 100644 --- a/Sources/Document.swift +++ b/Sources/Document.swift @@ -30,15 +30,13 @@ open class XMLDocument { return ^-^self.cDocument.pointee.version }() - /// The string encoding for the document. This is NSUTF8StringEncoding if no encoding is set, or it cannot be calculated. + /// The string encoding for the document. This is utf8 if no encoding is set, or it cannot be calculated. open fileprivate(set) lazy var encoding: String.Encoding = { - if let encodingName = ^-^self.cDocument.pointee.encoding { - let encoding = CFStringConvertIANACharSetNameToEncoding(encodingName as CFString?) - if encoding != kCFStringEncodingInvalidId { - return String.Encoding(rawValue: UInt(CFStringConvertEncodingToNSStringEncoding(encoding))) - } + guard let encodingStr = ^-^self.cDocument.pointee.encoding, + let encoding = String.Encoding(ianaCharsetName: encodingStr) else { + return String.Encoding.utf8 } - return String.Encoding.utf8 + return encoding }() // MARK: - Accessing the Root Element diff --git a/Sources/Error.swift b/Sources/Error.swift index 0c91cda..16fa9d2 100644 --- a/Sources/Error.swift +++ b/Sources/Error.swift @@ -43,7 +43,7 @@ public enum XMLError: Error { } let message = (^-^errorPtr.pointee.message)?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) let code = Int(errorPtr.pointee.code) - xmlResetError(errorPtr) + xmlResetError(UnsafeMutablePointer(mutating: errorPtr)) return .libXMLError(code: code, message: message ?? "") } } diff --git a/Sources/Helpers.swift b/Sources/Helpers.swift index c824dcf..6e9a8ab 100644 --- a/Sources/Helpers.swift +++ b/Sources/Helpers.swift @@ -146,3 +146,48 @@ internal func cXMLNode(_ node: xmlNodePtr?, matchesTag tag: XMLCharsComparable, } return matches } + +internal extension String.Encoding { + // CoreFoundation is not available on Windows, so mimic the functionality + // of CFStringConvertIANACharSetNameToEncoding() and + // CFStringConvertEncodingToNSStringEncoding() here. See the IANA charset + // name registry at https://www.iana.org/assignments/character-sets/character-sets.xhtml + init?(ianaCharsetName name: String) { + switch name.lowercased() { + case "iso-2022-jp": + self = .iso2022JP + case "iso-8859-1": + self = .isoLatin1 + case "iso-8859-2": + self = .isoLatin2 + case "unicode-1-1", "iso-10646-ucs-2", "utf-16": + self = .utf16 + case "utf-8": + self = .utf8 + case "utf-16be": + self = .utf16BigEndian + case "utf-16le": + self = .utf16LittleEndian + case "utf-32": + self = .utf32 + case "utf-32be": + self = .utf32BigEndian + case "utf-32le": + self = .utf32LittleEndian + case "windows-1250": + self = .windowsCP1250 + case "windows-1251": + self = .windowsCP1251 + case "windows-1252": + self = .windowsCP1252 + case "windows-1253": + self = .windowsCP1253 + case "windows-1254": + self = .windowsCP1254 + case "us-ascii": + self = .ascii + default: + return nil + } + } +} diff --git a/Sources/Node.swift b/Sources/Node.swift index b2b36f7..4970cf9 100644 --- a/Sources/Node.swift +++ b/Sources/Node.swift @@ -69,7 +69,15 @@ extension XMLNodeType { /// XInclude End public static var XIncludeEnd: xmlElementType { return XML_XINCLUDE_END } /// DocbDocument - public static var DocbDocument: xmlElementType { return XML_DOCB_DOCUMENT_NODE } + public static var DocbDocument: xmlElementType { + #if os(Windows) + // The DOCB type is not part of the enum in libxml2, it's a #define. On + // Windows it gets imported as an incompatible type, so explicitly cast it. + return xmlElementType(XML_DOCB_DOCUMENT_NODE) + #else + return XML_DOCB_DOCUMENT_NODE + #endif + } } infix operator ~= @@ -97,7 +105,8 @@ open class XMLNode { /// The element's line number. open fileprivate(set) lazy var lineNumber: Int = { - return xmlGetLineNo(self.cNode) + // On Windows `long` is a 32 bit value so explicitly cast to Int. + return Int(xmlGetLineNo(self.cNode)) }() // MARK: - Accessing Parent and Sibling Elements @@ -119,7 +128,9 @@ open class XMLNode { // MARK: - Accessing Contents /// Whether this is a HTML node open var isHTML: Bool { - return UInt32(self.cNode.pointee.doc.pointee.properties) & XML_DOC_HTML.rawValue == XML_DOC_HTML.rawValue + // On Windows the bitset is imported as an incompatible type, so explicitly cast it. + let xmlDocHtmlBit = UInt32(XML_DOC_HTML.rawValue) + return UInt32(self.cNode.pointee.doc.pointee.properties) & xmlDocHtmlBit == xmlDocHtmlBit } /// A string representation of the element's value. diff --git a/Vendor/CMakeLists.txt b/Vendor/CMakeLists.txt new file mode 100644 index 0000000..9a24993 --- /dev/null +++ b/Vendor/CMakeLists.txt @@ -0,0 +1,30 @@ +include(FetchContent) + +# Turn off noisy deprecation warnings from Microsoft. +add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_WARNINGS) + +# Set libxml2 build options. +set(LIBXML2_WITH_ICONV OFF) +set(LIBXML2_WITH_PYTHON OFF) +set(BUILD_SHARED_LIBS OFF) + +# Fetch and build libxml2. +FetchContent_Declare(LibXml2 + GIT_REPOSITORY "https://github.com/GNOME/libxml2.git" + GIT_TAG "v2.13.3") +FetchContent_MakeAvailable(LibXml2) + +# libxml2 needs a modulemap to be usable from Swift. Glob the headers +# and write one to their headers directory. +file(GLOB LIBXML2_HEADERS "${libxml2_SOURCE_DIR}/include/libxml/*.h") +set(MODULE_CONTENT "module libxml2 {\n") +foreach(HEADER ${LIBXML2_HEADERS}) + get_filename_component(HEADER_NAME ${HEADER} NAME) + string(APPEND MODULE_CONTENT " header \"${HEADER_NAME}\"\n") +endforeach() +string(APPEND MODULE_CONTENT " export *\n}") +file(CONFIGURE OUTPUT ${libxml2_SOURCE_DIR}/include/libxml/module.modulemap + CONTENT "${MODULE_CONTENT}") + +# Make libxml2 available in the proper case. +add_library(libxml2 ALIAS LibXml2)