Skip to content

Parity: XMLElement: XML Namespaces (final part) #2487

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CoreFoundation/Parsing.subproj/CFXMLInterface.c
Original file line number Diff line number Diff line change
Expand Up @@ -1570,3 +1570,18 @@ void _CFXMLFreeDTD(_CFXMLDTDPtr dtd) {
void _CFXMLFreeProperty(_CFXMLNodePtr prop) {
xmlFreeProp(prop);
}

const char *_CFXMLSplitQualifiedName(const char *_Nonnull qname) {
int len = 0;
return (const char *)xmlSplitQName3((const xmlChar *)qname, &len);
}

bool _CFXMLGetLengthOfPrefixInQualifiedName(const char *_Nonnull qname, size_t *length) {
int len = 0;
if (xmlSplitQName3((const xmlChar *)qname, &len) != NULL) {
*length = len;
return true;
} else {
return false;
}
}
4 changes: 4 additions & 0 deletions CoreFoundation/Parsing.subproj/CFXMLInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <CoreFoundation/CoreFoundation.h>
#include <stdint.h>
#include <sys/types.h>
#include <stdbool.h>

CF_IMPLICIT_BRIDGING_ENABLED
CF_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -255,6 +256,9 @@ void _CFXMLFreeDocument(_CFXMLDocPtr doc);
void _CFXMLFreeDTD(_CFXMLDTDPtr dtd);
void _CFXMLFreeProperty(_CFXMLNodePtr prop);

const char *_Nullable _CFXMLSplitQualifiedName(const char *_Nonnull qname);
bool _CFXMLGetLengthOfPrefixInQualifiedName(const char *_Nonnull qname, size_t *_Nonnull length);

// Bridging

struct _NSXMLParserBridge {
Expand Down
43 changes: 40 additions & 3 deletions Foundation/XMLElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -252,23 +252,60 @@ open class XMLElement: XMLNode {
@abstract Returns the namespace matching this prefix.
*/
open func namespace(forPrefix name: String) -> XMLNode? {
NSUnimplemented()
return (namespaces ?? []).first { $0.name == name }
}

/*!
@method resolveNamespaceForName:
@abstract Returns the namespace who matches the prefix of the name given. Looks in the entire namespace chain.
*/
open func resolveNamespace(forName name: String) -> XMLNode? {
NSUnimplemented()
// Legitimate question: why not use XMLNode's methods?
// Because Darwin does the split manually here, and we want to match that rather than asking libxml2.
let prefix: String
if let colon = name.firstIndex(of: ":") {
prefix = String(name[name.startIndex ..< colon])
} else {
prefix = ""
}

var current: XMLElement? = self
while let examined = current {
if let namespace = examined.namespace(forPrefix: prefix) {
return namespace
}

current = examined.parent as? XMLElement
guard current?.kind == .element else { break }
}

if !prefix.isEmpty {
return XMLNode.predefinedNamespace(forPrefix: prefix)
}

return nil
}

/*!
@method resolvePrefixForNamespaceURI:
@abstract Returns the URI of this prefix. Looks in the entire namespace chain.
*/
open func resolvePrefix(forNamespaceURI namespaceURI: String) -> String? {
NSUnimplemented()
var current: XMLElement? = self
while let examined = current {
if let namespace = (examined.namespaces ?? []).first(where: { $0.stringValue == namespaceURI }) {
return namespace.name
}

current = examined.parent as? XMLElement
guard current?.kind == .element else { break }
}

if let namespace = XMLNode._defaultNamespacesByURI[namespaceURI] {
return namespace.name
}

return nil
}

/*!
Expand Down
95 changes: 74 additions & 21 deletions Foundation/XMLNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -718,36 +718,55 @@ open class XMLNode: NSObject, NSCopying {
@abstract Returns the local name bar in foo:bar.
*/
open class func localName(forName name: String) -> String {
// return name.withCString {
// var length: Int32 = 0
// let result = xmlSplitQName3(UnsafePointer<xmlChar>($0), &length)
// return String.fromCString(UnsafePointer<CChar>(result)) ?? ""
// }
NSUnimplemented()
if let localName = _CFXMLSplitQualifiedName(name) {
return String(cString: localName)
} else {
return name
}
}

/*!
@method localNameForName:
@abstract Returns the prefix foo in the name foo:bar.
*/
open class func prefix(forName name: String) -> String? {
// return name.withCString {
// var result: UnsafeMutablePointer<xmlChar> = nil
// let unused = xmlSplitQName2(UnsafePointer<xmlChar>($0), &result)
// defer {
// xmlFree(result)
// xmlFree(UnsafeMutablePointer<xmlChar>(unused))
// }
// return String.fromCString(UnsafePointer<CChar>(result))
// }
NSUnimplemented()
var size: size_t = 0
if _CFXMLGetLengthOfPrefixInQualifiedName(name, &size) {
return name.withCString {
$0.withMemoryRebound(to: UInt8.self, capacity: size) {
return String(decoding: UnsafeBufferPointer(start: $0, count: size), as: UTF8.self)
}
}
} else {
return nil
}
}

/*!
@method predefinedNamespaceForPrefix:
@abstract Returns the namespace belonging to one of the predefined namespaces xml, xs, or xsi
*/
open class func predefinedNamespace(forPrefix name: String) -> XMLNode? { NSUnimplemented() }
private static func defaultNamespace(prefix: String, value: String) -> XMLNode {
let node = XMLNode(kind: .namespace)
node.name = prefix
node.objectValue = value
return node
}
private static let _defaultNamespaces: [XMLNode] = [
XMLNode.defaultNamespace(prefix: "xml", value: "http://www.w3.org/XML/1998/namespace"),
XMLNode.defaultNamespace(prefix: "xml", value: "http://www.w3.org/2001/XMLSchema"),
XMLNode.defaultNamespace(prefix: "xml", value: "http://www.w3.org/2001/XMLSchema-instance"),
]

internal static let _defaultNamespacesByPrefix: [String: XMLNode] =
Dictionary(XMLNode._defaultNamespaces.map { ($0.name!, $0) }, uniquingKeysWith: { old, _ in old })

internal static let _defaultNamespacesByURI: [String: XMLNode] =
Dictionary(XMLNode._defaultNamespaces.map { ($0.stringValue!, $0) }, uniquingKeysWith: { old, _ in old })

open class func predefinedNamespace(forPrefix name: String) -> XMLNode? {
return XMLNode._defaultNamespacesByPrefix[name]
}

/*!
@method description
Expand Down Expand Up @@ -777,7 +796,39 @@ open class XMLNode: NSObject, NSCopying {
@method canonicalXMLStringPreservingComments:
@abstract W3 canonical form (http://www.w3.org/TR/xml-c14n). The input option NSXMLNodePreserveWhitespace should be set for true canonical form.
*/
open func canonicalXMLStringPreservingComments(_ comments: Bool) -> String { NSUnimplemented() }
open func canonicalXMLStringPreservingComments(_ comments: Bool) -> String {
var result = ""
switch kind {
case .text:
let scanner = Scanner(string: self.stringValue ?? "")
let toReplace = CharacterSet(charactersIn: "&<>\r")
while let string = scanner.scanUpToCharacters(from: toReplace) {
result += string
if scanner.scanString("&") != nil {
result += "&amp;"
} else if scanner.scanString("<") != nil {
result += "&lt;"
} else if scanner.scanString(">") != nil {
result += "&gt;"
} else if scanner.scanString("\r") != nil {
result += "&#xD;"
} else {
fatalError("We scanned up to one of the characters to replace, but couldn't find it when we went to consume it.")
}
}
result += scanner.string[scanner.currentIndex...]


case .comment:
if comments {
result = "<!--\(stringValue ?? "")-->"
}

default: break
}

return result
}

/*!
@method nodesForXPath:error:
Expand All @@ -786,7 +837,7 @@ open class XMLNode: NSObject, NSCopying {
*/
open func nodes(forXPath xpath: String) throws -> [XMLNode] {
guard let nodes = _CFXMLNodesForXPath(_xmlNode, xpath) else {
NSUnimplemented()
return []
}

var result: [XMLNode] = []
Expand All @@ -803,12 +854,14 @@ open class XMLNode: NSObject, NSCopying {
@abstract Returns the objects resulting from applying an XQuery to this node using the node as the context item ("."). Constants are a name-value dictionary for constants declared "external" in the query. normalizeAdjacentTextNodesPreservingCDATA:NO should be called if there are adjacent text nodes since they are not allowed under the XPath/XQuery Data Model.
@returns An array whose elements are kinds of NSArray, NSData, NSDate, NSNumber, NSString, NSURL, or NSXMLNode.
*/
@available(*, unavailable, message: "XQuery is not available in swift-corelibs-foundation")
open func objects(forXQuery xquery: String, constants: [String : Any]?) throws -> [Any] {
NSUnimplemented()
NSUnsupported()
}

@available(*, unavailable, message: "XQuery is not available in swift-corelibs-foundation")
open func objects(forXQuery xquery: String) throws -> [Any] {
NSUnimplemented()
NSUnsupported()
}

internal var _childNodes: Set<XMLNode> = []
Expand Down
7 changes: 7 additions & 0 deletions Foundation/XMLParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,13 @@ internal func NSUnimplemented(_ fn: String = #function, file: StaticString = #fi
fatalError("\(fn) is not yet implemented", file: file, line: line)
}

internal func NSUnsupported(_ fn: String = #function, file: StaticString = #file, line: UInt = #line) -> Never {
#if os(Android)
NSLog("\(fn) is not supported on this platform. \(file):\(line)")
#endif
fatalError("\(fn) is not supported on this platform", file: file, line: line)
}

extension NSObject {
func withUnretainedReference<T, R>(_ work: (UnsafePointer<T>) -> R) -> R {
let selfPtr = Unmanaged.passUnretained(self).toOpaque().assumingMemoryBound(to: T.self)
Expand Down