diff options
Diffstat (limited to 'ext/gorg/xsl/xsl.c')
-rw-r--r-- | ext/gorg/xsl/xsl.c | 894 |
1 files changed, 894 insertions, 0 deletions
diff --git a/ext/gorg/xsl/xsl.c b/ext/gorg/xsl/xsl.c new file mode 100644 index 0000000..d8d40b6 --- /dev/null +++ b/ext/gorg/xsl/xsl.c @@ -0,0 +1,894 @@ +/* + Copyright 2004, Xavier Neys (neysx@gentoo.org) + + This file is part of gorg. + + gorg is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + gorg is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with gorg; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "xsl.h" + +/* + * Copied from xmlIO.c from libxml2 + */ +static int xmlFileWrite (void * context, const char * buffer, int len) +{ + int items; + + if ((context == NULL) || (buffer == NULL)) + return(-1); + items = fwrite(&buffer[0], len, 1, (FILE *) context); + if ((items == 0) && (ferror((FILE *) context))) { + //xmlIOErr(0, "fwrite()"); + __xmlIOErr(XML_FROM_IO, 0, "fwrite() failed"); + return(-1); + } + return(items * len); +} + +extern int xmlLoadExtDtdDefaultValue; +static int xmlOptions = XSLT_PARSE_OPTIONS | XML_PARSE_NOWARNING; + +/*Enum xmlParserOption { + XML_PARSE_RECOVER = 1 : recover on errors + XML_PARSE_NOENT = 2 : substitute entities + XML_PARSE_DTDLOAD = 4 : load the external subset + XML_PARSE_DTDATTR = 8 : default DTD attributes + XML_PARSE_DTDVALID = 16 : validate with the DTD + XML_PARSE_NOERROR = 32 : suppress error reports + XML_PARSE_NOWARNING = 64 : suppress warning reports + XML_PARSE_PEDANTIC = 128 : pedantic error reporting + XML_PARSE_NOBLANKS = 256 : remove blank nodes + XML_PARSE_SAX1 = 512 : use the SAX1 interface internally + XML_PARSE_XINCLUDE = 1024 : Implement XInclude substitition + XML_PARSE_NONET = 2048 : Forbid network access + XML_PARSE_NODICT = 4096 : Do not reuse the context dictionnary + XML_PARSE_NSCLEAN = 8192 : remove redundant namespaces declarations + XML_PARSE_NOCDATA = 16384 : merge CDATA as text nodes + XML_PARSE_NOXINCNODE = 32768 : do not generate XINCLUDE START/END nodes +}*/ + +/* + * Library global values that need to be accessed by the callbacks + * Make sure the lib init routine registers them with ruby's GC + */ +VALUE g_xroot=Qnil; +VALUE g_xfiles=Qnil; +VALUE g_xmsg=Qnil; +VALUE g_mutex=Qnil; +VALUE g_xtrack=Qnil; // true/false, no need to register this one + +/* + * Store ID's of ruby methodes to speed up calls to rb_funcall* + * so that we do not have to call rb_intern("methodName") repeatedly. + */ +struct { + int include; + int to_a; + int to_s; + int length; + int synchronize; +} id; + + +/* + * Add file to list of requested files, if not already in our array + */ +void addTrackedFile(char *f, const char *rw) +{ + VALUE rbNewPath; + VALUE rwo; + VALUE rbNewEntry; + + if (Qtrue == g_xtrack) + { + switch(*rw) + { + case 'R': + case 'r': + rwo = rb_str_new2("r"); + break; + case 'W': + case 'w': + rwo = rb_str_new2("w"); + break; + default: + rwo = rb_str_new2("o"); + } + rbNewPath = rb_str_new2(f); + rbNewEntry = rb_ary_new(); + rb_ary_push(rbNewEntry, rwo); + rb_ary_push(rbNewEntry, rbNewPath); + if (Qtrue != rb_funcall(g_xfiles, id.include, 1, rbNewEntry)) + rb_ary_push(g_xfiles, rbNewEntry); + } +} + +/* + * libxml2 File I/O Match Callback : + * return 1 if we must handle the file ourselves + */ +int XRootMatch(const char * URI) { + int r = 0; +//printf("NSX-RootMatch: %s\n",URI); + if ( URI != NULL && (*URI == '/' || !strncmp(URI, "file:///", 8))) + r = 1; + else + if (!strncmp(URI, "ftp://", 6) || !strncmp(URI, "http://", 7)) + // Add URI to list of requested files to let caller know remote files are used + addTrackedFile((char *)URI, "o"); + + return r; +} + + +/* + * libxml2 File I/O Open Callback : + * open the file, prepend $xroot if necessary and add file to list of requested files on input + */ +void *XRootOpen (const char *filename, const char* rw) { + char *path = NULL; + char *fakexml = NULL; + FILE *fd; + char *rbxrootPtr=""; + int rbxrootLen=0; + char empty[] = "<?xml version='1.0'?><missing file='%s'/>"; + int pip[2]; + struct stat notused; + +//printf("NSX-RootOpen: %s\n", filename); + + if (filename == NULL || (*filename != '/' && strncmp(filename, "file:///", 8))){ + return NULL; // I told you before, I can't help you with that file ;-) + } + + if (g_xroot != Qnil) + { + rbxrootPtr = RSTRING(g_xroot)->ptr; + rbxrootLen = RSTRING(g_xroot)->len; + } + path = (char *) malloc((strlen(filename) + rbxrootLen + 1) * sizeof(char)); + if (path == NULL) + return NULL; + + if (!strncmp(filename, "file:///", 8)) + { + // Absolute path, do not prepend xroot, e.g. file:///etc/xml/catalog + strcpy ( path, filename+7); + } + else + { + // If requested file is already under xroot, do not prepend path with xroot + // Example: + // Say we have xroot="/htdocs" + // when calling document('../xml/file.xml') in /htdocs/xsl/mysheet.xsl, + // the lib will already have replaced the .. with /htdocs + // and there is no need to add /htdocs + // On the other hand, if we call document('/xml/file.xml') in /htdocs/xsl/mysheet.xsl, + // because we know our root is /htdocs, then we need to prepend xroot to get /htdocs/xml/file.xml + // The consequence of that is that /${DocRoot}/${DocRoot}/whatever is not usable. Get over it. + // + // Besides, it is also possible that a file is located outside the $DocumentRoot, e.g. ~usename/file.xml + // that apache would have expanded to /home/username/public_html/file.xml e.g. + if (rbxrootLen && strncmp(rbxrootPtr, filename, rbxrootLen) && stat(filename,¬used)) + { + // Requested file is not already under $DocRoot, prepend it + strcpy (path, rbxrootPtr); + strcat (path, filename); + } + else + { + // Use the filename that was requested as-is + strcpy(path, filename); + } + } + + // Add file to list of requested files + addTrackedFile(path, rw); + + fd = fopen(path, rw); + free(path); + + if (*rw == 'r' && fd == NULL && strncmp(filename, "file:///", 8) && strlen(filename)>4 && strncmp((strlen(filename)-4)+filename, ".dtd", 4) && strncmp((strlen(filename)-4)+filename, ".xsl", 4)) + // Return fake xml + // We don't know for sure that libxml2 wants an xml file from a document(), + // but what the heck, let's just pretend + if (pipe(pip)) + return (void *) NULL; + else + { + fakexml = (char *) malloc((strlen(filename) + sizeof(empty)) * sizeof(char)); + if (path == NULL) + return NULL; + sprintf(fakexml, empty, filename); + write(pip[1], fakexml, strlen(fakexml)); + close(pip[1]); + free(fakexml); + return (void *) fdopen(pip[0], "r"); + } + else + return (void *) fd; +} + +int XRootClose (void * context) { + if (context == (void *) -1) + return 0; + else + return xmlFileClose(context); +} + +void *XRootInputOpen (const char *filename) { + return XRootOpen (filename, "r"); +} + +void *XRootOutputOpen (const char *filename) { + return XRootOpen (filename, "w"); +} + + +/* + * Intercept xsl:message output strings, + * If one starts with "%%GORG%%" then it to our @xmsg array. + * If not, pass it to the default generic handler of libxslt + */ +void xslMessageHandler(void *ctx ATTRIBUTE_UNUSED, const char *msg, ...) +{ + va_list args; + char *str; + int len; + + va_start(args, msg); + len = vasprintf(&str, msg, args); + va_end(args); + + if (len > 0) + { + if (!strncmp(str, "%%GORG%%", 8)) + { + if (len > 8) + { + rb_ary_push(g_xmsg, rb_str_new2(str+8)); + } + } + else + { + // Not for gorg, spit it out on stderr as libxslt would do + fputs(str, stderr); + } + // Need to free pointer that was allocated by vasprintf + free(str); + } +} + + +/* + * Try to distinguish between a filename and some xml + * without accessing the filesystem or parsing the string as xml + * + * If the string is long (>FILENAME_MAX) or + * starts with "<?xml" or "<?xsl" or + * contains newline chars, + * we assume it is some kind of xml, otherwise we assume it is a filename + */ +int looksLikeXML(VALUE v) +{ + return (RSTRING(v)->len > FILENAME_MAX) + || (!strncmp(RSTRING(v)->ptr, "<?xml", 5)) + || (!strncmp(RSTRING(v)->ptr, "<?xsl", 5)) + || (strstr(RSTRING(v)->ptr, "\n")); +// We could also try with " " but some are stupid enough to use spaces in filenames +} + +// I got stumped and needed this ;-) +void dumpCleanup(char * str, struct S_cleanup c) +{ +printf( "%s\n" + "\nparams=%08x" + "\ndocxml=%08x" + "\ndocxsl=%08x" + "\ndocres=%08x" + "\n xsl=%08x" + "\ndocstr=%08x" + "\n=======================\n", str, c.params, c.docxml, c.docxsl, c.docres, c.xsl, c.docstr); +} + +/* + * my_raise : cleanup and raise ruby exception + * + * cleanup frees xsl docs and allocated memory, pointers are in passed struct + * then raises the passed exception + * + * struct of pointers can be NULL (no memory to free) and + * exception can be NULL (clean up only, do not call rb_raise) + * + * Set last error level and last error message if applicable and available + */ +void my_raise(VALUE obj, s_cleanup *clean, VALUE rbExcep, char *err) +{ + xmlErrorPtr xmlErr = NULL; + VALUE hErr; + + if (!NIL_P(obj)) + { + xmlErr = xmlGetLastError(); + hErr = rb_hash_new(); + if (xmlErr) + { + // It seems we usually get a \n at the end of the msg, get rid of it + if (*(xmlErr->message+strlen(xmlErr->message)-1) == '\n') + *(xmlErr->message+strlen(xmlErr->message)-1) = '\0'; + // Build hash with error level, code and message + rb_hash_aset(hErr, rb_str_new2("xmlErrCode"), INT2FIX(xmlErr->code)); + rb_hash_aset(hErr, rb_str_new2("xmlErrLevel"), INT2FIX(xmlErr->level)); + rb_hash_aset(hErr, rb_str_new2("xmlErrMsg"), rb_str_new2(xmlErr->message)); + } + else + { + // Build hash with only an error code of 0 + rb_hash_aset(hErr, rb_str_new2("xmlErrCode"), INT2FIX(0)); + rb_hash_aset(hErr, rb_str_new2("xmlErrLevel"), INT2FIX(0)); + } + rb_iv_set(obj, "@xerr", hErr); + } + + if (clean) + { + //dumpCleanup("Freeing pointers", *clean); + free(clean->params); + xmlFree(clean->docstr); + xmlFreeDoc(clean->docres); + xmlFreeDoc(clean->docxml); + //xmlFreeDoc(clean->docxsl); segfault /\/ Veillard said xsltFreeStylesheet(xsl) does it + xsltFreeStylesheet(clean->xsl); + } + // Clean up xml stuff + xmlCleanupInputCallbacks(); + xmlCleanupOutputCallbacks(); + xmlResetError(xmlErr); + xmlResetLastError(); + xsltCleanupGlobals(); + xmlCleanupParser(); + xsltSetGenericErrorFunc(NULL, NULL); + + // Reset global variables to let ruby's GC do its work + g_xroot = Qnil; + g_xfiles = Qnil; + g_xmsg = Qnil; + + // Raise exception if requested to + if (rbExcep != Qnil) + { + rb_raise(rbExcep, err); + } +} + + +/* + * Register input callback with libxml2 + * + * We need to repeat this call because libxml cleanup unregisters and we like cleaning up + */ +void my_register_xml(void) +{ + // Enable exslt + exsltRegisterAll(); + + // Register default callbacks, e.g.http:// + xmlRegisterDefaultInputCallbacks(); + xmlRegisterDefaultOutputCallbacks(); + +/* NO NEED xmlRegisterInputCallbacks(xmlIOHTTPMatch, xmlIOHTTPOpen, xmlIOHTTPRead, xmlIOHTTPClose); +xmlRegisterInputCallbacks(xmlFileMatch, xmlFileOpen, xmlFileRead, xmlFileClose);*/ + + // Add our own file input callback + if (xmlRegisterInputCallbacks(XRootMatch, XRootInputOpen, xmlFileRead, XRootClose) < 0) + { + rb_raise(rb_eSystemCallError, "Failed to register input callbacks"); + } + + // Add our own file output callback to support exslt:document + if (xmlRegisterOutputCallbacks(XRootMatch, XRootOutputOpen, xmlFileWrite, xmlFileClose) < 0) + { + rb_raise(rb_eSystemCallError, "Failed to register output callbacks"); + } + // Add our own xsl:message handler + xsltSetGenericErrorFunc(NULL, xslMessageHandler); + + xsltDebugSetDefaultTrace(XSLT_TRACE_NONE); + xmlSubstituteEntitiesDefault(1); + xmlLoadExtDtdDefaultValue=1; +} + + +/* + * Check that parameters are usable, i.e. like + * [p1, v1] : single parameter + * [[p1, v1], [p2, v2]...] : several pairs of (param name, value) + * {p1=>v1...} : a hash of (param name, value) + * nil : no parameter + * + * Raise an exceptiom if not happy or return the list of params as + * [[p1, v1], [p2, v2]...] + */ +VALUE check_params(VALUE xparams) +{ + VALUE retparams=Qnil; + + if (!NIL_P(xparams)) + { + VALUE ary; + VALUE param; + int len, plen; + int i; + + // Reject some single values straight away + switch (TYPE(xparams)) + { + case T_FLOAT: + case T_REGEXP: + case T_FIXNUM: + case T_BIGNUM: + case T_STRUCT: + case T_FILE: + case T_TRUE: + case T_FALSE: + case T_DATA: + case T_SYMBOL: + rb_raise(rb_eTypeError, "Invalid parameters"); + return Qnil; + } + // if xparams is not an array, try to make one + ary = rb_funcall(xparams, id.to_a, 0); + + // Now check that our array is a suitable array: + // empty array => Qnil + // array.length==2, could be 2 params [[p1,v1],[p2,v2]] or 1 param [p,v] + // if both items are arrays, we have a list of params, otherwise we have a single param + len = RARRAY(ary)->len; + switch (len) + { + case 0: + retparams = Qnil; + break; + case 2: + // fall through to default if we have 2 arrays, otherwise, we must have 2 strings + if (! (TYPE(rb_ary_entry(ary,0))==T_ARRAY && TYPE(rb_ary_entry(ary,1))==T_ARRAY)) + { + VALUE s1 = rb_funcall(rb_ary_entry(ary,0), id.to_s, 0); + VALUE s2 = rb_funcall(rb_ary_entry(ary,1), id.to_s, 0); + + // Both items must be strings + retparams = rb_ary_new3(2L, s1, s2); + break; + } + default: + // scan array and check that each item is an array of 2 strings + retparams = rb_ary_new(); + for (i=0; i < len; ++i) + { + if ( TYPE(rb_ary_entry(ary,i)) != T_ARRAY ) + { + rb_raise(rb_eTypeError, "Invalid parameters"); + return Qnil; + } + param = rb_ary_entry(ary,i); + plen = NUM2INT(rb_funcall(param, id.length, 0)); + if ( plen != 2 ) + { + rb_raise(rb_eTypeError, "Invalid parameters"); + return Qnil; + } + VALUE s1 = rb_funcall(rb_ary_entry(param,0), id.to_s, 0); + VALUE s2 = rb_funcall(rb_ary_entry(param,1), id.to_s, 0); + + rb_ary_push(retparams, rb_ary_new3(2L, s1, s2)); + } + } + } + return retparams; +} + + +/* + * Build array of pointers to strings + * + * return NULL or pointer + */ +char *build_params(VALUE rbparams) +{ + char *ret; + char **paramPtr; + char *paramData; + int i; + VALUE tempval; + VALUE tempstr; + char quotingChar; + + if (rbparams == Qnil) + // You shoud not call this if you have no params, see it as an error + return NULL; + + // Compute total block size in one go + tempval = rb_funcall(rbparams, id.to_s, 0); + ret = malloc ( ((RARRAY(rbparams)->len)*2+1) * sizeof(void *) // Two pointers per [param, value] + 1 NULL + + (RARRAY(rbparams)->len) * 4 * sizeof(char) // Quotes around values + 1 NULL per value + + (RSTRING(tempval)->len) * sizeof(char) // Size of param names & values + ); + if ( ret==NULL) + return NULL; // out of memory + + paramPtr = (char **)ret; + paramData = ret + ((RARRAY(rbparams)->len)*2+1) * sizeof(void *); + // Copy each param name & value + for (i=0; i<RARRAY(rbparams)->len; ++i) + { + tempval = rb_ary_entry(rbparams, i); // ith param, i.e. [name, value] + + // 1. Add param name + + tempstr = rb_ary_entry(tempval, 0); // param name + // Add param name address to list of pointers + *paramPtr++ = paramData; + // Copy param name into data block + strcpy(paramData, RSTRING(tempstr)->ptr); + // Move data pointer after inserted string + paramData += 1+ RSTRING(tempstr)->len; + + // 2. Copy param value, quoting it with ' or " + + tempstr = rb_ary_entry(tempval, 1); // param value + // Don't bother if param is a mix of ' and ", users should know better :-) + // or it's been checked already. Here we expect params to be OK. + quotingChar = '"'; + if ( strchr(RSTRING(tempstr)->ptr, quotingChar) ) + quotingChar = '\''; // Use ' instead of " + + // Add para value address in list of pointers + *paramPtr++ = paramData; + + // Start with quoting character + *paramData++ = quotingChar; + // Copy value + strcpy(paramData, RSTRING(tempstr)->ptr); + // Move data pointer after inserted string + paramData += RSTRING(tempstr)->len; + // Close quote + *paramData++ = quotingChar; + // End string with \0 + *paramData++ = '\0'; + } + // Terminate list of pointers with a NULL + *paramPtr = NULL; + + return ret; +} + + + + +/* + * Parse stylesheet and xml document, apply stylesheet and return result + */ +VALUE xsl_process_real(VALUE none, VALUE self) +{ + s_cleanup myPointers; + int docstrlen; + + VALUE rbxml, rbxsl, rbout, rbparams, rbxroot; + + // Get instance data in a reliable format + rbxml = rb_iv_get(self, "@xml"); + if (NIL_P(rbxml)) + rb_raise(rb_eArgError, "No XML data"); + rbxml = StringValue(rbxml); + if (!RSTRING(rbxml)->len) + rb_raise(rb_eArgError, "No XML data"); + rbxsl = rb_iv_get(self, "@xsl"); + if (NIL_P(rbxsl)) + rb_raise(rb_eArgError, "No Stylesheet"); + rbxsl = StringValue(rbxsl); + if (!RSTRING(rbxsl)->len) + rb_raise(rb_eArgError, "No Stylesheet"); + rbxroot = rb_iv_get(self, "@xroot"); + rbparams = check_params(rb_iv_get(self, "@xparams")); + + // Initialize our globals + if (!NIL_P(rbxroot)) + g_xroot = StringValue(rbxroot); + g_xtrack = RTEST(rb_iv_get(self, "@xtrack")) ? Qtrue : Qfalse; + g_xfiles = rb_ary_new(); + g_xmsg = rb_ary_new(); + + // Register callbacks and stuff + my_register_xml(); + + // Make sure our pointers are all NULL + memset(&myPointers, '\0', sizeof(myPointers)); + + // Build param array + if (rbparams != Qnil) + if (NULL==(myPointers.params=build_params(rbparams))) + my_raise(self, &myPointers, rb_eNoMemError, "Cannot allocate parameter block"); + + // Parse XSL + if (looksLikeXML(rbxsl)) + { + myPointers.docxsl = xmlParseMemory(RSTRING(rbxsl)->ptr, RSTRING(rbxsl)->len); +// myPointers.docxsl = xmlReadMemory(RSTRING(rbxsl)->ptr, RSTRING(rbxsl)->len, ".", NULL, 0); + if (myPointers.docxsl == NULL) + { + my_raise(self, &myPointers, rb_eSystemCallError, "XSL parsing error"); + return Qnil; + } + myPointers.xsl = xsltParseStylesheetDoc(myPointers.docxsl); + if (myPointers.xsl == NULL) + { + my_raise(self, &myPointers, rb_eSystemCallError, "XSL stylesheet parsing error"); + return Qnil; + } + } + else // xsl is a filename + { + myPointers.xsl = xsltParseStylesheetFile(RSTRING(rbxsl)->ptr); + if (myPointers.xsl == NULL) + { + my_raise(self, &myPointers, rb_eSystemCallError, "XSL file loading error"); + return Qnil; + } + } + + // Parse XML + if (looksLikeXML(rbxml)) + { + myPointers.docxml = xmlReadMemory(RSTRING(rbxml)->ptr, RSTRING(rbxml)->len, ".", NULL, xmlOptions); + if (myPointers.docxml == NULL) + { + my_raise(self, &myPointers, rb_eSystemCallError, "XML parsing error"); + return Qnil; + } + } + else // xml is a filename + { + myPointers.docxml = xmlReadFile(RSTRING(rbxml)->ptr, NULL, xmlOptions); + if (myPointers.docxml == NULL) + { + my_raise(self, &myPointers, rb_eSystemCallError, "XML file parsing error"); + return Qnil; + } + } + + // Apply stylesheet to xml + myPointers.docres = xsltApplyStylesheet(myPointers.xsl, myPointers.docxml, (void*)myPointers.params); + if (myPointers.docres == NULL) + { + my_raise(self, &myPointers, rb_eSystemCallError, "Stylesheet apply error"); + return Qnil; + } + + xsltSaveResultToString(&(myPointers.docstr), &docstrlen, myPointers.docres, myPointers.xsl); + if ( docstrlen >= 1 ) + rbout = rb_str_new2((char*)(myPointers.docstr)); + else + rbout = Qnil; + rb_iv_set(self, "@xres", rbout); + rb_iv_set(self, "@xfiles", g_xfiles); + rb_iv_set(self, "@xmsg", g_xmsg); + + // Clean up, no exception to raise + my_raise(self, &myPointers, Qnil, NULL); + return rbout; +} + +// Use g_mutex to make sure our callbacks do not mess up the globals +// if the user is running several transforms in parallel threads +static VALUE in_sync(VALUE self) +{ + return rb_funcall(self, id.synchronize, 0); +} + +VALUE xsl_process(VALUE self) +{ + rb_iterate(in_sync, g_mutex, xsl_process_real, self); +} + +/* + * @xerr + */ +VALUE xsl_xerr_get( VALUE self ) +{ + return rb_iv_get(self, "@xerr"); +} + +/* + * @xres + */ +VALUE xsl_xres_get( VALUE self ) +{ + return rb_iv_get(self, "@xres"); +} + +/* + * @xmsg + */ +VALUE xsl_xmsg_get( VALUE self ) +{ + return rb_iv_get(self, "@xmsg"); +} + +/* + * @xfiles + */ +VALUE xsl_xfiles_get( VALUE self ) +{ + return rb_iv_get(self, "@xfiles"); +} + +/* + * @xparams + */ +VALUE xsl_xparams_set( VALUE self, VALUE xparams ) +{ + // Check params and raise an exception if not happy + check_params(xparams); + // Store parameters + return rb_iv_set(self, "@xparams", xparams); +} + +VALUE xsl_xparams_get( VALUE self ) +{ + return rb_iv_get(self, "@xparams"); +} + +/* + * @xroot + */ +VALUE xsl_xroot_set( VALUE self, VALUE xroot ) +{ + // Throw an exception if xroot cannot be used as a string + if (!NIL_P(xroot)) StringValue(xroot); + // Store param in @xroot + rb_iv_set(self, "@xroot", xroot); + + return xroot; +} + +VALUE xsl_xroot_get( VALUE self ) +{ + return rb_iv_get(self, "@xroot"); +} + +/* + * @xtrack + */ +VALUE xsl_xtrack_set( VALUE self, VALUE xtrack ) +{ + // @xtrack is true if param is neither Qnil nor QFalse + rb_iv_set(self, "@xtrack", RTEST(xtrack) ? Qtrue : Qfalse); + + return xtrack; +} + +VALUE xsl_xtrack_get( VALUE self ) +{ + return rb_iv_get(self, "@xtrack"); +} + +/* + * @xml + */ +VALUE xsl_xml_set( VALUE self, VALUE xml ) +{ + // Throw an exception if xml cannot be used as a string + if (!NIL_P(xml)) StringValue(xml); + // Store param in @xml + rb_iv_set(self, "@xml", xml); + + return xml; +} + +VALUE xsl_xml_get( VALUE self ) +{ + return rb_iv_get(self, "@xml"); +} + +/* + * @xsl + */ +VALUE xsl_xsl_set( VALUE self, VALUE xsl ) +{ + // Throw an exception if xsl cannot be used as a string + if (!NIL_P(xsl)) StringValue(xsl); + // Store param in @xsl + rb_iv_set(self, "@xsl", xsl); + + return xsl; +} + +VALUE xsl_xsl_get( VALUE self ) +{ + return rb_iv_get(self, "@xsl"); +} + + +static VALUE xsl_init(VALUE self) +{ + rb_iv_set(self, "@xml", Qnil); + rb_iv_set(self, "@xsl", Qnil); + rb_iv_set(self, "@xfiles", Qnil); + rb_iv_set(self, "@xmsg", Qnil); + rb_iv_set(self, "@xparams", Qnil); + rb_iv_set(self, "@xroot", Qnil); + rb_iv_set(self, "@xtrack", Qfalse); + rb_iv_set(self, "@xerr", Qnil); + + return self; +} + + +VALUE mGorg; +VALUE cXSL; + +/* + * Library Initialization + */ +void Init_xsl( void ) +{ + mGorg = rb_define_module( "Gorg" ); + cXSL = rb_define_class_under( mGorg, "XSL", rb_cObject ); + + // Get our lib global mutex + rb_require("thread"); + g_mutex = rb_eval_string("Mutex.new"); + + // Get method ID's + id.include = rb_intern("include?"); + id.to_a = rb_intern("to_a"); + id.to_s = rb_intern("to_s"); + id.length = rb_intern("length"); + id.synchronize = rb_intern("synchronize"); + + // Register lib global variables with ruby's GC + rb_global_variable(&g_mutex); + rb_global_variable(&g_xfiles); + rb_global_variable(&g_xmsg); + rb_global_variable(&g_xroot); + + rb_define_const( cXSL, "ENGINE_VERSION", rb_str_new2(xsltEngineVersion) ); + rb_define_const( cXSL, "LIBXSLT_VERSION", INT2NUM(xsltLibxsltVersion) ); + rb_define_const( cXSL, "LIBXML_VERSION", INT2NUM(xsltLibxmlVersion) ); + rb_define_const( cXSL, "XSLT_NAMESPACE", rb_str_new2(XSLT_NAMESPACE) ); + rb_define_const( cXSL, "DEFAULT_VENDOR", rb_str_new2(XSLT_DEFAULT_VENDOR) ); + rb_define_const( cXSL, "DEFAULT_VERSION", rb_str_new2(XSLT_DEFAULT_VERSION) ); + rb_define_const( cXSL, "DEFAULT_URL", rb_str_new2(XSLT_DEFAULT_URL) ); + rb_define_const( cXSL, "NAMESPACE_LIBXSLT", rb_str_new2(XSLT_LIBXSLT_NAMESPACE) ); + + rb_define_method( cXSL, "initialize", xsl_init, 0 ); + + rb_define_method( cXSL, "xmsg", xsl_xmsg_get, 0 ); // Return array of '%%GORG%%.*' strings returned by the XSL transform with <xsl:message> + rb_define_method( cXSL, "xfiles", xsl_xfiles_get, 0 ); // Return array of names of all files that libxml2 opened during last process + rb_define_method( cXSL, "xparams", xsl_xparams_get, 0 ); // Return hash of params + rb_define_method( cXSL, "xparams=", xsl_xparams_set, 1 ); // Set hash of params to pass to the xslt processor {"name" => "value"...} + rb_define_method( cXSL, "xroot", xsl_xroot_get, 0 ); // Root dir where we should look for files with absolute path + rb_define_method( cXSL, "xroot=", xsl_xroot_set, 1 ); // See the root dir as a $DocumentRoot + rb_define_method( cXSL, "xtrack?", xsl_xtrack_get, 0 ); // Should I track the files that libxml2 opens + rb_define_method( cXSL, "xtrack=", xsl_xtrack_set, 1 ); // Track the files that libxml2 opens, or not + rb_define_method( cXSL, "xml", xsl_xml_get, 0 ); + rb_define_method( cXSL, "xml=", xsl_xml_set, 1 ); + rb_define_method( cXSL, "xsl", xsl_xsl_get, 0 ); + rb_define_method( cXSL, "xsl=", xsl_xsl_set, 1 ); + rb_define_method( cXSL, "xerr", xsl_xerr_get, 0 ); + rb_define_method( cXSL, "xres", xsl_xres_get, 0 ); + rb_define_method( cXSL, "process", xsl_process, 0 ); +} |