├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── SwiftXML │ ├── Builder │ └── XParseBuilder.swift │ ├── Iteration │ ├── BasicIterators.swift │ ├── ChainedIterators.swift │ ├── ConvenienceExtensions.swift │ ├── IterationExtensions.swift │ ├── IteratorProtocols.swift │ ├── SequenceConcatenation.swift │ ├── Sequences.swift │ └── WrappingIterators.swift │ ├── Parsing │ └── Parsing.swift │ ├── Util.swift │ └── XML │ ├── Document.swift │ ├── Namespaces.swift │ ├── Nodes.swift │ ├── Production.swift │ ├── Tools.swift │ └── Transformation.swift ├── Tests └── SwiftXMLTests │ ├── FromReadme.swift │ ├── SequenceTypesTest.swift │ ├── SwiftXMLTests.swift │ └── ToolsTests.swift └── tutorial.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | /.build 4 | /Packages 5 | /Package.resolved 6 | /*.xcodeproj 7 | /.idea 8 | /.swiftpm 9 | /.vscode 10 | /Sources/SwiftXML/main.swift 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | By submitting a pull request or committing changes directly, you represent that you have the right to license your contribution to Stefan Springer and the community, and agree that your contributions are licensed under the [SwiftXML 2 | license](LICENSE). 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | 204 | 205 | ## Runtime Library Exception to the Apache 2.0 License: ## 206 | 207 | 208 | As an exception, if you use this Software to compile your source code and 209 | portions of this Software are embedded into the binary product as a result, 210 | you may redistribute such product without providing attribution as would 211 | otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.8 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: "SwiftXML", 8 | platforms: [ 9 | .iOS(.v16), 10 | .macOS(.v13), 11 | .tvOS(.v16), 12 | .watchOS(.v9), 13 | ], 14 | products: [ 15 | // Products define the executables and libraries a package produces, and make them visible to other packages. 16 | .library( 17 | name: "SwiftXML", 18 | targets: ["SwiftXML"]), 19 | ], 20 | dependencies: [ 21 | // Dependencies declare other packages that this package depends on. 22 | .package(url: "https://github.com/stefanspringer1/SwiftXMLParser", from: "3.0.2"), 23 | .package(url: "https://github.com/stefanspringer1/AutoreleasepoolShim", from: "1.0.3"), 24 | ], 25 | targets: [ 26 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 27 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 28 | .target( 29 | name: "SwiftXML", 30 | dependencies: [ 31 | "SwiftXMLParser", 32 | "AutoreleasepoolShim", 33 | ] 34 | ), 35 | .testTarget( 36 | name: "SwiftXMLTests", 37 | dependencies: ["SwiftXML"]), 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /Sources/SwiftXML/Builder/XParseBuilder.swift: -------------------------------------------------------------------------------- 1 | //===--- XParseBuilder.swift ----------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import Foundation 12 | import SwiftXMLInterfaces 13 | 14 | public final class XParseBuilder: XEventHandler { 15 | 16 | public func parsingTime(seconds: Double) { 17 | // - 18 | } 19 | 20 | let document: XDocument 21 | let recognizeNamespaces: Bool 22 | let keepComments: Bool 23 | let keepCDATASections: Bool 24 | let externalWrapperElement: String? 25 | 26 | var currentBranch: XBranchInternal 27 | 28 | var resultingNamespaceURIToPrefix = [String:String]() 29 | var namespaceURIAndPrefixDuringBuild = [(String,String)]() 30 | 31 | public init( 32 | document: XDocument, 33 | recognizeNamespaces: Bool = false, 34 | keepComments: Bool = false, 35 | keepCDATASections: Bool = false, 36 | externalWrapperElement: String? = nil 37 | ) { 38 | 39 | self.document = document 40 | self.recognizeNamespaces = recognizeNamespaces 41 | self.keepComments = keepComments 42 | self.keepCDATASections = keepCDATASections 43 | self.externalWrapperElement = externalWrapperElement 44 | 45 | self.currentBranch = document 46 | 47 | } 48 | 49 | public func documentStart() {} 50 | 51 | public func enterExternalDataSource(data: Data, entityName: String?, systemID: String, url: URL?, textRange _: XTextRange?, dataRange _: XDataRange?) { 52 | if let elementName = externalWrapperElement { 53 | var attributes = [String:String]() 54 | attributes["name"] = entityName 55 | attributes["sytemID"] = systemID 56 | attributes["path"] = url?.path 57 | elementStart( 58 | name: elementName, 59 | attributes: &attributes, 60 | textRange: nil, 61 | dataRange: nil 62 | ) 63 | } 64 | } 65 | 66 | public func leaveExternalDataSource() { 67 | if let elementName = externalWrapperElement { 68 | elementEnd(name: elementName, textRange: nil, dataRange: nil) 69 | } 70 | } 71 | 72 | public func enterInternalDataSource(data: Data, entityName: String, textRange: XTextRange?, dataRange: XDataRange?) { 73 | // - 74 | } 75 | 76 | public func leaveInternalDataSource() { 77 | // - 78 | } 79 | 80 | 81 | public func xmlDeclaration(version: String, encoding: String?, standalone: String?, textRange _: XTextRange?, dataRange _: XDataRange?) { 82 | document.xmlVersion = version 83 | if let theEncoding = encoding { 84 | document.encoding = theEncoding 85 | } 86 | if let theStandalone = standalone { 87 | document.standalone = theStandalone 88 | } 89 | } 90 | 91 | public func documentTypeDeclarationStart(type: String, publicID: String?, systemID: String?, textRange _: XTextRange?, dataRange _: XDataRange?) { 92 | document.type = type 93 | document.publicID = publicID 94 | document.systemID = systemID 95 | } 96 | 97 | public func documentTypeDeclarationEnd(textRange _: XTextRange?, dataRange _: XDataRange?) { 98 | // - 99 | } 100 | 101 | public func elementStart(name: String, attributes: inout [String:String], textRange: XTextRange?, dataRange _: XDataRange?) { 102 | let element = XElement(name) 103 | 104 | if recognizeNamespaces { 105 | var namespaceDefinitionCount = 0 106 | for attributeName in attributes.keys { 107 | if attributeName.hasPrefix("xmlns:"), let uri = attributes[attributeName] { 108 | namespaceDefinitionCount += 1 109 | let prefix = String(attributeName.dropFirst(6)) 110 | if resultingNamespaceURIToPrefix[uri] == nil { 111 | resultingNamespaceURIToPrefix[uri] = prefix 112 | } 113 | namespaceURIAndPrefixDuringBuild.append((uri,prefix)) 114 | attributes[attributeName] = nil 115 | } 116 | } 117 | if namespaceDefinitionCount > 0 { 118 | element.attached["nsCount"] = namespaceDefinitionCount 119 | } 120 | 121 | if let colon = name.firstIndex(of: ":") { 122 | let prefixOfElement = String(name[..= 0 { 125 | let (uri,prefix) = namespaceURIAndPrefixDuringBuild[i] 126 | if prefix == prefixOfElement, let resultingPrefix = resultingNamespaceURIToPrefix[uri] { 127 | element.prefix = resultingPrefix 128 | element.name = String(name[colon...].dropFirst()) 129 | break 130 | } 131 | i -= 1 132 | } 133 | } 134 | } 135 | 136 | currentBranch._add(element) 137 | element.setAttributes(attributes: attributes) 138 | currentBranch = element 139 | element._sourceRange = textRange 140 | } 141 | 142 | public func elementEnd(name: String, textRange: XTextRange?, dataRange _: XDataRange?) { 143 | 144 | if recognizeNamespaces, let element = currentBranch as? XElement, let namespaceDefinitionCount = element.attached["nsCount"] as? Int { 145 | namespaceURIAndPrefixDuringBuild.removeLast(namespaceDefinitionCount) 146 | element.attached["nsCount"] = nil 147 | } 148 | 149 | if let endTagTextRange = textRange, let element = currentBranch as? XElement, let startTagTextRange = element._sourceRange { 150 | element._sourceRange = XTextRange( 151 | startLine: startTagTextRange.startLine, 152 | startColumn: startTagTextRange.startColumn, 153 | endLine: endTagTextRange.endLine, 154 | endColumn: endTagTextRange.endColumn 155 | ) 156 | } 157 | if let parent = currentBranch._parent { 158 | currentBranch = parent 159 | } 160 | else { 161 | currentBranch = document 162 | } 163 | } 164 | 165 | public func text(text: String, whitespace: WhitespaceIndicator, textRange: XTextRange?, dataRange _: XDataRange?) { 166 | let node = XText(text, whitespace: whitespace) 167 | node._sourceRange = textRange 168 | currentBranch._add(node) 169 | } 170 | 171 | public func cdataSection(text: String, textRange: XTextRange?, dataRange _: XDataRange?) { 172 | let node = keepCDATASections ? XCDATASection(text): XText(text) 173 | node._sourceRange = textRange 174 | currentBranch._add(node) 175 | } 176 | 177 | public func internalEntity(name: String, textRange: XTextRange?, dataRange _: XDataRange?) { 178 | let node = XInternalEntity(name) 179 | node._sourceRange = textRange 180 | currentBranch._add(node) 181 | } 182 | 183 | public func externalEntity(name: String, textRange: XTextRange?, dataRange _: XDataRange?) { 184 | let node = XExternalEntity(name) 185 | node._sourceRange = textRange 186 | currentBranch._add(node) 187 | } 188 | 189 | public func processingInstruction(target: String, data: String?, textRange: XTextRange?, dataRange _: XDataRange?) { 190 | let node = XProcessingInstruction(target: target, data: data) 191 | node._sourceRange = textRange 192 | currentBranch._add(node) 193 | } 194 | 195 | public func comment(text: String, textRange: XTextRange?, dataRange _: XDataRange?) { 196 | if keepComments { 197 | let node = XComment(text, withAdditionalSpace: false) 198 | node._sourceRange = textRange 199 | currentBranch._add(node) 200 | } 201 | } 202 | 203 | public func internalEntityDeclaration(name: String, value: String, textRange: XTextRange?, dataRange _: XDataRange?) { 204 | let decl = XInternalEntityDeclaration(name: name, value: value) 205 | decl._sourceRange = textRange 206 | document.internalEntityDeclarations[name] = decl 207 | } 208 | 209 | public func externalEntityDeclaration(name: String, publicID: String?, systemID: String, textRange: XTextRange?, dataRange _: XDataRange?) { 210 | let decl = XExternalEntityDeclaration(name: name, publicID: publicID, systemID: systemID) 211 | decl._sourceRange = textRange 212 | document.externalEntityDeclarations[name] = decl 213 | } 214 | 215 | public func unparsedEntityDeclaration(name: String, publicID: String?, systemID: String, notation: String, textRange: XTextRange?, dataRange _: XDataRange?) { 216 | let decl = XUnparsedEntityDeclaration(name: name, publicID: publicID, systemID: systemID, notationName: notation) 217 | decl._sourceRange = textRange 218 | document.unparsedEntityDeclarations[name] = decl 219 | } 220 | 221 | public func notationDeclaration(name: String, publicID: String?, systemID: String?, textRange: XTextRange?, dataRange _: XDataRange?) { 222 | let decl = XNotationDeclaration(name: name, publicID: publicID, systemID: systemID) 223 | decl._sourceRange = textRange 224 | document.notationDeclarations[name] = decl 225 | } 226 | 227 | public func elementDeclaration(name: String, literal: String, textRange: XTextRange?, dataRange _: XDataRange?) { 228 | let decl = XElementDeclaration(name: name, literal: literal) 229 | decl._sourceRange = textRange 230 | document.elementDeclarations[name] = decl 231 | } 232 | 233 | public func attributeListDeclaration(name: String, literal: String, textRange: XTextRange?, dataRange _: XDataRange?) { 234 | let decl = XAttributeListDeclaration(name: name, literal: literal) 235 | decl._sourceRange = textRange 236 | document.attributeListDeclarations[name] = decl 237 | } 238 | 239 | public func parameterEntityDeclaration(name: String, value: String, textRange: XTextRange?, dataRange _: XDataRange?) { 240 | let decl = XParameterEntityDeclaration(name: name, value: value) 241 | decl._sourceRange = textRange 242 | document.parameterEntityDeclarations[name] = decl 243 | } 244 | 245 | public func documentEnd() { 246 | if let root = document.firstChild { 247 | for (uri,prefix) in resultingNamespaceURIToPrefix { 248 | root["xmlns:\(prefix)"] = uri 249 | } 250 | } 251 | } 252 | 253 | } 254 | -------------------------------------------------------------------------------- /Sources/SwiftXML/Iteration/ConvenienceExtensions.swift: -------------------------------------------------------------------------------- 1 | //===--- ConvenienceExtensions.swift -------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) and the SwiftXML project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | //===----------------------------------------------------------------------===// 9 | 10 | public protocol Applying {} 11 | 12 | public extension Applying { 13 | 14 | /// Apply an operation on the instance and return the changed instance. 15 | func applying(_ operation: (inout Self) throws -> Void) rethrows -> Self { 16 | var copy = self 17 | try operation(©) 18 | return copy 19 | } 20 | 21 | } 22 | 23 | public protocol Pulling {} 24 | 25 | public extension Pulling { 26 | 27 | /// Apply an operation on the instance and return the changed instance. 28 | func pulling(_ operation: (inout Self) throws -> T) rethrows -> T { 29 | var copy = self 30 | return try operation(©) 31 | } 32 | 33 | } 34 | 35 | public protocol Fullfilling {} 36 | 37 | public extension Fullfilling { 38 | 39 | /// Test if a certain condition is true for the instance, return the instance if the condition is `true`, else return `nil`. 40 | func fullfilling(_ condition: (Self) throws -> Bool) rethrows -> Self? { 41 | return try condition(self) ? self : nil 42 | } 43 | 44 | } 45 | 46 | public protocol Fullfill {} 47 | 48 | public extension Fullfill { 49 | 50 | /// Test if a certain condition is true for the instance, return the result of this test. 51 | func fullfills(_ condition: (Self) throws -> Bool) rethrows -> Bool { 52 | return try condition(self) 53 | } 54 | } 55 | 56 | extension XContent: Applying, Pulling, Fullfilling, Fullfill {} 57 | -------------------------------------------------------------------------------- /Sources/SwiftXML/Iteration/IteratorProtocols.swift: -------------------------------------------------------------------------------- 1 | //===--- IteratorProtocols.swift ------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import Foundation 12 | 13 | /** 14 | The XNodeIteratorProtocol implements one more features over the IteratorProtocol, 15 | it can go backwards via the function "previous". 16 | */ 17 | public protocol XContentIteratorProtocol { 18 | mutating func next() -> XContent? 19 | mutating func previous() -> XContent? 20 | } 21 | 22 | /** 23 | The XTextIteratorProtocol implements one more features over the IteratorProtocol, 24 | it can go backwards via the function "previous". 25 | */ 26 | public protocol XTextIteratorProtocol { 27 | mutating func next() -> XText? 28 | mutating func previous() -> XText? 29 | } 30 | 31 | /** 32 | The XElementIteratorProtocol implements one more features over the IteratorProtocol, 33 | it can go backwards via the function "previous". 34 | */ 35 | public protocol XElementIteratorProtocol { 36 | mutating func next() -> XElement? 37 | mutating func previous() -> XElement? 38 | } 39 | 40 | /** 41 | XAttributeIteratorProtocol is the version of XNodeIteratorProtocol for 42 | attributes. 43 | */ 44 | protocol XAttributeIteratorProtocol { 45 | mutating func next() -> AttributeProperties? 46 | mutating func previous() -> AttributeProperties? 47 | } 48 | -------------------------------------------------------------------------------- /Sources/SwiftXML/Iteration/SequenceConcatenation.swift: -------------------------------------------------------------------------------- 1 | //===--- SequenceConcatenation.swift --------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import Foundation 12 | 13 | // elements of names: 14 | 15 | public final class XElementsOfNamesSequence: XElementSequence { 16 | 17 | private let prefix: String? 18 | private let names: [String] 19 | private let document: XDocument 20 | 21 | init(forPrefix prefix: String?, forNames names: [String], forDocument document: XDocument) { 22 | self.prefix = prefix 23 | self.names = names 24 | self.document = document 25 | } 26 | 27 | public override func makeIterator() -> XElementIterator { 28 | return XElementsOfNamesIterator(forPrefix: prefix, forNames: names, forDocument: document) 29 | } 30 | 31 | } 32 | 33 | public final class XElementsOfNamesIterator: XElementIterator { 34 | 35 | private let iterators: [XXBidirectionalElementNameIterator] 36 | private var foundElement = false 37 | private var iteratorIndex = 0 38 | 39 | init(forPrefix prefix: String?, forNames names: [String], forDocument document: XDocument) { 40 | iterators = names.map{ XXBidirectionalElementNameIterator( 41 | elementIterator: XElementsOfSameNameIterator( 42 | document: document, 43 | prefix: prefix, 44 | name: $0, 45 | keepLast: true 46 | ) 47 | ) } 48 | } 49 | 50 | public override func next() -> XElement? { 51 | guard iterators.count > 0 else { return nil } 52 | while true { 53 | if iteratorIndex == iterators.count { 54 | if foundElement { 55 | iteratorIndex = 0 56 | foundElement = false 57 | } 58 | else { 59 | return nil 60 | } 61 | } 62 | let iterator = iterators[iteratorIndex] 63 | if let next = iterator.next() { 64 | foundElement = true 65 | return next 66 | } 67 | else { 68 | iteratorIndex += 1 69 | } 70 | } 71 | } 72 | 73 | } 74 | 75 | // attributes of names: 76 | 77 | public final class XAttributesOfNamesSequence: XAttributeSequence { 78 | 79 | private let names: [String] 80 | private let document: XDocument 81 | 82 | init(forNames names: [String], forDocument document: XDocument) { 83 | self.names = names 84 | self.document = document 85 | } 86 | 87 | public override func makeIterator() -> XAttributeIterator { 88 | return XAttributesOfNamesIterator(forNames: names, forDocument: document) 89 | } 90 | 91 | } 92 | 93 | public final class XAttributesOfNamesIterator: XAttributeIterator { 94 | 95 | private let iterators: [XBidirectionalAttributeIterator] 96 | private var foundElement = false 97 | private var iteratorIndex = 0 98 | 99 | init(forNames names: [String], forDocument document: XDocument) { 100 | iterators = names.map{ 101 | XBidirectionalAttributeIterator( 102 | forAttributeName: $0, attributeIterator: XAttributesOfSameNameIterator( 103 | document: document, 104 | attributeName: $0, 105 | keepLast: true 106 | ) 107 | ) 108 | } 109 | } 110 | 111 | public override func next() -> XAttributeSpot? { 112 | guard iterators.count > 0 else { return nil } 113 | while true { 114 | if iteratorIndex == iterators.count { 115 | if foundElement { 116 | iteratorIndex = 0 117 | foundElement = false 118 | } 119 | else { 120 | return nil 121 | } 122 | } 123 | let iterator = iterators[iteratorIndex] 124 | if let next = iterator.next() { 125 | foundElement = true 126 | return XAttributeSpot(name: next.name, value: next.value, element: next.element) 127 | } 128 | else { 129 | iteratorIndex += 1 130 | } 131 | } 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /Sources/SwiftXML/Iteration/Sequences.swift: -------------------------------------------------------------------------------- 1 | //===--- Sequences.swift --------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import Foundation 12 | 13 | public struct TypedIterator { 14 | 15 | private var iterator: any IteratorProtocol 16 | 17 | public init(for sequence: any Sequence) { 18 | self.iterator = sequence.makeIterator() as any IteratorProtocol 19 | } 20 | 21 | public mutating func next() -> T? { 22 | return iterator.next() as! T? 23 | } 24 | } 25 | 26 | // >>>>>>>>>>>>>>>> 27 | // with conditions: 28 | 29 | public final class XContentSequenceWithCondition: XContentSequence { 30 | 31 | let sequence: XContentSequence 32 | let condition: (XContent) -> Bool 33 | 34 | public init(sequence: XContentSequence, condition: @escaping (XContent) -> Bool) { 35 | self.sequence = sequence 36 | self.condition = condition 37 | } 38 | 39 | public override func makeIterator() -> XContentIterator { 40 | return XContentIteratorWithCondition( 41 | iterator: sequence.makeIterator(), 42 | condition: condition 43 | ) 44 | } 45 | } 46 | 47 | public final class XContentSequenceWhileCondition: XContentSequence { 48 | 49 | let sequence: XContentSequence 50 | let condition: (XContent) -> Bool 51 | 52 | public init(sequence: XContentSequence, while condition: @escaping (XContent) -> Bool) { 53 | self.sequence = sequence 54 | self.condition = condition 55 | } 56 | 57 | public override func makeIterator() -> XContentIterator { 58 | return XContentIteratorWhileCondition( 59 | iterator: sequence.makeIterator(), 60 | while: condition 61 | ) 62 | } 63 | } 64 | 65 | public final class XContentSequenceUntilCondition: XContentSequence { 66 | 67 | let sequence: XContentSequence 68 | let condition: (XContent) -> Bool 69 | 70 | public init(sequence: XContentSequence, until condition: @escaping (XContent) -> Bool) { 71 | self.sequence = sequence 72 | self.condition = condition 73 | } 74 | 75 | public override func makeIterator() -> XContentIterator { 76 | return XContentIteratorUntilCondition( 77 | iterator: sequence.makeIterator(), 78 | until: condition 79 | ) 80 | } 81 | } 82 | 83 | public final class XContentSequenceIncludingCondition: XContentSequence { 84 | 85 | let sequence: XContentSequence 86 | let condition: (XContent) -> Bool 87 | 88 | public init(sequence: XContentSequence, untilAndIncluding condition: @escaping (XContent) -> Bool) { 89 | self.sequence = sequence 90 | self.condition = condition 91 | } 92 | 93 | public override func makeIterator() -> XContentIterator { 94 | return XContentIteratorIncludingCondition( 95 | iterator: sequence.makeIterator(), 96 | untilAndIncluding: condition 97 | ) 98 | } 99 | } 100 | 101 | public final class XTextSequenceWithCondition: XTextSequence { 102 | 103 | let sequence: XTextSequence 104 | let condition: (XText) -> Bool 105 | 106 | public init(sequence: XTextSequence, condition: @escaping (XText) -> Bool) { 107 | self.sequence = sequence 108 | self.condition = condition 109 | } 110 | 111 | public override func makeIterator() -> XTextIterator { 112 | return XTextIteratorWithCondition( 113 | iterator: sequence.makeIterator(), 114 | condition: condition 115 | ) 116 | } 117 | } 118 | 119 | public final class XTextSequenceWhileCondition: XTextSequence { 120 | 121 | let sequence: XTextSequence 122 | let condition: (XText) -> Bool 123 | 124 | public init(sequence: XTextSequence, while condition: @escaping (XText) -> Bool) { 125 | self.sequence = sequence 126 | self.condition = condition 127 | } 128 | 129 | public override func makeIterator() -> XTextIterator { 130 | return XTextIteratorWhileCondition( 131 | iterator: sequence.makeIterator(), 132 | while: condition 133 | ) 134 | } 135 | } 136 | 137 | public final class XTextSequenceUntilCondition: XTextSequence { 138 | 139 | let sequence: XTextSequence 140 | let condition: (XText) -> Bool 141 | 142 | public init(sequence: XTextSequence, until condition: @escaping (XText) -> Bool) { 143 | self.sequence = sequence 144 | self.condition = condition 145 | } 146 | 147 | public override func makeIterator() -> XTextIterator { 148 | return XTextIteratorUntilCondition( 149 | iterator: sequence.makeIterator(), 150 | until: condition 151 | ) 152 | } 153 | } 154 | 155 | public final class XTextSequenceIncludingCondition: XTextSequence { 156 | 157 | let sequence: XTextSequence 158 | let condition: (XText) -> Bool 159 | 160 | public init(sequence: XTextSequence, untilAndIncluding condition: @escaping (XText) -> Bool) { 161 | self.sequence = sequence 162 | self.condition = condition 163 | } 164 | 165 | public override func makeIterator() -> XTextIterator { 166 | return XTextIteratorIncludingCondition( 167 | iterator: sequence.makeIterator(), 168 | untilAndIncluding: condition 169 | ) 170 | } 171 | } 172 | 173 | public final class XElementSequenceWithCondition: XElementSequence { 174 | 175 | let sequence: XElementSequence 176 | let condition: (XElement) -> Bool 177 | 178 | public init(sequence: XElementSequence, condition: @escaping (XElement) -> Bool) { 179 | self.sequence = sequence 180 | self.condition = condition 181 | } 182 | 183 | public init(sequence: XElementSequence, prefix: String?, elementName: String) { 184 | self.sequence = sequence 185 | self.condition = { $0.prefix == prefix && $0.name == elementName } 186 | } 187 | 188 | public override func makeIterator() -> XElementIterator { 189 | return XElementIteratorWithCondition( 190 | iterator: sequence.makeIterator(), 191 | condition: condition 192 | ) 193 | } 194 | } 195 | 196 | public final class XElementSequenceWhileCondition: XElementSequence { 197 | 198 | let sequence: XElementSequence 199 | let condition: (XElement) -> Bool 200 | 201 | public init(sequence: XElementSequence, while condition: @escaping (XElement) -> Bool) { 202 | self.sequence = sequence 203 | self.condition = condition 204 | } 205 | 206 | public init(sequence: XElementSequence, prefix: String?, elementName: String) { 207 | self.sequence = sequence 208 | self.condition = { $0.prefix == prefix && $0.name == elementName } 209 | } 210 | 211 | public override func makeIterator() -> XElementIterator { 212 | return XElementIteratorWhileCondition( 213 | iterator: sequence.makeIterator(), 214 | while: condition 215 | ) 216 | } 217 | } 218 | 219 | public final class XElementSequenceUntilCondition: XElementSequence { 220 | 221 | let sequence: XElementSequence 222 | let condition: (XElement) -> Bool 223 | 224 | public init(sequence: XElementSequence, until condition: @escaping (XElement) -> Bool) { 225 | self.sequence = sequence 226 | self.condition = condition 227 | } 228 | 229 | public init(sequence: XElementSequence, prefix: String?, elementName: String) { 230 | self.sequence = sequence 231 | self.condition = { $0.prefix == prefix && $0.name == elementName } 232 | } 233 | 234 | public override func makeIterator() -> XElementIterator { 235 | return XElementIteratorUntilCondition( 236 | iterator: sequence.makeIterator(), 237 | until: condition 238 | ) 239 | } 240 | } 241 | 242 | public final class XElementSequenceWithConditionAndUntilCondition: XElementSequence { 243 | 244 | let sequence: XElementSequence 245 | let condition: (XElement) -> Bool 246 | let untilCondition: (XElement) -> Bool 247 | 248 | public init(sequence: XElementSequence, condition: @escaping (XElement) -> Bool, until untilCondition: @escaping (XElement) -> Bool) { 249 | self.sequence = sequence 250 | self.condition = condition 251 | self.untilCondition = untilCondition 252 | } 253 | 254 | public init(sequence: XElementSequence, prefix: String?, elementName: String, until untilCondition: @escaping (XElement) -> Bool) { 255 | self.sequence = sequence 256 | self.condition = { $0.prefix == prefix && $0.name == elementName } 257 | self.untilCondition = untilCondition 258 | } 259 | 260 | public override func makeIterator() -> XElementIterator { 261 | return XElementIteratorWithConditionAndUntilCondition( 262 | iterator: sequence.makeIterator(), 263 | condition: condition, 264 | until: untilCondition 265 | ) 266 | } 267 | } 268 | 269 | public final class XElementSequenceWithConditionAndWhileCondition: XElementSequence { 270 | 271 | let sequence: XElementSequence 272 | let condition: (XElement) -> Bool 273 | let whileCondition: (XElement) -> Bool 274 | 275 | public init(sequence: XElementSequence, condition: @escaping (XElement) -> Bool, while whileCondition: @escaping (XElement) -> Bool) { 276 | self.sequence = sequence 277 | self.condition = condition 278 | self.whileCondition = whileCondition 279 | } 280 | 281 | public init(sequence: XElementSequence, prefix: String?, elementName: String, while whileCondition: @escaping (XElement) -> Bool) { 282 | self.sequence = sequence 283 | self.condition = { $0.prefix == prefix && $0.name == elementName } 284 | self.whileCondition = whileCondition 285 | } 286 | 287 | public override func makeIterator() -> XElementIterator { 288 | return XElementIteratorWithConditionAndWhileCondition( 289 | iterator: sequence.makeIterator(), 290 | condition: condition, 291 | while: whileCondition 292 | ) 293 | } 294 | } 295 | 296 | public final class XContentSequenceWithConditionAndUntilCondition: XContentSequence { 297 | 298 | let sequence: XContentSequence 299 | let condition: (XContent) -> Bool 300 | let untilCondition: (XContent) -> Bool 301 | 302 | public init(sequence: XContentSequence, condition: @escaping (XContent) -> Bool, until untilCondition: @escaping (XContent) -> Bool) { 303 | self.sequence = sequence 304 | self.condition = condition 305 | self.untilCondition = untilCondition 306 | } 307 | 308 | public override func makeIterator() -> XContentIterator { 309 | return XContentIteratorWithConditionAndUntilCondition( 310 | iterator: sequence.makeIterator(), 311 | condition: condition, 312 | until: untilCondition 313 | ) 314 | } 315 | } 316 | 317 | public final class XContentSequenceWithConditionAndWhileCondition: XContentSequence { 318 | 319 | let sequence: XContentSequence 320 | let condition: (XContent) -> Bool 321 | let whileCondition: (XContent) -> Bool 322 | 323 | public init(sequence: XContentSequence, condition: @escaping (XContent) -> Bool, while whileCondition: @escaping (XContent) -> Bool) { 324 | self.sequence = sequence 325 | self.condition = condition 326 | self.whileCondition = whileCondition 327 | } 328 | 329 | public override func makeIterator() -> XContentIterator { 330 | return XContentIteratorWithConditionAndWhileCondition( 331 | iterator: sequence.makeIterator(), 332 | condition: condition, 333 | while: whileCondition 334 | ) 335 | } 336 | } 337 | 338 | public final class XTextSequenceWithConditionAndUntilCondition: XTextSequence { 339 | 340 | let sequence: XTextSequence 341 | let condition: (XText) -> Bool 342 | let untilCondition: (XText) -> Bool 343 | 344 | public init(sequence: XTextSequence, condition: @escaping (XText) -> Bool, until untilCondition: @escaping (XText) -> Bool) { 345 | self.sequence = sequence 346 | self.condition = condition 347 | self.untilCondition = untilCondition 348 | } 349 | 350 | public override func makeIterator() -> XTextIterator { 351 | return XTextIteratorWithConditionAndUntilCondition( 352 | iterator: sequence.makeIterator(), 353 | condition: condition, 354 | until: untilCondition 355 | ) 356 | } 357 | } 358 | 359 | public final class XTextSequenceWithConditionAndWhileCondition: XTextSequence { 360 | 361 | let sequence: XTextSequence 362 | let condition: (XText) -> Bool 363 | let whileCondition: (XText) -> Bool 364 | 365 | public init(sequence: XTextSequence, condition: @escaping (XText) -> Bool, while whileCondition: @escaping (XText) -> Bool) { 366 | self.sequence = sequence 367 | self.condition = condition 368 | self.whileCondition = whileCondition 369 | } 370 | 371 | public override func makeIterator() -> XTextIterator { 372 | return XTextIteratorWithConditionAndWhileCondition( 373 | iterator: sequence.makeIterator(), 374 | condition: condition, 375 | while: whileCondition 376 | ) 377 | } 378 | } 379 | 380 | public final class XElementSequenceIncludingCondition: XElementSequence { 381 | 382 | let sequence: XElementSequence 383 | let condition: (XElement) -> Bool 384 | 385 | public init(sequence: XElementSequence, untilAndIncluding condition: @escaping (XElement) -> Bool) { 386 | self.sequence = sequence 387 | self.condition = condition 388 | } 389 | 390 | public init(sequence: XElementSequence, prefix: String?, elementName: String) { 391 | self.sequence = sequence 392 | self.condition = { $0.prefix == prefix && $0.name == elementName } 393 | } 394 | 395 | public override func makeIterator() -> XElementIterator { 396 | return XElementIteratorIncludingCondition( 397 | iterator: sequence.makeIterator(), 398 | untilAndIncluding: condition 399 | ) 400 | } 401 | } 402 | 403 | public final class XAttributeSequenceWithCondition: XAttributeSequence { 404 | 405 | let sequence: XAttributeSequence 406 | let condition: (XAttributeSpot) -> Bool 407 | 408 | public init(sequence: XAttributeSequence, condition: @escaping (XAttributeSpot) -> Bool) { 409 | self.sequence = sequence 410 | self.condition = condition 411 | } 412 | 413 | public override func makeIterator() -> XAttributeIterator { 414 | return XAttributeIteratorWithCondition( 415 | iterator: sequence.makeIterator(), 416 | condition: condition 417 | ) 418 | } 419 | } 420 | 421 | public final class XAttributeSequenceWhileCondition: XAttributeSequence { 422 | 423 | let sequence: XAttributeSequence 424 | let condition: (XAttributeSpot) -> Bool 425 | 426 | public init(sequence: XAttributeSequence, while condition: @escaping (XAttributeSpot) -> Bool) { 427 | self.sequence = sequence 428 | self.condition = condition 429 | } 430 | 431 | public override func makeIterator() -> XAttributeIterator { 432 | return XAttributeIteratorWhileCondition( 433 | iterator: sequence.makeIterator(), 434 | while: condition 435 | ) 436 | } 437 | } 438 | 439 | public final class XAttributeSequenceUntilCondition: XAttributeSequence { 440 | 441 | let sequence: XAttributeSequence 442 | let condition: (XAttributeSpot) -> Bool 443 | 444 | public init(sequence: XAttributeSequence, until condition: @escaping (XAttributeSpot) -> Bool) { 445 | self.sequence = sequence 446 | self.condition = condition 447 | } 448 | 449 | public override func makeIterator() -> XAttributeIterator { 450 | return XAttributeIteratorUntilCondition( 451 | iterator: sequence.makeIterator(), 452 | until: condition 453 | ) 454 | } 455 | } 456 | 457 | public final class XAttributeSequenceIncludingCondition: XAttributeSequence { 458 | 459 | let sequence: XAttributeSequence 460 | let condition: (XAttributeSpot) -> Bool 461 | 462 | public init(sequence: XAttributeSequence, untilAndIncluding condition: @escaping (XAttributeSpot) -> Bool) { 463 | self.sequence = sequence 464 | self.condition = condition 465 | } 466 | 467 | public override func makeIterator() -> XAttributeIterator { 468 | return XAttributeIteratorIncludingCondition( 469 | iterator: sequence.makeIterator(), 470 | untilAndIncluding: condition 471 | ) 472 | } 473 | } 474 | 475 | // <<<<<<<<<<<<<<<< 476 | 477 | public final class XTraversalSequence: XContentSequence { 478 | 479 | let node: XNode 480 | let directionIndicator: XDirectionIndicator 481 | 482 | public init(node: XNode, directionIndicator: XDirectionIndicator) { 483 | self.node = node 484 | self.directionIndicator = directionIndicator 485 | } 486 | 487 | public override func makeIterator() -> XContentIterator { 488 | return XBidirectionalContentIterator(contentIterator: XTreeIterator(startNode: node, directionIndicator: directionIndicator)) 489 | } 490 | } 491 | 492 | public final class XNextSequence: XContentSequence { 493 | 494 | let content: XContent 495 | 496 | public init(content: XContent) { 497 | self.content = content 498 | } 499 | 500 | public override func makeIterator() -> XContentIterator { 501 | return XBidirectionalContentIterator(contentIterator: XNextIterator(content: content)) 502 | } 503 | } 504 | 505 | public final class XNextIncludingSelfSequence: XContentSequence { 506 | 507 | let content: XContent 508 | 509 | public init(content: XContent) { 510 | self.content = content 511 | } 512 | 513 | public override func makeIterator() -> XContentIterator { 514 | return XBidirectionalContentIterator(contentIterator: XNextIncludingSelfIterator(content: content)) 515 | } 516 | } 517 | 518 | public final class XPreviousSequence: XContentSequence { 519 | 520 | let content: XContent 521 | 522 | public init(content: XContent) { 523 | self.content = content 524 | } 525 | 526 | public override func makeIterator() -> XBidirectionalContentIterator { 527 | return XBidirectionalContentIterator(contentIterator: XPreviousIterator(content: content)) 528 | } 529 | } 530 | 531 | public final class XPreviousIncludingSelfSequence: XContentSequence { 532 | 533 | let content: XContent 534 | 535 | public init(content: XContent) { 536 | self.content = content 537 | } 538 | 539 | public override func makeIterator() -> XBidirectionalContentIterator { 540 | return XBidirectionalContentIterator(contentIterator: XPreviousIncludingSelfIterator(content: content)) 541 | } 542 | } 543 | 544 | public final class XNextTextsSequence: XTextSequence { 545 | 546 | let content: XContent 547 | 548 | public init(content: XContent) { 549 | self.content = content 550 | } 551 | 552 | public override func makeIterator() -> XBidirectionalTextIterator { 553 | return XBidirectionalTextIterator(textIterator: XNextTextsIterator(node: content)) 554 | } 555 | } 556 | 557 | public final class XNextTextsIncludingSelfSequence: XTextSequence { 558 | 559 | let text: XText 560 | 561 | public init(text: XText) { 562 | self.text = text 563 | } 564 | 565 | public override func makeIterator() -> XBidirectionalTextIterator { 566 | return XBidirectionalTextIterator(textIterator: XNextTextsIncludingSelfIterator(text: text)) 567 | } 568 | } 569 | 570 | public final class XPreviousTextsSequence: XTextSequence { 571 | 572 | let content: XContent 573 | 574 | public init(content: XContent) { 575 | self.content = content 576 | } 577 | 578 | public override func makeIterator() -> XBidirectionalTextIterator { 579 | return XBidirectionalTextIterator(textIterator: XPreviousTextsIterator(content: content)) 580 | } 581 | } 582 | 583 | public final class XPreviousTextsIncludingSelfSequence: XTextSequence { 584 | 585 | let text: XText 586 | 587 | public init(text: XText) { 588 | self.text = text 589 | } 590 | 591 | public override func makeIterator() -> XBidirectionalTextIterator { 592 | return XBidirectionalTextIterator(textIterator: XPreviousTextsIncludingSelfIterator(text: text)) 593 | } 594 | } 595 | 596 | public final class XNextElementsSequence: XElementSequence { 597 | 598 | let content: XContent 599 | 600 | public init(content: XContent) { 601 | self.content = content 602 | } 603 | 604 | public override func makeIterator() -> XBidirectionalElementIterator { 605 | return XBidirectionalElementIterator(elementIterator: XNextElementsIterator(content: content)) 606 | } 607 | } 608 | 609 | public final class XNextCloseElementsSequence: XElementSequence { 610 | 611 | let content: XContent 612 | 613 | public init(content: XContent) { 614 | self.content = content 615 | } 616 | 617 | public override func makeIterator() -> XBidirectionalElementIterator { 618 | return XBidirectionalElementIterator(elementIterator: XNextCloseElementsIterator(content: content)) 619 | } 620 | } 621 | 622 | public final class XNextElementsIncludingSelfSequence: XElementSequence { 623 | 624 | let element: XElement 625 | 626 | public init(element: XElement) { 627 | self.element = element 628 | } 629 | 630 | public override func makeIterator() -> XBidirectionalElementIterator { 631 | return XBidirectionalElementIterator(elementIterator: XNextElementsIncludingSelfIterator(element: element)) 632 | } 633 | } 634 | 635 | public final class XNextCloseElementsIncludingSelfSequence: XElementSequence { 636 | 637 | let element: XElement 638 | 639 | public init(element: XElement) { 640 | self.element = element 641 | } 642 | 643 | public override func makeIterator() -> XBidirectionalElementIterator { 644 | return XBidirectionalElementIterator(elementIterator: XNextCloseElementsIncludingSelfIterator(element: element)) 645 | } 646 | } 647 | 648 | public final class XPreviousElementsSequence: XElementSequence { 649 | 650 | let content: XContent 651 | 652 | public init(content: XContent) { 653 | self.content = content 654 | } 655 | 656 | public override func makeIterator() -> XBidirectionalElementIterator { 657 | return XBidirectionalElementIterator(elementIterator: XPreviousElementsIterator(content: content)) 658 | } 659 | } 660 | 661 | public final class XPreviousCloseElementsSequence: XElementSequence { 662 | 663 | let content: XContent 664 | 665 | public init(content: XContent) { 666 | self.content = content 667 | } 668 | 669 | public override func makeIterator() -> XBidirectionalElementIterator { 670 | return XBidirectionalElementIterator(elementIterator: XPreviousCloseElementsIterator(content: content)) 671 | } 672 | } 673 | 674 | public final class XPreviousElementsIncludingSelfSequence: XElementSequence { 675 | 676 | let element: XElement 677 | 678 | public init(element: XElement) { 679 | self.element = element 680 | } 681 | 682 | public override func makeIterator() -> XBidirectionalElementIterator { 683 | return XBidirectionalElementIterator(elementIterator: XPreviousElementsIncludingSelfIterator(element: element)) 684 | } 685 | } 686 | 687 | public final class XPreviousCloseElementsIncludingSelfSequence: XElementSequence { 688 | 689 | let element: XElement 690 | 691 | public init(element: XElement) { 692 | self.element = element 693 | } 694 | 695 | public override func makeIterator() -> XBidirectionalElementIterator { 696 | return XBidirectionalElementIterator(elementIterator: XPreviousCloseElementsIncludingSelfIterator(element: element)) 697 | } 698 | } 699 | 700 | public final class XSequenceOfContent: XContentSequence { 701 | 702 | let node: XNode 703 | 704 | public init(node: XNode) { 705 | self.node = node 706 | } 707 | 708 | public override func makeIterator() -> XContentIterator { 709 | return XBidirectionalContentIterator(contentIterator: XContentsIterator(node: node)) 710 | } 711 | } 712 | 713 | public final class XReversedSequenceOfContent: XContentSequence { 714 | 715 | let node: XNode 716 | 717 | public init(node: XNode) { 718 | self.node = node 719 | } 720 | 721 | public override func makeIterator() -> XContentIterator { 722 | return XBidirectionalContentIterator(contentIterator: XReversedContentsIterator(node: node)) 723 | } 724 | } 725 | 726 | public final class XSequenceOfImmediateTexts: XTextSequence { 727 | 728 | let node: XNode 729 | 730 | public init(node: XNode) { 731 | self.node = node 732 | } 733 | 734 | public override func makeIterator() -> XTextIterator { 735 | return XBidirectionalTextIterator(textIterator: XNextTextsIterator(node: node)) 736 | } 737 | } 738 | 739 | public final class XSequenceOfAllTexts: XTextSequence { 740 | 741 | let node: XNode 742 | 743 | public init(node: XNode) { 744 | self.node = node 745 | } 746 | 747 | public override func makeIterator() -> XTextIterator { 748 | return XBidirectionalTextIterator(textIterator: XAllTextsIterator(node: node)) 749 | } 750 | } 751 | 752 | public final class XReversedSequenceOfAllContent: XContentSequence { 753 | 754 | let node: XNode 755 | 756 | public init(node: XNode) { 757 | self.node = node 758 | } 759 | 760 | public override func makeIterator() -> XContentIterator { 761 | return XBidirectionalContentIterator(contentIterator: XReversedAllContentIterator(node: node)) 762 | } 763 | } 764 | 765 | public final class XReversedSequenceOfAllTexts: XTextSequence { 766 | 767 | let node: XNode 768 | 769 | public init(node: XNode) { 770 | self.node = node 771 | } 772 | 773 | public override func makeIterator() -> XTextIterator { 774 | return XBidirectionalTextIterator(textIterator: XReversedAllTextsIterator(node: node)) 775 | } 776 | } 777 | 778 | public final class XReversedSequenceOfImmediateTexts: XTextSequence { 779 | 780 | let node: XNode 781 | 782 | public init(node: XNode) { 783 | self.node = node 784 | } 785 | 786 | public override func makeIterator() -> XTextIterator { 787 | return XBidirectionalTextIterator(textIterator: XReversedAllTextsIterator(node: node)) 788 | } 789 | } 790 | 791 | public final class XChildrenSequence: XElementSequence { 792 | 793 | let node: XNode 794 | 795 | public init(node: XNode) { 796 | self.node = node 797 | } 798 | 799 | public override func makeIterator() -> XElementIterator { 800 | return XBidirectionalElementIterator(elementIterator: XChildrenIterator(node: node)) 801 | } 802 | } 803 | 804 | public final class XReversedChildrenSequence: XElementSequence { 805 | 806 | let node: XNode 807 | 808 | public init(node: XNode) { 809 | self.node = node 810 | } 811 | 812 | public override func makeIterator() -> XElementIterator { 813 | return XBidirectionalElementIterator(elementIterator: XReversedChildrenIterator(node: node)) 814 | } 815 | } 816 | 817 | public final class XAncestorsSequence: XElementSequence { 818 | 819 | let node: XNode 820 | 821 | public init(node: XNode) { 822 | self.node = node 823 | } 824 | 825 | public override func makeIterator() -> XBidirectionalElementIterator { 826 | return XBidirectionalElementIterator(elementIterator: XAncestorsIterator(startNode: node)) 827 | } 828 | } 829 | 830 | public final class XAncestorsSequenceIncludingSelf: XElementSequence { 831 | 832 | let node: XNode 833 | 834 | public init(node: XNode) { 835 | self.node = node 836 | } 837 | 838 | public override func makeIterator() -> XBidirectionalElementIterator { 839 | return XBidirectionalElementIterator(elementIterator: XAncestorsIteratorIncludingSelf(startNode: node)) 840 | } 841 | } 842 | 843 | public final class XAllContentSequence: XContentSequence { 844 | 845 | let node: XNode 846 | 847 | public init(node: XNode) { 848 | self.node = node 849 | } 850 | 851 | public override func makeIterator() -> XBidirectionalContentIterator { 852 | return XBidirectionalContentIterator(contentIterator: XAllContentsIterator(node: node)) 853 | } 854 | } 855 | 856 | public final class XAllContentIncludingSelfSequence: XContentSequence { 857 | 858 | let node: XNode 859 | 860 | public init(node: XNode) { 861 | self.node = node 862 | } 863 | 864 | public override func makeIterator() -> XBidirectionalContentIterator { 865 | return XBidirectionalContentIterator(contentIterator: XAllContentsIncludingSelfIterator(node: node)) 866 | } 867 | } 868 | 869 | public final class XAllTextsSequence: XTextSequence { 870 | 871 | let node: XNode 872 | 873 | public init(node: XNode) { 874 | self.node = node 875 | } 876 | 877 | public override func makeIterator() -> XTextIterator { 878 | return XBidirectionalTextIterator(textIterator: XAllTextsIterator(node: node)) 879 | } 880 | } 881 | 882 | public final class XDescendantsSequence: XElementSequence { 883 | 884 | let node: XNode 885 | 886 | public init(node: XNode) { 887 | self.node = node 888 | } 889 | 890 | public override func makeIterator() -> XElementIterator { 891 | return XBidirectionalElementIterator(elementIterator: XDescendantsIterator(node: node)) 892 | } 893 | } 894 | 895 | public final class XDescendantsIncludingSelfSequence: XElementSequence { 896 | 897 | let element: XElement 898 | 899 | public init(element: XElement) { 900 | self.element = element 901 | } 902 | 903 | public override func makeIterator() -> XBidirectionalElementIterator { 904 | return XBidirectionalElementIterator(elementIterator: XDescendantsIncludingSelfIterator(element: element)) 905 | } 906 | } 907 | 908 | public final class XElementsOfSameNameSequence: XElementSequence { 909 | 910 | let document: XDocument 911 | let prefix: String? 912 | let elementName: String 913 | 914 | public init(document: XDocument, prefix: String?, name: String) { 915 | self.document = document 916 | self.prefix = prefix 917 | self.elementName = name 918 | } 919 | 920 | public override func makeIterator() -> XElementIterator { 921 | return XXBidirectionalElementNameIterator( 922 | elementIterator: XElementsOfSameNameIterator( 923 | document: document, 924 | prefix: prefix, 925 | name: elementName 926 | ) 927 | ) 928 | } 929 | } 930 | 931 | public final class XAttributesOfSameNameSequence: XAttributeSequence { 932 | 933 | let document: XDocument 934 | let attributeName: String 935 | 936 | public init(document: XDocument, attributeName: String) { 937 | self.document = document 938 | self.attributeName = attributeName 939 | } 940 | 941 | public override func makeIterator() -> XAttributeIterator { 942 | return XBidirectionalAttributeIterator( 943 | forAttributeName: attributeName, attributeIterator: XAttributesOfSameNameIterator( 944 | document: document, 945 | attributeName: attributeName 946 | ) 947 | ) 948 | } 949 | } 950 | 951 | public final class XAttributesOfSameValueSequence: XAttributeSequence { 952 | 953 | let document: XDocument 954 | let attributeName: String 955 | let attributeValue: String 956 | 957 | public init(document: XDocument, attributeName: String, attributeValue: String) { 958 | self.document = document 959 | self.attributeName = attributeName 960 | self.attributeValue = attributeValue 961 | } 962 | 963 | public override func makeIterator() -> XAttributeIterator { 964 | return XBidirectionalAttributeIterator( 965 | forAttributeName: attributeName, attributeIterator: XAttributesOfSameValueIterator( 966 | document: document, 967 | attributeName: attributeName, 968 | attributeValue: attributeValue 969 | ) 970 | ) 971 | } 972 | } 973 | 974 | /** 975 | A sequence iterating only over one element. This ist mainly for testing. 976 | */ 977 | public final class XElementSelfSequence: XElementSequence { 978 | 979 | let element: XElement 980 | 981 | public init(element: XElement) { 982 | self.element = element 983 | } 984 | 985 | public override func makeIterator() -> XBidirectionalElementIterator { 986 | return XBidirectionalElementIterator( 987 | elementIterator: XElementSelfIterator( 988 | element: element 989 | ) 990 | ) 991 | } 992 | } 993 | 994 | /** 995 | A sequence iterating only over one node. This ist mainly for testing. 996 | */ 997 | public final class XContentSelfSequence: XContentSequence { 998 | 999 | let content: XContent 1000 | 1001 | public init(content: XContent) { 1002 | self.content = content 1003 | } 1004 | 1005 | public override func makeIterator() -> XBidirectionalContentIterator { 1006 | return XBidirectionalContentIterator( 1007 | contentIterator: XContentSelfIterator( 1008 | content: content 1009 | ) 1010 | ) 1011 | } 1012 | } 1013 | -------------------------------------------------------------------------------- /Sources/SwiftXML/Iteration/WrappingIterators.swift: -------------------------------------------------------------------------------- 1 | //===--- WrappingIterators.swift ------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import Foundation 12 | 13 | /** 14 | The XNodeIterator does the work of making sure that an XML tree can be manipulated 15 | during iteration. It is ignorant about what precise iteration takes place. The 16 | precise iteration is implemented in "iteratorImplementation" which implements 17 | the XIteratorProtocol. 18 | */ 19 | public final class XBidirectionalContentIterator: XContentIterator { 20 | 21 | var previousIterator: XBidirectionalContentIterator? = nil 22 | var nextIterator: XBidirectionalContentIterator? = nil 23 | 24 | public typealias Element = XContent 25 | 26 | var contentIterator: XContentIteratorProtocol 27 | 28 | public init(contentIterator: XContentIteratorProtocol) { 29 | self.contentIterator = contentIterator } 30 | 31 | weak var current: Element? = nil 32 | var prefetched = false 33 | 34 | public override func next() -> XContent? { 35 | if prefetched { 36 | prefetched = false 37 | return current 38 | } 39 | current?.removeContentIterator(self) 40 | current = contentIterator.next() 41 | current?.addContentIterator(self) 42 | return current 43 | } 44 | 45 | public override func previous() -> XContent? { 46 | prefetched = false 47 | current?.removeContentIterator(self) 48 | current = contentIterator.previous() 49 | current?.addContentIterator(self) 50 | return current 51 | } 52 | 53 | public func prefetch() { 54 | current?.removeContentIterator(self) 55 | current = contentIterator.next() 56 | current?.addContentIterator(self) 57 | prefetched = true 58 | } 59 | } 60 | 61 | public final class XBidirectionalTextIterator: XTextIterator { 62 | 63 | var previousIterator: XBidirectionalTextIterator? = nil 64 | var nextIterator: XBidirectionalTextIterator? = nil 65 | 66 | public typealias Element = XText 67 | 68 | var textIterator: XTextIteratorProtocol 69 | 70 | public init(textIterator: XTextIteratorProtocol) { 71 | self.textIterator = textIterator } 72 | 73 | weak var current: XText? = nil 74 | var prefetched = false 75 | 76 | public override func next() -> XText? { 77 | if prefetched { 78 | prefetched = false 79 | return current 80 | } 81 | current?.removeTextIterator(self) 82 | current = textIterator.next() 83 | current?.addTextIterator(self) 84 | return current 85 | } 86 | 87 | public override func previous() -> XText? { 88 | prefetched = false 89 | current?.removeTextIterator(self) 90 | current = textIterator.previous() 91 | current?.addTextIterator(self) 92 | return current 93 | } 94 | 95 | public func prefetch() { 96 | current?.removeTextIterator(self) 97 | current = textIterator.next() 98 | current?.addTextIterator(self) 99 | prefetched = true 100 | } 101 | } 102 | 103 | 104 | public final class XBidirectionalElementIterator: XElementIterator { 105 | 106 | var previousIterator: XBidirectionalElementIterator? = nil 107 | var nextIterator: XBidirectionalElementIterator? = nil 108 | 109 | public typealias Element = XElement 110 | 111 | var elementIterator: XElementIteratorProtocol 112 | 113 | public init(elementIterator: XElementIteratorProtocol) { 114 | self.elementIterator = elementIterator 115 | } 116 | 117 | weak var current: XElement? = nil 118 | var prefetched = false 119 | 120 | public override func next() -> XElement? { 121 | if prefetched { 122 | prefetched = false 123 | return current 124 | } 125 | current?.removeElementIterator(self) 126 | current = elementIterator.next() 127 | current?.addElementIterator(self) 128 | return current 129 | } 130 | 131 | public override func previous() -> XElement? { 132 | prefetched = false 133 | current?.removeElementIterator(self) 134 | current = elementIterator.previous() 135 | current?.addElementIterator(self) 136 | return current 137 | } 138 | 139 | public func prefetch() { 140 | current?.removeElementIterator(self) 141 | current = elementIterator.next() 142 | current?.addElementIterator(self) 143 | prefetched = true 144 | } 145 | } 146 | 147 | public final class XXBidirectionalElementNameIterator: XElementIterator { 148 | 149 | public typealias Element = XElement 150 | 151 | var elementIterator: XElementIteratorProtocol 152 | 153 | var keepLast: Bool 154 | 155 | public init(elementIterator: XElementIteratorProtocol, keepLast: Bool = false) { 156 | self.elementIterator = elementIterator 157 | self.keepLast = keepLast 158 | } 159 | 160 | weak var current: XElement? = nil 161 | var prefetched = false 162 | 163 | public override func next() -> XElement? { 164 | if prefetched { 165 | prefetched = false 166 | return current 167 | } 168 | let next = elementIterator.next() 169 | if keepLast && next == nil { 170 | return nil 171 | } 172 | else { 173 | current?.removeNameIterator(self) 174 | current = next 175 | current?.addNameIterator(self) 176 | return current 177 | } 178 | } 179 | 180 | public override func previous() -> XElement? { 181 | prefetched = false 182 | current?.removeNameIterator(self) 183 | current = elementIterator.previous() 184 | current?.addNameIterator(self) 185 | return current 186 | } 187 | 188 | public func prefetch() { 189 | current?.removeNameIterator(self) 190 | current = elementIterator.next() 191 | current?.addNameIterator(self) 192 | prefetched = true 193 | } 194 | } 195 | 196 | public struct XAttributeSpot { public let name: String; public let value: String; public let element: XElement } 197 | 198 | public final class XBidirectionalAttributeIterator: XAttributeIterator { 199 | 200 | var previousIterator: XBidirectionalAttributeIterator? = nil 201 | var nextIterator: XBidirectionalAttributeIterator? = nil 202 | 203 | public typealias Element = XAttributeSpot 204 | 205 | var attributeIterator: XAttributeIteratorProtocol 206 | 207 | var keepLast: Bool 208 | var name: String 209 | 210 | init(forAttributeName name: String, attributeIterator: XAttributeIteratorProtocol, keepLast: Bool = false) { 211 | self.name = name 212 | self.attributeIterator = attributeIterator 213 | self.keepLast = keepLast 214 | } 215 | 216 | weak var current: AttributeProperties? = nil 217 | var prefetched = false 218 | 219 | public override func next() -> XAttributeSpot? { 220 | if prefetched { 221 | prefetched = false 222 | } 223 | else { 224 | let next = attributeIterator.next() 225 | if keepLast && next == nil { 226 | return nil 227 | } 228 | else { 229 | current?.removeAttributeIterator(self) 230 | current = next 231 | current?.addAttributeIterator(self) 232 | } 233 | } 234 | if let value = current?.value, let element = current?.element { 235 | return XAttributeSpot(name: name, value: value, element: element) 236 | } 237 | else { 238 | current?.removeAttributeIterator(self) 239 | return nil 240 | } 241 | } 242 | 243 | public func previous() -> XAttributeSpot? { 244 | prefetched = false 245 | current?.removeAttributeIterator(self) 246 | current = attributeIterator.previous() 247 | if let value = current?.value, let element = current?.element { 248 | current?.addAttributeIterator(self) 249 | return XAttributeSpot(name: name, value:value, element: element) 250 | } 251 | else { 252 | return nil 253 | } 254 | } 255 | 256 | public func prefetch() { 257 | current?.removeAttributeIterator(self) 258 | current = attributeIterator.next() 259 | current?.addAttributeIterator(self) 260 | prefetched = true 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /Sources/SwiftXML/Parsing/Parsing.swift: -------------------------------------------------------------------------------- 1 | //===--- Parsing.swift ----------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import Foundation 12 | import SwiftXMLInterfaces 13 | import SwiftXMLParser 14 | 15 | public func parseXML( 16 | from documentSource: XDocumentSource, 17 | recognizeNamespaces: Bool = false, 18 | registeringAttributes attributeRegisterMode: AttributeRegisterMode = .none, 19 | registeringValuesForAttributes attributeValueRegisterMode: AttributeRegisterMode = .none, 20 | sourceInfo: String? = nil, 21 | textAllowedInElementWithName: ((String) -> Bool)? = nil, 22 | internalEntityAutoResolve: Bool = false, 23 | internalEntityResolver: InternalEntityResolver? = nil, 24 | internalEntityResolverHasToResolve: Bool = true, 25 | insertExternalParsedEntities: Bool = false, 26 | externalParsedEntitySystemResolver: ((String) -> URL?)? = nil, 27 | externalParsedEntityGetter: ((String) -> Data?)? = nil, 28 | externalWrapperElement: String? = nil, 29 | keepComments: Bool = false, 30 | keepCDATASections: Bool = false, 31 | eventHandlers: [XEventHandler]? = nil, 32 | immediateTextHandlingNearEntities: ImmediateTextHandlingNearEntities = .atExternalEntities 33 | ) throws -> XDocument { 34 | 35 | let document = XDocument(registeringAttributes: attributeRegisterMode, registeringValuesForAttributes: attributeValueRegisterMode) 36 | 37 | switch documentSource { 38 | case .url(url: let url): 39 | document._sourcePath = url.path 40 | case .path(path: let path): 41 | document._sourcePath = path 42 | default: 43 | break 44 | } 45 | 46 | let parser = ConvenienceParser( 47 | parser: XParser( 48 | internalEntityAutoResolve: internalEntityAutoResolve, 49 | internalEntityResolver: internalEntityResolver, 50 | internalEntityResolverHasToResolve: internalEntityResolverHasToResolve, 51 | textAllowedInElementWithName: textAllowedInElementWithName, 52 | insertExternalParsedEntities: insertExternalParsedEntities, 53 | externalParsedEntitySystemResolver: externalParsedEntitySystemResolver, 54 | externalParsedEntityGetter: externalParsedEntityGetter 55 | ), 56 | mainEventHandler: XParseBuilder( 57 | document: document, 58 | recognizeNamespaces: recognizeNamespaces, 59 | keepComments: keepComments, 60 | keepCDATASections: keepCDATASections, 61 | externalWrapperElement: externalWrapperElement 62 | ) 63 | ) 64 | 65 | try parser.parse(from: documentSource, sourceInfo: sourceInfo, eventHandlers: eventHandlers, immediateTextHandlingNearEntities: immediateTextHandlingNearEntities) 66 | 67 | return document 68 | } 69 | 70 | public func parseXML( 71 | fromPath path: String, 72 | registeringAttributes attributeRegisterMode: AttributeRegisterMode = .none, 73 | registeringValuesForAttributes attributeValueRegisterMode: AttributeRegisterMode = .none, 74 | sourceInfo: String? = nil, 75 | textAllowedInElementWithName: ((String) -> Bool)? = nil, 76 | internalEntityAutoResolve: Bool = false, 77 | internalEntityResolver: InternalEntityResolver? = nil, 78 | internalEntityResolverHasToResolve: Bool = true, 79 | insertExternalParsedEntities: Bool = false, 80 | externalParsedEntitySystemResolver: ((String) -> URL?)? = nil, 81 | externalParsedEntityGetter: ((String) -> Data?)? = nil, 82 | externalWrapperElement: String? = nil, 83 | keepComments: Bool = false, 84 | keepCDATASections: Bool = false, 85 | eventHandlers: [XEventHandler]? = nil, 86 | immediateTextHandlingNearEntities: ImmediateTextHandlingNearEntities = .atExternalEntities 87 | ) throws -> XDocument { 88 | try parseXML( 89 | from: .path(path), 90 | registeringAttributes: attributeRegisterMode, 91 | registeringValuesForAttributes: attributeValueRegisterMode, 92 | sourceInfo: sourceInfo, 93 | textAllowedInElementWithName: textAllowedInElementWithName, 94 | internalEntityAutoResolve: internalEntityAutoResolve, 95 | internalEntityResolver: internalEntityResolver, 96 | internalEntityResolverHasToResolve: internalEntityResolverHasToResolve, 97 | insertExternalParsedEntities: insertExternalParsedEntities, 98 | externalParsedEntitySystemResolver: externalParsedEntitySystemResolver, 99 | externalParsedEntityGetter: externalParsedEntityGetter, 100 | externalWrapperElement: externalWrapperElement, 101 | keepComments: keepComments, 102 | keepCDATASections: keepCDATASections, 103 | eventHandlers: eventHandlers, 104 | immediateTextHandlingNearEntities: immediateTextHandlingNearEntities 105 | ) 106 | } 107 | 108 | public func parseXML( 109 | fromURL url: URL, 110 | registeringAttributes attributeRegisterMode: AttributeRegisterMode = .none, 111 | registeringValuesForAttributes attributeValueRegisterMode: AttributeRegisterMode = .none, 112 | sourceInfo: String? = nil, 113 | textAllowedInElementWithName: ((String) -> Bool)? = nil, 114 | internalEntityAutoResolve: Bool = false, 115 | internalEntityResolver: InternalEntityResolver? = nil, 116 | internalEntityResolverHasToResolve: Bool = true, 117 | insertExternalParsedEntities: Bool = false, 118 | externalParsedEntitySystemResolver: ((String) -> URL?)? = nil, 119 | externalParsedEntityGetter: ((String) -> Data?)? = nil, 120 | externalWrapperElement: String? = nil, 121 | keepComments: Bool = false, 122 | keepCDATASections: Bool = false, 123 | eventHandlers: [XEventHandler]? = nil, 124 | immediateTextHandlingNearEntities: ImmediateTextHandlingNearEntities = .atExternalEntities 125 | ) throws -> XDocument { 126 | try parseXML( 127 | from: .url(url), 128 | registeringAttributes: attributeRegisterMode, 129 | registeringValuesForAttributes: attributeValueRegisterMode, 130 | sourceInfo: sourceInfo, 131 | textAllowedInElementWithName: textAllowedInElementWithName, 132 | internalEntityAutoResolve: internalEntityAutoResolve, 133 | internalEntityResolver: internalEntityResolver, 134 | internalEntityResolverHasToResolve: internalEntityResolverHasToResolve, 135 | insertExternalParsedEntities: insertExternalParsedEntities, 136 | externalParsedEntitySystemResolver: externalParsedEntitySystemResolver, 137 | externalParsedEntityGetter: externalParsedEntityGetter, 138 | externalWrapperElement: externalWrapperElement, 139 | keepComments: keepComments, 140 | keepCDATASections: keepCDATASections, 141 | eventHandlers: eventHandlers, 142 | immediateTextHandlingNearEntities: immediateTextHandlingNearEntities 143 | ) 144 | } 145 | 146 | public func parseXML( 147 | fromText text: String, 148 | recognizeNamespaces: Bool = false, 149 | registeringAttributes attributeRegisterMode: AttributeRegisterMode = .none, 150 | registeringValuesForAttributes attributeValueRegisterMode: AttributeRegisterMode = .none, 151 | sourceInfo: String? = nil, 152 | textAllowedInElementWithName: ((String) -> Bool)? = nil, 153 | internalEntityAutoResolve: Bool = false, 154 | internalEntityResolver: InternalEntityResolver? = nil, 155 | internalEntityResolverHasToResolve: Bool = true, 156 | insertExternalParsedEntities: Bool = false, 157 | externalParsedEntitySystemResolver: ((String) -> URL?)? = nil, 158 | externalParsedEntityGetter: ((String) -> Data?)? = nil, 159 | externalWrapperElement: String? = nil, 160 | keepComments: Bool = false, 161 | keepCDATASections: Bool = false, 162 | eventHandlers: [XEventHandler]? = nil, 163 | immediateTextHandlingNearEntities: ImmediateTextHandlingNearEntities = .atExternalEntities 164 | ) throws -> XDocument { 165 | try parseXML( 166 | from: .text(text), 167 | recognizeNamespaces: recognizeNamespaces, 168 | registeringAttributes: attributeRegisterMode, 169 | registeringValuesForAttributes: attributeValueRegisterMode, 170 | sourceInfo: sourceInfo, 171 | textAllowedInElementWithName: textAllowedInElementWithName, 172 | internalEntityAutoResolve: internalEntityAutoResolve, 173 | internalEntityResolver: internalEntityResolver, 174 | internalEntityResolverHasToResolve: internalEntityResolverHasToResolve, 175 | insertExternalParsedEntities: insertExternalParsedEntities, 176 | externalParsedEntitySystemResolver: externalParsedEntitySystemResolver, 177 | externalParsedEntityGetter: externalParsedEntityGetter, 178 | externalWrapperElement: externalWrapperElement, 179 | keepComments: keepComments, 180 | keepCDATASections: keepCDATASections, 181 | eventHandlers: eventHandlers, 182 | immediateTextHandlingNearEntities: immediateTextHandlingNearEntities 183 | ) 184 | } 185 | 186 | public func parseXML( 187 | fromData data: Data, 188 | recognizeNamespaces: Bool = false, 189 | registeringAttributes attributeRegisterMode: AttributeRegisterMode = .none, 190 | registeringValuesForAttributes attributeValueRegisterMode: AttributeRegisterMode = .none, 191 | sourceInfo: String? = nil, 192 | internalEntityAutoResolve: Bool = false, 193 | internalEntityResolver: InternalEntityResolver? = nil, 194 | internalEntityResolverHasToResolve: Bool = true, 195 | eventHandlers: [XEventHandler]? = nil, 196 | immediateTextHandlingNearEntities: ImmediateTextHandlingNearEntities = .atExternalEntities, 197 | textAllowedInElementWithName: ((String) -> Bool)? = nil, 198 | keepComments: Bool = false, 199 | keepCDATASections: Bool = false, 200 | insertExternalParsedEntities: Bool = false, 201 | externalParsedEntitySystemResolver: ((String) -> URL?)? = nil, 202 | externalParsedEntityGetter: ((String) -> Data?)? = nil, 203 | externalWrapperElement: String? = nil, 204 | externalWrapperNameAttribute: String? = nil, 205 | externalWrapperPathAttribute: String? = nil 206 | ) throws -> XDocument { 207 | try parseXML( 208 | from: .data(data), 209 | recognizeNamespaces: recognizeNamespaces, 210 | registeringAttributes: attributeRegisterMode, 211 | registeringValuesForAttributes: attributeValueRegisterMode, 212 | sourceInfo: sourceInfo, 213 | textAllowedInElementWithName: textAllowedInElementWithName, 214 | internalEntityAutoResolve: internalEntityAutoResolve, 215 | internalEntityResolver: internalEntityResolver, 216 | internalEntityResolverHasToResolve: internalEntityResolverHasToResolve, 217 | insertExternalParsedEntities: insertExternalParsedEntities, 218 | externalParsedEntitySystemResolver: externalParsedEntitySystemResolver, 219 | externalParsedEntityGetter: externalParsedEntityGetter, 220 | externalWrapperElement: externalWrapperElement, 221 | keepComments: keepComments, 222 | keepCDATASections: keepCDATASections, 223 | eventHandlers: eventHandlers, 224 | immediateTextHandlingNearEntities: immediateTextHandlingNearEntities 225 | ) 226 | } 227 | -------------------------------------------------------------------------------- /Sources/SwiftXML/Util.swift: -------------------------------------------------------------------------------- 1 | //===--- Util.swift -------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import Foundation 12 | import AutoreleasepoolShim 13 | 14 | public extension String { 15 | 16 | var escapingAllForXML: String { 17 | self 18 | .replacingOccurrences(of: "&", with: "&") 19 | .replacingOccurrences(of: "<", with: "<") 20 | .replacingOccurrences(of: ">", with: ">") 21 | .replacingOccurrences(of: "\"", with: """) 22 | .replacingOccurrences(of: "'", with: "'") 23 | } 24 | 25 | var escapingForXML: String { 26 | self 27 | .replacingOccurrences(of: "&", with: "&") 28 | .replacingOccurrences(of: "<", with: "<") 29 | } 30 | 31 | var escapingDoubleQuotedValueForXML: String { 32 | self 33 | .replacingOccurrences(of: "&", with: "&") 34 | .replacingOccurrences(of: "<", with: "<") 35 | .replacingOccurrences(of: "\"", with: """) 36 | } 37 | 38 | var escapingSimpleQuotedValueForXML: String { 39 | self 40 | .replacingOccurrences(of: "&", with: "&") 41 | .replacingOccurrences(of: "<", with: "<") 42 | .replacingOccurrences(of: "'", with: "'") 43 | } 44 | 45 | } 46 | 47 | public func sortByName(_ declarations: [String:XDeclarationInInternalSubset]) -> [XDeclarationInInternalSubset] { 48 | var sorted = [XDeclarationInInternalSubset]() 49 | for name in declarations.keys.sorted() { 50 | if let theDeclaration = declarations[name] { 51 | sorted.append(theDeclaration) 52 | } 53 | } 54 | return sorted 55 | } 56 | 57 | struct Stack { 58 | var elements = [Element]() 59 | mutating func push(_ item: Element) { 60 | elements.append(item) 61 | } 62 | mutating func change(_ item: Element) { 63 | _ = pop() 64 | elements.append(item) 65 | } 66 | mutating func pop() -> Element? { 67 | if elements.isEmpty { 68 | return nil 69 | } 70 | else { 71 | return elements.removeLast() 72 | } 73 | } 74 | func peek() -> Element? { 75 | return elements.last 76 | } 77 | func peekAll() -> [Element] { 78 | return elements 79 | } 80 | } 81 | 82 | public final class WeaklyListed { 83 | var next: WeaklyListed? = nil 84 | 85 | weak var element: T? 86 | 87 | init(_ element: T) { 88 | self.element = element 89 | } 90 | 91 | // prevent stack overflow when destroying the list, 92 | // to be applied on the first element in that list, 93 | // cf. https://forums.swift.org/t/deep-recursion-in-deinit-should-not-happen/54987 94 | // !!! This should not be necessary anymore with Swift 5.7 or on masOS 13. !!! 95 | func removeFollowing() { 96 | var node = self 97 | while isKnownUniquelyReferenced(&node.next) { 98 | (node, node.next) = (node.next!, nil) 99 | } 100 | } 101 | } 102 | 103 | /** 104 | A list that stores its elements weakly. It looks for zombies whenever operating 105 | on it; therefore it is only suitable for a small number of elements. 106 | */ 107 | public final class WeakList: LazySequenceProtocol { 108 | 109 | var first: WeaklyListed? = nil 110 | 111 | public func remove(_ o: T) { 112 | var previous: WeaklyListed? = nil 113 | var iterated = first 114 | while let item = iterated { 115 | if item.element == nil || item.element === o { 116 | previous?.next = item.next 117 | item.next = nil 118 | if item === first { 119 | first = nil 120 | } 121 | } 122 | previous = iterated 123 | iterated = item.next 124 | } 125 | } 126 | 127 | public func append(_ o: T) { 128 | if first == nil { 129 | first = WeaklyListed(o) 130 | } 131 | else { 132 | var previous: WeaklyListed? = nil 133 | var iterated = first 134 | while let item = iterated { 135 | if item.element == nil || item.element === o { 136 | previous?.next = item.next 137 | item.next = nil 138 | } 139 | previous = iterated 140 | iterated = item.next 141 | if iterated == nil { 142 | previous?.next = WeaklyListed(o) 143 | } 144 | } 145 | } 146 | } 147 | 148 | public func makeIterator() -> WeakListIterator { 149 | return WeakListIterator(start: first) 150 | } 151 | 152 | deinit { 153 | first?.removeFollowing() 154 | } 155 | } 156 | 157 | public final class WeakListIterator: IteratorProtocol { 158 | 159 | var started = false 160 | var current: WeaklyListed? 161 | 162 | public init(start: WeaklyListed?) { 163 | current = start 164 | } 165 | 166 | public func next() -> T? { 167 | var previous: WeaklyListed? = nil 168 | if started { 169 | previous = current 170 | current = current?.next 171 | } 172 | else { 173 | started = true 174 | } 175 | while let item = current, item.element == nil { 176 | previous?.next = item.next 177 | current = item.next 178 | item.next = nil 179 | } 180 | return current?.element 181 | } 182 | } 183 | 184 | public struct SimplePropertiesParseError: LocalizedError { 185 | 186 | private let message: String 187 | 188 | init(_ message: String) { 189 | self.message = message 190 | } 191 | 192 | public var errorDescription: String? { 193 | return message 194 | } 195 | } 196 | 197 | func escapeInSimplePropertiesList(_ text: String) -> String { 198 | return text 199 | .replacingOccurrences(of: "\\", with: "\\\\") 200 | .replacingOccurrences(of: "=", with: "\\=") 201 | .replacingOccurrences(of: ":", with: "\\:") 202 | .replacingOccurrences(of: "\n", with: "\\n") 203 | .replacingOccurrences(of: "#", with: "\\#") 204 | } 205 | func unescapeInSimplePropertiesList(_ text: String) -> String { 206 | return text.components(separatedBy: "\\\\").map { $0 207 | .replacingOccurrences(of: "\\#", with: "#") 208 | .replacingOccurrences(of: "\\n", with: "\n") 209 | .replacingOccurrences(of: "\\:", with: ":") 210 | .replacingOccurrences(of: "\\=", with: "=") 211 | .replacingOccurrences(of: "\\\\", with: "\\") 212 | }.joined(separator: "\\") 213 | } 214 | 215 | extension Sequence { 216 | 217 | func forEachAsync ( 218 | _ operation: (Element) async throws -> Void 219 | ) async rethrows { 220 | for element in self { 221 | try await operation(element) 222 | } 223 | } 224 | } 225 | 226 | extension Array where Element == String? { 227 | 228 | func joined(separator: String) -> String? { 229 | var nonNils = [String]() 230 | for s in self { 231 | if let s = s { 232 | nonNils.append(s) 233 | } 234 | } 235 | return nonNils.isEmpty ? nil : nonNils.joined(separator: separator) 236 | } 237 | 238 | func joinedNonEmpties(separator: String) -> String? { 239 | var nonEmpties = [String]() 240 | for s in self { 241 | if let s = s, !s.isEmpty { 242 | nonEmpties.append(s) 243 | } 244 | } 245 | return nonEmpties.isEmpty ? nil : nonEmpties.joined(separator: separator) 246 | } 247 | } 248 | 249 | extension StringProtocol { 250 | 251 | /// Test if a text contains a part matching a certain regular expression. 252 | /// 253 | /// Use a regular expression of the form "^...$" to test if the whole text matches the expression. 254 | func contains(regex: String) -> Bool { 255 | var match: Range? 256 | autoreleasepool { 257 | match = self.range(of: regex, options: .regularExpression) 258 | } 259 | return match != nil 260 | } 261 | 262 | } 263 | 264 | extension String { 265 | 266 | var avoidingDoubleHyphens: String { 267 | 268 | var result = if self.contains("--") { 269 | self.replacingOccurrences(of: "--", with: "(HYPHEN)(HYPHEN)") 270 | } else { 271 | self 272 | } 273 | 274 | if result.hasPrefix("-") { 275 | result = "(HYPHEN)\(result.dropFirst())" 276 | } 277 | 278 | if result.hasSuffix("-") { 279 | result = "\(result.dropLast())(HYPHEN)" 280 | } 281 | 282 | return result 283 | } 284 | 285 | } 286 | 287 | /// A wrapper around Set that can passed around by reference. 288 | class Referenced { 289 | 290 | public var referenced: T 291 | 292 | public init(_ referenced: T) { 293 | self.referenced = referenced 294 | } 295 | 296 | } 297 | 298 | class TieredDictionary { 299 | 300 | private var dictionary = [K1:Referenced<[K2:V]>]() 301 | 302 | public init() {} 303 | 304 | public var isEmpty: Bool { dictionary.isEmpty } 305 | 306 | public func put(key1: K1, key2: K2, value: V?) { 307 | let indexForKey1 = dictionary[key1] ?? { 308 | let newIndex = Referenced([K2:V]()) 309 | dictionary[key1] = newIndex 310 | return newIndex 311 | }() 312 | indexForKey1.referenced[key2] = value 313 | if indexForKey1.referenced.isEmpty { 314 | dictionary[key1] = nil 315 | } 316 | } 317 | 318 | public func removeValue(forKey1 key1: K1, andKey2 key2: K2) -> V? { 319 | guard let indexForKey1 = dictionary[key1] else { return nil } 320 | guard let value = indexForKey1.referenced[key2] else { return nil } 321 | indexForKey1.referenced[key2] = nil 322 | if indexForKey1.referenced.isEmpty { 323 | dictionary[key1] = nil 324 | } 325 | return value 326 | } 327 | 328 | public subscript(key1: K1, key2: K2) -> V? { 329 | 330 | set { 331 | put(key1: key1, key2: key2, value: newValue) 332 | } 333 | 334 | get { 335 | return dictionary[key1]?.referenced[key2] 336 | } 337 | 338 | } 339 | 340 | public subscript(key1: K1) -> [K2:V]? { 341 | 342 | get { 343 | return dictionary[key1]?.referenced 344 | } 345 | 346 | } 347 | 348 | public var leftKeys: Dictionary>>.Keys { dictionary.keys } 349 | 350 | public func rightKeys(forLeftKey leftKey: K1) -> Dictionary.Keys? { 351 | return dictionary[leftKey]?.referenced.keys 352 | } 353 | 354 | public var rightKeys: Set { 355 | var keys = Set() 356 | leftKeys.forEach { leftKey in 357 | rightKeys(forLeftKey: leftKey)?.forEach { rightKey in 358 | keys.insert(rightKey) 359 | } 360 | } 361 | return keys 362 | } 363 | 364 | public var all: [V] { 365 | leftKeys.compactMap { dictionary[$0]?.referenced.values }.flatMap{ $0 } 366 | } 367 | 368 | public func all(forFirstKey key1: K1) -> Dictionary.Values? { 369 | dictionary[key1]?.referenced.values 370 | } 371 | 372 | public func removeAll(keepingCapacity keepCapacity: Bool = false) { 373 | dictionary.removeAll(keepingCapacity: keepCapacity) 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /Sources/SwiftXML/XML/Document.swift: -------------------------------------------------------------------------------- 1 | //===--- Document.swift ---------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import Foundation 12 | 13 | final class XValue { 14 | var value: String 15 | 16 | init(_ value: String) { 17 | self.value = value 18 | } 19 | } 20 | 21 | public enum AttributeRegisterMode { 22 | case none; case selected(_ attributeNames: [String]); case all 23 | } 24 | 25 | public final class XDocument: XNode, XBranchInternal { 26 | 27 | public var firstChild: XElement? { _firstChild } 28 | 29 | public func firstChild(_ name: String) -> XElement? { 30 | _firstChild(name) 31 | } 32 | 33 | public func firstChild(prefix: String?, _ name: String) -> XElement? { 34 | _firstChild(prefix: prefix, name) 35 | } 36 | 37 | public func firstChild(_ names: [String]) -> XElement? { 38 | _firstChild(names) 39 | } 40 | 41 | public func firstChild(prefix: String?, _ names: [String]) -> XElement? { 42 | _firstChild(prefix: prefix, names) 43 | } 44 | 45 | public func firstChild(_ names: String...) -> XElement? { 46 | _firstChild(names) 47 | } 48 | 49 | public func firstChild(prefix: String?, _ names: String...) -> XElement? { 50 | _firstChild(prefix: prefix, names) 51 | } 52 | 53 | public func firstChild(_ condition: (XElement) -> Bool) -> XElement? { 54 | _firstChild(condition) 55 | } 56 | 57 | public var xPath: String { "/" } 58 | 59 | var __firstContent: XContent? = nil 60 | 61 | var __lastContent: XContent? = nil 62 | 63 | weak var _document: XDocument? 64 | 65 | var _lastInTree: XNode! 66 | 67 | public override var top: XElement? { 68 | self.children.first 69 | } 70 | 71 | override func getLastInTree() -> XNode { 72 | return _lastInTree 73 | } 74 | 75 | var _sourcePath: String? = nil 76 | 77 | /// After cloning, this is the reference to the original node or to the cloned node respectively, 78 | /// acoording to the parameter used when cloning. 79 | /// 80 | /// Note that this is a weak reference, the clone must be contained by other means to exist. 81 | public override var backlink: XDocument? { super.backlink as? XDocument } 82 | 83 | /// Get the backlink or – if it is `nil` – the subject itself. 84 | public override var backlinkOrSelf: XDocument { self.backlink ?? self } 85 | 86 | /// Setting the backlink manually. The identical node is returned. 87 | public func setting(backlink: XDocument) -> XDocument { 88 | _backlink = backlink 89 | return self 90 | } 91 | 92 | /// Copying the backlink from another node. The identical node is returned. 93 | public func copyingBacklink(from node: XDocument) -> XDocument { 94 | _backlink = node._backlink 95 | return self 96 | } 97 | 98 | /// Here, the `backlink` reference are followed while they are non-nil. 99 | /// 100 | /// It is thhe oldest source or furthest target of cloning respectively, so to speak. 101 | public override var finalBacklink: XDocument? { get { super.finalBacklink as? XDocument } } 102 | 103 | public var sourcePath: String? { 104 | get { 105 | return _sourcePath 106 | } 107 | } 108 | 109 | private var _versions = [XDocument]() 110 | public var versions: [XDocument] { _versions } 111 | public var lastVersion: XDocument? { _versions.last } 112 | 113 | public func makeVersion( 114 | keepAttachments: Bool = false, 115 | registeringAttributes attributeRegisterMode: AttributeRegisterMode? = nil, 116 | registeringValuesForAttributes attributeValueRegisterMode: AttributeRegisterMode? = nil 117 | ) { 118 | let clone = _shallowClone(keepAttachments: keepAttachments, registeringAttributes: attributeRegisterMode, registeringValuesForAttributes: attributeValueRegisterMode, pointingToClone: true) 119 | _versions.append(clone) 120 | clone._addClones(from: self, pointingToClone: true, keepAttachments: keepAttachments) 121 | } 122 | 123 | /// Remove the last version. 124 | public func forgetLastVersion() { 125 | if _versions.count > 0 { 126 | _versions.removeLast() 127 | } 128 | } 129 | 130 | /// Remove versions but keep the last n ones. 131 | public func forgetVersions(keeping n: Int = 0) { 132 | if _versions.count > 0 { 133 | let oldVersions = _versions 134 | _versions = [XDocument]() 135 | if n > 0 { 136 | let startIndex = oldVersions.count - n 137 | if startIndex >= 0 { 138 | let endIndex = oldVersions.count - 1 139 | for index in startIndex...endIndex { 140 | _versions.append(oldVersions[index]) 141 | } 142 | } 143 | } 144 | let lastVersion = _versions.first ?? self 145 | lastVersion._backlink = nil 146 | for content in lastVersion.allContent { content._backlink = nil } 147 | } 148 | } 149 | 150 | public var xmlVersion = "1.0" 151 | public var encoding: String? = nil 152 | public var standalone: String? = nil 153 | 154 | var type: String? = nil 155 | public var publicID: String? = nil 156 | public var systemID: String? = nil 157 | 158 | // ------------------------------------------------------------------------ 159 | // repeat methods from XBranchInternal: 160 | 161 | public var firstContent: XContent? { _firstContent } 162 | 163 | public func firstContent(_ condition: (XContent) -> Bool) -> XContent? { 164 | return _firstContent(condition) 165 | } 166 | 167 | public var lastContent: XContent? { _lastContent } 168 | 169 | public func lastContent(_ condition: (XContent) -> Bool) -> XContent? { 170 | return _lastContent(condition) 171 | } 172 | 173 | public var singleContent: XContent? { _singleContent } 174 | 175 | public var isEmpty: Bool { _isEmpty } 176 | 177 | public func add(@XContentBuilder builder: () -> [XContent]) { 178 | return _add(builder()) 179 | } 180 | 181 | public func addFirst(@XContentBuilder builder: () -> [XContent]) { 182 | return _addFirst(builder()) 183 | } 184 | 185 | public func setContent(@XContentBuilder builder: () -> [XContent]) { 186 | return _setContent(builder()) 187 | } 188 | 189 | public func clear() { 190 | return _clear() 191 | } 192 | 193 | // ------------------------------------------------------------------------ 194 | 195 | public override var shallowClone: XDocument { 196 | shallowClone() 197 | } 198 | 199 | public func shallowClone( 200 | keepAttachments: Bool = false, 201 | registeringAttributes attributeRegisterMode: AttributeRegisterMode? = nil, 202 | registeringValuesForAttributes attributeValueRegisterMode: AttributeRegisterMode? = nil 203 | ) -> XDocument { 204 | _shallowClone( 205 | keepAttachments: keepAttachments, 206 | registeringAttributes: attributeRegisterMode, 207 | registeringValuesForAttributes: attributeValueRegisterMode, 208 | pointingToClone: false 209 | ) 210 | } 211 | 212 | private func _shallowClone( 213 | keepAttachments: Bool = false, 214 | registeringAttributes attributeRegisterMode: AttributeRegisterMode? = nil, 215 | registeringValuesForAttributes attributeValueRegisterMode: AttributeRegisterMode? = nil, 216 | pointingToClone: Bool 217 | ) -> XDocument { 218 | let theClone = XDocument( 219 | registeringAttributes: attributeRegisterMode ?? _attributeRegisterMode, 220 | registeringValuesForAttributes: attributeValueRegisterMode ?? _attributeValueRegisterMode 221 | ) 222 | if pointingToClone { 223 | self._backlink = theClone 224 | } else { 225 | theClone._backlink = self 226 | } 227 | theClone.xmlVersion = xmlVersion 228 | theClone.encoding = encoding 229 | theClone.standalone = standalone 230 | theClone.type = type 231 | theClone.publicID = publicID 232 | theClone.systemID = systemID 233 | theClone._sourcePath = _sourcePath 234 | if keepAttachments { theClone.attached = attached } 235 | for (name, declaration) in internalEntityDeclarations { theClone.internalEntityDeclarations[name] = declaration.clone } 236 | for (name, declaration) in parameterEntityDeclarations { theClone.parameterEntityDeclarations[name] = declaration.clone } 237 | for (name, declaration) in externalEntityDeclarations { theClone.externalEntityDeclarations[name] = declaration.clone } 238 | for (name, declaration) in unparsedEntityDeclarations { theClone.unparsedEntityDeclarations[name] = declaration.clone } 239 | for (name, declaration) in notationDeclarations { theClone.notationDeclarations[name] = declaration.clone } 240 | for (name, declaration) in elementDeclarations { theClone.elementDeclarations[name] = declaration.clone } 241 | for (name, declaration) in attributeListDeclarations { theClone.attributeListDeclarations[name] = declaration.clone } 242 | return theClone 243 | } 244 | 245 | public override var clone: XDocument { 246 | clone() 247 | } 248 | 249 | public func clone( 250 | keepAttachments: Bool = false, 251 | registeringAttributes attributeRegisterMode: AttributeRegisterMode? = nil, 252 | registeringValuesForAttributes attributeValueRegisterMode: AttributeRegisterMode? = nil 253 | ) -> XDocument { 254 | _clone( 255 | keepAttachments: keepAttachments, 256 | registeringAttributes: attributeRegisterMode, 257 | registeringValuesForAttributes: attributeValueRegisterMode, 258 | pointingToClone: false 259 | ) 260 | } 261 | 262 | private func _clone( 263 | keepAttachments: Bool = false, 264 | registeringAttributes attributeRegisterMode: AttributeRegisterMode? = nil, 265 | registeringValuesForAttributes attributeValueRegisterMode: AttributeRegisterMode? = nil, 266 | pointingToClone: Bool 267 | ) -> XDocument { 268 | let theClone = _shallowClone( 269 | keepAttachments: keepAttachments, 270 | registeringAttributes: attributeRegisterMode, 271 | registeringValuesForAttributes: attributeValueRegisterMode, 272 | pointingToClone: pointingToClone 273 | ) 274 | theClone._addClones(from: self, pointingToClone: pointingToClone, keepAttachments: keepAttachments) 275 | if keepAttachments { theClone.attached = attached } 276 | return theClone 277 | } 278 | 279 | public var internalEntityDeclarations = [String:XInternalEntityDeclaration]() 280 | public var parameterEntityDeclarations = [String:XParameterEntityDeclaration]() 281 | public var externalEntityDeclarations = [String:XExternalEntityDeclaration]() 282 | public var unparsedEntityDeclarations = [String:XUnparsedEntityDeclaration]() 283 | public var notationDeclarations = [String:XNotationDeclaration]() 284 | public var elementDeclarations = [String:XElementDeclaration]() 285 | public var attributeListDeclarations = [String:XAttributeListDeclaration]() 286 | 287 | // ------------------------------------------------------------------------- 288 | // elements of same name: 289 | // ------------------------------------------------------------------------- 290 | 291 | var _elementsOfName_first = [String:XElement]() 292 | var _elementsOfName_last = [String:XElement]() 293 | 294 | var _elementsOfPrefixAndName_first = TieredDictionary() 295 | var _elementsOfPrefixAndName_last = TieredDictionary() 296 | 297 | func registerElement(element: XElement) { 298 | 299 | // register via name: 300 | let name = element.name 301 | if let prefix = element.prefix { 302 | if let theLast = _elementsOfPrefixAndName_last[prefix,name] { 303 | theLast.nextWithSameName = element 304 | element.previousWithSameName = theLast 305 | } 306 | else { 307 | _elementsOfPrefixAndName_first[prefix,name] = element 308 | } 309 | _elementsOfPrefixAndName_last[prefix,name] = element 310 | } else { 311 | if let theLast = _elementsOfName_last[name] { 312 | theLast.nextWithSameName = element 313 | element.previousWithSameName = theLast 314 | } 315 | else { 316 | _elementsOfName_first[name] = element 317 | } 318 | _elementsOfName_last[name] = element 319 | } 320 | 321 | // register according attributes: 322 | for (attributeName,attributeValue) in element._attributes { 323 | if _document?.attributeToBeRegistered(withName: attributeName) == true { 324 | let attributeProperties = AttributeProperties(value: attributeValue, element: element) 325 | element._registeredAttributes[attributeName] = attributeProperties 326 | registerAttribute(attributeProperties: attributeProperties, withName: attributeName) 327 | } 328 | } 329 | 330 | // register according attributes values: 331 | for (attributeName,attributeValue) in element._attributes { 332 | if _document?.attributeValueToBeRegistered(forAttributeName: attributeName) == true { 333 | let attributeProperties = AttributeProperties(value: attributeValue, element: element) 334 | element._registeredAttributeValues[attributeName] = attributeProperties 335 | registerAttributeValue(attributeProperties: attributeProperties, withName: attributeName) 336 | } 337 | } 338 | 339 | element._document = self 340 | } 341 | 342 | func unregisterElement(element: XElement) { 343 | element.gotoPreviousOnNameIterators() 344 | let name = element.name 345 | element.previousWithSameName?.nextWithSameName = element.nextWithSameName 346 | element.nextWithSameName?.previousWithSameName = element.previousWithSameName 347 | if let prefix = element.prefix { 348 | if _elementsOfPrefixAndName_first[prefix,name] === element { 349 | _elementsOfPrefixAndName_first[prefix,name] = element.nextWithSameName 350 | } 351 | if _elementsOfPrefixAndName_last[prefix,name] === element { 352 | _elementsOfPrefixAndName_last[prefix,name] = element.previousWithSameName 353 | } 354 | } else { 355 | if _elementsOfName_first[name] === element { 356 | _elementsOfName_first[name] = element.nextWithSameName 357 | } 358 | if _elementsOfName_last[name] === element { 359 | _elementsOfName_last[name] = element.previousWithSameName 360 | } 361 | } 362 | 363 | element.previousWithSameName = nil 364 | element.nextWithSameName = nil 365 | 366 | // unregister registered attributes: 367 | for (attributeName,attributeProperties) in element._registeredAttributes { 368 | unregisterAttribute(attributeProperties: attributeProperties, withName: attributeName) 369 | } 370 | element._registeredAttributes.removeAll() 371 | 372 | // unregister registered attribute values: 373 | for (attributeName,attributeProperties) in element._registeredAttributeValues { 374 | unregisterAttributeValue(attributeProperties: attributeProperties, withName: attributeName) 375 | } 376 | element._registeredAttributes.removeAll() 377 | 378 | element._document = nil 379 | } 380 | 381 | public func elements(prefix: String? = nil, _ name: String) -> XElementSequence { 382 | return XElementsOfSameNameSequence(document: self, prefix: prefix, name: name) 383 | } 384 | 385 | public func elements(prefix: String? = nil, _ names: String...) -> XElementSequence { 386 | return elements(prefix: prefix, names) 387 | } 388 | 389 | public func elements(prefix: String? = nil, _ names: [String]) -> XElementSequence { 390 | return XElementsOfNamesSequence(forPrefix: prefix, forNames: names, forDocument: self) 391 | } 392 | 393 | // ------------------------------------------------------------------------- 394 | // attributes of same name or also same value: 395 | // ------------------------------------------------------------------------- 396 | 397 | var _attributesOfName_first = [String:AttributeProperties]() 398 | var _attributesOfName_last = [String:AttributeProperties]() 399 | 400 | var _attributesOfValue_first = TieredDictionary() 401 | var _attributesOfValue_last = TieredDictionary() 402 | 403 | func registerAttribute(attributeProperties: AttributeProperties, withName name: String) { 404 | if let theLast = _attributesOfName_last[name] { 405 | theLast.nextWithCondition = attributeProperties 406 | attributeProperties.previousWithCondition = theLast 407 | } 408 | else { 409 | _attributesOfName_first[name] = attributeProperties 410 | } 411 | _attributesOfName_last[name] = attributeProperties 412 | attributeProperties.nextWithCondition = nil 413 | } 414 | 415 | func registerAttributeValue(attributeProperties: AttributeProperties, withName name: String) { 416 | if let theLast = _attributesOfValue_last[name,attributeProperties.value] { 417 | theLast.nextWithCondition = attributeProperties 418 | attributeProperties.previousWithCondition = theLast 419 | } 420 | else { 421 | _attributesOfValue_first[name,attributeProperties.value] = attributeProperties 422 | } 423 | _attributesOfValue_last[name,attributeProperties.value] = attributeProperties 424 | attributeProperties.nextWithCondition = nil 425 | } 426 | 427 | func unregisterAttribute(attributeProperties: AttributeProperties, withName name: String) { 428 | attributeProperties.gotoPreviousOnAttributeIterators() 429 | attributeProperties.previousWithCondition?.nextWithCondition = attributeProperties.nextWithCondition 430 | attributeProperties.nextWithCondition?.previousWithCondition = attributeProperties.previousWithCondition 431 | if _attributesOfName_first[name] === attributeProperties { 432 | _attributesOfName_first[name] = attributeProperties.nextWithCondition 433 | } 434 | if _attributesOfName_last[name] === attributeProperties { 435 | _attributesOfName_last[name] = attributeProperties.previousWithCondition 436 | } 437 | attributeProperties.previousWithCondition = nil 438 | attributeProperties.nextWithCondition = nil 439 | } 440 | 441 | func unregisterAttributeValue(attributeProperties: AttributeProperties, withName name: String) { 442 | attributeProperties.gotoPreviousOnAttributeIterators() 443 | attributeProperties.previousWithCondition?.nextWithCondition = attributeProperties.nextWithCondition 444 | attributeProperties.nextWithCondition?.previousWithCondition = attributeProperties.previousWithCondition 445 | if _attributesOfValue_first[name,attributeProperties.value] === attributeProperties { 446 | _attributesOfValue_first[name,attributeProperties.value] = attributeProperties.nextWithCondition 447 | } 448 | if _attributesOfValue_last[name,attributeProperties.value] === attributeProperties { 449 | _attributesOfValue_last[name,attributeProperties.value] = attributeProperties.previousWithCondition 450 | } 451 | attributeProperties.previousWithCondition = nil 452 | attributeProperties.nextWithCondition = nil 453 | } 454 | 455 | public func registeredAttributes(_ name: String) -> XAttributeSequence { 456 | return XAttributesOfSameNameSequence(document: self, attributeName: name) 457 | } 458 | 459 | public func registeredAttributes(_ names: String...) -> XAttributeSequence { 460 | return registeredAttributes(names) 461 | } 462 | 463 | public func registeredAttributes(_ names: [String]) -> XAttributeSequence { 464 | return XAttributesOfNamesSequence(forNames: names, forDocument: self) 465 | } 466 | 467 | public func registeredValues(_ value: String, forAttribute name: String) -> XAttributeSequence { 468 | return XAttributesOfSameValueSequence(document: self, attributeName: name, attributeValue: value) 469 | } 470 | 471 | deinit { 472 | 473 | // destroy lists of elements with same name: 474 | for element in _elementsOfName_first.values { element.removeFollowingWithSameName() } 475 | 476 | // destroy lists of attributes with same name: 477 | _attributesOfName_first.values.forEach { attribute in attribute.removeFollowingWithSameName() } 478 | 479 | } 480 | 481 | // ------------------------------------------------------------------------- 482 | 483 | private let _attributeRegisterMode: AttributeRegisterMode 484 | private let _attributeValueRegisterMode: AttributeRegisterMode 485 | 486 | func attributeToBeRegistered(withName name: String) -> Bool { 487 | switch _attributeRegisterMode { 488 | case .none: 489 | false 490 | case .selected(let attributeNames): 491 | attributeNames.contains(name) 492 | case .all: 493 | true 494 | } 495 | } 496 | 497 | func attributeValueToBeRegistered(forAttributeName name: String) -> Bool { 498 | switch _attributeValueRegisterMode { 499 | case .none: 500 | false 501 | case .selected(let attributeNames): 502 | attributeNames.contains(name) 503 | case .all: 504 | true 505 | } 506 | } 507 | 508 | public init( 509 | attached: [String:Any?]? = nil, 510 | registeringAttributes attributeRegisterMode: AttributeRegisterMode = .none, 511 | registeringValuesForAttributes attributeValueRegisterMode: AttributeRegisterMode = .none 512 | ) { 513 | self._attributeRegisterMode = attributeRegisterMode 514 | self._attributeValueRegisterMode = attributeValueRegisterMode 515 | super.init() 516 | _document = self 517 | self._lastInTree = self 518 | if let attached { 519 | for (key,value) in attached { 520 | if let value { 521 | self.attached[key] = value 522 | } 523 | } 524 | } 525 | } 526 | 527 | public convenience init( 528 | attached: [String:Any?]? = nil, 529 | registeringAttributes attributeRegisterMode: AttributeRegisterMode = .none, 530 | registeringValuesForAttributes attributeValueRegisterMode: AttributeRegisterMode = .none, 531 | @XContentBuilder builder: () -> [XContent] 532 | ) { 533 | self.init(attached: attached, registeringAttributes: attributeRegisterMode, registeringValuesForAttributes: attributeValueRegisterMode) 534 | for node in builder() { 535 | _add(node) 536 | } 537 | } 538 | 539 | func getType() -> String? { 540 | var node = __firstContent 541 | while let theNode = node { 542 | if let element = node as? XElement { 543 | return element.name 544 | } 545 | node = theNode._next 546 | } 547 | return nil 548 | } 549 | 550 | func hasInternalSubset() -> Bool { 551 | return !internalEntityDeclarations.isEmpty || 552 | !parameterEntityDeclarations.isEmpty || 553 | !externalEntityDeclarations.isEmpty || 554 | !unparsedEntityDeclarations.isEmpty || 555 | !notationDeclarations.isEmpty || 556 | !elementDeclarations.isEmpty || 557 | !attributeListDeclarations.isEmpty 558 | } 559 | 560 | override func produceEntering(activeProduction: XActiveProduction) throws { 561 | try activeProduction.writeDocumentStart(document: self) 562 | try activeProduction.writeXMLDeclaration(version: xmlVersion, encoding: encoding, standalone: standalone) 563 | let _hasInternalSubset = hasInternalSubset() 564 | try activeProduction.writeDocumentTypeDeclarationBeforeInternalSubset(type: getType() ?? "?", publicID: publicID, systemID: systemID, hasInternalSubset: _hasInternalSubset) 565 | if _hasInternalSubset { 566 | try activeProduction.writeDocumentTypeDeclarationInternalSubsetStart() 567 | for declaration in activeProduction.sortDeclarationsInInternalSubset(document: self) { 568 | switch declaration { 569 | case let internalEntityDeclaration as XInternalEntityDeclaration: try activeProduction.writeInternalEntityDeclaration(internalEntityDeclaration: internalEntityDeclaration) 570 | case let parameterEntityDeclaration as XParameterEntityDeclaration: try activeProduction.writeParameterEntityDeclaration(parameterEntityDeclaration: parameterEntityDeclaration) 571 | case let externalEntityDeclaration as XExternalEntityDeclaration: try activeProduction.writeExternalEntityDeclaration(externalEntityDeclaration: externalEntityDeclaration) 572 | case let unparsedEntityDeclaration as XUnparsedEntityDeclaration: try activeProduction.writeUnparsedEntityDeclaration(unparsedEntityDeclaration: unparsedEntityDeclaration) 573 | case let notationDeclaration as XNotationDeclaration: try activeProduction.writeNotationDeclaration(notationDeclaration: notationDeclaration) 574 | case let elementDeclaration as XElementDeclaration: try activeProduction.writeElementDeclaration(elementDeclaration: elementDeclaration) 575 | case let attributeListDeclaration as XAttributeListDeclaration: try activeProduction.writeAttributeListDeclaration(attributeListDeclaration: attributeListDeclaration) 576 | default: 577 | break 578 | } 579 | } 580 | try activeProduction.writeDocumentTypeDeclarationInternalSubsetEnd() 581 | } 582 | try activeProduction.writeDocumentTypeDeclarationAfterInternalSubset(hasInternalSubset: _hasInternalSubset) 583 | } 584 | 585 | func produceLeaving(activeProduction: ActiveDefaultProduction) throws { 586 | try activeProduction.writeDocumentEnd(document: self) 587 | } 588 | 589 | public func trimWhiteSpace() { 590 | self._trimWhiteSpace() 591 | } 592 | 593 | public func trimmimgWhiteSpace() -> XDocument { 594 | self._trimWhiteSpace() 595 | return self 596 | } 597 | } 598 | -------------------------------------------------------------------------------- /Sources/SwiftXML/XML/Namespaces.swift: -------------------------------------------------------------------------------- 1 | //===--- Namespaces.swift -------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import Foundation 12 | 13 | public enum NamespaceReference { 14 | 15 | case uri(_ uri: String) 16 | case fullPrefix(_ fullPrefix: String) 17 | 18 | public init(usingURI uri: String) { 19 | self = .uri(uri) 20 | } 21 | 22 | public init(usingPossiblyFullPrefix possiblyFullPrefix: String? = nil) { 23 | if let possiblyFullPrefix, !possiblyFullPrefix.isEmpty { 24 | self = .fullPrefix(possiblyFullPrefix.hasSuffix(":") ? possiblyFullPrefix : "\(possiblyFullPrefix):") 25 | } else { 26 | self = .fullPrefix("") 27 | } 28 | } 29 | 30 | } 31 | 32 | extension XDocument { 33 | 34 | /// Read the the prefix for a namespace URL string from the root element. 35 | public func prefix(forNamespace namespace: String) -> String? { 36 | self.children.first?.prefix(forNamespace: namespace) 37 | } 38 | 39 | /// Read the the full prefix for a namespace URL string from the root element. 40 | /// "Full" means that a closing ":" is added automatically. 41 | /// If no prefix is defined, an empty string is returned. 42 | public func fullPrefix(forNamespace namespace: String) -> String { 43 | self.children.first?.fullPrefix(forNamespace: namespace) ?? "" 44 | } 45 | 46 | /// Read the the full prefix for a namespace reference. 47 | public func fullPrefix(forNamespaceReference namespaceReference: NamespaceReference) -> String { 48 | switch namespaceReference { 49 | case .uri(uri: let uri): 50 | fullPrefix(forNamespace: uri) 51 | case .fullPrefix(fullPrefix: let fullPrefix): 52 | fullPrefix 53 | } 54 | } 55 | 56 | /// Read a map from the namespace URL strings to the full prefixes from the root element. 57 | /// "Full" means that a closing ":" is added automatically. 58 | public var fullPrefixesForNamespaces: [String:String] { 59 | self.children.first?.fullPrefixesForNamespaces ?? [String:String]() 60 | } 61 | 62 | /// Add the according namespace declaration at the root element. 63 | /// The prefix might be a "full" prefix, i.e. it could contain a closing ":". 64 | /// An existing namespace declaration for the same namespace but with another prefix is not (!) removed. 65 | public func setNamespace(_ namespace: String, withPossiblyFullPrefix possiblyFullPrefix: String) { 66 | self.children.first?.setNamespace(namespace, withPossiblyFullPrefix: possiblyFullPrefix) 67 | } 68 | 69 | } 70 | 71 | extension XElement { 72 | 73 | /// Read the the prefix for a namespace URL string from the element. 74 | public func prefix(forNamespace namespace: String) -> String? { 75 | var foundPrefix: String? = nil 76 | for attributeName in attributeNames { 77 | if attributeName.hasPrefix("xmlns:"), let value = self[attributeName], value == namespace { 78 | foundPrefix = String(attributeName.dropFirst(6)) 79 | break 80 | } 81 | } 82 | return foundPrefix 83 | } 84 | 85 | /// Read the the full prefix for a namespace URL string from the element. 86 | /// "Full" means that a closing ":" is added automatically. 87 | /// If no prefix is defined, an empty string is returned. 88 | public func fullPrefix(forNamespace namespace: String) -> String { 89 | if let foundPrefix = prefix(forNamespace: namespace) { 90 | return "\(foundPrefix):" 91 | } else { 92 | return "" 93 | } 94 | } 95 | 96 | /// Read the the full prefix for a namespace reference. 97 | public func fullPrefix(forNamespaceReference namespaceReference: NamespaceReference) -> String { 98 | switch namespaceReference { 99 | case .uri(uri: let uri): 100 | fullPrefix(forNamespace: uri) 101 | case .fullPrefix(fullPrefix: let fullPrefix): 102 | fullPrefix 103 | } 104 | } 105 | 106 | /// Read a map from the namespace URL strings to the full prefixes from the element. 107 | /// "Full" means that a closing ":" is added automatically. 108 | public var fullPrefixesForNamespaces: [String:String] { 109 | var result = [String:String]() 110 | let namespaceDeclarationPrefix = "xmlns:" 111 | for attributeName in attributeNames { 112 | if attributeName.hasPrefix(namespaceDeclarationPrefix), let value = self[attributeName] { 113 | result[value] = String(attributeName.dropFirst(namespaceDeclarationPrefix.count)) + ":" 114 | } 115 | } 116 | return result 117 | } 118 | 119 | /// Add the according namespace declaration at the element. 120 | /// The prefix might be a "full" prefix, i.e. it could contain a closing ":". 121 | /// An existing namespace declaration for the same namespace but with another prefix is not (!) removed. 122 | public func setNamespace(_ namespace: String, withPossiblyFullPrefix possiblyFullPrefix: String) { 123 | if !possiblyFullPrefix.isEmpty { 124 | self["xmlns:\(possiblyFullPrefix.hasSuffix(":") ? String(possiblyFullPrefix.dropLast()) : possiblyFullPrefix)"] = namespace 125 | } 126 | } 127 | 128 | } 129 | 130 | extension XBranch { 131 | 132 | /// Read the the full prefix for a namespace URL string from the root element. 133 | /// "Full" means that a closing ":" is added automatically. 134 | /// If no prefix is defined, an empty string is returned. 135 | public func fullPrefix(forNamespace namespace: String) -> String { 136 | (self as? XDocument)?.fullPrefix(forNamespace: namespace) ?? (self as? XElement)?.fullPrefix(forNamespace: namespace) ?? "" 137 | } 138 | 139 | /// Read the the full prefix for a namespace reference. 140 | public func fullPrefix(forNamespaceReference namespaceReference: NamespaceReference) -> String { 141 | (self as? XDocument)?.fullPrefix(forNamespaceReference: namespaceReference) ?? (self as? XElement)?.fullPrefix(forNamespaceReference: namespaceReference) ?? "" 142 | } 143 | 144 | /// Read a map from the namespace URL strings to the full prefixes from the root element. 145 | /// "Full" means that a closing ":" is added automatically. 146 | public var fullPrefixesForNamespaces: [String:String] { 147 | (self as? XDocument)?.fullPrefixesForNamespaces ?? (self as? XElement)?.fullPrefixesForNamespaces ?? [String:String]() 148 | } 149 | 150 | /// Add the according namespace declaration at the root element. 151 | /// The prefix might be a "full" prefix, i.e. it could contain a closing ":". 152 | /// An existing namespace declaration for the same namespace but with another prefix is not (!) removed. 153 | public func setNamespace(_ namespace: String, withPossiblyFullPrefix possiblyFullPrefix: String) { 154 | (self as? XDocument)?.setNamespace(namespace, withPossiblyFullPrefix: possiblyFullPrefix) ?? (self as? XElement)?.setNamespace(namespace, withPossiblyFullPrefix: possiblyFullPrefix) 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /Sources/SwiftXML/XML/Production.swift: -------------------------------------------------------------------------------- 1 | //===--- Production.swift -------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import Foundation 12 | 13 | public let X_DEFAULT_INDENTATION = " " 14 | public let X_DEFAULT_LINEBREAK = "\n" 15 | 16 | /** 17 | The formatter has two changes over the XFormatter: 18 | - It writes to the file directly, no need to build complicated strings. 19 | - It uses the nodes of the XML tree directly. 20 | */ 21 | 22 | public protocol Writer { 23 | func write(_ text: String) throws 24 | } 25 | 26 | extension FileHandle { 27 | func write(text: String) throws { 28 | try self.write(contentsOf: text.data(using: .utf8)!) 29 | } 30 | } 31 | 32 | public class FileWriter: Writer { 33 | 34 | private var _file: FileHandle = FileHandle.standardOutput 35 | 36 | public init(_ file: FileHandle) { 37 | self._file = file 38 | } 39 | 40 | open func write(_ text: String) throws { 41 | try _file.write(text: text) 42 | } 43 | } 44 | 45 | public class CollectingWriter: Writer, CustomStringConvertible { 46 | 47 | public init() {} 48 | 49 | private var texts = [String]() 50 | 51 | public var description: String { get { texts.joined() } } 52 | 53 | public func write(_ text: String) { 54 | texts.append(text) 55 | } 56 | } 57 | 58 | public protocol XProductionTemplate { 59 | func activeProduction(for writer: Writer, atNode node: XNode) -> XActiveProduction 60 | } 61 | 62 | public protocol XActiveProduction { 63 | 64 | func write(_ text: String) throws 65 | 66 | func sortDeclarationsInInternalSubset(document: XDocument) -> [XDeclarationInInternalSubset] 67 | 68 | func writeDocumentStart(document: XDocument) throws 69 | 70 | func writeXMLDeclaration(version: String, encoding: String?, standalone: String?) throws 71 | 72 | func writeDocumentTypeDeclarationBeforeInternalSubset(type: String, publicID: String?, systemID: String?, hasInternalSubset: Bool) throws 73 | 74 | func writeDocumentTypeDeclarationInternalSubsetStart() throws 75 | 76 | func writeDocumentTypeDeclarationInternalSubsetEnd() throws 77 | 78 | func writeDocumentTypeDeclarationAfterInternalSubset(hasInternalSubset: Bool) throws 79 | 80 | func writeElementStartBeforeAttributes(element: XElement) throws 81 | 82 | func sortAttributeNames(attributeNames: [String], element: XElement) -> [String] 83 | 84 | func writeAttributeValue(name: String, value: String, element: XElement) throws 85 | 86 | func writeAttribute(name: String, value: String, element: XElement) throws 87 | 88 | func writeElementStartAfterAttributes(element: XElement) throws 89 | 90 | func writeElementEnd(element: XElement) throws 91 | 92 | func writeText(text: XText) throws 93 | 94 | func writeLiteral(literal: XLiteral) throws 95 | 96 | func writeCDATASection(cdataSection: XCDATASection) throws 97 | 98 | func writeProcessingInstruction(processingInstruction: XProcessingInstruction) throws 99 | 100 | func writeComment(comment: XComment) throws 101 | 102 | func writeInternalEntityDeclaration(internalEntityDeclaration: XInternalEntityDeclaration) throws 103 | 104 | func writeExternalEntityDeclaration(externalEntityDeclaration: XExternalEntityDeclaration) throws 105 | 106 | func writeUnparsedEntityDeclaration(unparsedEntityDeclaration: XUnparsedEntityDeclaration) throws 107 | 108 | func writeNotationDeclaration(notationDeclaration: XNotationDeclaration) throws 109 | 110 | func writeParameterEntityDeclaration(parameterEntityDeclaration: XParameterEntityDeclaration) throws 111 | 112 | func writeInternalEntity(internalEntity: XInternalEntity) throws 113 | 114 | func writeExternalEntity(externalEntity: XExternalEntity) throws 115 | 116 | func writeElementDeclaration(elementDeclaration: XElementDeclaration) throws 117 | 118 | func writeAttributeListDeclaration(attributeListDeclaration: XAttributeListDeclaration) throws 119 | 120 | func writeDocumentEnd(document: XDocument) throws 121 | } 122 | 123 | public class DefaultProductionTemplate: XProductionTemplate { 124 | 125 | public let writeEmptyTags: Bool 126 | public let linebreak: String 127 | public let escapeGreaterThan: Bool 128 | public let escapeAllInText: Bool 129 | public let escapeAll: Bool 130 | 131 | public init( 132 | writeEmptyTags: Bool = true, 133 | escapeGreaterThan: Bool = false, 134 | escapeAllInText: Bool = false, 135 | escapeAll: Bool = false, 136 | linebreak: String = X_DEFAULT_LINEBREAK 137 | ) { 138 | self.writeEmptyTags = writeEmptyTags 139 | self.linebreak = linebreak 140 | self.escapeGreaterThan = escapeGreaterThan 141 | self.escapeAllInText = escapeAllInText 142 | self.escapeAll = escapeAll 143 | } 144 | 145 | public func activeProduction(for writer: Writer, atNode node: XNode) -> XActiveProduction { 146 | ActiveDefaultProduction( 147 | writer: writer, 148 | writeEmptyTags: writeEmptyTags, 149 | escapeGreaterThan: escapeGreaterThan, 150 | escapeAllInText: escapeAllInText, 151 | escapeAll: escapeAll, 152 | linebreak: linebreak 153 | ) 154 | } 155 | 156 | } 157 | 158 | open class ActiveDefaultProduction: XActiveProduction { 159 | 160 | private var writer: Writer 161 | 162 | public func write(_ text: String) throws { 163 | try writer.write(text) 164 | } 165 | 166 | let escapeGreaterThan: Bool 167 | let escapeAllInText: Bool 168 | let escapeAll: Bool 169 | private let writeEmptyTags: Bool 170 | 171 | private let _linebreak: String 172 | 173 | public var linebreak: String { 174 | get { _linebreak } 175 | } 176 | 177 | public init( 178 | writer: Writer, 179 | writeEmptyTags: Bool = true, 180 | escapeGreaterThan: Bool = false, 181 | escapeAllInText: Bool = false, 182 | escapeAll: Bool = false, 183 | linebreak: String = X_DEFAULT_LINEBREAK 184 | ) { 185 | self.writer = writer 186 | self.writeEmptyTags = writeEmptyTags 187 | self.escapeGreaterThan = escapeGreaterThan 188 | self.escapeAllInText = escapeAllInText 189 | self.escapeAll = escapeAll 190 | self._linebreak = linebreak 191 | } 192 | 193 | private var _declarationInInternalSubsetIndentation = " " 194 | 195 | public var declarationInInternalSubsetIndentation: String { 196 | set { 197 | _declarationInInternalSubsetIndentation = newValue 198 | } 199 | get { 200 | return _declarationInInternalSubsetIndentation 201 | } 202 | } 203 | 204 | open func sortDeclarationsInInternalSubset(document: XDocument) -> [XDeclarationInInternalSubset] { 205 | var sorted = [XDeclarationInInternalSubset]() 206 | for declarations in [ 207 | sortByName(document.internalEntityDeclarations), 208 | sortByName(document.externalEntityDeclarations), 209 | sortByName(document.notationDeclarations), 210 | sortByName(document.unparsedEntityDeclarations), 211 | sortByName(document.elementDeclarations), 212 | sortByName(document.attributeListDeclarations), 213 | sortByName(document.parameterEntityDeclarations) 214 | ] { 215 | for declaration in declarations { 216 | sorted.append(declaration) 217 | } 218 | } 219 | return sorted 220 | } 221 | 222 | open func writeDocumentStart(document: XDocument) throws { 223 | } 224 | 225 | public static func defaultXMLDeclaration(version: String, encoding: String?, standalone: String?, linebreak: String) -> String? { 226 | if version != "1.0" || encoding != nil || standalone != nil { 227 | "\(linebreak)" 228 | } else { 229 | nil 230 | } 231 | } 232 | 233 | open func writeXMLDeclaration(version: String, encoding: String?, standalone: String?) throws { 234 | if let defaultXMLDeclaration = Self.defaultXMLDeclaration(version: version, encoding: encoding, standalone: standalone, linebreak: linebreak) { 235 | try write(defaultXMLDeclaration) 236 | } 237 | } 238 | 239 | open func writeDocumentTypeDeclarationBeforeInternalSubset(type: String, publicID: String?, systemID: String?, hasInternalSubset: Bool) throws { 240 | if publicID != nil || systemID != nil || hasInternalSubset { 241 | try write("\(linebreak)") 244 | } 245 | } 246 | } 247 | 248 | open func writeDocumentTypeDeclarationInternalSubsetStart() throws { 249 | try write("\(linebreak)[\(linebreak)") 250 | } 251 | 252 | open func writeDocumentTypeDeclarationInternalSubsetEnd() throws { 253 | try write("]") 254 | } 255 | 256 | open func writeDocumentTypeDeclarationAfterInternalSubset(hasInternalSubset: Bool) throws { 257 | if hasInternalSubset { 258 | try write(">\(linebreak)") 259 | } 260 | } 261 | 262 | open func writeElementStartBeforeAttributes(element: XElement) throws { 263 | if let prefix = element.prefix { 264 | try write("<\(prefix):\(element.name)") 265 | } else { 266 | try write("<\(element.name)") 267 | } 268 | } 269 | 270 | open func sortAttributeNames(attributeNames: [String], element: XElement) -> [String] { 271 | return attributeNames 272 | } 273 | 274 | open func writeAttributeValue(name: String, value: String, element: XElement) throws { 275 | try write( 276 | ( 277 | escapeAll ? value.escapingAllForXML : 278 | (escapeGreaterThan ? value.escapingDoubleQuotedValueForXML.replacingOccurrences(of: ">", with: ">") : value.escapingDoubleQuotedValueForXML) 279 | ) 280 | .replacingOccurrences(of: "\n", with: " ").replacingOccurrences(of: "\r", with: " ") 281 | ) 282 | } 283 | 284 | open func writeAttribute(name: String, value: String, element: XElement) throws { 285 | try write(" \(name)=\"") 286 | try writeAttributeValue(name: name, value: value, element: element) 287 | try write("\"") 288 | } 289 | 290 | open func writeAsEmptyTagIfEmpty(element: XElement) -> Bool { 291 | return writeEmptyTags 292 | } 293 | 294 | open func writeElementStartAfterAttributes(element: XElement) throws { 295 | if element.isEmpty && writeAsEmptyTagIfEmpty(element: element) { 296 | try write("/>") 297 | } 298 | else { 299 | try write(">") 300 | } 301 | } 302 | 303 | open func writeElementEnd(element: XElement) throws { 304 | if !(element.isEmpty && writeAsEmptyTagIfEmpty(element: element)) { 305 | if let prefix = element.prefix { 306 | try write("") 307 | } else { 308 | try write("") 309 | } 310 | } 311 | } 312 | 313 | open func writeText(text: XText) throws { 314 | try write( 315 | escapeAll || escapeAllInText ? text.value.escapingAllForXML : 316 | (escapeGreaterThan ? text.value.escapingForXML.replacingOccurrences(of: ">", with: ">") : text.value.escapingForXML) 317 | ) 318 | } 319 | 320 | open func writeLiteral(literal: XLiteral) throws { 321 | try write(literal._value) 322 | } 323 | 324 | open func writeCDATASection(cdataSection: XCDATASection) throws { 325 | try write("") 326 | } 327 | 328 | open func writeProcessingInstruction(processingInstruction: XProcessingInstruction) throws { 329 | try write("") 330 | } 331 | 332 | open func writeComment(comment: XComment) throws { 333 | try write("") 334 | } 335 | 336 | open func writeInternalEntityDeclaration(internalEntityDeclaration: XInternalEntityDeclaration) throws { 337 | try write("\(declarationInInternalSubsetIndentation)\(linebreak)") 338 | } 339 | 340 | open func writeExternalEntityDeclaration(externalEntityDeclaration: XExternalEntityDeclaration) throws { 341 | try write("\(declarationInInternalSubsetIndentation)\(linebreak)") 342 | } 343 | 344 | open func writeUnparsedEntityDeclaration(unparsedEntityDeclaration: XUnparsedEntityDeclaration) throws { 345 | try write("\(declarationInInternalSubsetIndentation)\(linebreak)") 346 | } 347 | 348 | open func writeNotationDeclaration(notationDeclaration: XNotationDeclaration) throws { 349 | try write("\(declarationInInternalSubsetIndentation)") 350 | } 351 | 352 | open func writeParameterEntityDeclaration(parameterEntityDeclaration: XParameterEntityDeclaration) throws { 353 | try write("\(declarationInInternalSubsetIndentation)") 354 | } 355 | 356 | open func writeInternalEntity(internalEntity: XInternalEntity) throws { 357 | try write("&\(internalEntity._name);") 358 | } 359 | 360 | open func writeExternalEntity(externalEntity: XExternalEntity) throws { 361 | try write("&\(externalEntity._name);") 362 | } 363 | 364 | open func writeElementDeclaration(elementDeclaration: XElementDeclaration) throws { 365 | try write("\(declarationInInternalSubsetIndentation)\(elementDeclaration._literal)\(linebreak)") 366 | } 367 | 368 | open func writeAttributeListDeclaration(attributeListDeclaration: XAttributeListDeclaration) throws { 369 | try write("\(declarationInInternalSubsetIndentation)\(attributeListDeclaration._literal)\(linebreak)") 370 | } 371 | 372 | open func writeDocumentEnd(document: XDocument) throws { 373 | 374 | } 375 | } 376 | 377 | public class PrettyPrintProductionTemplate: XProductionTemplate { 378 | 379 | public let writeEmptyTags: Bool 380 | public let indentation: String 381 | public let linebreak: String 382 | 383 | public init(writeEmptyTags: Bool = true, indentation: String = X_DEFAULT_INDENTATION, linebreak: String = X_DEFAULT_LINEBREAK) { 384 | self.writeEmptyTags = writeEmptyTags 385 | self.indentation = indentation 386 | self.linebreak = linebreak 387 | } 388 | 389 | public func activeProduction(for writer: Writer, atNode node: XNode) -> XActiveProduction { 390 | ActivePrettyPrintProduction(writer: writer, writeEmptyTags: writeEmptyTags, indentation: indentation, linebreak: linebreak) 391 | } 392 | 393 | } 394 | 395 | open class ActivePrettyPrintProduction: ActiveDefaultProduction { 396 | 397 | private var indentation: String 398 | 399 | public init( 400 | writer: Writer, 401 | writeEmptyTags: Bool = true, 402 | indentation: String = X_DEFAULT_INDENTATION, 403 | escapeGreaterThan: Bool = false, 404 | escapeAllInText: Bool = false, 405 | escapeAll: Bool = false, 406 | linebreak: String = X_DEFAULT_LINEBREAK 407 | ) { 408 | self.indentation = indentation 409 | super.init( 410 | writer: writer, 411 | writeEmptyTags: writeEmptyTags, 412 | escapeGreaterThan: escapeGreaterThan, 413 | escapeAllInText: escapeAllInText, 414 | escapeAll: escapeAll, 415 | linebreak: linebreak 416 | ) 417 | } 418 | 419 | private var indentationLevel = 0 420 | 421 | private var mixed = [Bool]() 422 | 423 | open func mightHaveMixedContent(element: XElement) -> Bool { 424 | return element.content.contains(where: { $0 is XText || $0 is XInternalEntity || $0 is XInternalEntity }) 425 | } 426 | 427 | /// This can be used to suppress the "pretty print" before an element. 428 | public var suppressPrettyPrintBeforeElement = false 429 | public var forcePrettyPrintAtElement = false 430 | 431 | open override func writeElementStartBeforeAttributes(element: XElement) throws { 432 | if forcePrettyPrintAtElement { mixed.append(false) } 433 | if forcePrettyPrintAtElement || (suppressPrettyPrintBeforeElement == false && mixed.last != true) { 434 | if indentationLevel > 0 { 435 | try write(linebreak) 436 | for _ in 1...indentationLevel { 437 | try write(indentation) 438 | } 439 | } 440 | } 441 | try super.writeElementStartBeforeAttributes(element: element) 442 | } 443 | 444 | open override func writeElementStartAfterAttributes(element: XElement) throws { 445 | try super.writeElementStartAfterAttributes(element: element) 446 | if !element.isEmpty { 447 | mixed.append(mixed.last == true || mightHaveMixedContent(element: element)) 448 | indentationLevel += 1 449 | } 450 | } 451 | 452 | open override func writeElementEnd(element: XElement) throws { 453 | if !element.isEmpty { 454 | indentationLevel -= 1 455 | if forcePrettyPrintAtElement || mixed.last != true { 456 | try write(linebreak) 457 | if indentationLevel > 0 { 458 | for _ in 1...indentationLevel { 459 | try write(indentation) 460 | } 461 | } 462 | } 463 | mixed.removeLast() 464 | } 465 | if forcePrettyPrintAtElement { mixed.removeLast() } 466 | try super.writeElementEnd(element: element) 467 | } 468 | } 469 | 470 | public class HTMLProductionTemplate: XProductionTemplate { 471 | 472 | public let indentation: String 473 | public let linebreak: String 474 | public let htmlNamespaceReference: NamespaceReference 475 | public let suppressDocumentTypeDeclaration: Bool 476 | public let writeAsASCII: Bool 477 | public let escapeGreaterThan: Bool 478 | public let escapeAllInText: Bool 479 | public let escapeAll: Bool 480 | public let suppressUncessaryPrettyPrintAtAnchors: Bool 481 | 482 | public init( 483 | indentation: String = X_DEFAULT_INDENTATION, 484 | linebreak: String = X_DEFAULT_LINEBREAK, 485 | withHTMLNamespaceReference htmlNamespaceReference: NamespaceReference = .fullPrefix(""), 486 | suppressDocumentTypeDeclaration: Bool = false, 487 | writeAsASCII: Bool = false, 488 | escapeGreaterThan: Bool = false, 489 | escapeAllInText: Bool = false, 490 | escapeAll: Bool = false, 491 | suppressUncessaryPrettyPrintAtAnchors: Bool = false 492 | ) { 493 | self.indentation = indentation 494 | self.linebreak = linebreak 495 | self.htmlNamespaceReference = htmlNamespaceReference 496 | self.suppressDocumentTypeDeclaration = suppressDocumentTypeDeclaration 497 | self.writeAsASCII = writeAsASCII 498 | self.escapeGreaterThan = escapeGreaterThan 499 | self.escapeAllInText = escapeAllInText 500 | self.escapeAll = escapeAll 501 | self.suppressUncessaryPrettyPrintAtAnchors = suppressUncessaryPrettyPrintAtAnchors 502 | } 503 | 504 | open func activeProduction(for writer: Writer, atNode node: XNode) -> XActiveProduction { 505 | ActiveHTMLProduction( 506 | writer: writer, 507 | indentation: indentation, 508 | linebreak: linebreak, 509 | atNode: node, 510 | withHTMLNamespaceReference: htmlNamespaceReference, 511 | suppressDocumentTypeDeclaration: suppressDocumentTypeDeclaration, 512 | writeAsASCII: writeAsASCII, 513 | escapeGreaterThan: escapeGreaterThan, 514 | escapeAllInText: escapeAllInText, 515 | escapeAll: escapeAll, 516 | suppressUncessaryPrettyPrintAtAnchors: suppressUncessaryPrettyPrintAtAnchors 517 | ) 518 | } 519 | 520 | } 521 | 522 | open class ActiveHTMLProduction: ActivePrettyPrintProduction { 523 | 524 | public var htmlEmptyTags: [String] 525 | public var htmlStrictBlocks: [String] 526 | public var htmlStrictInlines: [String] 527 | public var blockOrInline: [String] 528 | public var suppressDocumentTypeDeclaration: Bool 529 | public let fullHTMLPrefix: String 530 | public let writeAsASCII: Bool 531 | public let suppressUncessaryPrettyPrintAtAnchors: Bool 532 | 533 | public init( 534 | writer: Writer, 535 | indentation: String = X_DEFAULT_INDENTATION, 536 | linebreak: String = X_DEFAULT_LINEBREAK, 537 | atNode node: XNode, 538 | withHTMLNamespaceReference htmlNamespaceReference: NamespaceReference, 539 | suppressDocumentTypeDeclaration: Bool = false, 540 | writeAsASCII: Bool = false, 541 | escapeGreaterThan: Bool = false, 542 | escapeAllInText: Bool = false, 543 | escapeAll: Bool = false, 544 | suppressUncessaryPrettyPrintAtAnchors: Bool = false 545 | ) { 546 | fullHTMLPrefix = ((node as? XDocument ?? node.top) as XBranch?)?.fullPrefix(forNamespaceReference: htmlNamespaceReference) ?? "" 547 | htmlEmptyTags = [ 548 | "\(fullHTMLPrefix)area", 549 | "\(fullHTMLPrefix)base", 550 | "\(fullHTMLPrefix)br", 551 | "\(fullHTMLPrefix)col", 552 | "\(fullHTMLPrefix)embed", 553 | "\(fullHTMLPrefix)hr", 554 | "\(fullHTMLPrefix)img", 555 | "\(fullHTMLPrefix)input", 556 | "\(fullHTMLPrefix)link", 557 | "\(fullHTMLPrefix)meta", 558 | "\(fullHTMLPrefix)param", 559 | "\(fullHTMLPrefix)source", 560 | "\(fullHTMLPrefix)track", 561 | "\(fullHTMLPrefix)wbr", 562 | ] 563 | htmlStrictBlocks = [ 564 | "\(fullHTMLPrefix)div", 565 | "\(fullHTMLPrefix)p", 566 | "\(fullHTMLPrefix)table", 567 | "\(fullHTMLPrefix)thead", 568 | "\(fullHTMLPrefix)tbody", 569 | "\(fullHTMLPrefix)tfoot", 570 | "\(fullHTMLPrefix)tr", 571 | "\(fullHTMLPrefix)th", 572 | "\(fullHTMLPrefix)td", 573 | ] 574 | htmlStrictInlines = [ 575 | "\(fullHTMLPrefix)abbr", 576 | "\(fullHTMLPrefix)acronym", 577 | "\(fullHTMLPrefix)b", 578 | "\(fullHTMLPrefix)bdo", 579 | "\(fullHTMLPrefix)big", 580 | "\(fullHTMLPrefix)br", 581 | "\(fullHTMLPrefix)cite", 582 | "\(fullHTMLPrefix)code", 583 | "\(fullHTMLPrefix)dfn", 584 | "\(fullHTMLPrefix)em", 585 | "\(fullHTMLPrefix)i", 586 | "\(fullHTMLPrefix)input", 587 | "\(fullHTMLPrefix)kbd", 588 | "\(fullHTMLPrefix)output", 589 | "\(fullHTMLPrefix)q", 590 | "\(fullHTMLPrefix)samp", 591 | "\(fullHTMLPrefix)small", 592 | "\(fullHTMLPrefix)span", 593 | "\(fullHTMLPrefix)strong", 594 | "\(fullHTMLPrefix)sub", 595 | "\(fullHTMLPrefix)sup", 596 | "\(fullHTMLPrefix)time", 597 | "\(fullHTMLPrefix)var", 598 | ] 599 | blockOrInline = [ 600 | "\(fullHTMLPrefix)a", 601 | "\(fullHTMLPrefix)img", 602 | ] 603 | self.suppressDocumentTypeDeclaration = suppressDocumentTypeDeclaration 604 | self.writeAsASCII = writeAsASCII 605 | self.suppressUncessaryPrettyPrintAtAnchors = suppressUncessaryPrettyPrintAtAnchors 606 | super.init( 607 | writer: writer, 608 | writeEmptyTags: false, 609 | indentation: indentation, 610 | escapeGreaterThan: escapeGreaterThan, 611 | escapeAllInText: escapeAllInText, 612 | escapeAll: escapeAll, 613 | linebreak: linebreak 614 | ) 615 | } 616 | 617 | open override func writeXMLDeclaration(version: String, encoding: String?, standalone: String?) throws { 618 | // do not write the XML declaration for HTML 619 | } 620 | 621 | open override func writeDocumentTypeDeclarationBeforeInternalSubset(type: String, publicID: String?, systemID: String?, hasInternalSubset: Bool) throws { 622 | if !suppressDocumentTypeDeclaration { 623 | try write("\(linebreak)") 630 | } 631 | } 632 | 633 | open override func writeAsEmptyTagIfEmpty(element: XElement) -> Bool { 634 | return htmlEmptyTags.contains(element.name) 635 | } 636 | 637 | private func isStrictlyInline(_ node: XNode?) -> Bool { 638 | guard let node else { return false } 639 | return node is XText || { 640 | if let element = node as? XElement { 641 | return htmlStrictInlines.contains(element.name) 642 | } 643 | else { 644 | return false 645 | } 646 | }() 647 | } 648 | 649 | private func isStrictlyBlock(_ node: XNode?) -> Bool { 650 | guard let node else { return false } 651 | if let element = node as? XElement { 652 | return htmlStrictBlocks.contains(element.name) 653 | } 654 | else { 655 | return false 656 | } 657 | } 658 | 659 | open override func mightHaveMixedContent(element: XElement) -> Bool { 660 | return element.children({ !self.blockOrInline.contains($0.name) }).absent || element.content.contains(where: { isStrictlyInline($0) }) 661 | } 662 | 663 | open func sort(texts: [String], preferring preferred: String) -> [String] { 664 | return texts.sorted { name1, name2 in 665 | if name2 == preferred { 666 | return false 667 | } 668 | else { 669 | return name1 == preferred || name1 < name2 670 | } 671 | } 672 | } 673 | 674 | open override func writeElementStartBeforeAttributes(element: XElement) throws { 675 | let oldSuppressPrettyPrintBeforeElement = suppressPrettyPrintBeforeElement 676 | let oldForcePrettyPrintAtElement = forcePrettyPrintAtElement 677 | suppressPrettyPrintBeforeElement = isStrictlyInline(element) || ( 678 | suppressUncessaryPrettyPrintAtAnchors && element.name == "a" 679 | && !isStrictlyBlock(element.previousTouching) 680 | ) 681 | forcePrettyPrintAtElement = htmlStrictBlocks.contains(element.name) 682 | try super.writeElementStartBeforeAttributes(element: element) 683 | suppressPrettyPrintBeforeElement = oldSuppressPrettyPrintBeforeElement 684 | forcePrettyPrintAtElement = oldForcePrettyPrintAtElement 685 | } 686 | 687 | open override func writeElementEnd(element: XElement) throws { 688 | let oldForcePrettyPrintAtElement = forcePrettyPrintAtElement 689 | forcePrettyPrintAtElement = (element.lastContent as? XElement)?.fullfills{ htmlStrictBlocks.contains($0.name) } == true 690 | try super.writeElementEnd(element: element) 691 | forcePrettyPrintAtElement = oldForcePrettyPrintAtElement 692 | } 693 | 694 | open override func sortAttributeNames(attributeNames: [String], element: XElement) -> [String] { 695 | if element.name == "meta" { 696 | return sort(texts: attributeNames, preferring: "name") 697 | } 698 | else if element.name == "script" { 699 | return sort(texts: attributeNames, preferring: "src") 700 | } 701 | else { 702 | return super.sortAttributeNames(attributeNames: attributeNames, element: element) 703 | } 704 | } 705 | 706 | open override func writeText(text: XText) throws { 707 | var result = (escapeAll || escapeAllInText) ? text._value.escapingAllForXML : text._value.escapingForXML 708 | if escapeGreaterThan { 709 | result = result.replacingOccurrences(of: ">", with: ">") 710 | } 711 | if writeAsASCII { 712 | result = result.asciiWithXMLCharacterReferences 713 | } 714 | try write(result) 715 | } 716 | 717 | open override func writeAttributeValue(name: String, value: String, element: XElement) throws { 718 | var result = ( 719 | escapeAll ? value.escapingAllForXML : 720 | (escapeGreaterThan ? value.escapingDoubleQuotedValueForXML.replacingOccurrences(of: ">", with: ">") : value.escapingDoubleQuotedValueForXML) 721 | ) 722 | .replacingOccurrences(of: "\n", with: " ").replacingOccurrences(of: "\r", with: " ") 723 | if writeAsASCII { 724 | result = result.asciiWithXMLCharacterReferences 725 | } 726 | try write(result) 727 | } 728 | 729 | } 730 | 731 | fileprivate extension String { 732 | 733 | var asciiWithXMLCharacterReferences: String { 734 | var texts = [String]() 735 | for scalar in self.unicodeScalars { 736 | if scalar.value < 127 { 737 | texts.append(String(scalar)) 738 | } else { 739 | texts.append("&#x\(String(format: "%04x", scalar.value));") 740 | } 741 | } 742 | return texts.joined() 743 | } 744 | 745 | } 746 | -------------------------------------------------------------------------------- /Sources/SwiftXML/XML/Tools.swift: -------------------------------------------------------------------------------- 1 | //===--- Tools.swift ------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import Foundation 12 | 13 | /// Info that a correction in the call to `copyXStructure` has to use. 14 | public struct StructureCopyInfo { 15 | public let structure: XContent 16 | public let start: XContent 17 | public let cloneForStart: XContent 18 | public let end: XContent 19 | public let cloneForEnd: XContent 20 | } 21 | 22 | /// Copies the structure from `start` to `end`, optionally up to the `upTo` value. 23 | /// `start` and `end` must have a common ancestor. 24 | /// Returns `nil` if there is no common ancestor. 25 | /// The returned element is a clone of the `upTo` value if a) it is not `nil` 26 | /// and b) `upTo` is an ancestor of the common ancestor or the ancestor itself. 27 | /// Else it is the clone of the common ancestor (but generally with a different 28 | /// content in both cases). The `correction` can do some corrections. 29 | public func copyXStructure(from start: XContent, to end: XContent, upTo: XElement? = nil, correction: ((StructureCopyInfo) -> XContent)? = nil) -> XContent? { 30 | 31 | func addUpTo(fromCopy copy: XContent) -> XContent { 32 | guard let upTo else { return copy } 33 | var result = copy 34 | while let backlink = result.backlink, backlink !== upTo, let parent = backlink.parent { 35 | let parentClone = parent.shallowClone 36 | parentClone.add { result } 37 | result = parentClone 38 | } 39 | return result 40 | } 41 | 42 | if start === end { 43 | let startClone = start.clone 44 | let result = addUpTo(fromCopy: startClone) 45 | if let correction { 46 | return correction(StructureCopyInfo(structure: result, start: start, cloneForStart: startClone, end: start, cloneForEnd: startClone)) 47 | } else { 48 | return result 49 | } 50 | } 51 | 52 | let allAncestorsForStart = Array(start.ancestorsIncludingSelf(untilAndIncluding: { $0 === upTo })) 53 | 54 | guard let commonAncestor = end.ancestorsIncludingSelf(untilAndIncluding: { $0 === upTo }).filter({ ancestor in allAncestorsForStart.contains(where: { $0 === ancestor }) }).first else { 55 | return nil 56 | } 57 | 58 | var ancestorsForStart = start.ancestorsIncludingSelf(until: { $0 === commonAncestor }).reversed() 59 | if ancestorsForStart.last === start { 60 | ancestorsForStart.removeLast() 61 | } 62 | 63 | var ancestorsForEnd = end.ancestorsIncludingSelf(until: { $0 === commonAncestor }).reversed() 64 | if ancestorsForEnd.last === end { 65 | ancestorsForEnd.removeLast() 66 | } 67 | 68 | let startClone = start.clone 69 | 70 | func processAncestorsForStart() -> XContent { 71 | var content: XContent = startClone 72 | var orginalContent = start 73 | while let ancestor = ancestorsForStart.popLast() { 74 | let cloneOfAncestor = ancestor.shallowClone 75 | cloneOfAncestor.add { 76 | content 77 | orginalContent.next.map { $0.clone } 78 | } 79 | content = cloneOfAncestor 80 | orginalContent = ancestor 81 | } 82 | return content 83 | } 84 | 85 | let endClone = end.clone 86 | 87 | func processAncestorsForEnd() -> XContent { 88 | var content: XContent = endClone 89 | var orginalContent = end 90 | while let ancestor = ancestorsForEnd.popLast() { 91 | let cloneOfAncestor = ancestor.shallowClone 92 | cloneOfAncestor.add { 93 | ancestor.content(until: { $0 === orginalContent }).map { $0.clone } 94 | content 95 | } 96 | content = cloneOfAncestor 97 | orginalContent = ancestor 98 | } 99 | return content 100 | } 101 | 102 | let combined = commonAncestor.shallowClone 103 | let structureForStart = processAncestorsForStart() 104 | let structureForEnd = processAncestorsForEnd() 105 | combined.add { 106 | structureForStart 107 | } 108 | let stopForMiddle = structureForEnd.backlink! 109 | for middle in structureForStart.backlink!.next(until: { $0 === stopForMiddle }) { 110 | combined.add { middle.clone } 111 | } 112 | combined.add { 113 | structureForEnd 114 | } 115 | 116 | let result = addUpTo(fromCopy: combined) 117 | 118 | if let correction { 119 | return correction(StructureCopyInfo(structure: result, start: start, cloneForStart: startClone, end: end, cloneForEnd: endClone)) 120 | } else { 121 | return result 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Sources/SwiftXML/XML/Transformation.swift: -------------------------------------------------------------------------------- 1 | //===--- Transformation.swift ---------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import Foundation 12 | 13 | public typealias XElementAction = (XElement)->() 14 | 15 | public typealias XAttributeAction = (XAttributeSpot)->() 16 | 17 | public struct XRule { 18 | 19 | public let prefix: String? 20 | public let names: [String] 21 | public let action: Any 22 | 23 | #if DEBUG 24 | 25 | public let actionFile: String 26 | public let actionLine: Int 27 | 28 | public init(forPrefix prefix: String? = nil, forElements names: [String], file: String = #file, line: Int = #line, action: @escaping XElementAction) { 29 | self.prefix = prefix 30 | self.names = names 31 | self.action = action 32 | self.actionFile = file 33 | self.actionLine = line 34 | } 35 | 36 | public init(forPrefix prefix: String? = nil, forElements names: String..., file: String = #file, line: Int = #line, action: @escaping XElementAction) { 37 | self.prefix = prefix 38 | self.names = names 39 | self.action = action 40 | self.actionFile = file 41 | self.actionLine = line 42 | } 43 | 44 | public init(forRegisteredAttributes names: [String], file: String = #file, line: Int = #line, action: @escaping XAttributeAction) { 45 | self.prefix = nil 46 | self.names = names 47 | self.action = action 48 | self.actionFile = file 49 | self.actionLine = line 50 | } 51 | 52 | public init(forRegisteredAttributes names: String..., file: String = #file, line: Int = #line, action: @escaping XAttributeAction) { 53 | self.prefix = nil 54 | self.names = names 55 | self.action = action 56 | self.actionFile = file 57 | self.actionLine = line 58 | } 59 | 60 | #else 61 | 62 | public init(forPrefix prefix: String? = nil, forElements names: [String], action: @escaping XElementAction) { 63 | self.prefix = prefix 64 | self.names = names 65 | self.action = action 66 | } 67 | 68 | public init(forPrefix prefix: String? = nil, forElements names: String..., action: @escaping XElementAction) { 69 | self.prefix = prefix 70 | self.names = names 71 | self.action = action 72 | } 73 | 74 | public init(forRegisteredAttributes names: [String], action: @escaping XAttributeAction) { 75 | self.prefix = nil 76 | self.names = names 77 | self.action = action 78 | } 79 | 80 | public init(forRegisteredAttributes names: String..., action: @escaping XAttributeAction) { 81 | self.prefix = nil 82 | self.names = names 83 | self.action = action 84 | } 85 | 86 | #endif 87 | } 88 | 89 | public protocol XRulesConvertible { 90 | func asXRules() -> [XRule] 91 | } 92 | 93 | extension XRule: XRulesConvertible { 94 | public func asXRules() -> [XRule] { 95 | return [self] 96 | } 97 | } 98 | 99 | extension Optional: XRulesConvertible where Wrapped == XRule { 100 | public func asXRules() -> [XRule] { 101 | switch self { 102 | case .some(let wrapped): return [wrapped] 103 | case .none: return [] 104 | } 105 | } 106 | } 107 | 108 | extension Array: XRulesConvertible where Element == XRule { 109 | public func asXRules() -> [XRule] { 110 | return self 111 | } 112 | } 113 | 114 | 115 | @resultBuilder 116 | public struct XRulesBuilder { 117 | public static func buildBlock(_ components: XRulesConvertible...) -> [XRule] { 118 | return components.flatMap({ $0.asXRules() }) 119 | } 120 | 121 | public static func buildEither(first component: XRulesConvertible) -> [XRule] { 122 | return component.asXRules() 123 | } 124 | 125 | public static func buildEither(second component: XRulesConvertible) -> [XRule] { 126 | return component.asXRules() 127 | } 128 | } 129 | 130 | public class XTransformation { 131 | 132 | let rules: [XRule] 133 | 134 | public init(@XRulesBuilder builder: () -> [XRule]) { 135 | self.rules = builder() 136 | } 137 | 138 | var stopped = false 139 | 140 | public func stop() { 141 | stopped = true 142 | } 143 | 144 | public func execute(inDocument document: XDocument) { 145 | 146 | #if DEBUG 147 | struct AppliedAction { let iterator: any IteratorProtocol; let action: Any; let actionFile: String; let actionLine: Int } 148 | #else 149 | struct AppliedAction { let iterator: any IteratorProtocol; let action: Any } 150 | #endif 151 | 152 | var iteratorsWithAppliedActions = [AppliedAction]() 153 | 154 | for rule in rules { 155 | if let elementAction = rule.action as? XElementAction { 156 | for name in rule.names { 157 | #if DEBUG 158 | iteratorsWithAppliedActions.append(AppliedAction( 159 | iterator: XXBidirectionalElementNameIterator(elementIterator: XElementsOfSameNameIterator(document: document, prefix: rule.prefix, name: name, keepLast: true), keepLast: true), 160 | action: elementAction, 161 | actionFile: rule.actionFile, 162 | actionLine: rule.actionLine 163 | )) 164 | #else 165 | iteratorsWithAppliedActions.append(AppliedAction( 166 | iterator: XXBidirectionalElementNameIterator(elementIterator: XElementsOfSameNameIterator(document: document, prefix: rule.prefix, name: name, keepLast: true), keepLast: true), 167 | action: elementAction 168 | )) 169 | #endif 170 | } 171 | } else if let attributeAction = rule.action as? XAttributeAction { 172 | rule.names.forEach { name in 173 | #if DEBUG 174 | iteratorsWithAppliedActions.append(AppliedAction( 175 | iterator: XBidirectionalAttributeIterator(forAttributeName: name, attributeIterator: XAttributesOfSameNameIterator(document: document, attributeName: name, keepLast: true), keepLast: true), 176 | action: attributeAction, 177 | actionFile: rule.actionFile, 178 | actionLine: rule.actionLine 179 | )) 180 | #else 181 | iteratorsWithAppliedActions.append(AppliedAction( 182 | iterator: XBidirectionalAttributeIterator(forAttributeName: name, attributeIterator: XAttributesOfSameNameIterator(document: document, attributeName: name, keepLast: true), keepLast: true), 183 | action: attributeAction 184 | )) 185 | #endif 186 | } 187 | } 188 | } 189 | 190 | var working = true; stopped = false 191 | while !stopped && working { 192 | working = false 193 | actions: for appliedAction in iteratorsWithAppliedActions { 194 | if stopped { break actions } 195 | if let iterator = appliedAction.iterator as? XXBidirectionalElementNameIterator, let action = appliedAction.action as? XElementAction { 196 | action: while let next = iterator.next() { 197 | if stopped { break action } 198 | working = true 199 | action(next) 200 | } 201 | } else if let iterator = appliedAction.iterator as? XBidirectionalAttributeIterator, let action = appliedAction.action as? XAttributeAction { 202 | action: while let attribute = iterator.next() { 203 | if stopped { break action } 204 | working = true 205 | action(attribute) 206 | } 207 | } 208 | } 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /Tests/SwiftXMLTests/FromReadme.swift: -------------------------------------------------------------------------------- 1 | //===--- FromReadme.swift ----------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import XCTest 12 | import class Foundation.Bundle 13 | @testable import SwiftXML 14 | 15 | final class FromReadmeTests: XCTestCase { 16 | 17 | func testParsingAndJoiningIDs() throws { 18 | let document = try parseXML(fromText: """ 19 | 20 | 21 | 22 | 23 | 24 | """) 25 | 26 | XCTAssertEqual(document.children.children["id"].joined(separator: ", "), "1, 2, 3") 27 | } 28 | 29 | func testRemoveElementsWhileIteration() throws{ 30 | let document = try parseXML(fromText: """ 31 | 32 | """) 33 | 34 | document.traverse { content in 35 | if let element = content as? XElement, element["remove"] == "true" { 36 | element.remove() 37 | } 38 | } 39 | 40 | XCTAssertEqual(document.children.children["id"].joined(separator: ", "), "2, 4") 41 | } 42 | 43 | func testPrintContentWithSourceRanges() throws{ 44 | let document = try parseXML(fromText: """ 45 | 46 | Hello 47 | 48 | """, textAllowedInElementWithName: { $0 == "b" }) 49 | 50 | XCTAssertEqual( 51 | document.allContent.map{ "\($0.sourceRange!): \($0)" }.joined(separator: "\n"), 52 | """ 53 | 1:1 - 3:4: 54 | 2:5 - 2:16: 55 | 2:8 - 2:12: "Hello" 56 | """ 57 | ) 58 | } 59 | 60 | func testExistingItems() throws{ 61 | let document = try parseXML(fromText: """ 62 | 63 | """) 64 | 65 | if let theBs = document.descendants("b").existing { 66 | XCTAssertEqual(theBs["id"].joined(separator: ", "), "1, 2, 3") 67 | } 68 | } 69 | 70 | func testContentSequenceCondition() throws{ 71 | let document = try parseXML(fromText: """ 72 | 73 | """) 74 | 75 | for descendant in document.descendants({ element in element["take"] == "true" }) { 76 | XCTAssertEqual(descendant["take"]!, "true") 77 | } 78 | } 79 | 80 | func testChainedIterators() throws{ 81 | let document = try parseXML(fromText: """ 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | """) 90 | 91 | var output = "" 92 | for element in document.descendants.descendants { 93 | output += element.serialized(pretty: true) 94 | } 95 | output = output.replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "\n", with: "") 96 | XCTAssertEqual(output, "") 97 | } 98 | 99 | func testFirstChildOfEachChild() throws{ 100 | let element = XElement("z") { 101 | XElement("a") { 102 | XElement("a1") 103 | XElement("a2") 104 | } 105 | XElement("b") { 106 | XElement("b1") 107 | XElement("b2") 108 | } 109 | } 110 | 111 | var output = "" 112 | for element in element.children.map({ $0.children.first }) { output += element?.name ?? "-" } 113 | XCTAssertEqual(output, "a1b1") 114 | } 115 | 116 | func testReplaceElementInHierarchy() throws{ 117 | let b = XElement("b") 118 | 119 | let a = XElement("a") { 120 | b 121 | "Hello" 122 | } 123 | 124 | let expectedOutputBeforeReplace = "Hello" 125 | 126 | XCTAssertEqual(a.serialized(), expectedOutputBeforeReplace) 127 | 128 | b.replace { 129 | XElement("wrapper1") { 130 | b 131 | XElement("wrapper2") { 132 | b.next 133 | } 134 | } 135 | } 136 | 137 | let expectedOutputAfterReplace = """ 138 | 139 | 140 | 141 | Hello 142 | 143 | 144 | """ 145 | 146 | XCTAssertEqual(a.serialized(pretty: true), expectedOutputAfterReplace) 147 | } 148 | 149 | func testConsumeForeignTypeAsXML() throws { 150 | 151 | struct MyStruct: XContentConvertible { 152 | 153 | let text1: String 154 | let text2: String 155 | 156 | func collectXML(by xmlCollector: inout XMLCollector) { 157 | xmlCollector.collect(XElement("text1") { text1 }) 158 | xmlCollector.collect(XElement("text2") { text2 }) 159 | } 160 | 161 | } 162 | 163 | let myStruct1 = MyStruct(text1: "hello", text2: "world") 164 | let myStruct2 = MyStruct(text1: "greeting", text2: "you") 165 | 166 | let element = XElement("x") { 167 | myStruct1 168 | myStruct2 169 | } 170 | 171 | XCTAssertEqual(element.serialized(pretty: true), #""" 172 | 173 | hello 174 | world 175 | greeting 176 | you 177 | 178 | """# 179 | ) 180 | } 181 | 182 | func testAddElementToDocument() throws { 183 | let document = try parseXML(fromText: """ 184 | 185 | """) 186 | 187 | for element in document.elements("b") { 188 | if element["id"] == "2" { 189 | element.insertNext { 190 | XElement("c") { 191 | element.previous 192 | } 193 | } 194 | } 195 | } 196 | 197 | let expectedOutput = """ 198 | 199 | """ 200 | 201 | XCTAssertEqual(document.serialized(), expectedOutput) 202 | } 203 | 204 | func testElementContentManipulation() throws { 205 | let element = XElement("top") { 206 | XElement("a1") { 207 | XElement("a2") 208 | } 209 | XElement("b1") { 210 | XElement("b2") 211 | } 212 | XElement("c1") { 213 | XElement("c2") 214 | } 215 | } 216 | 217 | XCTAssertEqual(element.serialized(), "") 218 | 219 | print("\n---- 1 ----\n") 220 | 221 | for content in element.content { 222 | content.replace(.skipping) { 223 | content.content 224 | } 225 | } 226 | 227 | XCTAssertEqual(element.serialized(), "") 228 | 229 | print("\n---- 2 ----\n") 230 | 231 | for content in element.contentReversed { 232 | content.insertPrevious(.skipping) { 233 | XElement("I" + ((content as? XElement)?.name ?? "?")) 234 | } 235 | } 236 | 237 | XCTAssertEqual(element.serialized(), "") 238 | } 239 | 240 | func testAddElementToDescendants() throws { 241 | let e = XElement("a") { 242 | XElement("b") 243 | XElement("c") 244 | } 245 | 246 | for descendant in e.descendants({ $0.name != "added" }) { 247 | descendant.add { XElement("added") } 248 | } 249 | 250 | XCTAssertEqual(e.serialized(), "") 251 | } 252 | 253 | func testAddElementToSelectedDescendants() throws { 254 | let myElement = XElement("a") { 255 | XElement("to-add") 256 | XElement("b") 257 | XElement("c") 258 | } 259 | 260 | for descendant in myElement.descendants({ $0.name != "to-add" }) { 261 | descendant.add { 262 | myElement.descendants("to-add") 263 | } 264 | } 265 | 266 | XCTAssertEqual(myElement.serialized(), "") 267 | } 268 | 269 | func testInsertNextElementToSelectedDescendants() throws { 270 | let myElement = XElement("top") { 271 | XElement("a") 272 | } 273 | 274 | for element in myElement.descendants { 275 | if element.name == "a" { 276 | element.insertNext() { 277 | XElement("b") 278 | } 279 | } 280 | else if element.name == "b" { 281 | element.insertNext { 282 | XElement("c") 283 | } 284 | } 285 | } 286 | 287 | XCTAssertEqual(myElement.serialized(), "") 288 | } 289 | 290 | func testInsertNextElementToSelectedDescendantsButSkipping() throws { 291 | let myElement = XElement("top") { 292 | XElement("a") 293 | } 294 | 295 | for element in myElement.descendants { 296 | if element.name == "a" { 297 | element.insertNext(.skipping) { 298 | XElement("b") 299 | } 300 | } 301 | else if element.name == "b" { 302 | element.insertNext { 303 | XElement("c") 304 | } 305 | } 306 | } 307 | 308 | XCTAssertEqual(myElement.serialized(), "") 309 | } 310 | 311 | func testReplaceNodeWithContent() throws { 312 | let document = try parseXML(fromText: """ 313 | Hello 314 | """) 315 | for bold in document.descendants("bold") { bold.replace { bold.content } } 316 | 317 | XCTAssertEqual(document.serialized(), "Hello") 318 | } 319 | 320 | func testPulling() { 321 | 322 | do { 323 | let firstFence1 = XElement("fence") { "u" } 324 | let t: String = firstFence1.applying{ $0.remove() }.pulling{ ($0.content({ $0 is XText }).first as? XText)?.value ?? "" } 325 | XCTAssertEqual(t, "u") 326 | } 327 | 328 | do { 329 | let firstFence2 = XElement("fence") { "" } 330 | let t: String = firstFence2.applying{ $0.remove() }.pulling{ ($0.content({ $0 is XText }).first as? XText)?.value ?? "" } 331 | XCTAssertEqual(t, "") 332 | } 333 | 334 | } 335 | 336 | func testDescendants() throws { 337 | let element = XElement("z") { 338 | XElement("a") { 339 | XElement("a1") 340 | XElement("a2") 341 | } 342 | XElement("b") { 343 | XElement("b1") 344 | XElement("b2") 345 | } 346 | } 347 | 348 | XCTAssertEqual(element.descendants.map{ $0.description }.joined(separator: ", "), ", , , , , ") 349 | } 350 | 351 | func testWithAndWhen() throws { 352 | 353 | let element1 = XElement("a") { 354 | XElement("child-of-a") { 355 | XElement("more", ["special": "yes"]) 356 | } 357 | } 358 | 359 | let element2 = XElement("b") 360 | 361 | if let childOfA = element1.fullfilling({ $0.name == "a" })?.children.first, 362 | childOfA.children.first?.fullfills({ $0["special"] == "yes" && $0["moved"] != "yes" }) == true { 363 | element2.add { 364 | childOfA.applying { $0["moved"] = "yes" } 365 | } 366 | } 367 | 368 | XCTAssertEqual(element2.serialized(), #""#) 369 | } 370 | 371 | func testApplyingForSequence() throws { 372 | 373 | let myElement = XElement("a") { 374 | XElement("b", ["inserted": "yes"]) { 375 | XElement("c", ["inserted": "yes"]) 376 | } 377 | } 378 | 379 | let inserted = Array(myElement.descendants.filter{ $0["inserted"] == "yes" }.applying{ $0["found"] = "yes" }) 380 | 381 | XCTAssertEqual(inserted.description, #"[, ]"#) 382 | } 383 | 384 | 385 | func testTransformationWithInverseOrder() throws { 386 | 387 | let document = try parseXML(fromText: """ 388 | 389 |
390 | 391 | This is a hint. 392 | 393 | 394 | This is a warning. 395 | 396 |
397 |
398 | """, textAllowedInElementWithName: { $0 == "paragraph" }) 399 | 400 | let transformation = XTransformation { 401 | 402 | XRule(forElements: "paragraph") { element in 403 | let style: String? = if element.parent?.name == "warning" { 404 | "color:Red" 405 | } else { 406 | nil 407 | } 408 | element.replace { 409 | XElement("p", ["style": style]) { 410 | element.content 411 | } 412 | } 413 | } 414 | 415 | XRule(forElements: "hint", "warning") { element in 416 | element.replace { 417 | XElement("div") { 418 | XElement("p", ["style": "bold"]) { 419 | element.name.uppercased() 420 | } 421 | element.content 422 | } 423 | } 424 | } 425 | } 426 | 427 | transformation.execute(inDocument: document) 428 | 429 | XCTAssertEqual( 430 | document.serialized(pretty: true), 431 | """ 432 | 433 |
434 |
435 |

HINT

436 |

This is a hint.

437 |
438 |
439 |

WARNING

440 |

This is a warning.

441 |
442 |
443 |
444 | """ 445 | ) 446 | } 447 | 448 | func testTransformationWithAnnotations() throws { 449 | 450 | let document = try parseXML(fromText: """ 451 | 452 |
453 | 454 | This is a hint. 455 | 456 | 457 | This is a warning. 458 | 459 |
460 |
461 | """, textAllowedInElementWithName: { $0 == "paragraph" }) 462 | 463 | let transformation = XTransformation { 464 | 465 | XRule(forElements: "hint", "warning") { element in 466 | element.replace { 467 | XElement("div", attached: ["source": element.name]) { 468 | XElement("p", ["style": "bold"]) { 469 | element.name.uppercased() 470 | } 471 | element.content 472 | } 473 | } 474 | } 475 | 476 | XRule(forElements: "paragraph") { element in 477 | let style: String? = if element.parent?.attached["source"] as? String == "warning" { 478 | "color:Red" 479 | } else { 480 | nil 481 | } 482 | element.replace { 483 | XElement("p", ["style": style]) { 484 | element.content 485 | } 486 | } 487 | } 488 | } 489 | 490 | transformation.execute(inDocument: document) 491 | 492 | XCTAssertEqual( 493 | document.serialized(pretty: true), 494 | """ 495 | 496 |
497 |
498 |

HINT

499 |

This is a hint.

500 |
501 |
502 |

WARNING

503 |

This is a warning.

504 |
505 |
506 |
507 | """ 508 | ) 509 | } 510 | 511 | func testTransformationWithBackLinks() throws { 512 | 513 | let document = try parseXML(fromText: """ 514 | 515 |
516 | 517 | This is a hint. 518 | 519 | 520 | This is a warning. 521 | 522 |
523 |
524 | """, textAllowedInElementWithName: { $0 == "paragraph" }) 525 | 526 | let transformation = XTransformation { 527 | 528 | XRule(forElements: "hint", "warning") { element in 529 | element.replace { 530 | XElement("div", withBackLinkFrom: element) { 531 | XElement("p", ["style": "bold"]) { 532 | element.name.uppercased() 533 | } 534 | element.content 535 | } 536 | } 537 | } 538 | 539 | XRule(forElements: "paragraph") { element in 540 | let style: String? = if element.parent?.backlink?.name == "warning" { 541 | "color:Red" 542 | } else { 543 | nil 544 | } 545 | element.replace { 546 | XElement("p", ["style": style]) { 547 | element.content 548 | } 549 | } 550 | } 551 | } 552 | 553 | // make a clone with inverse backlinks, 554 | // pointing from the original document to the clone: 555 | document.makeVersion() 556 | do { 557 | let backlink: XDocument? = document.backlink 558 | XCTAssert(backlink != nil) 559 | } 560 | 561 | transformation.execute(inDocument: document) 562 | 563 | // remove the clone: 564 | document.forgetLastVersion() 565 | 566 | XCTAssertEqual( 567 | document.serialized(pretty: true), 568 | """ 569 | 570 |
571 |
572 |

HINT

573 |

This is a hint.

574 |
575 |
576 |

WARNING

577 |

This is a warning.

578 |
579 |
580 |
581 | """ 582 | ) 583 | } 584 | 585 | func testTransformWithTraversal() throws { 586 | 587 | let document = try parseXML(fromText: """ 588 | 589 |
590 | 591 | This is a hint. 592 | 593 | 594 | This is a warning. 595 | 596 |
597 |
598 | """, textAllowedInElementWithName: { $0 == "paragraph" }) 599 | 600 | for section in document.elements("section") { 601 | section.traverse { node in 602 | // - 603 | } up: { node in 604 | if let element = node as? XElement { 605 | guard node !== section else { return } 606 | switch element.name { 607 | case "paragraph": 608 | let style: String? = if element.parent?.name == "warning" { 609 | "color:Red" 610 | } else { 611 | nil 612 | } 613 | element.replace { 614 | XElement("p", ["style": style]) { 615 | element.content 616 | } 617 | } 618 | case "hint", "warning": 619 | element.replace { 620 | XElement("div") { 621 | XElement("p", ["style": "bold"]) { 622 | element.name.uppercased() 623 | } 624 | element.content 625 | } 626 | } 627 | default: 628 | break 629 | } 630 | } 631 | } 632 | } 633 | 634 | XCTAssertEqual( 635 | document.serialized(pretty: true), 636 | """ 637 | 638 |
639 |
640 |

HINT

641 |

This is a hint.

642 |
643 |
644 |

WARNING

645 |

This is a warning.

646 |
647 |
648 |
649 | """ 650 | ) 651 | } 652 | 653 | func testSubscriptsOfSequences() throws { 654 | 655 | let document = try parseXML( 656 | fromText: """ 657 | 658 | The Title 659 |

The first paragraph.

660 |

The second paragraph.

661 | This is the annex. 662 |
663 | """, 664 | textAllowedInElementWithName: { ["title", "p", "annex"].contains($0) } 665 | ) 666 | 667 | XCTAssertEqual(document.children.children("p")["id"].joined(separator: " "), "1 2") 668 | XCTAssertEqual(document.children.children("p")[2]?.description ?? "-", #"

"#) 669 | XCTAssertEqual(document.children.children("p")[99]?.description ?? "-", "-") 670 | XCTAssertEqual(document.allTexts[2]?.value ?? "-", "The first paragraph.") 671 | } 672 | 673 | func testTextHandling() throws { 674 | 675 | let document = try parseXML(fromText: """ 676 | 677 | Hello world! 678 | world world world 679 | 680 | """) 681 | 682 | let searchText = "world" 683 | 684 | document.traverse { node in 685 | if let text = node as? XText { 686 | if text.value.contains(searchText) { 687 | text.isolated = true 688 | var addSearchText = false 689 | for part in text.value.components(separatedBy: searchText) { 690 | text.insertPrevious { 691 | if addSearchText { 692 | XElement("span", ["style": "background:LightGreen"]) { 693 | searchText 694 | } 695 | } 696 | part 697 | } 698 | addSearchText = true 699 | } 700 | text.remove() 701 | text.isolated = false 702 | } 703 | } 704 | } 705 | 706 | XCTAssertEqual(document.serialized(), """ 707 | 708 | Hello world! 709 | world world world 710 | 711 | """) 712 | } 713 | 714 | func testRegisteredAttributeValues() throws { 715 | let source = """ 716 | 717 | 718 | 719 | First reference to "1". 720 | Second reference to "1". 721 | 722 | """ 723 | 724 | let document = try parseXML(fromText: source, registeringValuesForAttributes: .selected(["id", "refid"])) 725 | 726 | XCTAssertEqual( 727 | """ 728 | id="1": 729 | \(document.registeredValues("1", forAttribute: "id").map{ $0.element.description }.joined(separator: "\n")) 730 | 731 | refid="1": 732 | \(document.registeredValues("1", forAttribute: "refid").map{ $0.element.serialized() }.joined(separator: "\n")) 733 | """, 734 | """ 735 | id="1": 736 | 737 | 738 | refid="1": 739 | First reference to "1". 740 | Second reference to "1". 741 | """ 742 | ) 743 | } 744 | } 745 | -------------------------------------------------------------------------------- /Tests/SwiftXMLTests/SequenceTypesTest.swift: -------------------------------------------------------------------------------- 1 | //===--- FromReadme.swift ----------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import XCTest 12 | import class Foundation.Bundle 13 | @testable import SwiftXML 14 | 15 | 16 | final class SequenceTypesTestTests: XCTestCase { 17 | 18 | func testWithoutNames() throws { 19 | 20 | let document = try parseXML(fromText: """ 21 | texttext 22 | """) 23 | 24 | let d = document.children.children("d").first 25 | XCTAssertNotNil(d) 26 | 27 | // previous: 28 | XCTAssertEqual(d?.previousElements.map{ $0.name }.joined(separator: ", "), "c, b, a") 29 | XCTAssertEqual(d?.previousElementsIncludingSelf.map{ $0.name }.joined(separator: ", "), "d, c, b, a") 30 | XCTAssertEqual(d?.previousCloseElements.map{ $0.name }.joined(separator: ", "), "c, b") 31 | XCTAssertEqual(d?.previousCloseElementsIncludingSelf.map{ $0.name }.joined(separator: ", "), "d, c, b") 32 | 33 | // next: 34 | XCTAssertEqual(d?.nextElements.map{ $0.name }.joined(separator: ", "), "e, f, g") 35 | XCTAssertEqual(d?.nextElementsIncludingSelf.map{ $0.name }.joined(separator: ", "), "d, e, f, g") 36 | XCTAssertEqual(d?.nextCloseElements.map{ $0.name }.joined(separator: ", "), "e, f") 37 | XCTAssertEqual(d?.nextCloseElementsIncludingSelf.map{ $0.name }.joined(separator: ", "), "d, e, f") 38 | 39 | } 40 | 41 | func testWithNames() throws { 42 | 43 | let document = try parseXML(fromText: """ 44 | texttexttexttext 45 | """) 46 | 47 | let d = document.children.children("d").first 48 | XCTAssertNotNil(d) 49 | 50 | // previous: 51 | XCTAssertEqual(d?.previousElements("c").map{ $0.name }.joined(separator: ", "), "c, c, c") 52 | XCTAssertEqual(d?.previousElements(while: { $0.name == "c" }).map{ $0.name }.joined(separator: ", "), "c, c, c") 53 | XCTAssertEqual(d?.previousElementsIncludingSelf(while: { $0.name == "c" }).map{ $0.name }.joined(separator: ", "), "") 54 | XCTAssertEqual(d?.previousElementsIncludingSelf(while: { $0.name == "d" || $0.name == "c" }).map{ $0.name }.joined(separator: ", "), "d, c, c, c") 55 | XCTAssertEqual(d?.previousCloseElements(while: { $0.name == "c" }).map{ $0.name }.joined(separator: ", "), "c, c") 56 | XCTAssertEqual(d?.previousCloseElementsIncludingSelf("c").map{ $0.name }.joined(separator: ", "), "c, c") 57 | XCTAssertEqual(d?.previousCloseElementsIncludingSelf(while: { $0.name == "c" }).map{ $0.name }.joined(separator: ", "), "") 58 | XCTAssertEqual(d?.previousCloseElementsIncludingSelf(while: { $0.name == "d" || $0.name == "c" }).map{ $0.name }.joined(separator: ", "), "d, c, c") 59 | 60 | // next: 61 | XCTAssertEqual(d?.nextElements("e").map{ $0.name }.joined(separator: ", "), "e, e, e") 62 | XCTAssertEqual(d?.nextElements(while: { $0.name == "e" }).map{ $0.name }.joined(separator: ", "), "e, e, e") 63 | XCTAssertEqual(d?.nextElementsIncludingSelf(while: { $0.name == "e" }).map{ $0.name }.joined(separator: ", "), "") 64 | XCTAssertEqual(d?.nextElementsIncludingSelf(while: { $0.name == "d" || $0.name == "e" }).map{ $0.name }.joined(separator: ", "), "d, e, e, e") 65 | XCTAssertEqual(d?.nextCloseElements(while: { $0.name == "e" }).map{ $0.name }.joined(separator: ", "), "e, e") 66 | XCTAssertEqual(d?.nextCloseElementsIncludingSelf("e").map{ $0.name }.joined(separator: ", "), "e, e") 67 | XCTAssertEqual(d?.nextCloseElementsIncludingSelf(while: { $0.name == "e" }).map{ $0.name }.joined(separator: ", "), "") 68 | XCTAssertEqual(d?.nextCloseElementsIncludingSelf(while: { $0.name == "d" || $0.name == "e" }).map{ $0.name }.joined(separator: ", "), "d, e, e") 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Tests/SwiftXMLTests/ToolsTests.swift: -------------------------------------------------------------------------------- 1 | //===--- ToolsTests.swift -------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftXML.org open source project 4 | // 5 | // Copyright (c) 2021-2023 Stefan Springer (https://stefanspringer.com) 6 | // and the SwiftXML project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | //===----------------------------------------------------------------------===// 10 | 11 | import XCTest 12 | import class Foundation.Bundle 13 | import AutoreleasepoolShim 14 | @testable import SwiftXML 15 | 16 | final class ToolsTests: XCTestCase { 17 | 18 | func testCopyXStructure1() throws { 19 | let document = try parseXML(fromText: """ 20 | 21 | 22 | Typ „alphabetisch“ (alphabetic) 23 |

Das folgende ist eine Aufzählung:aAnleitung A

Das ist zu tun. 27 | ...usw. ...;

bAnleitung B

Und noch anderes. 29 | ...usw. ...

30 | 31 | """) 32 | 33 | let start = document.descendants("sec").first?.firstChild("p")?.allTexts.first 34 | let end = document.descendants("sec").first?.firstChild("p")?.descendants("term").first?.allTexts.dropFirst().first! 35 | 36 | XCTAssertEqual(start?.serialized(), #""" 37 | Das folgende ist eine Aufzählung: 38 | """#) 39 | XCTAssertEqual(end?.serialized(), #""" 40 | Anleitung A 41 | """#) 42 | 43 | let copyOfStructure = copyXStructure(from: start!, to: end!, upTo: start!.ancestors({ $0.name == "sec" }).first! )?.content 44 | XCTAssertEqual(copyOfStructure?.map{ $0.serialized() }.joined(), #""" 45 |

Das folgende ist eine Aufzählung:aAnleitung A

46 | """#) 47 | } 48 | 49 | func testCopyXStructure2() throws { 50 | let document = try parseXML(fromText: """ 51 | 52 | 53 | Typ „alphabetisch“ (alphabetic) 54 |

Das folgende ist eine Aufzählung:aVorgang für Fall A

Das ist zu tun. 58 | ... usw. ...;

bVorgang für Fall B

Das ist dann zu tun. 60 | ... usw. ...

61 |
62 | """) 63 | 64 | let start = document.descendants("sec").first?.children("p").first?.descendants("p").first?.allTexts.first 65 | let end = start 66 | 67 | XCTAssertEqual(start?.serialized().replacing(regex: #"\s+"#, with: " ").trimming(), #""" 68 | Das ist zu tun. 69 | """#) 70 | XCTAssertEqual(end?.serialized().replacing(regex: #"\s+"#, with: " ").trimming(), #""" 71 | Das ist zu tun. 72 | """#) 73 | 74 | let copyOfStructure = copyXStructure(from: start!, to: end!, upTo: start!.ancestors({ $0.name == "sec" }).first!)?.content 75 | XCTAssertEqual(copyOfStructure?.map{ $0.serialized() }.joined(), #""" 76 |

Das ist zu tun.

77 | """#) 78 | } 79 | 80 | func testCopyXStructure3() throws { 81 | let document = try parseXML(fromText: """ 82 | 83 | 84 | Der Titel 85 |

Das folgende

86 |

ist

87 |

eine Aufzählung:aAnleitung A

Das ist zu tun. 91 | ... usw. ...;

bAnleitung B

Das ist dann zu tun. 93 | ... usw. ....

94 |
95 | """) 96 | 97 | let start = document.descendants("sec").first?.firstChild("p")?.allTexts.first 98 | let end = document.descendants("sec").first?.children("p").dropFirst(2).first?.descendants("term").first?.allTexts.dropFirst().first! 99 | 100 | XCTAssertEqual(start?.serialized(), #""" 101 | Das folgende 102 | """#) 103 | XCTAssertEqual(end?.serialized(), #""" 104 | Anleitung A 105 | """#) 106 | 107 | let copyOfStructure = copyXStructure(from: start!, to: end!, upTo: start!.ancestors({ $0.name == "sec" }).first!)?.content 108 | copyOfStructure?.echo(pretty: true) 109 | XCTAssertEqual(copyOfStructure?.map{ $0.serialized() }.joined(), #""" 110 |

Das folgende

111 |

ist

112 |

eine Aufzählung:aAnleitung A

113 | """#) 114 | } 115 | 116 | func testCopyXStructure4() throws { 117 | let document = try parseXML(fromText: """ 118 |

In Abschnitt 1 und Abschnitt 2 muss man schauen.

119 | """) 120 | 121 | let start = document.allTexts.first 122 | let end = document.allTexts.dropFirst(4).first 123 | 124 | XCTAssertEqual(start?.serialized().replacing(regex: #"\s+"#, with: " ").trimming(), #""" 125 | In 126 | """#) 127 | XCTAssertEqual(end?.serialized().replacing(regex: #"\s+"#, with: " ").trimming(), #""" 128 | muss man schauen. 129 | """#) 130 | 131 | let copyOfStructure = copyXStructure(from: start!, to: end!, upTo: document.firstChild!) 132 | XCTAssertEqual(copyOfStructure?.serialized(), #""" 133 |

In Abschnitt 1 und Abschnitt 2 muss man schauen.

134 | """#) 135 | } 136 | 137 | func testCopyXStructure5() throws { 138 | let document = try parseXML(fromText: """ 139 |
140 |

Ja,

141 |

das ist so 1 %, echt.

142 |
143 | """, textAllowedInElementWithName: { ["p", "span"].contains($0) }) 144 | 145 | let start = document.firstChild?.children.first?.allTexts.first 146 | let end = document.firstChild?.children.dropFirst().first?.allTexts.dropFirst(2).first 147 | 148 | XCTAssertEqual(start?.serialized().replacing(regex: #"\s+"#, with: " ").trimming(), #""" 149 | Ja, 150 | """#) 151 | XCTAssertEqual(end?.serialized().replacing(regex: #"\s+"#, with: " ").trimming(), #""" 152 | , echt. 153 | """#) 154 | 155 | let copyOfStructure = copyXStructure(from: start!, to: end!, upTo: document.firstChild!) 156 | XCTAssertEqual(copyOfStructure?.serialized(pretty: true, indentation: " "), #""" 157 |
158 |

Ja,

159 |

das ist so 1 %, echt.

160 |
161 | """#) 162 | } 163 | 164 | func testHTMLOutput0() throws { 165 | let source = """ 166 |

The title

1st paragraph

2nd paragraph

179 |
180 | """ 181 | ) 182 | } 183 | 184 | func testHTMLOutput1() throws { 185 | let source = """ 186 |
187 | """ 188 | XCTAssertEqual( 189 | try parseXML(fromText: source).serialized(usingProductionTemplate: HTMLProductionTemplate()), 190 | """ 191 | 192 |
193 | """ 194 | ) 195 | } 196 | 197 | func testHTMLOutput2() throws { 198 | let source = """ 199 |
200 | """ 201 | XCTAssertEqual( 202 | try parseXML(fromText: source).serialized(usingProductionTemplate: HTMLProductionTemplate()), 203 | """ 204 | 205 |
206 | """ 207 | ) 208 | } 209 | 210 | func testHTMLOutput3() throws { 211 | let source = """ 212 |

213 | """ 214 | XCTAssertEqual( 215 | try parseXML(fromText: source).serialized(usingProductionTemplate: HTMLProductionTemplate()), 216 | """ 217 | 218 |
219 | 220 |

221 |
222 | """ 223 | ) 224 | } 225 | 226 | func testHTMLOutput4() throws { 227 | let source = """ 228 |

229 | """ 230 | XCTAssertEqual( 231 | try parseXML(fromText: source).serialized( 232 | usingProductionTemplate: HTMLProductionTemplate() 233 | ), 234 | """ 235 | 236 |
237 | 238 | 239 |

240 |
241 | """ 242 | ) 243 | } 244 | 245 | func testHTMLOutput5() throws { 246 | let source = """ 247 |

248 | """ 249 | XCTAssertEqual( 250 | try parseXML(fromText: source).serialized( 251 | usingProductionTemplate: HTMLProductionTemplate( 252 | suppressUncessaryPrettyPrintAtAnchors: true 253 | ) 254 | ), 255 | """ 256 | 257 |
258 |

259 |
260 | """ 261 | ) 262 | } 263 | 264 | func testHTMLOutput6() throws { 265 | let source = """ 266 |
267 | """ 268 | XCTAssertEqual( 269 | try parseXML(fromText: source).serialized( 270 | usingProductionTemplate: HTMLProductionTemplate( 271 | suppressUncessaryPrettyPrintAtAnchors: true 272 | ) 273 | ), 274 | """ 275 | 276 |
277 | """ 278 | ) 279 | } 280 | 281 | func testHTMLOutput7() throws { 282 | let source = """ 283 |
Hello
world
!
284 | """ 285 | XCTAssertEqual( 286 | try parseXML(fromText: source).serialized( 287 | usingProductionTemplate: HTMLProductionTemplate( 288 | suppressUncessaryPrettyPrintAtAnchors: true 289 | ) 290 | ), 291 | """ 292 | 293 |
Hello 294 |
world
295 |
!
296 |
297 | """ 298 | ) 299 | } 300 | 301 | func testHTMLOutput8() throws { 302 | let source = """ 303 |
1
304 | """ 305 | XCTAssertEqual( 306 | try parseXML(fromText: source).serialized( 307 | usingProductionTemplate: HTMLProductionTemplate( 308 | suppressUncessaryPrettyPrintAtAnchors: true 309 | ) 310 | ), 311 | """ 312 | 313 |
314 |
315 | 316 | 317 | 318 | 319 |
1
320 |
321 |
322 | """ 323 | ) 324 | } 325 | 326 | func testHTMLOutput9() throws { 327 | let source = """ 328 |
Hello
1
329 | """ 330 | XCTAssertEqual( 331 | try parseXML(fromText: source).serialized( 332 | usingProductionTemplate: HTMLProductionTemplate( 333 | suppressUncessaryPrettyPrintAtAnchors: true 334 | ) 335 | ), 336 | """ 337 | 338 |
Hello 339 |
340 | 341 | 342 | 343 | 344 |
1
345 |
346 |
347 | """ 348 | ) 349 | } 350 | 351 | func testHTMLOutput10() throws { 352 | let source = """ 353 |
leading span of the wrapper block
a block within the block
followed by text an anchorand a text and some more text and another span
and another div
354 | """ 355 | XCTAssertEqual( 356 | try parseXML(fromText: source).serialized( 357 | usingProductionTemplate: HTMLProductionTemplate( 358 | suppressUncessaryPrettyPrintAtAnchors: true 359 | ) 360 | ), 361 | """ 362 | 363 |
leading span of the wrapper block 364 |
a block within the block
followed by text an anchorand a text and some more text and another span 365 |
and another div
366 |
367 | """ 368 | ) 369 | } 370 | 371 | } 372 | 373 | /// An error with a description. 374 | /// 375 | /// When printing such an error, its descrition is printed. 376 | public struct ErrorWithDescription: LocalizedError, CustomStringConvertible { 377 | 378 | private let message: String 379 | 380 | public init(_ message: String?) { 381 | self.message = message ?? "(unkown error))" 382 | } 383 | 384 | public var description: String { message } 385 | 386 | public var errorDescription: String? { message } 387 | } 388 | 389 | extension String { 390 | 391 | /// Replace all text matching a certain certain regular expression. 392 | /// 393 | /// Use lookarounds (e.g. lookaheads) to avoid having to apply your regular expression several times. 394 | func replacing(regex: String, with theReplacement: String) -> String { 395 | var result = self 396 | autoreleasepool { 397 | result = self.replacingOccurrences(of: regex, with: theReplacement, options: .regularExpression, range: nil) 398 | } 399 | return result 400 | } 401 | 402 | /// Trimming all whitespace. 403 | func trimming() -> String { 404 | return self.self.trimmingLeft().trimmingRight() 405 | } 406 | 407 | /// Trimming left whitespace. 408 | func trimmingLeft() -> String { 409 | guard let index = firstIndex(where: { !CharacterSet(charactersIn: String($0)).isSubset(of: .whitespacesAndNewlines) }) else { 410 | return "" 411 | } 412 | return String(self[index...]) 413 | } 414 | 415 | /// Trimming right whitespace. 416 | func trimmingRight() -> String { 417 | guard let index = lastIndex(where: { !CharacterSet(charactersIn: String($0)).isSubset(of: .whitespacesAndNewlines) }) else { 418 | return "" 419 | } 420 | return String(self[...index]) 421 | } 422 | 423 | } 424 | -------------------------------------------------------------------------------- /tutorial.md: -------------------------------------------------------------------------------- 1 | TODO 2 | --------------------------------------------------------------------------------