From a3729a119787e2946703b1813a74a75b710a48aa Mon Sep 17 00:00:00 2001 From: Martin von Gagern Date: Fri, 30 Jan 2015 14:07:07 +0100 Subject: [PATCH 1/5] Implement arbitrary extension functions written in JavaScript. For asynchroneous calls, the callbacks are performed on the main event loop while the worker thread is blocked on a mutex. At the moment, only boolean, numeric and string values can be passed between the XSLT processor and the JavaScript function. Other types would be highly desirable, but might require a lot more work. --- binding.gyp | 9 +- index.js | 12 ++- package.json | 2 +- src/extensions.cc | 194 ++++++++++++++++++++++++++++++++++++++++++++ src/node_libxslt.cc | 7 +- test/libxslt.js | 36 +++++++- 6 files changed, 254 insertions(+), 6 deletions(-) create mode 100644 src/extensions.cc diff --git a/binding.gyp b/binding.gyp index f7872ab..1a9d420 100644 --- a/binding.gyp +++ b/binding.gyp @@ -2,7 +2,12 @@ "targets": [ { "target_name": "node-libxslt", - "sources": [ "src/node_libxslt.cc", "src/stylesheet.cc" ], + "sources": [ + "src/node_libxslt.cc", + "src/stylesheet.cc", + "src/extensions.cc" + ], + "cflags" : [ "-std=c++11" ], "include_dirs": [" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "./node_libxslt.h" +#include "./stylesheet.h" + +#if 0 +#define DBG(x) std::cerr << x << std::endl +#else +#define DBG(x) +#endif + +class Module; + +// Global variable used as registry +static std::map< std::basic_string, + std::unique_ptr > moduleMap; + +// Still looking for a more elegant way to identify the main event loop thread +static decltype(uv_thread_self()) main_thread; + +static inline std::basic_string +stringNode2xml(v8::Handle obj) { + v8::Local jstr = obj->ToString(); + size_t len = jstr->Utf8Length(); + std::unique_ptr cstr(new xmlChar[len + 1]); + jstr->WriteUtf8(reinterpret_cast(cstr.get())); + return { cstr.get(), len }; +} + +static v8::Local xpath2node(xmlXPathObjectPtr in) { + switch (in->type) { + case XPATH_BOOLEAN: + return NanNew(in->boolval); + case XPATH_NUMBER: + return NanNew(in->floatval); + case XPATH_STRING: + return NanNew(in->stringval); + default: + NanThrowTypeError("XPath argument type not supported yet."); + return NanNull(); + } +} + +static xmlXPathObjectPtr node2xpath(v8::Handle in) { + if (in->IsBoolean() || in->IsBooleanObject()) + return xmlXPathNewBoolean(in->BooleanValue()); + if (in->IsNumber() || in->IsNumberObject()) + return xmlXPathNewFloat(in->NumberValue()); + std::basic_string str = stringNode2xml(in); + return xmlXPathNewString(str.c_str()); +} + +class Module { +public: + std::map< std::basic_string, + std::unique_ptr > functions; +}; + +// Compatibility function, since 59658a8 uv_thread_self() returns uv_thread_t +static inline bool uv_thread_equal(unsigned long a, unsigned long b) { + return a == b; +} + +class Context { +public: + Context() { + uv_async_init(uv_default_loop(), &async, evalFunction2); + async.data = this; + uv_mutex_init(&mtx); + } + ~Context() { + uv_mutex_destroy(&mtx); + } + void evalFunction1(xmlXPathParserContextPtr ctxt, int nargs) { + this->ctxt = ctxt; + argc = nargs; + DBG("evalFunction1 called on " << uv_thread_self()); + if (uv_thread_equal(uv_thread_self(), main_thread)) { + // synchroneous operation, so we can call back to v8 directly + evalFunction3(); + } + else { + // called from worker thread, no v8 calls here please + uv_mutex_lock(&mtx); + uv_async_send(&async); + // wait for evalFunction3 to unlock the mutex + uv_mutex_lock(&mtx); + // the value is already on the ctx stack, so we continue + uv_mutex_unlock(&mtx); + } + } + +private: + uv_mutex_t mtx; + uv_async_t async; + xmlXPathParserContextPtr ctxt; + int argc; + + static NAUV_WORK_CB(evalFunction2) { + // called from main event loop + Context *c = static_cast(async->data); + c->evalFunction3(); + uv_mutex_unlock(&c->mtx); + } + void evalFunction3() { + // called from main event loop + NanScope(); + xmlXPathContextPtr pctxt = ctxt->context; + NanCallback& cb = + *moduleMap.at(pctxt->functionURI)->functions.at(pctxt->function); + std::unique_ptr[]> argv + (new v8::Handle[argc]); + int i = argc; + while (i > 0) { + xmlXPathObjectPtr xobj = valuePop(ctxt); + argv[--i] = xpath2node(xobj); + xmlXPathFreeObject(xobj); + } + v8::Handle res = cb.Call(argc, argv.get()); + valuePush(ctxt, node2xpath(res)); + // Now we signal the worker that the result is available + } +}; + +static void evalFunction(xmlXPathParserContextPtr ctxt, int nargs) { + // This is called on the XSLT thread, so we should not do Node stuff here. + xsltTransformContextPtr tctxt = xsltXPathGetTransformContext(ctxt); + Context *c = reinterpret_cast + (xsltGetExtData(tctxt, ctxt->context->functionURI)); + c->evalFunction1(ctxt, nargs); +} + +static void* initFunc(xsltTransformContextPtr ctxt, const xmlChar *uri) { + DBG("Context initialization started."); + Module& module = *moduleMap.at(uri); + for (auto i = module.functions.begin(), e = module.functions.end(); + i != e; ++i) { + xsltRegisterExtFunction(ctxt, i->first.c_str(), + uri, evalFunction); + DBG("Registering {" << (char*)uri << "}" << (char*)i->first.c_str()); + } + Context *c = new Context(); + DBG("Context initialization finished."); + return c; +} + +static void shutdownFunc(xsltTransformContextPtr ctxt, + const xmlChar *uri, void *data) { + Context *c = static_cast(data); + delete c; +} + +NAN_METHOD(RegisterFunction) { + NanScope(); + main_thread = uv_thread_self(); // let's hope this is the main thread. + DBG("Main thread is " << main_thread); + std::basic_string name = stringNode2xml(args[0]); + std::basic_string uri = stringNode2xml(args[1]); + std::unique_ptr cb(new NanCallback(args[2].As())); + auto i = moduleMap.find(uri); + if (i == moduleMap.end()) { + i = moduleMap.emplace(uri, nullptr).first; + i->second.reset(new Module()); + xsltRegisterExtModule(i->first.c_str(), initFunc, shutdownFunc); + DBG("Registered module " << (char*)i->first.c_str()); + } + Module& mod = *(i->second); + mod.functions.emplace(name, std::move(cb)); + DBG("Registered {" << (char*)uri.c_str() << "}" << (char*)name.c_str()); + NanReturnUndefined(); +} + +NAN_METHOD(ShutdownOnExit) { + DBG("Shutting down."); + // We need to clear the moduleMap while V8 is still alive. + // Otherwise the destructors of the persistent handles will segfault. + // To avoid accidents, we unregister all our modules as well. + NanScope(); + for (auto i = moduleMap.begin(), e = moduleMap.end(); i != e; ++i) + xsltUnregisterExtModule(i->first.c_str()); + moduleMap.clear(); + NanReturnUndefined(); +} diff --git a/src/node_libxslt.cc b/src/node_libxslt.cc index 77254fd..ea397eb 100644 --- a/src/node_libxslt.cc +++ b/src/node_libxslt.cc @@ -199,6 +199,9 @@ NAN_METHOD(RegisterEXSLT) { NanReturnUndefined(); } +NAN_METHOD(RegisterFunction); +NAN_METHOD(ShutdownOnExit); + // Compose the module by assigning the methods previously prepared void InitAll(Handle exports) { Stylesheet::Init(exports); @@ -207,5 +210,7 @@ void InitAll(Handle exports) { exports->Set(NanNew("applySync"), NanNew(ApplySync)->GetFunction()); exports->Set(NanNew("applyAsync"), NanNew(ApplyAsync)->GetFunction()); exports->Set(NanNew("registerEXSLT"), NanNew(RegisterEXSLT)->GetFunction()); + exports->Set(NanNew("registerFunction"), NanNew(RegisterFunction)->GetFunction()); + exports->Set(NanNew("shutdownOnExit"), NanNew(ShutdownOnExit)->GetFunction()); } -NODE_MODULE(node_libxslt, InitAll); \ No newline at end of file +NODE_MODULE(node_libxslt, InitAll); diff --git a/test/libxslt.js b/test/libxslt.js index dfffaf5..a85b7e7 100644 --- a/test/libxslt.js +++ b/test/libxslt.js @@ -176,4 +176,38 @@ describe('node-libxslt', function() { }); }); }); -}); \ No newline at end of file + + describe('custom extension functions', function(){ + var my_ns = "https://github.com/albanm/node-libxslt/issues/2"; + var my_xsl = + ('' + + '' + + '' + + '' + + ''); + var stylesheet; + function foo() { + return JSON.stringify([].slice.call(arguments)); + } + it('need to be registered', function() { + libxslt.registerFunction("foo", my_ns, foo); + }); + it('can be referenced in stylesheets', function() { + // remember that we need extension-element-prefixes in libxslt + stylesheet = libxslt.parse(my_xsl); + }); + it('can be called synchroneously', function() { + var res = stylesheet.apply(''); + res.should.match(/\[3,"a",true\]<\/res>/) + }); + it('can be called asynchroneously', function() { + stylesheet.apply('', null, function(err, res) { + should.not.exist(err); + res.should.match(/\[3,"a",true\]<\/res>/) + }); + }); + }); +}); From 9538f079f2472a9fd2e447db419bd923cc5c5228 Mon Sep 17 00:00:00 2001 From: Martin von Gagern Date: Fri, 30 Jan 2015 23:08:29 +0100 Subject: [PATCH 2/5] Close async for extension function evaluation. This ensures that the main event loop will terminate without a call to process.exit. And it satisfies the requirement from the manual that the async must be closed before being deallocated. Closing on the v8 thread avoids an abort when v8 itself shuts down, details unknown. --- src/extensions.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/extensions.cc b/src/extensions.cc index d14c77c..a09f338 100644 --- a/src/extensions.cc +++ b/src/extensions.cc @@ -129,6 +129,7 @@ class Context { } v8::Handle res = cb.Call(argc, argv.get()); valuePush(ctxt, node2xpath(res)); + uv_close(reinterpret_cast(&async), nullptr); // Now we signal the worker that the result is available } }; From becb3aff57c7fe16ee9e57fab9bdcf488b28e202 Mon Sep 17 00:00:00 2001 From: Martin von Gagern Date: Mon, 2 Feb 2015 11:55:58 +0100 Subject: [PATCH 3/5] Change extensions code from C++11 to C++03. --- binding.gyp | 1 - src/extensions.cc | 52 ++++++++++++++++++++++++++++++----------------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/binding.gyp b/binding.gyp index 1a9d420..02b7135 100644 --- a/binding.gyp +++ b/binding.gyp @@ -7,7 +7,6 @@ "src/stylesheet.cc", "src/extensions.cc" ], - "cflags" : [ "-std=c++11" ], "include_dirs": [", - std::unique_ptr > moduleMap; +static std::map< std::basic_string, Module* > moduleMap; +typedef std::map< std::basic_string, Module* >::iterator moduleMapIter; // Still looking for a more elegant way to identify the main event loop thread -static decltype(uv_thread_self()) main_thread; +#if NAUV_UVVERSION < 0x010000 +static unsigned long main_thread; +#else +static uv_thread_t main_thread; +#endif static inline std::basic_string stringNode2xml(v8::Handle obj) { v8::Local jstr = obj->ToString(); size_t len = jstr->Utf8Length(); - std::unique_ptr cstr(new xmlChar[len + 1]); - jstr->WriteUtf8(reinterpret_cast(cstr.get())); - return { cstr.get(), len }; + xmlChar *cstr = new xmlChar[len + 1]; + jstr->WriteUtf8(reinterpret_cast(cstr)); + std::basic_string str(cstr, len); + delete[] cstr; + return str; } static v8::Local xpath2node(xmlXPathObjectPtr in) { @@ -63,8 +69,14 @@ static xmlXPathObjectPtr node2xpath(v8::Handle in) { class Module { public: - std::map< std::basic_string, - std::unique_ptr > functions; + ~Module() { + for (iter i = functions.begin(), e = functions.end(); + i != e; ++i) { + delete i->second; + } + } + std::map< std::basic_string, NanCallback* > functions; + typedef std::map< std::basic_string, NanCallback* >::iterator iter; }; // Compatibility function, since 59658a8 uv_thread_self() returns uv_thread_t @@ -119,17 +131,17 @@ class Context { xmlXPathContextPtr pctxt = ctxt->context; NanCallback& cb = *moduleMap.at(pctxt->functionURI)->functions.at(pctxt->function); - std::unique_ptr[]> argv - (new v8::Handle[argc]); + v8::Handle *argv = new v8::Handle[argc]; int i = argc; while (i > 0) { xmlXPathObjectPtr xobj = valuePop(ctxt); argv[--i] = xpath2node(xobj); xmlXPathFreeObject(xobj); } - v8::Handle res = cb.Call(argc, argv.get()); + v8::Handle res = cb.Call(argc, argv); + delete[] argv; valuePush(ctxt, node2xpath(res)); - uv_close(reinterpret_cast(&async), nullptr); + uv_close(reinterpret_cast(&async), NULL); // Now we signal the worker that the result is available } }; @@ -145,7 +157,7 @@ static void evalFunction(xmlXPathParserContextPtr ctxt, int nargs) { static void* initFunc(xsltTransformContextPtr ctxt, const xmlChar *uri) { DBG("Context initialization started."); Module& module = *moduleMap.at(uri); - for (auto i = module.functions.begin(), e = module.functions.end(); + for (Module::iter i = module.functions.begin(), e = module.functions.end(); i != e; ++i) { xsltRegisterExtFunction(ctxt, i->first.c_str(), uri, evalFunction); @@ -168,16 +180,15 @@ NAN_METHOD(RegisterFunction) { DBG("Main thread is " << main_thread); std::basic_string name = stringNode2xml(args[0]); std::basic_string uri = stringNode2xml(args[1]); - std::unique_ptr cb(new NanCallback(args[2].As())); - auto i = moduleMap.find(uri); + NanCallback *cb = new NanCallback(args[2].As()); + moduleMapIter i = moduleMap.find(uri); if (i == moduleMap.end()) { - i = moduleMap.emplace(uri, nullptr).first; - i->second.reset(new Module()); + i = moduleMap.insert(std::make_pair(uri, new Module())).first; xsltRegisterExtModule(i->first.c_str(), initFunc, shutdownFunc); DBG("Registered module " << (char*)i->first.c_str()); } Module& mod = *(i->second); - mod.functions.emplace(name, std::move(cb)); + mod.functions.insert(std::make_pair(name, cb)); // mod assumes ownership of cb DBG("Registered {" << (char*)uri.c_str() << "}" << (char*)name.c_str()); NanReturnUndefined(); } @@ -188,8 +199,11 @@ NAN_METHOD(ShutdownOnExit) { // Otherwise the destructors of the persistent handles will segfault. // To avoid accidents, we unregister all our modules as well. NanScope(); - for (auto i = moduleMap.begin(), e = moduleMap.end(); i != e; ++i) + for (moduleMapIter i = moduleMap.begin(), e = moduleMap.end(); + i != e; ++i) { xsltUnregisterExtModule(i->first.c_str()); + delete i->second; + } moduleMap.clear(); NanReturnUndefined(); } From 3e44e2ad3586b32c3ee04f5951aeeb2b7fd43aec Mon Sep 17 00:00:00 2001 From: Martin von Gagern Date: Sat, 4 Apr 2015 01:53:22 +0200 Subject: [PATCH 4/5] Rework extensions handling. The core idea is that we allocate the context before we apply the stylesheet. That way we can store task-specific stuff in that context, like the information whether we are single- or multi-threaded. We might also be storing per-context functions there. --- binding.gyp | 1 + .../linux/arm/libxslt/xsltconfig.h | 2 +- .../linux/ia32/libxslt/xsltconfig.h | 2 +- .../linux/x64/libxslt/xsltconfig.h | 2 +- .../mac/ia32/libxslt/xsltconfig.h | 2 +- .../mac/x64/libxslt/xsltconfig.h | 2 +- .../solaris/ia32/libxslt/xsltconfig.h | 2 +- .../solaris/x64/libxslt/xsltconfig.h | 2 +- .../win/ia32/libxslt/xsltconfig.h | 2 +- .../win/x64/libxslt/xsltconfig.h | 2 +- package.json | 2 +- src/extensions.cc | 204 ++++++++++++------ src/node_libxslt.cc | 42 +++- 13 files changed, 182 insertions(+), 85 deletions(-) diff --git a/binding.gyp b/binding.gyp index c5e9680..07876b8 100644 --- a/binding.gyp +++ b/binding.gyp @@ -7,6 +7,7 @@ "src/stylesheet.cc", "src/extensions.cc" ], + "cflags" : [ "-O0 -g" ], "include_dirs": [", Module* > moduleMap; typedef std::map< std::basic_string, Module* >::iterator moduleMapIter; -// Still looking for a more elegant way to identify the main event loop thread -#if NAUV_UVVERSION < 0x010000 -static unsigned long main_thread; -#else -static uv_thread_t main_thread; -#endif - static inline std::basic_string stringNode2xml(v8::Handle obj) { v8::Local jstr = obj->ToString(); @@ -79,81 +88,113 @@ class Module { typedef std::map< std::basic_string, NanCallback* >::iterator iter; }; -// Compatibility function, since 59658a8 uv_thread_self() returns uv_thread_t -static inline bool uv_thread_equal(unsigned long a, unsigned long b) { - return a == b; +const xmlChar* NodeXsltContext::NAMESPACE = (const xmlChar*) + "https://github.com/albanm/node-libxslt"; + +void NodeXsltContext::dispose() { + delete this; } -class Context { +// Synchroneous function evaluation, called from main event loop +void NodeXsltContext::evalFunction(xmlXPathParserContextPtr ctxt, int nargs) { + DBG("evalFunction 5"); + NanScope(); + xmlXPathContextPtr pctxt = ctxt->context; + NanCallback& cb = + *moduleMap.at(pctxt->functionURI)->functions.at(pctxt->function); + v8::Handle *args = new v8::Handle[nargs]; + int i = nargs; + while (i > 0) { + xmlXPathObjectPtr xobj = valuePop(ctxt); + args[--i] = xpath2node(xobj); + xmlXPathFreeObject(xobj); + } + v8::Handle res = cb.Call(nargs, args); + delete[] args; + valuePush(ctxt, node2xpath(res)); +} + +class AsyncNodeXsltContext : public NodeXsltContext { public: - Context() { - uv_async_init(uv_default_loop(), &async, evalFunction2); - async.data = this; + AsyncNodeXsltContext() { + DBG("Creating async"); uv_mutex_init(&mtx); + uv_async_init(uv_default_loop(), &async, receiveAsync); + async.data = this; } - ~Context() { - uv_mutex_destroy(&mtx); - } - void evalFunction1(xmlXPathParserContextPtr ctxt, int nargs) { - this->ctxt = ctxt; - argc = nargs; - DBG("evalFunction1 called on " << uv_thread_self()); - if (uv_thread_equal(uv_thread_self(), main_thread)) { - // synchroneous operation, so we can call back to v8 directly - evalFunction3(); - } - else { - // called from worker thread, no v8 calls here please - uv_mutex_lock(&mtx); - uv_async_send(&async); - // wait for evalFunction3 to unlock the mutex - uv_mutex_lock(&mtx); - // the value is already on the ctx stack, so we continue - uv_mutex_unlock(&mtx); - } + ~AsyncNodeXsltContext() { + DBG("Deleting context"); } + void dispose(); + void evalFunction(xmlXPathParserContextPtr ctxt, int nargs); private: + + static NAUV_WORK_CB(receiveAsync); + static void disposed(uv_handle_t* handle); + void evalFunctionSync(); + uv_mutex_t mtx; uv_async_t async; xmlXPathParserContextPtr ctxt; - int argc; + int nargs; - static NAUV_WORK_CB(evalFunction2) { - // called from main event loop - Context *c = static_cast(async->data); - c->evalFunction3(); - uv_mutex_unlock(&c->mtx); - } - void evalFunction3() { - // called from main event loop - NanScope(); - xmlXPathContextPtr pctxt = ctxt->context; - NanCallback& cb = - *moduleMap.at(pctxt->functionURI)->functions.at(pctxt->function); - v8::Handle *argv = new v8::Handle[argc]; - int i = argc; - while (i > 0) { - xmlXPathObjectPtr xobj = valuePop(ctxt); - argv[--i] = xpath2node(xobj); - xmlXPathFreeObject(xobj); - } - v8::Handle res = cb.Call(argc, argv); - delete[] argv; - valuePush(ctxt, node2xpath(res)); - uv_close(reinterpret_cast(&async), NULL); - // Now we signal the worker that the result is available - } }; +NodeXsltContext* createContext(bool async) { + if (async) return new AsyncNodeXsltContext(); + else return new NodeXsltContext(); +} + +void AsyncNodeXsltContext::dispose() { + DBG("Closing async"); + uv_close(reinterpret_cast(&async), disposed); + uv_mutex_destroy(&mtx); +} + +void AsyncNodeXsltContext::disposed(uv_handle_t* handle) { + void* data = reinterpret_cast(handle)->data; + delete static_cast(data); +} + +void AsyncNodeXsltContext::evalFunction(xmlXPathParserContextPtr ctxt, int nargs) { + DBG("evalFunction 2"); + // called from worker thread, no v8 calls here please + this->ctxt = ctxt; + this->nargs = nargs; + uv_mutex_lock(&mtx); + DBG("got lock, sending"); + uv_async_send(&async); + DBG("sent, waiting"); + uv_mutex_lock(&mtx); // block till evalFunctionSync is done + DBG("received result, done"); + uv_mutex_unlock(&mtx); // result is already on the stack +} + +inline void AsyncNodeXsltContext::evalFunctionSync() { + DBG("evalFunction 4"); + NodeXsltContext::evalFunction(ctxt, nargs); // Call sync implementation + DBG("have result, unlocking"); + uv_mutex_unlock(&mtx); // signal that we are done +} + +NAUV_WORK_CB(AsyncNodeXsltContext::receiveAsync) { + DBG("evalFunction 3"); + // Called from main event loop in response to async. + // The mutex is locked at this point. + static_cast(async->data)->evalFunctionSync(); +} + static void evalFunction(xmlXPathParserContextPtr ctxt, int nargs) { + DBG("evalFunction 1"); // This is called on the XSLT thread, so we should not do Node stuff here. xsltTransformContextPtr tctxt = xsltXPathGetTransformContext(ctxt); - Context *c = reinterpret_cast - (xsltGetExtData(tctxt, ctxt->context->functionURI)); - c->evalFunction1(ctxt, nargs); + NodeXsltContext **c = reinterpret_cast + (xsltGetExtData(tctxt, NodeXsltContext::NAMESPACE)); + (**c).evalFunction(ctxt, nargs); } +#if 0 static void* initFunc(xsltTransformContextPtr ctxt, const xmlChar *uri) { DBG("Context initialization started."); Module& module = *moduleMap.at(uri); @@ -173,18 +214,17 @@ static void shutdownFunc(xsltTransformContextPtr ctxt, Context *c = static_cast(data); delete c; } +#endif NAN_METHOD(RegisterFunction) { NanScope(); - main_thread = uv_thread_self(); // let's hope this is the main thread. - DBG("Main thread is " << main_thread); std::basic_string name = stringNode2xml(args[0]); std::basic_string uri = stringNode2xml(args[1]); NanCallback *cb = new NanCallback(args[2].As()); moduleMapIter i = moduleMap.find(uri); if (i == moduleMap.end()) { i = moduleMap.insert(std::make_pair(uri, new Module())).first; - xsltRegisterExtModule(i->first.c_str(), initFunc, shutdownFunc); + // xsltRegisterExtModule(i->first.c_str(), initFunc, shutdownFunc); DBG("Registered module " << (char*)i->first.c_str()); } Module& mod = *(i->second); @@ -193,17 +233,45 @@ NAN_METHOD(RegisterFunction) { NanReturnUndefined(); } +static void* initNodeXsltContext(xsltTransformContextPtr ctxt, const xmlChar *uri) { + DBG("Context initialization started."); + for (moduleMapIter i = moduleMap.begin(), ei = moduleMap.end(); i != ei; ++i) { + const xmlChar* uri = i->first.c_str(); + Module* module = i->second; + for (Module::iter j = module->functions.begin(), ej = module->functions.end(); j != ej; ++j) { + xsltRegisterExtFunction(ctxt, j->first.c_str(), uri, evalFunction); + DBG("Registering {" << (char*)uri << "}" << (char*)j->first.c_str()); + } + } + DBG("Context initialization finished."); + return new NodeXsltContext*(); +} + +static void shutdownNodeXsltContext(xsltTransformContextPtr ctxt, + const xmlChar *uri, void *data) { + NodeXsltContext** pnctxt = static_cast(data); + if (*pnctxt) (**pnctxt).dispose(); + delete pnctxt; +} + +class Initialization { public: Initialization(); }; +static Initialization runCodeUponLoading; +Initialization::Initialization() { + xsltRegisterExtModule(NodeXsltContext::NAMESPACE, + initNodeXsltContext, shutdownNodeXsltContext); +} + NAN_METHOD(ShutdownOnExit) { DBG("Shutting down."); // We need to clear the moduleMap while V8 is still alive. // Otherwise the destructors of the persistent handles will segfault. // To avoid accidents, we unregister all our modules as well. NanScope(); - for (moduleMapIter i = moduleMap.begin(), e = moduleMap.end(); - i != e; ++i) { + for (moduleMapIter i = moduleMap.begin(), e = moduleMap.end(); i != e; ++i) { xsltUnregisterExtModule(i->first.c_str()); delete i->second; } moduleMap.clear(); + xsltUnregisterExtModule(NodeXsltContext::NAMESPACE); NanReturnUndefined(); } diff --git a/src/node_libxslt.cc b/src/node_libxslt.cc index ba9cf74..985f53c 100644 --- a/src/node_libxslt.cc +++ b/src/node_libxslt.cc @@ -5,6 +5,7 @@ #include #include #include +#include // includes from libxmljs #include @@ -12,6 +13,13 @@ #include "./node_libxslt.h" #include "./stylesheet.h" +#include "./extensions.h" + +#if 0 +#define DBG(x) std::cerr << x << std::endl +#else +#define DBG(x) +#endif using namespace v8; @@ -108,23 +116,31 @@ NAN_METHOD(ApplySync) { char** params = PrepareParams(paramsArray); - xmlDoc* result = xsltApplyStylesheet(stylesheet->stylesheet_obj, docSource->xml_obj, (const char **)params); + xsltStylesheetPtr style = stylesheet->stylesheet_obj; + xmlDocPtr doc = docSource->xml_obj; + xsltTransformContextPtr ctxt = xsltNewTransformContext(style, doc); + NodeXsltContext **pnctxt = reinterpret_cast + (xsltGetExtData(ctxt, NodeXsltContext::NAMESPACE)); + *pnctxt = createContext(false); + xmlDoc* result = xsltApplyStylesheetUser( + style, doc, (const char **)params, /* output */ NULL, /* profile */ NULL, ctxt); + xsltFreeTransformContext(ctxt); if (!result) { freeArray(params, paramsArray->Length()); return NanThrowError("Failed to apply stylesheet"); } // for some obscure reason I didn't manage to create a new libxmljs document in applySync, - // but passing a document by reference and modifying its content works fine + // but passing a document by reference and modifying its content works fine // replace the empty document in docResult with the result of the stylesheet - docResult->xml_obj->_private = NULL; + docResult->xml_obj->_private = NULL; xmlFreeDoc(docResult->xml_obj); docResult->xml_obj = result; result->_private = docResult; freeArray(params, paramsArray->Length()); - NanReturnUndefined(); + NanReturnUndefined(); } // for memory the segfault i previously fixed were due to xml documents being deleted @@ -132,7 +148,14 @@ NAN_METHOD(ApplySync) { class ApplyWorker : public NanAsyncWorker { public: ApplyWorker(Stylesheet* stylesheet, libxmljs::XmlDocument* docSource, char** params, int paramsLength, libxmljs::XmlDocument* docResult, NanCallback *callback) - : NanAsyncWorker(callback), stylesheet(stylesheet), docSource(docSource), params(params), paramsLength(paramsLength), docResult(docResult) {} + : NanAsyncWorker(callback), stylesheet(stylesheet), docSource(docSource), params(params), paramsLength(paramsLength), docResult(docResult) { + xsltStylesheetPtr style = stylesheet->stylesheet_obj; + xmlDocPtr doc = docSource->xml_obj; + ctxt = xsltNewTransformContext(style, doc); + NodeXsltContext **pnctxt = reinterpret_cast + (xsltGetExtData(ctxt, NodeXsltContext::NAMESPACE)); + *pnctxt = createContext(true); + } ~ApplyWorker() {} // Executed inside the worker-thread. @@ -140,7 +163,10 @@ class ApplyWorker : public NanAsyncWorker { // here, so everything we need for input and output // should go on `this`. void Execute () { - result = xsltApplyStylesheet(stylesheet->stylesheet_obj, docSource->xml_obj, (const char **)params); + xsltStylesheetPtr style = stylesheet->stylesheet_obj; + xmlDocPtr doc = docSource->xml_obj; + result = xsltApplyStylesheetUser( + style, doc, (const char **)params, /* output */ NULL, /* profile */ NULL, ctxt); } // Executed when the async work is complete @@ -149,6 +175,7 @@ class ApplyWorker : public NanAsyncWorker { void HandleOKCallback () { NanScope(); + xsltFreeTransformContext(ctxt); if (!result) { Local argv[] = { NanError("Failed to apply stylesheet") }; freeArray(params, paramsLength); @@ -156,7 +183,7 @@ class ApplyWorker : public NanAsyncWorker { } else { Local argv[] = { NanNull() }; - // for some obscure reason I didn't manage to create a new libxmljs document in applySync, + // for some obscure reason I didn't manage to create a new libxmljs document, // but passing a document by reference and modifying its content works fine // replace the empty document in docResult with the result of the stylesheet docResult->xml_obj->_private = NULL; @@ -176,6 +203,7 @@ class ApplyWorker : public NanAsyncWorker { char** params; int paramsLength; libxmljs::XmlDocument* docResult; + xsltTransformContextPtr ctxt; xmlDoc* result; }; From 1894ec02faf11d16ac9ff36cdbf4a574ef442da2 Mon Sep 17 00:00:00 2001 From: Martin von Gagern Date: Sat, 4 Apr 2015 02:22:09 +0200 Subject: [PATCH 5/5] Include one file I forgot, and fix one warning. --- src/extensions.h | 9 +++++++++ src/node_libxslt.cc | 2 ++ 2 files changed, 11 insertions(+) create mode 100644 src/extensions.h diff --git a/src/extensions.h b/src/extensions.h new file mode 100644 index 0000000..d61cacd --- /dev/null +++ b/src/extensions.h @@ -0,0 +1,9 @@ +class NodeXsltContext { +public: + virtual ~NodeXsltContext() { } + virtual void dispose(); + virtual void evalFunction(xmlXPathParserContextPtr ctxt, int nargs); + static const xmlChar* NAMESPACE; +}; + +NodeXsltContext* createContext(bool async); diff --git a/src/node_libxslt.cc b/src/node_libxslt.cc index 985f53c..67a372b 100644 --- a/src/node_libxslt.cc +++ b/src/node_libxslt.cc @@ -1,4 +1,6 @@ +#ifndef BUILDING_NODE_EXTENSION #define BUILDING_NODE_EXTENSION +#endif #include #include #include