Skip to content

Commit f3882fa

Browse files
committed
Add namespace support for XML attributes
1 parent 42b387c commit f3882fa

File tree

6 files changed

+135
-16
lines changed

6 files changed

+135
-16
lines changed

CoreFoundation/Parsing.subproj/CFXMLInterface.c

+75-5
Original file line numberDiff line numberDiff line change
@@ -367,16 +367,38 @@ _CFXMLNodePtr _CFXMLNewComment(const unsigned char* value) {
367367
return xmlNewComment(value);
368368
}
369369

370-
_CFXMLNodePtr _CFXMLNewProperty(_CFXMLNodePtr node, const unsigned char* name, const unsigned char* value) {
371-
return xmlNewProp(node, name, value);
370+
_CFXMLNodePtr _CFXMLNewProperty(_CFXMLNodePtr node, const unsigned char* name, const unsigned char* uri, const unsigned char* value) {
371+
xmlNodePtr nodePtr = (xmlNodePtr)node;
372+
xmlChar *prefix = NULL;
373+
xmlChar *localName = xmlSplitQName2(name, &prefix);
374+
375+
_CFXMLNodePtr result;
376+
if (uri == NULL && localName == NULL) {
377+
result = xmlNewProp(node, name, value);
378+
} else {
379+
xmlNsPtr ns = xmlNewNs(nodePtr, uri, localName ? prefix : NULL);
380+
result = xmlNewNsProp(nodePtr, ns, localName ? localName : name, value);
381+
}
382+
383+
if (localName) {
384+
xmlFree(localName);
385+
}
386+
if (prefix) {
387+
xmlFree(prefix);
388+
}
389+
return result;
372390
}
373391

374392
CFStringRef _CFXMLNodeCopyURI(_CFXMLNodePtr node) {
375393
xmlNodePtr nodePtr = (xmlNodePtr)node;
376394
switch (nodePtr->type) {
377395
case XML_ATTRIBUTE_NODE:
378396
case XML_ELEMENT_NODE:
379-
return CFStringCreateWithCString(NULL, (const char*)nodePtr->ns->href, kCFStringEncodingUTF8);
397+
if (nodePtr->ns && nodePtr->ns->href) {
398+
return CFStringCreateWithCString(NULL, (const char*)nodePtr->ns->href, kCFStringEncodingUTF8);
399+
} else {
400+
return NULL;
401+
}
380402

381403
case XML_DOCUMENT_NODE:
382404
{
@@ -914,8 +936,56 @@ CFStringRef _Nullable _CFXMLCopyPathForNode(_CFXMLNodePtr node) {
914936
return result;
915937
}
916938

917-
_CFXMLNodePtr _CFXMLNodeHasProp(_CFXMLNodePtr node, const char* propertyName) {
918-
return xmlHasProp(node, (const xmlChar*)propertyName);
939+
static inline xmlNsPtr _searchNamespace(xmlNodePtr nodePtr, const xmlChar* prefix) {
940+
while (nodePtr != NULL) {
941+
xmlNsPtr ns = nodePtr->ns;
942+
while (ns != NULL) {
943+
if (xmlStrcmp(prefix, ns->prefix) == 0) {
944+
return ns;
945+
}
946+
ns = ns->next;
947+
}
948+
nodePtr = nodePtr->parent;
949+
}
950+
return NULL;
951+
}
952+
953+
void _CFXMLCompletePropURI(_CFXMLNodePtr propertyNode, _CFXMLNodePtr node) {
954+
xmlNodePtr propNodePtr = (xmlNodePtr)propertyNode;
955+
xmlNodePtr nodePtr = (xmlNodePtr)node;
956+
if (propNodePtr->type != XML_ATTRIBUTE_NODE || nodePtr->type != XML_ELEMENT_NODE) {
957+
return;
958+
}
959+
if (propNodePtr->ns != NULL
960+
&& propNodePtr->ns->href == NULL
961+
&& propNodePtr->ns->prefix != NULL) {
962+
xmlNsPtr ns = _searchNamespace(nodePtr, propNodePtr->ns->prefix);
963+
if (ns != NULL && ns->href != NULL) {
964+
propNodePtr->ns->href = xmlStrdup(ns->href);
965+
}
966+
}
967+
}
968+
969+
_CFXMLNodePtr _CFXMLNodeHasProp(_CFXMLNodePtr node, const unsigned char* propertyName, const unsigned char* uri) {
970+
xmlNodePtr nodePtr = (xmlNodePtr)node;
971+
xmlChar* prefix = NULL;
972+
xmlChar* localName = xmlSplitQName2(propertyName, &prefix);
973+
974+
if (!uri) {
975+
xmlNsPtr ns = _searchNamespace(nodePtr, prefix);
976+
uri = ns ? ns->href : NULL;
977+
}
978+
_CFXMLNodePtr result;
979+
result = xmlHasNsProp(node, localName ? localName : propertyName, uri);
980+
981+
if (localName) {
982+
xmlFree(localName);
983+
}
984+
if (prefix) {
985+
xmlFree(prefix);
986+
}
987+
988+
return result;
919989
}
920990

921991
_CFXMLDocPtr _CFXMLDocPtrFromDataWithOptions(CFDataRef data, unsigned int options) {

CoreFoundation/Parsing.subproj/CFXMLInterface.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ _CFXMLDocPtr _CFXMLNewDoc(const unsigned char* version);
144144
_CFXMLNodePtr _CFXMLNewProcessingInstruction(const unsigned char* name, const unsigned char* value);
145145
_CFXMLNodePtr _CFXMLNewTextNode(const unsigned char* value);
146146
_CFXMLNodePtr _CFXMLNewComment(const unsigned char* value);
147-
_CFXMLNodePtr _CFXMLNewProperty(_CFXMLNodePtr _Nullable node, const unsigned char* name, const unsigned char* value);
147+
_CFXMLNodePtr _CFXMLNewProperty(_CFXMLNodePtr _Nullable node, const unsigned char* name, const unsigned char* _Nullable uri, const unsigned char* value);
148148

149149
CFStringRef _Nullable _CFXMLNodeCopyURI(_CFXMLNodePtr node);
150150
void _CFXMLNodeSetURI(_CFXMLNodePtr node, const unsigned char* _Nullable URI);
@@ -197,7 +197,8 @@ CFStringRef _CFXMLCopyStringWithOptions(_CFXMLNodePtr node, uint32_t options);
197197
CF_RETURNS_RETAINED CFArrayRef _Nullable _CFXMLNodesForXPath(_CFXMLNodePtr node, const unsigned char* xpath);
198198
CFStringRef _Nullable _CFXMLCopyPathForNode(_CFXMLNodePtr node);
199199

200-
_CFXMLNodePtr _Nullable _CFXMLNodeHasProp(_CFXMLNodePtr node, const char* propertyName);
200+
void _CFXMLCompletePropURI(_CFXMLNodePtr propertyNode, _CFXMLNodePtr node);
201+
_CFXMLNodePtr _Nullable _CFXMLNodeHasProp(_CFXMLNodePtr node, const unsigned char* propertyName, const unsigned char* _Nullable uri);
201202

202203
_CFXMLDocPtr _CFXMLDocPtrFromDataWithOptions(CFDataRef data, unsigned int options);
203204

Docs/Status.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ There is no _Complete_ status for test coverage because there are always additio
141141
| `XMLDocument` | Mostly Complete | Substantial | `init()`, `replacementClass(for:)`, and `object(byApplyingXSLT...)` remain unimplemented |
142142
| `XMLDTD` | Mostly Complete | Substantial | `init()` remains unimplemented |
143143
| `XMLDTDNode` | Complete | Incomplete | |
144-
| `XMLElement` | Incomplete | Incomplete | `init(xmlString:)`, `elements(forLocalName:uri:)`, `attribute(forLocalName:uri:)`, namespace support, and others remain unimplemented |
144+
| `XMLElement` | Incomplete | Incomplete | `init(xmlString:)`, `elements(forLocalName:uri:)`, namespace support, and others remain unimplemented |
145145
| `XMLNode` | Incomplete | Incomplete | `localName(forName:)`, `prefix(forName:)`, `predefinedNamespace(forPrefix:)`, and others remain unimplemented |
146146
| `XMLParser` | Complete | Incomplete | |
147147

Foundation/XMLElement.swift

+5-3
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ open class XMLElement: XMLNode {
9090
}
9191

9292
removeAttribute(forName: name)
93+
_CFXMLCompletePropURI(attribute._xmlNode, _xmlNode);
9394
addChild(attribute)
9495
}
9596

@@ -98,7 +99,7 @@ open class XMLElement: XMLNode {
9899
@abstract Removes an attribute based on its name.
99100
*/
100101
open func removeAttribute(forName name: String) {
101-
if let prop = _CFXMLNodeHasProp(_xmlNode, name) {
102+
if let prop = _CFXMLNodeHasProp(_xmlNode, name, nil) {
102103
let propNode = XMLNode._objectNodeForNode(_CFXMLNodePtr(prop))
103104
_childNodes.remove(propNode)
104105
// We can't use `xmlRemoveProp` because someone else may still have a reference to this attribute
@@ -170,7 +171,7 @@ open class XMLElement: XMLNode {
170171
@abstract Returns an attribute matching this name.
171172
*/
172173
open func attribute(forName name: String) -> XMLNode? {
173-
guard let attribute = _CFXMLNodeHasProp(_xmlNode, name) else { return nil }
174+
guard let attribute = _CFXMLNodeHasProp(_xmlNode, name, nil) else { return nil }
174175
return XMLNode._objectNodeForNode(attribute)
175176
}
176177

@@ -179,7 +180,8 @@ open class XMLElement: XMLNode {
179180
@abstract Returns an attribute matching this localname URI pair.
180181
*/
181182
open func attribute(forLocalName localName: String, uri URI: String?) -> XMLNode? {
182-
NSUnimplemented()
183+
guard let attribute = _CFXMLNodeHasProp(_xmlNode, localName, URI) else { return nil }
184+
return XMLNode._objectNodeForNode(attribute)
183185
}
184186

185187
/*!

Foundation/XMLNode.swift

+4-5
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ open class XMLNode: NSObject, NSCopying {
122122
_xmlNode = _CFXMLNewNode(nil, "")
123123

124124
case .attribute:
125-
_xmlNode = _CFXMLNodePtr(_CFXMLNewProperty(nil, "", ""))
125+
_xmlNode = _CFXMLNodePtr(_CFXMLNewProperty(nil, "", nil, ""))
126126

127127
case .DTDKind:
128128
_xmlNode = _CFXMLNewDTD(nil, "", "", "")
@@ -199,7 +199,7 @@ open class XMLNode: NSObject, NSCopying {
199199
@abstract Returns an attribute <tt>name="stringValue"</tt>.
200200
*/
201201
open class func attribute(withName name: String, stringValue: String) -> Any {
202-
let attribute = _CFXMLNewProperty(nil, name, stringValue)
202+
let attribute = _CFXMLNewProperty(nil, name, nil, stringValue)
203203

204204
return XMLNode(ptr: attribute)
205205
}
@@ -209,10 +209,9 @@ open class XMLNode: NSObject, NSCopying {
209209
@abstract Returns an attribute whose full QName is specified.
210210
*/
211211
open class func attribute(withName name: String, uri: String, stringValue: String) -> Any {
212-
let attribute = XMLNode.attribute(withName: name, stringValue: stringValue) as! XMLNode
213-
// attribute.URI = URI
212+
let attribute = _CFXMLNewProperty(nil, name, uri, stringValue)
214213

215-
return attribute
214+
return XMLNode(ptr: attribute)
216215
}
217216

218217
/*!

TestFoundation/TestXMLDocument.swift

+47
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class TestXMLDocument : LoopbackServerTest {
2020
("test_stringValue", test_stringValue),
2121
("test_objectValue", test_objectValue),
2222
("test_attributes", test_attributes),
23+
("test_attributesWithNamespace", test_attributesWithNamespace),
2324
("test_comments", test_comments),
2425
("test_processingInstruction", test_processingInstruction),
2526
("test_parseXMLString", test_parseXMLString),
@@ -264,6 +265,52 @@ class TestXMLDocument : LoopbackServerTest {
264265
XCTAssertEqual(element.attribute(forName:"hello")?.stringValue, "world", "\(element.attribute(forName:"hello")?.stringValue as Optional)")
265266
XCTAssertEqual(element.attribute(forName:"foobar")?.stringValue, "buzbaz", "\(element.attributes ?? [])")
266267
}
268+
269+
func test_attributesWithNamespace() {
270+
let uriNs1 = "http://example.com/ns1"
271+
let uriNs2 = "http://example.com/ns2"
272+
273+
let root = XMLNode.element(withName: "root") as! XMLElement
274+
root.addNamespace(XMLNode.namespace(withName: "ns1", stringValue: uriNs1) as! XMLNode)
275+
276+
let element = XMLNode.element(withName: "element") as! XMLElement
277+
element.addNamespace(XMLNode.namespace(withName: "ns2", stringValue: uriNs2) as! XMLNode)
278+
root.addChild(element)
279+
280+
// Add attributes without URI
281+
element.addAttribute(XMLNode.attribute(withName: "name", stringValue: "John") as! XMLNode)
282+
element.addAttribute(XMLNode.attribute(withName: "ns1:name", stringValue: "Tom") as! XMLNode)
283+
284+
// Add attributes with URI
285+
element.addAttribute(XMLNode.attribute(withName: "ns1:age", uri: uriNs1, stringValue: "44") as! XMLNode)
286+
element.addAttribute(XMLNode.attribute(withName: "ns2:address", uri: uriNs2, stringValue: "Foobar City") as! XMLNode)
287+
288+
// Retrieve attributes without URI
289+
XCTAssertEqual(element.attribute(forName: "name")?.stringValue, "John", "name==John")
290+
XCTAssertEqual(element.attribute(forName: "ns1:name")?.stringValue, "Tom", "ns1:name==Tom")
291+
XCTAssertEqual(element.attribute(forName: "ns1:age")?.stringValue, "44", "ns1:age==44")
292+
XCTAssertEqual(element.attribute(forName: "ns2:address")?.stringValue, "Foobar City", "ns2:addresss==Foobar City")
293+
294+
// Retrieve attributes with URI
295+
XCTAssertEqual(element.attribute(forLocalName: "name", uri: nil)?.stringValue, "John", "name==John")
296+
XCTAssertEqual(element.attribute(forLocalName: "name", uri: uriNs1)?.stringValue, "Tom", "name==Tom")
297+
XCTAssertEqual(element.attribute(forLocalName: "age", uri: uriNs1)?.stringValue, "44", "age==44")
298+
XCTAssertNil(element.attribute(forLocalName: "address", uri: uriNs1), "address==nil")
299+
XCTAssertEqual(element.attribute(forLocalName: "address", uri: uriNs2)?.stringValue, "Foobar City", "addresss==Foobar City")
300+
301+
// Overwrite attributes
302+
element.addAttribute(XMLNode.attribute(withName: "ns1:age", stringValue: "33") as! XMLNode)
303+
XCTAssertEqual(element.attribute(forName: "ns1:age")?.stringValue, "33", "ns1:age==33")
304+
element.addAttribute(XMLNode.attribute(withName: "ns1:name", uri: uriNs1, stringValue: "Tommy") as! XMLNode)
305+
XCTAssertEqual(element.attribute(forLocalName: "name", uri: uriNs1)?.stringValue, "Tommy", "ns1:name==Tommy")
306+
307+
// Remove attributes
308+
element.removeAttribute(forName: "name")
309+
XCTAssertNil(element.attribute(forLocalName: "name", uri: nil), "name removed")
310+
XCTAssertNotNil(element.attribute(forLocalName: "name", uri: uriNs1), "ns1:name not removed")
311+
element.removeAttribute(forName: "ns1:name")
312+
XCTAssertNil(element.attribute(forLocalName: "name", uri: uriNs1), "ns1:name removed")
313+
}
267314

268315
func test_comments() {
269316
let element = XMLElement(name: "root")

0 commit comments

Comments
 (0)