Skip to content

Commit

Permalink
Added XPath support and made some minor API modifications.
Browse files Browse the repository at this point in the history
* XPath now supported with  iterateXPath and childrenInXPath.
* Parameter name withEncoding changed to encoding.
* Minor implementation changes.
  • Loading branch information
John Blanco committed Apr 14, 2012
1 parent fbec9fe commit b3f2828
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 46 deletions.
2 changes: 1 addition & 1 deletion EncodingTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ - (void)setUp {
}

- (void)testChinese {
RXMLElement *rxml = [RXMLElement elementFromXMLString:chineseXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:chineseXML_ encoding:NSUTF8StringEncoding];
STAssertEqualObjects([rxml attribute:@"data"], @"以晴为主", nil);
}

Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ RaptureXML processes XML in two steps: load and path. This means that you first

You can load the XML with any of the following constructors:

RXMLElement *rootXML = [RXMLElement elementFromXMLString:@"...my xml..." withEncoding:NSUTF8StringEncoding];
RXMLElement *rootXML = [RXMLElement elementFromXMLString:@"...my xml..." encoding:NSUTF8StringEncoding];
RXMLElement *rootXML = [RXMLElement elementFromXMLFile:@"myfile.xml"];
RXMLElement *rootXML = [RXMLElement elementFromXMLFilename:@"myfile" elementFromXMLFilename:@"xml"];
RXMLElement *rootXML = [RXMLElement elementFromURL:[NSURL URLWithString:@"...my url..."]];
Expand Down Expand Up @@ -115,6 +115,22 @@ The wildcard processes every tag rather than the one you would've named. You ca

This gives us all the tags for the coach. Easy enough?

# XPath #

If you don't want to use the custom RaptureXML iteration query syntax, you can use the standard XPath query language as well. Here's how you query all players with XPath:

[rootXML iterate:@"//player" with: ^(RXMLElement *player) {
NSLog(@"Player: %@ (#%@)", [player child:@"name"], [player attribute:@"number"]);
}];

And remember, you can also test attributes using XPath as well. Here's how you can find the player with #5:

[rootXML iterate:@"//player[number='5']" with: ^(RXMLElement *player) {
NSLog(@"Player #5: %@", [player child:@"name"]);
}];

In the near future, the custom query language for RaptureXML will be deprecated and XPath will fully replace it. If you're not familiar with XPath, you can use this [handy guide](http://www.w3schools.com/xpath/xpath_syntax.asp).

# Namespaces #

Namespaces are supported for most methods, however not for iterations. If you want to use namespaces for that kind of thing, use the -children method manually. When specifying namespaces, be sure to specify the namespace URI and *not* the prefix. For example, if your XML looked like:
Expand Down
6 changes: 5 additions & 1 deletion RaptureXML.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
02041DBD1526BF2A00D1F36A /* SimpleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEB8ECA1467ED9C00024989 /* SimpleTests.m */; };
02041DBF1526CACD00D1F36A /* TextConversionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEB8ECB1467ED9C00024989 /* TextConversionTests.m */; };
02041DC01526CB0000D1F36A /* WildcardTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEB8ECC1467ED9C00024989 /* WildcardTests.m */; };
0213E4411539CE1B00B42A2D /* XPathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0213E43D1539CDDF00B42A2D /* XPathTests.m */; };
0252B2DF142ADFC60018B75D /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0252B2DE142ADFC60018B75D /* SenTestingKit.framework */; };
0252B2E0142ADFC60018B75D /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0252B2C3142ADFC60018B75D /* UIKit.framework */; };
0252B2E1142ADFC60018B75D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0252B2C5142ADFC60018B75D /* Foundation.framework */; };
Expand All @@ -31,6 +32,7 @@
/* Begin PBXFileReference section */
02041DB71526A71200D1F36A /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; };
02041DC21526D03A00D1F36A /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
0213E43D1539CDDF00B42A2D /* XPathTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XPathTests.m; sourceTree = SOURCE_ROOT; };
0252B2C3142ADFC60018B75D /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
0252B2C5142ADFC60018B75D /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
0252B2C7142ADFC60018B75D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -82,7 +84,6 @@
0252B2B4142ADFC60018B75D = {
isa = PBXGroup;
children = (
02041DC21526D03A00D1F36A /* MediaPlayer.framework */,
027DAC3814FBF4B5001BA563 /* Library-Prefix.pch */,
027DAC2B14FBF443001BA563 /* RaptureXML */,
0252B2E5142ADFC60018B75D /* Tests */,
Expand All @@ -103,6 +104,7 @@
0252B2C2142ADFC60018B75D /* Frameworks */ = {
isa = PBXGroup;
children = (
02041DC21526D03A00D1F36A /* MediaPlayer.framework */,
02041DB71526A71200D1F36A /* libxml2.dylib */,
0252B305142AE3FF0018B75D /* libz.dylib */,
0252B2C3142ADFC60018B75D /* UIKit.framework */,
Expand All @@ -126,6 +128,7 @@
0DEB8ECB1467ED9C00024989 /* TextConversionTests.m */,
0DEB8ECC1467ED9C00024989 /* WildcardTests.m */,
02F3A4041526D7BC00E8C822 /* EncodingTests.m */,
0213E43D1539CDDF00B42A2D /* XPathTests.m */,
);
name = Tests;
path = RaptureXMLTests;
Expand Down Expand Up @@ -256,6 +259,7 @@
02F3A4011526D39800E8C822 /* DeepChildrenTests.m in Sources */,
02F3A4021526D45B00E8C822 /* ErrorTests.m in Sources */,
02F3A4051526D7BC00E8C822 /* EncodingTests.m in Sources */,
0213E4411539CE1B00B42A2D /* XPathTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
20 changes: 12 additions & 8 deletions RaptureXML/RXMLElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,42 +31,46 @@
#import <Foundation/Foundation.h>
#import <libxml2/libxml/xmlreader.h>
#import <libxml2/libxml/xmlmemory.h>
#import <libxml/xpath.h>
#import <libxml/xpathInternals.h>

@interface RXMLElement : NSObject {
xmlDocPtr doc_;
xmlNodePtr node_;
}

- (id)initFromXMLString:(NSString *)xmlString withEncoding:(NSStringEncoding)encoding;
- (id)initFromXMLString:(NSString *)xmlString encoding:(NSStringEncoding)encoding;
- (id)initFromXMLFile:(NSString *)filename;
- (id)initFromXMLFile:(NSString *)filename fileExtension:(NSString*)extension;
- (id)initFromURL:(NSURL *)url;
- (id)initFromXMLData:(NSData *)data;
- (id)initFromXMLNode:(xmlNodePtr)node;

+ (id)elementFromXMLString:(NSString *)xmlString withEncoding:(NSStringEncoding)encoding;
+ (id)elementFromXMLString:(NSString *)xmlString encoding:(NSStringEncoding)encoding;
+ (id)elementFromXMLFile:(NSString *)filename;
+ (id)elementFromXMLFilename:(NSString *)filename fileExtension:(NSString *)extension;
+ (id)elementFromURL:(NSURL *)url;
+ (id)elementFromXMLData:(NSData *)data;
+ (id)elementFromXMLNode:(xmlNodePtr)node;

- (NSString *)attribute:(NSString *)attName;
- (NSString *)attribute:(NSString *)attName inNamespace:(NSString *)namespace;
- (NSString *)attribute:(NSString *)attName inNamespace:(NSString *)ns;

- (NSInteger)attributeAsInt:(NSString *)attName;
- (NSInteger)attributeAsInt:(NSString *)attName inNamespace:(NSString *)namespace;
- (NSInteger)attributeAsInt:(NSString *)attName inNamespace:(NSString *)ns;

- (double)attributeAsDouble:(NSString *)attName;
- (double)attributeAsDouble:(NSString *)attName inNamespace:(NSString *)namespace;
- (double)attributeAsDouble:(NSString *)attName inNamespace:(NSString *)ns;

- (RXMLElement *)child:(NSString *)tagName;
- (RXMLElement *)child:(NSString *)tagName inNamespace:(NSString *)namespace;
- (RXMLElement *)child:(NSString *)tagName inNamespace:(NSString *)ns;

- (NSArray *)children:(NSString *)tagName;
- (NSArray *)children:(NSString *)tagName inNamespace:(NSString *)namespace;
- (NSArray *)children:(NSString *)tagName inNamespace:(NSString *)ns;
- (NSArray *)childrenInXPath:(NSString *)query;

- (void)iterate:(NSString *)query with:(void (^)(RXMLElement *))blk;
- (void)iterateXPath:(NSString *)query with:(void (^)(RXMLElement *))blk;
- (void)iterateElements:(NSArray *)elements with:(void (^)(RXMLElement *))blk;

@property (nonatomic, readonly) NSString *tag;
Expand All @@ -77,5 +81,5 @@

@end

typedef void (^RXMLBlock)(RXMLElement *);
typedef void (^RXMLBlock)(RXMLElement *element);

80 changes: 63 additions & 17 deletions RaptureXML/RXMLElement.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

@implementation RXMLElement

- (id)initFromXMLString:(NSString *)xmlString withEncoding:(NSStringEncoding)encoding {
- (id)initFromXMLString:(NSString *)xmlString encoding:(NSStringEncoding)encoding {
if ((self = [super init])) {
NSData *data = [xmlString dataUsingEncoding:encoding];

Expand Down Expand Up @@ -131,8 +131,8 @@ - (id)initFromXMLNode:(xmlNodePtr)node {
return self;
}

+ (id)elementFromXMLString:(NSString *)attributeXML_ withEncoding:(NSStringEncoding)encoding {
return [[[RXMLElement alloc] initFromXMLString:attributeXML_ withEncoding:encoding] autorelease];
+ (id)elementFromXMLString:(NSString *)attributeXML_ encoding:(NSStringEncoding)encoding {
return [[[RXMLElement alloc] initFromXMLString:attributeXML_ encoding:encoding] autorelease];
}

+ (id)elementFromXMLFile:(NSString *)filename {
Expand Down Expand Up @@ -197,8 +197,8 @@ - (NSString *)attribute:(NSString *)attName {
return nil;
}

- (NSString *)attribute:(NSString *)attName inNamespace:(NSString *)namespace {
const unsigned char *attCStr = xmlGetNsProp(node_, (const xmlChar *)[attName cStringUsingEncoding:NSUTF8StringEncoding], (const xmlChar *)[namespace cStringUsingEncoding:NSUTF8StringEncoding]);
- (NSString *)attribute:(NSString *)attName inNamespace:(NSString *)ns {
const unsigned char *attCStr = xmlGetNsProp(node_, (const xmlChar *)[attName cStringUsingEncoding:NSUTF8StringEncoding], (const xmlChar *)[ns cStringUsingEncoding:NSUTF8StringEncoding]);

if (attCStr) {
return [NSString stringWithUTF8String:(const char *)attCStr];
Expand All @@ -211,16 +211,16 @@ - (NSInteger)attributeAsInt:(NSString *)attName {
return [[self attribute:attName] intValue];
}

- (NSInteger)attributeAsInt:(NSString *)attName inNamespace:(NSString *)namespace {
return [[self attribute:attName inNamespace:namespace] intValue];
- (NSInteger)attributeAsInt:(NSString *)attName inNamespace:(NSString *)ns {
return [[self attribute:attName inNamespace:ns] intValue];
}

- (double)attributeAsDouble:(NSString *)attName {
return [[self attribute:attName] doubleValue];
}

- (double)attributeAsDouble:(NSString *)attName inNamespace:(NSString *)namespace {
return [[self attribute:attName inNamespace:namespace] doubleValue];
- (double)attributeAsDouble:(NSString *)attName inNamespace:(NSString *)ns {
return [[self attribute:attName inNamespace:ns] doubleValue];
}

- (BOOL)isValid {
Expand All @@ -234,8 +234,7 @@ - (RXMLElement *)child:(NSString *)tagName {
xmlNodePtr cur = node_;

// navigate down
for (NSInteger i=0; i < components.count; ++i) {
NSString *iTagName = [components objectAtIndex:i];
for (NSString *iTagName in components) {
const xmlChar *tagNameC = (const xmlChar *)[iTagName cStringUsingEncoding:NSUTF8StringEncoding];

if ([iTagName isEqualToString:@"*"]) {
Expand Down Expand Up @@ -267,14 +266,13 @@ - (RXMLElement *)child:(NSString *)tagName {
return nil;
}

- (RXMLElement *)child:(NSString *)tagName inNamespace:(NSString *)namespace {
- (RXMLElement *)child:(NSString *)tagName inNamespace:(NSString *)ns {
NSArray *components = [tagName componentsSeparatedByString:@"."];
xmlNodePtr cur = node_;
const xmlChar *namespaceC = (const xmlChar *)[namespace cStringUsingEncoding:NSUTF8StringEncoding];
const xmlChar *namespaceC = (const xmlChar *)[ns cStringUsingEncoding:NSUTF8StringEncoding];

// navigate down
for (NSInteger i=0; i < components.count; ++i) {
NSString *iTagName = [components objectAtIndex:i];
for (NSString *iTagName in components) {
const xmlChar *tagNameC = (const xmlChar *)[iTagName cStringUsingEncoding:NSUTF8StringEncoding];

if ([iTagName isEqualToString:@"*"]) {
Expand Down Expand Up @@ -322,9 +320,9 @@ - (NSArray *)children:(NSString *)tagName {
return [[children copy] autorelease];
}

- (NSArray *)children:(NSString *)tagName inNamespace:(NSString *)namespace {
- (NSArray *)children:(NSString *)tagName inNamespace:(NSString *)ns {
const xmlChar *tagNameC = (const xmlChar *)[tagName cStringUsingEncoding:NSUTF8StringEncoding];
const xmlChar *namespaceC = (const xmlChar *)[namespace cStringUsingEncoding:NSUTF8StringEncoding];
const xmlChar *namespaceC = (const xmlChar *)[ns cStringUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *children = [NSMutableArray array];
xmlNodePtr cur = node_->children;

Expand All @@ -339,9 +337,52 @@ - (NSArray *)children:(NSString *)tagName inNamespace:(NSString *)namespace {
return [[children copy] autorelease];
}

- (NSArray *)childrenInXPath:(NSString *)query {
// check for a query
if (!query) {
return [NSArray array];
}

xmlXPathContextPtr context = xmlXPathNewContext(doc_);

if (context == NULL) {
return nil;
}

xmlXPathObjectPtr object = xmlXPathEvalExpression((xmlChar *)[query cStringUsingEncoding:NSUTF8StringEncoding], context);
if(object == NULL) {
return nil;
}

xmlNodeSetPtr nodes = object->nodesetval;
if (nodes == NULL) {
return nil;
}

NSMutableArray *resultNodes = [NSMutableArray array];

for (NSInteger i = 0; i < nodes->nodeNr; i++) {
RXMLElement *element = [RXMLElement elementFromXMLNode:nodes->nodeTab[i]];

if (element != NULL) {
[resultNodes addObject:element];
}
}

xmlXPathFreeObject(object);
xmlXPathFreeContext(context);

return resultNodes;
}

#pragma mark -

- (void)iterate:(NSString *)query with:(void (^)(RXMLElement *))blk {
// check for a query
if (!query) {
return;
}

NSArray *components = [query componentsSeparatedByString:@"."];
xmlNodePtr cur = node_;

Expand Down Expand Up @@ -409,6 +450,11 @@ - (void)iterate:(NSString *)query with:(void (^)(RXMLElement *))blk {
}
}

- (void)iterateXPath:(NSString *)query with:(void (^)(RXMLElement *))blk {
NSArray *children = [self childrenInXPath:query];
[self iterateElements:children with:blk];
}

- (void)iterateElements:(NSArray *)elements with:(void (^)(RXMLElement *))blk {
for (RXMLElement *iElement in elements) {
blk(iElement);
Expand Down
16 changes: 8 additions & 8 deletions Tests/BoundaryTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,51 +33,51 @@ - (void)setUp {
}

- (void)testEmptyXML {
RXMLElement *rxml = [RXMLElement elementFromXMLString:emptyXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:emptyXML_ encoding:NSUTF8StringEncoding];
STAssertFalse(rxml.isValid, nil);
}

- (void)testEmptyTopTagXML {
RXMLElement *rxml = [RXMLElement elementFromXMLString:emptyTopTagXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:emptyTopTagXML_ encoding:NSUTF8StringEncoding];
STAssertTrue(rxml.isValid, nil);
STAssertEqualObjects(rxml.text, @"", nil);
STAssertEqualObjects([rxml children:@"*"], [NSArray array], nil);
}

- (void)testAttribute {
RXMLElement *rxml = [RXMLElement elementFromXMLString:attributeXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:attributeXML_ encoding:NSUTF8StringEncoding];
STAssertTrue(rxml.isValid, nil);
STAssertEqualObjects([rxml attribute:@"foo"], @"bar", nil);
}

- (void)testNamespaceAttribute {
RXMLElement *rxml = [RXMLElement elementFromXMLString:namespaceXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:namespaceXML_ encoding:NSUTF8StringEncoding];
STAssertTrue(rxml.isValid, nil);
STAssertEqualObjects([rxml attribute:@"foo" inNamespace:@"*"], @"bar", nil);
STAssertEquals([rxml attributeAsInt:@"one" inNamespace:@"*"], 1, nil);
}

- (void)testChild {
RXMLElement *rxml = [RXMLElement elementFromXMLString:childXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:childXML_ encoding:NSUTF8StringEncoding];
STAssertTrue(rxml.isValid, nil);
STAssertEqualObjects([rxml child:@"empty_child"].text, @"", nil);
STAssertEqualObjects([rxml child:@"text_child"].text, @"foo", nil);
}

- (void)testNamespaceChild {
RXMLElement *rxml = [RXMLElement elementFromXMLString:namespaceXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:namespaceXML_ encoding:NSUTF8StringEncoding];
STAssertTrue(rxml.isValid, nil);
STAssertEqualObjects([rxml child:@"text" inNamespace:@"*"].text, @"something", nil);
}

- (void)testChildren {
RXMLElement *rxml = [RXMLElement elementFromXMLString:childrenXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:childrenXML_ encoding:NSUTF8StringEncoding];
STAssertTrue(rxml.isValid, nil);
STAssertEquals([rxml children:@"child"].count, 3U, nil);
}

- (void)testNamespaceChildren {
RXMLElement *rxml = [RXMLElement elementFromXMLString:namespaceXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:namespaceXML_ encoding:NSUTF8StringEncoding];
STAssertTrue(rxml.isValid, nil);
STAssertEquals([rxml children:@"text" inNamespace:@"*"].count, 1U, nil);
}
Expand Down
8 changes: 4 additions & 4 deletions Tests/ErrorTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@ - (void)setUp {
}

- (void)testBadXML {
RXMLElement *rxml = [RXMLElement elementFromXMLString:badXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:badXML_ encoding:NSUTF8StringEncoding];
STAssertFalse([rxml isValid], nil);
}

- (void)testMissingTag {
RXMLElement *rxml = [RXMLElement elementFromXMLString:simplifiedXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:simplifiedXML_ encoding:NSUTF8StringEncoding];
RXMLElement *hexagon = [rxml child:@"hexagon"];

STAssertNil(hexagon, nil);
}

- (void)testMissingTagIteration {
RXMLElement *rxml = [RXMLElement elementFromXMLString:simplifiedXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:simplifiedXML_ encoding:NSUTF8StringEncoding];
__block NSInteger i = 0;

[rxml iterate:@"hexagon" with:^(RXMLElement *e) {
Expand All @@ -53,7 +53,7 @@ - (void)testMissingTagIteration {
}

- (void)testMissingAttribute {
RXMLElement *rxml = [RXMLElement elementFromXMLString:simplifiedXML_ withEncoding:NSUTF8StringEncoding];
RXMLElement *rxml = [RXMLElement elementFromXMLString:simplifiedXML_ encoding:NSUTF8StringEncoding];
NSString *missingName = [rxml attribute:@"name"];

STAssertNil(missingName, nil);
Expand Down
Loading

0 comments on commit b3f2828

Please sign in to comment.