Вопрос

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).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top