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(); + + - + \ No newline at end of file diff --git a/test/expat.spec.js b/test/expat.spec.js new file mode 100644 index 00000000..c4eefe02 --- /dev/null +++ b/test/expat.spec.js @@ -0,0 +1,575 @@ +class Cat { + + constructor(groupID) { + this._groupID = groupID; + this._keywords = []; + } + + appendKeyword(keyword) { + this._keywords.push(keyword); + } +} + +class KeywordParser extends hpccWasm.StackParser { + currCat; + _json = {}; + + startElement(tag, attrs) { + super.startElement(tag, attrs); + switch (tag) { + case "cat": + this.currCat = `keyword_${attrs["group"]}`; + this._json[this.currCat] = []; + break; + case "keyword": + this._json[this.currCat].push(attrs["name"]); + break; + } + } +} + +describe("expat", function () { + it("simple", function () { + const xml = "content"; + var callback = { + startElement(tag, attrs) { console.log("start", tag, attrs); }, + endElement(tag) { console.log("end", tag); }, + characterData(content) { console.log("characterData", content); } + }; + return hpccWasm.parse(xml, callback).then(response => { + expect(response).to.be.true; + }); + }); + + it("parse", function () { + const callback = new KeywordParser(); + return hpccWasm.parse(encodedXml(), callback).then(response => { + console.log(response, callback._json); + expect(response).to.be.true; + }); + }); +}); + +function encodedXml(str) { + return xml() + .split("&").join("&") + .split("\"<").join("\"<") + .split("<\"").join("<\"") + // .split("\">").join("\">") + .split(">\"").join(">\"") + .split("<<").join("<<") + .split(">>").join(">>") + ; +} + +function xml() { + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `; +} \ No newline at end of file diff --git a/test/graphviz.spec.js b/test/graphviz.spec.js index cbb75e73..2eb90981 100644 --- a/test/graphviz.spec.js +++ b/test/graphviz.spec.js @@ -1,6 +1,3 @@ -const expect = chai.expect; -const hpccWasm = window["@hpcc-js/wasm"]; - const dot = ` digraph G { node [shape=rect]; diff --git a/test/init.spec.js b/test/init.spec.js new file mode 100644 index 00000000..6ebd9d82 --- /dev/null +++ b/test/init.spec.js @@ -0,0 +1,2 @@ +const expect = chai.expect; +const hpccWasm = window["@hpcc-js/wasm"];