Skip to content

Commit bd2b4e0

Browse files
authoredAug 23, 2019
Merge pull request swiftlang#2487 from millenomi/xmlelement-namespaces
Parity: XMLElement: XML Namespaces (final part)
2 parents 0035432 + 66f9fb2 commit bd2b4e0

File tree

5 files changed

+140
-24
lines changed

5 files changed

+140
-24
lines changed
 

‎CoreFoundation/Parsing.subproj/CFXMLInterface.c

+15
Original file line numberDiff line numberDiff line change
@@ -1570,3 +1570,18 @@ void _CFXMLFreeDTD(_CFXMLDTDPtr dtd) {
15701570
void _CFXMLFreeProperty(_CFXMLNodePtr prop) {
15711571
xmlFreeProp(prop);
15721572
}
1573+
1574+
const char *_CFXMLSplitQualifiedName(const char *_Nonnull qname) {
1575+
int len = 0;
1576+
return (const char *)xmlSplitQName3((const xmlChar *)qname, &len);
1577+
}
1578+
1579+
bool _CFXMLGetLengthOfPrefixInQualifiedName(const char *_Nonnull qname, size_t *length) {
1580+
int len = 0;
1581+
if (xmlSplitQName3((const xmlChar *)qname, &len) != NULL) {
1582+
*length = len;
1583+
return true;
1584+
} else {
1585+
return false;
1586+
}
1587+
}

‎CoreFoundation/Parsing.subproj/CFXMLInterface.h

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <CoreFoundation/CoreFoundation.h>
1818
#include <stdint.h>
1919
#include <sys/types.h>
20+
#include <stdbool.h>
2021

2122
CF_IMPLICIT_BRIDGING_ENABLED
2223
CF_ASSUME_NONNULL_BEGIN
@@ -255,6 +256,9 @@ void _CFXMLFreeDocument(_CFXMLDocPtr doc);
255256
void _CFXMLFreeDTD(_CFXMLDTDPtr dtd);
256257
void _CFXMLFreeProperty(_CFXMLNodePtr prop);
257258

259+
const char *_Nullable _CFXMLSplitQualifiedName(const char *_Nonnull qname);
260+
bool _CFXMLGetLengthOfPrefixInQualifiedName(const char *_Nonnull qname, size_t *_Nonnull length);
261+
258262
// Bridging
259263

260264
struct _NSXMLParserBridge {

‎Foundation/XMLElement.swift

+40-3
Original file line numberDiff line numberDiff line change
@@ -252,23 +252,60 @@ open class XMLElement: XMLNode {
252252
@abstract Returns the namespace matching this prefix.
253253
*/
254254
open func namespace(forPrefix name: String) -> XMLNode? {
255-
NSUnimplemented()
255+
return (namespaces ?? []).first { $0.name == name }
256256
}
257257

258258
/*!
259259
@method resolveNamespaceForName:
260260
@abstract Returns the namespace who matches the prefix of the name given. Looks in the entire namespace chain.
261261
*/
262262
open func resolveNamespace(forName name: String) -> XMLNode? {
263-
NSUnimplemented()
263+
// Legitimate question: why not use XMLNode's methods?
264+
// Because Darwin does the split manually here, and we want to match that rather than asking libxml2.
265+
let prefix: String
266+
if let colon = name.firstIndex(of: ":") {
267+
prefix = String(name[name.startIndex ..< colon])
268+
} else {
269+
prefix = ""
270+
}
271+
272+
var current: XMLElement? = self
273+
while let examined = current {
274+
if let namespace = examined.namespace(forPrefix: prefix) {
275+
return namespace
276+
}
277+
278+
current = examined.parent as? XMLElement
279+
guard current?.kind == .element else { break }
280+
}
281+
282+
if !prefix.isEmpty {
283+
return XMLNode.predefinedNamespace(forPrefix: prefix)
284+
}
285+
286+
return nil
264287
}
265288

266289
/*!
267290
@method resolvePrefixForNamespaceURI:
268291
@abstract Returns the URI of this prefix. Looks in the entire namespace chain.
269292
*/
270293
open func resolvePrefix(forNamespaceURI namespaceURI: String) -> String? {
271-
NSUnimplemented()
294+
var current: XMLElement? = self
295+
while let examined = current {
296+
if let namespace = (examined.namespaces ?? []).first(where: { $0.stringValue == namespaceURI }) {
297+
return namespace.name
298+
}
299+
300+
current = examined.parent as? XMLElement
301+
guard current?.kind == .element else { break }
302+
}
303+
304+
if let namespace = XMLNode._defaultNamespacesByURI[namespaceURI] {
305+
return namespace.name
306+
}
307+
308+
return nil
272309
}
273310

274311
/*!

‎Foundation/XMLNode.swift

+74-21
Original file line numberDiff line numberDiff line change
@@ -718,36 +718,55 @@ open class XMLNode: NSObject, NSCopying {
718718
@abstract Returns the local name bar in foo:bar.
719719
*/
720720
open class func localName(forName name: String) -> String {
721-
// return name.withCString {
722-
// var length: Int32 = 0
723-
// let result = xmlSplitQName3(UnsafePointer<xmlChar>($0), &length)
724-
// return String.fromCString(UnsafePointer<CChar>(result)) ?? ""
725-
// }
726-
NSUnimplemented()
721+
if let localName = _CFXMLSplitQualifiedName(name) {
722+
return String(cString: localName)
723+
} else {
724+
return name
725+
}
727726
}
728727

729728
/*!
730729
@method localNameForName:
731730
@abstract Returns the prefix foo in the name foo:bar.
732731
*/
733732
open class func prefix(forName name: String) -> String? {
734-
// return name.withCString {
735-
// var result: UnsafeMutablePointer<xmlChar> = nil
736-
// let unused = xmlSplitQName2(UnsafePointer<xmlChar>($0), &result)
737-
// defer {
738-
// xmlFree(result)
739-
// xmlFree(UnsafeMutablePointer<xmlChar>(unused))
740-
// }
741-
// return String.fromCString(UnsafePointer<CChar>(result))
742-
// }
743-
NSUnimplemented()
733+
var size: size_t = 0
734+
if _CFXMLGetLengthOfPrefixInQualifiedName(name, &size) {
735+
return name.withCString {
736+
$0.withMemoryRebound(to: UInt8.self, capacity: size) {
737+
return String(decoding: UnsafeBufferPointer(start: $0, count: size), as: UTF8.self)
738+
}
739+
}
740+
} else {
741+
return nil
742+
}
744743
}
745744

746745
/*!
747746
@method predefinedNamespaceForPrefix:
748747
@abstract Returns the namespace belonging to one of the predefined namespaces xml, xs, or xsi
749748
*/
750-
open class func predefinedNamespace(forPrefix name: String) -> XMLNode? { NSUnimplemented() }
749+
private static func defaultNamespace(prefix: String, value: String) -> XMLNode {
750+
let node = XMLNode(kind: .namespace)
751+
node.name = prefix
752+
node.objectValue = value
753+
return node
754+
}
755+
private static let _defaultNamespaces: [XMLNode] = [
756+
XMLNode.defaultNamespace(prefix: "xml", value: "http://www.w3.org/XML/1998/namespace"),
757+
XMLNode.defaultNamespace(prefix: "xml", value: "http://www.w3.org/2001/XMLSchema"),
758+
XMLNode.defaultNamespace(prefix: "xml", value: "http://www.w3.org/2001/XMLSchema-instance"),
759+
]
760+
761+
internal static let _defaultNamespacesByPrefix: [String: XMLNode] =
762+
Dictionary(XMLNode._defaultNamespaces.map { ($0.name!, $0) }, uniquingKeysWith: { old, _ in old })
763+
764+
internal static let _defaultNamespacesByURI: [String: XMLNode] =
765+
Dictionary(XMLNode._defaultNamespaces.map { ($0.stringValue!, $0) }, uniquingKeysWith: { old, _ in old })
766+
767+
open class func predefinedNamespace(forPrefix name: String) -> XMLNode? {
768+
return XMLNode._defaultNamespacesByPrefix[name]
769+
}
751770

752771
/*!
753772
@method description
@@ -777,7 +796,39 @@ open class XMLNode: NSObject, NSCopying {
777796
@method canonicalXMLStringPreservingComments:
778797
@abstract W3 canonical form (http://www.w3.org/TR/xml-c14n). The input option NSXMLNodePreserveWhitespace should be set for true canonical form.
779798
*/
780-
open func canonicalXMLStringPreservingComments(_ comments: Bool) -> String { NSUnimplemented() }
799+
open func canonicalXMLStringPreservingComments(_ comments: Bool) -> String {
800+
var result = ""
801+
switch kind {
802+
case .text:
803+
let scanner = Scanner(string: self.stringValue ?? "")
804+
let toReplace = CharacterSet(charactersIn: "&<>\r")
805+
while let string = scanner.scanUpToCharacters(from: toReplace) {
806+
result += string
807+
if scanner.scanString("&") != nil {
808+
result += "&amp;"
809+
} else if scanner.scanString("<") != nil {
810+
result += "&lt;"
811+
} else if scanner.scanString(">") != nil {
812+
result += "&gt;"
813+
} else if scanner.scanString("\r") != nil {
814+
result += "&#xD;"
815+
} else {
816+
fatalError("We scanned up to one of the characters to replace, but couldn't find it when we went to consume it.")
817+
}
818+
}
819+
result += scanner.string[scanner.currentIndex...]
820+
821+
822+
case .comment:
823+
if comments {
824+
result = "<!--\(stringValue ?? "")-->"
825+
}
826+
827+
default: break
828+
}
829+
830+
return result
831+
}
781832

782833
/*!
783834
@method nodesForXPath:error:
@@ -786,7 +837,7 @@ open class XMLNode: NSObject, NSCopying {
786837
*/
787838
open func nodes(forXPath xpath: String) throws -> [XMLNode] {
788839
guard let nodes = _CFXMLNodesForXPath(_xmlNode, xpath) else {
789-
NSUnimplemented()
840+
return []
790841
}
791842

792843
var result: [XMLNode] = []
@@ -803,12 +854,14 @@ open class XMLNode: NSObject, NSCopying {
803854
@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.
804855
@returns An array whose elements are kinds of NSArray, NSData, NSDate, NSNumber, NSString, NSURL, or NSXMLNode.
805856
*/
857+
@available(*, unavailable, message: "XQuery is not available in swift-corelibs-foundation")
806858
open func objects(forXQuery xquery: String, constants: [String : Any]?) throws -> [Any] {
807-
NSUnimplemented()
859+
NSUnsupported()
808860
}
809861

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

814867
internal var _childNodes: Set<XMLNode> = []

‎Foundation/XMLParser.swift

+7
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,13 @@ internal func NSUnimplemented(_ fn: String = #function, file: StaticString = #fi
963963
fatalError("\(fn) is not yet implemented", file: file, line: line)
964964
}
965965

966+
internal func NSUnsupported(_ fn: String = #function, file: StaticString = #file, line: UInt = #line) -> Never {
967+
#if os(Android)
968+
NSLog("\(fn) is not supported on this platform. \(file):\(line)")
969+
#endif
970+
fatalError("\(fn) is not supported on this platform", file: file, line: line)
971+
}
972+
966973
extension NSObject {
967974
func withUnretainedReference<T, R>(_ work: (UnsafePointer<T>) -> R) -> R {
968975
let selfPtr = Unmanaged.passUnretained(self).toOpaque().assumingMemoryBound(to: T.self)

0 commit comments

Comments
 (0)