I have the following helper function for transforming XML via XSLT:
- (NSXMLDocument *)transform:(NSString *)xml :(NSString *)xslt
{
NSError *xmlDocErr = nil;
NSXMLDocument *transformedXmlDoc = nil;
NSXMLDocument *xmlDoc = [[NSXMLDocument alloc]
initWithXMLString:xml
options:NSXMLDocumentValidate
error:&xmlDocErr];
if (xmlDocErr) {
NSLog(@"Error: %@", [xmlDocErr localizedDescription]);
}
else {
transformedXmlDoc = [xmlDoc objectByApplyingXSLTString:xslt
arguments:nil
error:&xmlDocErr];
if (xmlDocErr) {
NSLog(@"Error: %@", [xmlDocErr localizedDescription]);
}
}
return transformedXmlDoc;
}
It works as expected, but there's a slight quirk I could use assistance with.
When I try to use an XSLT function that's unknown to NSXMLDocument (say, EXSLT's node-set()
), I get output in Xcode similar to the below - the first line, in particular, is of interest:
xmlXPathCompOpEval: function node-set not found
XPath error: Unregistered function runtime
error: element for-each
Failed to evaluate the 'select' expression.
That's cool; it's exactly what I'd expect.
The interesting thing to me, however, is that the output doesn't contain "Error: "
anywhere (which should be the case if that output had been captured by my [xmlDocErr localizedDescription]
calls).
So, here's the question: how can I grab the above output (so that I can use it for displaying relevant messages to my user)?
Thanks so much!
The error is happening deep within libxml
, on line 13479 of xpath.c
, which ends up calling xmlGenericErrorDefaultFunc()
on line 71 of error.c
, which prints to stderr
. So the easiest way to do this is to capture stderr
while the XSLT processing is going on:
- (NSXMLDocument *)transform:(NSString *)xml :(NSString *)xslt
{
NSError *xmlDocErr = nil;
NSXMLDocument *transformedXmlDoc = nil;
NSXMLDocument *xmlDoc = [[NSXMLDocument alloc]
initWithXMLString:xml
options:NSXMLDocumentValidate
error:&xmlDocErr];
if (xmlDocErr) {
NSLog(@"Error: %@", [xmlDocErr localizedDescription]);
}
else {
// Pipe for stderr
NSPipe *pipe = [NSPipe pipe];
// Duplicate of stderr (will use later)
int cntl = fcntl(STDERR_FILENO,F_DUPFD);
// Redirect stderr through our pipe
dup2([[pipe fileHandleForWriting] fileDescriptor], STDERR_FILENO);
transformedXmlDoc = [xmlDoc objectByApplyingXSLTString:xslt
arguments:nil
error:&xmlDocErr];
// Get the data
NSData *dat = [[pipe fileHandleForReading] availableData];
// Redirect stderr through our duplicate, to restore default output behavior
dup2(cntl, STDERR_FILENO);
// Did anything get logged?
if ([dat length]>0) {
NSLog(@"Error: %@", [[NSString alloc] initWithData:dat encoding:NSASCIIStringEncoding]);
}
if (xmlDocErr) {
NSLog(@"Error: %@", [xmlDocErr localizedDescription]);
}
}
return transformedXmlDoc;
}
But this is a bit of a hack, so be careful...
If you aren't satisfied with that solution, it should be possible to override the variable xmlGenericError
(which, by default, references xmlGenericErrorDefaultFunc
) with a custom error-handling function of your own using initGenericErrorDefaultFunc
on line 864 of xmlerror.h
. That would be a lot safer, but also more complicated (if it's possible at all).