diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 00000000..53d78aa6
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,46 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
+ // for the documentation about the tasks.json format
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "type": "npm",
+ "label": "Compile Watch",
+ "script": "compile-es6-watch",
+ "problemMatcher": [
+ "$tsc-watch"
+ ],
+ "presentation": {
+ "group": "group-build"
+ }
+ },
+ {
+ "type": "npm",
+ "label": "Bundle Watch",
+ "script": "bundle-watch",
+ "problemMatcher": [],
+ "presentation": {
+ "group": "group-build"
+ }
+ },
+ {
+ "type": "npm",
+ "label": "Web Server",
+ "script": "dev-start",
+ "problemMatcher": [],
+ "presentation": {
+ "group": "group-build"
+ }
+ },
+ {
+ "label": "build",
+ "dependsOn": [
+ "Compile Watch",
+ "Bundle Watch",
+ "Web Server"
+ ],
+ "group": "build",
+ "problemMatcher": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 0a8c80d0..07bf88d3 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,7 @@ npm install --save @hpcc-js/wasm
* `index.js` / `index.min.js` files: Exposes _all_ the available APIs for all WASM files.
* WASM Files:
* `graphvizlib.wasm`
+ * `expatlib.wasm`
* ...more to follow...
**Important**: WASM files are dynamically loaded at runtime (this is a browser / emscripten requirement), which has a few implications for the consumer:
@@ -29,6 +30,7 @@ npm install --save @hpcc-js/wasm
## API Reference
* [Common](#common)
* [GraphViz](#graphviz)
+* [Expat](#expat)
### Common
Utility functions relating to @hpcc-js/wasm as a package
@@ -201,6 +203,58 @@ Convenience function that performs **patchwork** layout, is equivalent to `layou
Convenience function that performs **twopi** layout, is equivalent to `layout(dotSource, outputFormat, "twopi");`.
+### Expat (`expatlib.wasm`)
+Expat WASM library, provides a simplified wrapper around the Expat XML Parser library, see [libexpat.github.io](https://libexpat.github.io/) for c++ details.
+
+#### Hello World
+```html
+
+
+
+
+
+ GraphViz WASM
+
+
+
+
+
+
+
+
+
+
+```
+
+#### Expat API
+
+# **parse**(_xml_, _callback_) ยท [<>](https://github.com/hpcc-systems/hpcc-js-wasm/blob/master/src/expat.ts "Source")
+
+* **_xml_**: XML String.
+* **_callback_**: Callback Object with the following methods:
+ * **startElement**(_tag_: string, _attrs_: {[key: string]: string]): void;
+ * **endElement**(_tag_: string): void;
+ * **characterData**(_content_: string): void;
+
+Parses the XML with suitable callbacks.
+
+**Note:** _characterData_ may get called several times for a single tag element.
+
## Building @hpcc-js/wasm
_Building is supported on both Linux (tested with Ubuntu 18.04) and Windows with WSL enabled (Ubuntu-18.04). Building in other environments should work, but may be missing certain prerequisites._
diff --git a/cpp/expat/CMakeLists.txt b/cpp/expat/CMakeLists.txt
index ea2bb6ed..a76d2dca 100644
--- a/cpp/expat/CMakeLists.txt
+++ b/cpp/expat/CMakeLists.txt
@@ -15,3 +15,5 @@ ADD_LIBRARY(expat STATIC
${EXPAT_LIB_DIR}/xmltok_ns.c
${EXPAT_LIB_DIR}/xmltok.c
)
+
+ADD_SUBDIRECTORY(expatlib)
diff --git a/cpp/expat/expatlib/CMakeLists.txt b/cpp/expat/expatlib/CMakeLists.txt
new file mode 100644
index 00000000..23da6506
--- /dev/null
+++ b/cpp/expat/expatlib/CMakeLists.txt
@@ -0,0 +1,33 @@
+PROJECT(expatlib)
+
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s WASM=1 -s INVOKE_RUN=0 -s ENVIRONMENT=web -s ALLOW_MEMORY_GROWTH=1")
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s MODULARIZE=1 -s EXPORT_NAME='${CMAKE_PROJECT_NAME}'")
+
+# Generate Glue from IDL file ---
+ADD_CUSTOM_COMMAND(
+ MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/main.idl
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/main_glue.js ${CMAKE_CURRENT_BINARY_DIR}/main_glue.cpp
+ COMMAND python ${CMAKE_BINARY_DIR}/../emsdk/upstream/emscripten/tools/webidl_binder.py ${CMAKE_CURRENT_SOURCE_DIR}/main.idl ${CMAKE_CURRENT_BINARY_DIR}/main_glue
+)
+SET_PROPERTY(SOURCE main.cpp APPEND PROPERTY OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/main_glue.cpp)
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --post-js ${CMAKE_CURRENT_BINARY_DIR}/main_glue.js")
+# --- --- ---
+
+INCLUDE_DIRECTORIES(
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${EXPAT_LIB_DIR}
+)
+
+ADD_EXECUTABLE(expatlib
+ main.cpp
+)
+
+TARGET_LINK_LIBRARIES(expatlib
+ expat
+)
+
+INSTALL(FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/expatlib.wasm
+ DESTINATION dist
+ COMPONENT runtime
+)
diff --git a/cpp/expat/expatlib/main.cpp b/cpp/expat/expatlib/main.cpp
new file mode 100644
index 00000000..39be9c12
--- /dev/null
+++ b/cpp/expat/expatlib/main.cpp
@@ -0,0 +1,100 @@
+#include "stack_parser.h"
+
+#include
+
+class CExpat : public CExpatImpl
+{
+private:
+ typedef CExpatImpl BaseClass;
+
+protected:
+ std::string m_tag;
+ std::string m_attrs;
+ std::string m_content;
+
+public:
+ CExpat()
+ {
+ }
+
+ void OnPostCreate()
+ {
+ EnableStartElementHandler();
+ EnableEndElementHandler();
+ EnableCharacterDataHandler();
+ }
+
+ bool create()
+ {
+ return BaseClass::Create();
+ }
+
+ void destroy()
+ {
+ BaseClass::Destroy();
+ }
+
+ bool parse(const char *xml)
+ {
+ return BaseClass::Parse(xml, (int)strlen(xml), XML_TRUE);
+ }
+
+ const char *tag() const
+ {
+ return m_tag.c_str();
+ }
+
+ const char *attrs() const
+ {
+ return m_attrs.c_str();
+ }
+
+ const char *content() const
+ {
+ return m_content.c_str();
+ }
+
+ virtual void startElement()
+ {
+ }
+
+ virtual void endElement()
+ {
+ }
+
+ virtual void characterData()
+ {
+ }
+
+ virtual void OnStartElement(const XML_Char *pszName, const XML_Char **papszAttrs)
+ {
+ m_tag = pszName;
+ m_attrs = "";
+ for (XML_Char **itr = (XML_Char **)papszAttrs; *itr != NULL; itr += 2)
+ {
+ if (!m_attrs.empty())
+ {
+ m_attrs += "\1\1";
+ }
+ m_attrs += *itr;
+ m_attrs += "\1";
+ m_attrs += *(itr + 1);
+ }
+ startElement();
+ }
+
+ virtual void OnEndElement(const XML_Char *pszName)
+ {
+ m_tag = pszName;
+ endElement();
+ }
+
+ virtual void OnCharacterData(const XML_Char *pszData, int nLength)
+ {
+ m_content.assign(pszData, nLength);
+ characterData();
+ }
+};
+
+// Include JS Glue ---
+#include "main_glue.cpp"
diff --git a/cpp/expat/expatlib/main.idl b/cpp/expat/expatlib/main.idl
new file mode 100644
index 00000000..33044193
--- /dev/null
+++ b/cpp/expat/expatlib/main.idl
@@ -0,0 +1,21 @@
+interface CExpat
+{
+ void CExpat();
+ boolean create();
+ void destroy();
+ boolean parse([Const] DOMString xml);
+ [Const] DOMString tag();
+ [Const] DOMString attrs();
+ [Const] DOMString content();
+ void startElement();
+ void endElement();
+ void characterData();
+};
+
+[JSImplementation = "CExpat"]
+interface CExpatJS {
+ void CExpatJS();
+ void startElement();
+ void endElement();
+ void characterData();
+};
diff --git a/cpp/expat/expatlib/stack_parser.h b/cpp/expat/expatlib/stack_parser.h
new file mode 100644
index 00000000..aba9e2b5
--- /dev/null
+++ b/cpp/expat/expatlib/stack_parser.h
@@ -0,0 +1,340 @@
+#include
+
+#include
+
+template
+class CExpatImpl
+{
+public:
+ CExpatImpl()
+ {
+ m_p = NULL;
+ }
+
+ ~CExpatImpl()
+ {
+ Destroy();
+ }
+
+public:
+ bool Create(const XML_Char *pszEncoding = NULL, const XML_Char *pszSep = NULL)
+ {
+ Destroy();
+
+ if (pszEncoding != NULL && pszEncoding[0] == 0)
+ pszEncoding = NULL;
+ if (pszSep != NULL && pszSep[0] == 0)
+ pszSep = NULL;
+
+ m_p = XML_ParserCreate_MM(pszEncoding, NULL, pszSep);
+ if (m_p == NULL)
+ return false;
+
+ _T *pThis = static_cast<_T *>(this);
+ pThis->OnPostCreate();
+
+ XML_SetUserData(m_p, (void *)this);
+ return true;
+ }
+
+ void Destroy()
+ {
+ if (m_p != NULL)
+ XML_ParserFree(m_p);
+ m_p = NULL;
+ }
+
+ bool Parse(const char *pszBuffer, int nLength = -1, bool fIsFinal = true)
+ {
+ if (nLength < 0)
+ nLength = strlen(pszBuffer) * sizeof(char);
+
+ return XML_Parse(m_p, (const char *)pszBuffer, nLength, fIsFinal) != 0;
+ }
+
+ bool ParseBuffer(int nLength, bool fIsFinal = true)
+ {
+ return XML_ParseBuffer(m_p, nLength, fIsFinal) != 0;
+ }
+
+ void *GetBuffer(int nLength)
+ {
+ return XML_GetBuffer(m_p, nLength);
+ }
+
+ void EnableStartElementHandler(bool fEnable = true)
+ {
+ XML_SetStartElementHandler(m_p, fEnable ? StartElementHandler : NULL);
+ }
+
+ void EnableEndElementHandler(bool fEnable = true)
+ {
+ XML_SetEndElementHandler(m_p, fEnable ? EndElementHandler : NULL);
+ }
+
+ void EnableElementHandler(bool fEnable = true)
+ {
+ EnableStartElementHandler(fEnable);
+ EnableEndElementHandler(fEnable);
+ }
+
+ void EnableCharacterDataHandler(bool fEnable = true)
+ {
+ XML_SetCharacterDataHandler(m_p, fEnable ? CharacterDataHandler : NULL);
+ }
+
+ void EnableProcessingInstructionHandler(bool fEnable = true)
+ {
+ XML_SetProcessingInstructionHandler(m_p, fEnable ? ProcessingInstructionHandler : NULL);
+ }
+
+ void EnableCommentHandler(bool fEnable = true)
+ {
+ XML_SetCommentHandler(m_p, fEnable ? CommentHandler : NULL);
+ }
+
+ void EnableStartCdataSectionHandler(bool fEnable = true)
+ {
+ XML_SetStartCdataSectionHandler(m_p, fEnable ? StartCdataSectionHandler : NULL);
+ }
+
+ void EnableEndCdataSectionHandler(bool fEnable = true)
+ {
+ XML_SetEndCdataSectionHandler(m_p, fEnable ? EndCdataSectionHandler : NULL);
+ }
+
+ void EnableCdataSectionHandler(bool fEnable = true)
+ {
+ EnableStartCdataSectionHandler(fEnable);
+ EnableEndCdataSectionHandler(fEnable);
+ }
+
+ void EnableDefaultHandler(bool fEnable = true, bool fExpand = true)
+ {
+ if (fExpand)
+ {
+ XML_SetDefaultHandlerExpand(m_p, fEnable ? DefaultHandler : NULL);
+ }
+ else
+ XML_SetDefaultHandler(m_p, fEnable ? DefaultHandler : NULL);
+ }
+
+ // void EnableExternalEntityRefHandler(bool fEnable = true)
+ // {
+ // assert(m_p != NULL);
+ // XML_SetExternalEntityRefHandler(m_p, fEnable ? ExternalEntityRefHandler : NULL);
+ // }
+
+ void EnableUnknownEncodingHandler(bool fEnable = true)
+ {
+ XML_SetUnknownEncodingHandler(m_p, fEnable ? UnknownEncodingHandler : NULL, 0);
+ }
+
+ void EnableStartNamespaceDeclHandler(bool fEnable = true)
+ {
+ XML_SetStartNamespaceDeclHandler(m_p, fEnable ? StartNamespaceDeclHandler : NULL);
+ }
+
+ void EnableEndNamespaceDeclHandler(bool fEnable = true)
+ {
+ XML_SetEndNamespaceDeclHandler(m_p, fEnable ? EndNamespaceDeclHandler : NULL);
+ }
+
+ void EnableNamespaceDeclHandler(bool fEnable = true)
+ {
+ EnableStartNamespaceDeclHandler(fEnable);
+ EnableEndNamespaceDeclHandler(fEnable);
+ }
+
+ void EnableXmlDeclHandler(bool fEnable = true)
+ {
+ XML_SetXmlDeclHandler(m_p, fEnable ? XmlDeclHandler : NULL);
+ }
+
+ void EnableStartDoctypeDeclHandler(bool fEnable = true)
+ {
+ XML_SetStartDoctypeDeclHandler(m_p, fEnable ? StartDoctypeDeclHandler : NULL);
+ }
+
+ void EnableEndDoctypeDeclHandler(bool fEnable = true)
+ {
+ XML_SetEndDoctypeDeclHandler(m_p, fEnable ? EndDoctypeDeclHandler : NULL);
+ }
+
+ void EnableDoctypeDeclHandler(bool fEnable = true)
+ {
+ EnableStartDoctypeDeclHandler(fEnable);
+ EnableEndDoctypeDeclHandler(fEnable);
+ }
+
+ enum XML_Error GetErrorCode()
+ {
+ return XML_GetErrorCode(m_p);
+ }
+
+ long GetCurrentByteIndex()
+ {
+ return XML_GetCurrentByteIndex(m_p);
+ }
+
+ int GetCurrentLineNumber()
+ {
+ return XML_GetCurrentLineNumber(m_p);
+ }
+
+ int GetCurrentColumnNumber()
+ {
+ return XML_GetCurrentColumnNumber(m_p);
+ }
+
+ int GetCurrentByteCount()
+ {
+ return XML_GetCurrentByteCount(m_p);
+ }
+
+ const char *GetInputContext(int *pnOffset, int *pnSize)
+ {
+ return XML_GetInputContext(m_p, pnOffset, pnSize);
+ }
+
+ const XML_LChar *GetErrorString()
+ {
+ return XML_ErrorString(GetErrorCode());
+ }
+
+ static const XML_LChar *GetExpatVersion()
+ {
+ return XML_ExpatVersion();
+ }
+
+ /* static void GetExpatVersion(int *pnMajor, int *pnMinor, int *pnMicro)
+ {
+ XML_expat_version v = XML_ExpatVersionInfo();
+ if(pnMajor)
+ *pnMajor = v .major;
+ if(pnMinor)
+ *pnMinor = v .minor;
+ if(pnMicro)
+ *pnMicro = v .micro;
+ }*/
+
+ static const XML_LChar *GetErrorString(enum XML_Error nError)
+ {
+ return XML_ErrorString(nError);
+ }
+
+ void OnStartElement(const XML_Char *pszName, const XML_Char **papszAttrs) {}
+ void OnEndElement(const XML_Char *pszName) {}
+ void OnCharacterData(const XML_Char *pszData, int nLength) {}
+ void OnProcessingInstruction(const XML_Char *pszTarget, const XML_Char *pszData) {}
+ void OnComment(const XML_Char *pszData) {}
+ void OnStartCdataSection() {}
+ void OnEndCdataSection() {}
+ void OnDefault(const XML_Char *pszData, int nLength) {}
+ bool OnExternalEntityRef(const XML_Char *pszContext, const XML_Char *pszBase, const XML_Char *pszSystemID, const XML_Char *pszPublicID) { return false; }
+ bool OnUnknownEncoding(const XML_Char *pszName, XML_Encoding *pInfo) { return false; }
+ void OnStartNamespaceDecl(const XML_Char *pszPrefix, const XML_Char *pszURI) {}
+ void OnEndNamespaceDecl(const XML_Char *pszPrefix) {}
+ void OnXmlDecl(const XML_Char *pszVersion, const XML_Char *pszEncoding, bool fStandalone) {}
+ void OnStartDoctypeDecl(const XML_Char *pszDoctypeName, const XML_Char *pszSysID, const XML_Char *pszPubID, bool fHasInternalSubset) {}
+ void OnEndDoctypeDecl() {}
+
+protected:
+ void OnPostCreate()
+ {
+ }
+
+ static void XMLCALL StartElementHandler(void *pUserData, const XML_Char *pszName, const XML_Char **papszAttrs)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnStartElement(pszName, papszAttrs);
+ }
+
+ static void XMLCALL EndElementHandler(void *pUserData, const XML_Char *pszName)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnEndElement(pszName);
+ }
+
+ static void XMLCALL CharacterDataHandler(void *pUserData, const XML_Char *pszData, int nLength)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnCharacterData(pszData, nLength);
+ }
+
+ static void XMLCALL ProcessingInstructionHandler(void *pUserData, const XML_Char *pszTarget, const XML_Char *pszData)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnProcessingInstruction(pszTarget, pszData);
+ }
+
+ static void XMLCALL CommentHandler(void *pUserData, const XML_Char *pszData)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnComment(pszData);
+ }
+
+ static void XMLCALL StartCdataSectionHandler(void *pUserData)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnStartCdataSection();
+ }
+
+ static void XMLCALL EndCdataSectionHandler(void *pUserData)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnEndCdataSection();
+ }
+
+ static void XMLCALL DefaultHandler(void *pUserData,
+ const XML_Char *pszData, int nLength)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnDefault(pszData, nLength);
+ }
+
+ static int XMLCALL ExternalEntityRefHandler(void *pUserData, const XML_Char *pszContext, const XML_Char *pszBase, const XML_Char *pszSystemID, const XML_Char *pszPublicID)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ return pThis->OnExternalEntityRef(pszContext, pszBase, pszSystemID, pszPublicID) ? 1 : 0;
+ }
+
+ static int XMLCALL UnknownEncodingHandler(void *pUserData, const XML_Char *pszName, XML_Encoding *pInfo)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ return pThis->OnUnknownEncoding(pszName, pInfo) ? 1 : 0;
+ }
+
+ static void XMLCALL StartNamespaceDeclHandler(void *pUserData,
+ const XML_Char *pszPrefix, const XML_Char *pszURI)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnStartNamespaceDecl(pszPrefix, pszURI);
+ }
+
+ static void XMLCALL EndNamespaceDeclHandler(void *pUserData, const XML_Char *pszPrefix)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnEndNamespaceDecl(pszPrefix);
+ }
+
+ static void XMLCALL XmlDeclHandler(void *pUserData, const XML_Char *pszVersion, const XML_Char *pszEncoding, int nStandalone)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnXmlDecl(pszVersion, pszEncoding, nStandalone != 0);
+ }
+
+ static void XMLCALL StartDoctypeDeclHandler(void *pUserData, const XML_Char *pszDoctypeName, const XML_Char *pszSysID, const XML_Char *pszPubID, int nHasInternalSubset)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnStartDoctypeDecl(pszDoctypeName, pszSysID, pszPubID, nHasInternalSubset != 0);
+ }
+
+ static void XMLCALL EndDoctypeDeclHandler(void *pUserData)
+ {
+ _T *pThis = static_cast<_T *>((CExpatImpl<_T> *)pUserData);
+ pThis->OnEndDoctypeDecl();
+ }
+
+ XML_Parser m_p;
+};
diff --git a/src/expat.ts b/src/expat.ts
new file mode 100644
index 00000000..78b0324c
--- /dev/null
+++ b/src/expat.ts
@@ -0,0 +1,83 @@
+// @ts-ignore
+import * as expatlib from "../build/expat/expatlib/expatlib";
+import { loadWasm } from "./util";
+
+export type Attributes = { [key: string]: string };
+export interface IParser {
+ startElement(tag: string, attrs: Attributes): void;
+ endElement(tag: string): void;
+ characterData(content: string): void;
+}
+
+class StackElement {
+
+ private _content = "";
+ get content(): string {
+ return this._content;
+ }
+
+ constructor(readonly tag: string, readonly attrs: Attributes) {
+ }
+
+ appendContent(content: string) {
+ this._content += content;
+ }
+}
+
+export class StackParser implements IParser {
+ private _stack: StackElement[] = [];
+
+ parse(xml: string): Promise {
+ return parse(xml, this);
+ }
+
+ top(): StackElement {
+ return this._stack[this._stack.length - 1];
+ }
+
+ startElement(tag: string, attrs: Attributes): StackElement {
+ const retVal = new StackElement(tag, attrs);
+ this._stack.push(retVal);
+ return retVal;
+ }
+
+ endElement(tag: string): StackElement {
+ return this._stack.pop()!;
+ }
+
+ characterData(content: string): void {
+ this.top().appendContent(content);
+ }
+}
+
+function parseAttrs(attrs: string): Attributes {
+ const retVal: Attributes = {};
+ const keys = attrs;
+ const sep = `${String.fromCharCode(1)}`;
+ const sep2 = `${sep}${sep}`;
+ keys.split(sep2).filter((key: string) => !!key).forEach((key: string) => {
+ const parts = key.split(sep);
+ retVal[parts[0]] = parts[1];
+ });
+ return retVal;
+}
+
+export function parse(xml: string, callback: IParser): Promise {
+ return loadWasm(expatlib).then(module => {
+ const parser = new module.CExpatJS();
+ parser.startElement = function () {
+ callback.startElement(this.tag(), parseAttrs(this.attrs()));
+ }
+ parser.endElement = function () {
+ callback.endElement(this.tag());
+ }
+ parser.characterData = function () {
+ callback.characterData(this.content());
+ }
+ parser.create();
+ const retVal = parser.parse(xml);
+ parser.destroy();
+ module.destroy(parser);
+ return retVal;
+ });
+}
diff --git a/src/index.ts b/src/index.ts
index e80982a0..8c628e16 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,2 +1,3 @@
+export * from "./expat";
export * from "./graphviz";
export { wasmFolder } from "./util";
diff --git a/test.html b/test.html
index 23b3883f..8c490c8f 100644
--- a/test.html
+++ b/test.html
@@ -19,10 +19,12 @@
mocha.setup('bdd');
mocha.checkLeaks();
+
+