XSLT on iOS

|

Implementing XSLT in an app seemed like a trivial concept as it is such a mature concept. But alas Apple have ensured we don”t get away that easily – they have left the XSLT libraries out of developer scope! The big tease!

There is a short way or a long way – the short way is just to put the stylesheet into your XML file and open it directly in a WebView. Unfortunately our needs were greater and I wanted to use parameters as well.

In order to solve this all threads will point you to libxslt which is actually already on the device but unavailable for use and if you do use it sneakily, your app will get rejected. FYI its used in WebViews.

Luckily the kind folks of the the Internet have provided a royalty free open source equivalent.

There are numerous threads out there which give you guidance on how to get this working but unfortunately none of them worked for us. After 2 days of fighting with c code, configure, make files and hybrid scripts I reverted to doing it old school! So bare with the slightly hacky nature of the solution I am about to present.

My steps to success are as follows (don”t worry I”ve included a test project for download):

  1. Download the libxslt-1.1.28 archive from here
  2. Extract the folder and add the libxslt folder (only) to your project
  3. Rename the config.h.in to config.h and add it to the libxslt folder
  4. Change all import references that belong <libxslt/……> to direct file references e.g. import <libxslt/xslt.h> changes to import “xslt.h”
  5. Add the libxslt folder to your project (as a group not a reference) – ignore the option to build it as an extra project
  6. Add libxml2 to your reference frameworks
  7. Add libxml2 header path to your Header Search Paths – I used: ${SDKROOT}/usr/include/libxml2
  8. Write your XSLT code (example provided below):

 NSString* filePath = [[NSBundle mainBundle] pathForResource: @"Test" ofType: @"xml"];
 NSData *xmlMem = [[NSData alloc] initWithContentsOfFile:filePath];
 NSString* styleSheetPath = [[NSBundle mainBundle] pathForResource: @"Test" ofType:@"xslt"]; 
 xmlDocPtr doc, res;
 xsltStylesheetPtr sty;
 // tells the libxml2 parser to substitute entities as it parses your file
 xmlSubstituteEntitiesDefault(1);
 // This tells libxml to load external entity subsets
 xmlLoadExtDtdDefaultValue = 1;
 // This following line is only really needed if you want to use XSLT params
 const char *params[3] = { [@"param1" cStringUsingEncoding:NSUTF8StringEncoding], [@""paramdata"" cStringUsingEncoding:NSUTF8StringEncoding], NULL };
 sty = xsltParseStylesheetFile((const xmlChar *)[styleSheetPath cStringUsingEncoding: NSUTF8StringEncoding]);
 //doc = xmlParseFile([filePath cStringUsingEncoding: NSUTF8StringEncoding]);
 // This example loads the XML file from a memory location as a sample, you can reference file directly with different xmlParse methods
 doc = xmlParseMemory([xmlMem bytes], [xmlMem length]);
 // Pass in NULL instead of params if you don"t need to use them
 res = xsltApplyStylesheet(sty, doc, params);
 xmlChar* xmlResultBuffer = nil;
 int length = 0;
 xsltSaveResultToString(&xmlResultBuffer, &length, res, sty;
 NSString* result = [NSString stringWithCString: (char *)xmlResultBuffer encoding: NSUTF8StringEncoding];
 NSLog(@"Result: %@", result);
 [self.webView loadHTMLString:result baseURL:nil];
 free(xmlResultBuffer);
 xsltFreeStylesheet(sty);
 xmlFreeDoc(res);
 xmlFreeDoc(doc);
 xsltCleanupGlobals();
 xmlCleanupParser();

Hopefully that should save you a lot of headaches. I recommend just downloading my sample project rather than doing the renames etc above as I may have missed something. Just grab the libxslt folder from my sample project and do steps 5-8. I hope this helps!

 

Sample Project (created with Xcode 4 for iOS 6)