Skip to content

Commit

Permalink
Parity: Parity: XMLElement: XML Namespaces (final part)
Browse files Browse the repository at this point in the history
Fixes what `NSUnimplemented()` invocations remained in FoundationXML:

 - Finish implementing namespace accessors.
 - Finish implementing namespace utility methods for working with qnames;
 - Note that XQuery is unavailable in s-c-f.
  • Loading branch information
millenomi committed Aug 23, 2019
1 parent 0035432 commit 66f9fb2
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 24 deletions.
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

0 comments on commit 66f9fb2

Please sign in to comment.