XMLDocument.swift
1 // This source file is part of the Swift.org open source project 2 // 3 // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors 4 // Licensed under Apache License v2.0 with Runtime Library Exception 5 // 6 // See http://swift.org/LICENSE.txt for license information 7 // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 8 // 9 10 #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 11 import SwiftFoundation 12 #else 13 import Foundation 14 #endif 15 @_implementationOnly import CoreFoundation 16 @_implementationOnly import CFXMLInterface 17 18 // Input options 19 // NSXMLNodeOptionsNone 20 // NSXMLNodePreserveAll 21 // NSXMLNodePreserveNamespaceOrder 22 // NSXMLNodePreserveAttributeOrder 23 // NSXMLNodePreserveEntities 24 // NSXMLNodePreservePrefixes 25 // NSXMLNodePreserveCDATA 26 // NSXMLNodePreserveEmptyElements 27 // NSXMLNodePreserveQuotes 28 // NSXMLNodePreserveWhitespace 29 // NSXMLNodeLoadExternalEntities 30 // NSXMLNodeLoadExternalEntitiesSameOriginOnly 31 32 // NSXMLDocumentTidyHTML 33 // NSXMLDocumentTidyXML 34 35 // NSXMLDocumentValidate 36 37 // Output options 38 // NSXMLNodePrettyPrint 39 // NSXMLDocumentIncludeContentTypeDeclaration 40 41 extension XMLDocument { 42 43 /*! 44 @typedef XMLDocument.ContentKind 45 @abstract Define what type of document this is. 46 @constant XMLDocument.ContentKind.xml The default document type 47 @constant XMLDocument.ContentKind.xhtml Set if XMLNode.Options.documentTidyHTML is set and HTML is detected 48 @constant XMLDocument.ContentKind.html Outputs empty tags without a close tag, eg <br> 49 @constant XMLDocument.ContentKind.text Output the string value of the document 50 */ 51 public enum ContentKind : UInt { 52 53 case xml 54 case xhtml 55 case html 56 case text 57 } 58 } 59 60 /*! 61 @class XMLDocument 62 @abstract An XML Document 63 @discussion Note: if the application of a method would result in more than one element in the children array, an exception is thrown. Trying to add a document, namespace, attribute, or node with a parent also throws an exception. To add a node with a parent first detach or create a copy of it. 64 */ 65 open class XMLDocument : XMLNode { 66 private var _xmlDoc: _CFXMLDocPtr { 67 return _CFXMLDocPtr(_xmlNode) 68 } 69 70 public convenience init() { 71 self.init(rootElement: nil) 72 } 73 74 /*! 75 @method initWithXMLString:options:error: 76 @abstract Returns a document created from either XML or HTML, if the HTMLTidy option is set. Parse errors are returned in <tt>error</tt>. 77 */ 78 public convenience init(xmlString string: String, options mask: XMLNode.Options = []) throws { 79 setupXMLParsing() 80 guard let data = string.data(using: .utf8) else { 81 // TODO: Throw an error 82 fatalError("String: '\(string)' could not be converted to NSData using UTF-8 encoding") 83 } 84 85 try self.init(data: data, options: mask) 86 } 87 88 /*! 89 @method initWithContentsOfURL:options:error: 90 @abstract Returns a document created from the contents of an XML or HTML URL. Connection problems such as 404, parse errors are returned in <tt>error</tt>. 91 */ 92 public convenience init(contentsOf url: URL, options mask: XMLNode.Options = []) throws { 93 setupXMLParsing() 94 let data = try Data(contentsOf: url, options: .mappedIfSafe) 95 96 try self.init(data: data, options: mask) 97 } 98 99 /*! 100 @method initWithData:options:error: 101 @abstract Returns a document created from data. Parse errors are returned in <tt>error</tt>. 102 */ 103 public init(data: Data, options mask: XMLNode.Options = []) throws { 104 setupXMLParsing() 105 let docPtr = _CFXMLDocPtrFromDataWithOptions(unsafeBitCast(data as NSData, to: CFData.self), UInt32(mask.rawValue)) 106 super.init(ptr: _CFXMLNodePtr(docPtr)) 107 108 if mask.contains(.documentValidate) { 109 try validate() 110 } 111 } 112 113 /*! 114 @method initWithRootElement: 115 @abstract Returns a document with a single child, the root element. 116 */ 117 public init(rootElement element: XMLElement?) { 118 setupXMLParsing() 119 precondition(element?.parent == nil) 120 121 super.init(kind: .document, options: []) 122 if let element = element { 123 _CFXMLDocSetRootElement(_xmlDoc, element._xmlNode) 124 _childNodes.insert(element) 125 } 126 } 127 128 /*! 129 @method characterEncoding 130 @abstract Sets the character encoding to an IANA type. 131 */ 132 open var characterEncoding: String? { 133 get { 134 let returned = _CFXMLDocCopyCharacterEncoding(_xmlDoc) 135 return returned == nil ? nil : unsafeBitCast(returned!, to: NSString.self) as String 136 } 137 set { 138 if let value = newValue { 139 _CFXMLDocSetCharacterEncoding(_xmlDoc, value) 140 } else { 141 _CFXMLDocSetCharacterEncoding(_xmlDoc, nil) 142 } 143 } 144 } 145 146 /*! 147 @method version 148 @abstract Sets the XML version. Should be 1.0 or 1.1. 149 */ 150 open var version: String? { 151 get { 152 let returned = _CFXMLDocCopyVersion(_xmlDoc) 153 return returned == nil ? nil : unsafeBitCast(returned!, to: NSString.self) as String 154 } 155 set { 156 if let value = newValue { 157 precondition(value == "1.0" || value == "1.1") 158 _CFXMLDocSetVersion(_xmlDoc, value) 159 } else { 160 _CFXMLDocSetVersion(_xmlDoc, nil) 161 } 162 } 163 } 164 165 /*! 166 @method standalone 167 @abstract Set whether this document depends on an external DTD. If this option is set the standalone declaration will appear on output. 168 */ 169 open var isStandalone: Bool { 170 get { 171 return _CFXMLDocStandalone(_xmlDoc) 172 } 173 set { 174 _CFXMLDocSetStandalone(_xmlDoc, newValue) 175 } 176 }//primitive 177 178 /*! 179 @method documentContentKind 180 @abstract The kind of document. 181 */ 182 open var documentContentKind: XMLDocument.ContentKind { 183 get { 184 let properties = _CFXMLDocProperties(_xmlDoc) 185 186 if properties & Int32(_kCFXMLDocTypeHTML) != 0 { 187 return .html 188 } 189 190 return .xml 191 } 192 193 set { 194 var properties = _CFXMLDocProperties(_xmlDoc) 195 switch newValue { 196 case .html: 197 properties |= Int32(_kCFXMLDocTypeHTML) 198 199 default: 200 properties &= ~Int32(_kCFXMLDocTypeHTML) 201 } 202 203 _CFXMLDocSetProperties(_xmlDoc, properties) 204 } 205 }//primitive 206 207 /*! 208 @method MIMEType 209 @abstract Set the MIME type, eg text/xml. 210 */ 211 open var mimeType: String? 212 213 /*! 214 @method DTD 215 @abstract Set the associated DTD. This DTD will be output with the document. 216 */ 217 /*@NSCopying*/ open var dtd: XMLDTD? { 218 get { 219 return XMLDTD._objectNodeForNode(_CFXMLDocDTD(_xmlDoc)!) 220 } 221 set { 222 if let currDTD = _CFXMLDocDTD(_xmlDoc) { 223 if _CFXMLNodeGetPrivateData(currDTD) != nil { 224 let DTD = XMLDTD._objectNodeForNode(currDTD) 225 _CFXMLUnlinkNode(currDTD) 226 _childNodes.remove(DTD) 227 } else { 228 _CFXMLFreeDTD(currDTD) 229 } 230 } 231 232 if let value = newValue { 233 guard let dtd = value.copy() as? XMLDTD else { 234 fatalError("Failed to copy DTD") 235 } 236 _CFXMLDocSetDTD(_xmlDoc, dtd._xmlDTD) 237 _childNodes.insert(dtd) 238 } else { 239 _CFXMLDocSetDTD(_xmlDoc, nil) 240 } 241 } 242 }//primitive 243 244 /*! 245 @method setRootElement: 246 @abstract Set the root element. Removes all other children including comments and processing-instructions. 247 */ 248 open func setRootElement(_ root: XMLElement) { 249 precondition(root.parent == nil) 250 251 for child in _childNodes { 252 child.detach() 253 } 254 255 _CFXMLDocSetRootElement(_xmlDoc, root._xmlNode) 256 _childNodes.insert(root) 257 } 258 259 /*! 260 @method rootElement 261 @abstract The root element. 262 */ 263 open func rootElement() -> XMLElement? { 264 guard let rootPtr = _CFXMLDocRootElement(_xmlDoc) else { 265 return nil 266 } 267 268 return XMLNode._objectNodeForNode(rootPtr) as? XMLElement 269 } 270 271 open override var childCount: Int { 272 return _CFXMLNodeGetElementChildCount(_xmlNode) 273 } 274 275 /*! 276 @method insertChild:atIndex: 277 @abstract Inserts a child at a particular index. 278 */ 279 open func insertChild(_ child: XMLNode, at index: Int) { 280 _insertChild(child, atIndex: index) 281 } 282 283 /*! 284 @method insertChildren:atIndex: 285 @abstract Insert several children at a particular index. 286 */ 287 open func insertChildren(_ children: [XMLNode], at index: Int) { 288 _insertChildren(children, atIndex: index) 289 } 290 291 /*! 292 @method removeChildAtIndex:atIndex: 293 @abstract Removes a child at a particular index. 294 */ 295 open func removeChild(at index: Int) { 296 _removeChildAtIndex(index) 297 } 298 299 /*! 300 @method setChildren: 301 @abstract Removes all existing children and replaces them with the new children. Set children to nil to simply remove all children. 302 */ 303 open func setChildren(_ children: [XMLNode]?) { 304 _setChildren(children) 305 } 306 307 /*! 308 @method addChild: 309 @abstract Adds a child to the end of the existing children. 310 */ 311 open func addChild(_ child: XMLNode) { 312 _addChild(child) 313 } 314 315 /*! 316 @method replaceChildAtIndex:withNode: 317 @abstract Replaces a child at a particular index with another child. 318 */ 319 open func replaceChild(at index: Int, with node: XMLNode) { 320 _replaceChildAtIndex(index, withNode: node) 321 } 322 323 /*! 324 @method XMLData 325 @abstract Invokes XMLDataWithOptions with XMLNode.Options.none. 326 */ 327 /*@NSCopying*/ open var xmlData: Data { return xmlData() } 328 329 /*! 330 @method XMLDataWithOptions: 331 @abstract The representation of this node as it would appear in an XML document, encoded based on characterEncoding. 332 */ 333 open func xmlData(options: XMLNode.Options = []) -> Data { 334 let string = xmlString(options: options) 335 // TODO: support encodings other than UTF-8 336 337 return string.data(using: .utf8) ?? Data() 338 } 339 340 /*! 341 @method objectByApplyingXSLT:arguments:error: 342 @abstract Applies XSLT with arguments (NSString key/value pairs) to this document, returning a new document. 343 */ 344 @available(*, unavailable, message: "XSLT application is not currently supported in swift-corelibs-foundation") 345 open func object(byApplyingXSLT xslt: Data, arguments: [String : String]?) throws -> Any { 346 NSUnimplemented() 347 } 348 349 /*! 350 @method objectByApplyingXSLTString:arguments:error: 351 @abstract Applies XSLT as expressed by a string with arguments (NSString key/value pairs) to this document, returning a new document. 352 */ 353 @available(*, unavailable, message: "XSLT application is not currently supported in swift-corelibs-foundation") 354 open func object(byApplyingXSLTString xslt: String, arguments: [String : String]?) throws -> Any { 355 NSUnimplemented() 356 } 357 358 /*! 359 @method objectByApplyingXSLTAtURL:arguments:error: 360 @abstract Applies the XSLT at a URL with arguments (NSString key/value pairs) to this document, returning a new document. Error may contain a connection error from the URL. 361 */ 362 @available(*, unavailable, message: "XSLT application is not currently supported in swift-corelibs-foundation") 363 open func objectByApplyingXSLT(at xsltURL: URL, arguments argument: [String : String]?) throws -> Any { 364 NSUnimplemented() 365 } 366 367 open func validate() throws { 368 var unmanagedError: Unmanaged<CFError>? = nil 369 let result = _CFXMLDocValidate(_xmlDoc, &unmanagedError) 370 if !result, 371 let unmanagedError = unmanagedError { 372 let error = unmanagedError.takeRetainedValue() 373 throw _CFErrorSPIForFoundationXMLUseOnly(unsafelyAssumingIsCFError: error)._nsObject 374 } 375 } 376 377 internal override class func _objectNodeForNode(_ node: _CFXMLNodePtr) -> XMLDocument { 378 precondition(_CFXMLNodeGetType(node) == _kCFXMLTypeDocument) 379 380 if let privateData = _CFXMLNodeGetPrivateData(node) { 381 return unsafeBitCast(privateData, to: XMLDocument.self) 382 } 383 384 return XMLDocument(ptr: node) 385 } 386 387 internal override init(ptr: _CFXMLNodePtr) { 388 super.init(ptr: ptr) 389 } 390 }