crossbind
GitHub

Expat for WASI

v2.8.5WASI

Expat 2.8.5 for command-line programs under wasmtime, precompiled for wasm32-wasip3, single-threaded as @crossbind/port-expat-wasi.

npm install @crossbind/port-expat-wasi@beta

Install

shell
npm install @crossbind/port-expat-wasi@beta crossbind@beta

An XML summary command

WASI has no JavaScript bindings: the program is a main() that links the library, built into one .wasm and run with wasmtime. This one streams each file through Expat in 64 KiB reads and prints what is inside, or the first error with its line and column.

crossbind.config.js
import expatWasi from '@crossbind/port-expat-wasi/crossbind.config.js';
 
export default {
general: { name: 'xml-stats' },
dependencies: [expatWasi],
paths: { config: import.meta.url },
};
src/native/main.cpp
// A command-line XML summary for WASI:
// xml-stats <file>...
// Each file streams through Expat in 64 KiB reads (XML_GetBuffer, XML_ParseBuffer), so any size
// fits. Well-formed files get their size, element and attribute counts, depth and most common
// elements; the first error ends that file with Expat's message and position, and the exit code 1.
#include <expat.h>
 
#include <algorithm>
#include <cstdio>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
 
namespace {
 
struct Stats {
std::map<std::string, long long> elements;
long long attributes = 0;
int depth = 0;
int maxDepth = 0;
};
 
void XMLCALL onStart(void* data, const XML_Char* name, const XML_Char** atts) {
Stats& stats = *static_cast<Stats*>(data);
stats.elements[name] += 1;
for (int i = 0; atts[i]; i += 2) stats.attributes += 1;
stats.maxDepth = std::max(stats.maxDepth, ++stats.depth);
}
 
void XMLCALL onEnd(void* data, const XML_Char*) { static_cast<Stats*>(data)->depth -= 1; }
 
const char* plural(long long count) { return count == 1 ? "" : "s"; }
 
bool summarize(const char* path) {
std::unique_ptr<FILE, int (*)(FILE*)> file(std::fopen(path, "rb"), std::fclose);
if (!file) {
std::fprintf(stderr, "%s: cannot open\n", path);
return false;
}
std::unique_ptr<XML_ParserStruct, void (*)(XML_Parser)> parser(XML_ParserCreate(nullptr), XML_ParserFree);
if (!parser) {
std::fprintf(stderr, "out of memory\n");
return false;
}
Stats stats;
XML_SetUserData(parser.get(), &stats);
XML_SetElementHandler(parser.get(), onStart, onEnd);
 
long long bytes = 0;
for (bool last = false; !last;) {
void* buffer = XML_GetBuffer(parser.get(), 1 << 16);
if (!buffer) {
std::fprintf(stderr, "out of memory\n");
return false;
}
const size_t got = std::fread(buffer, 1, 1 << 16, file.get());
last = got == 0;
bytes += static_cast<long long>(got);
if (XML_ParseBuffer(parser.get(), static_cast<int>(got), last) != XML_STATUS_OK) {
std::fprintf(stderr, "%s: %s at line %lu, column %lu\n", path, XML_ErrorString(XML_GetErrorCode(parser.get())),
static_cast<unsigned long>(XML_GetCurrentLineNumber(parser.get())), static_cast<unsigned long>(XML_GetCurrentColumnNumber(parser.get())));
return false;
}
}
 
std::vector<std::pair<std::string, long long>> top(stats.elements.begin(), stats.elements.end());
std::sort(top.begin(), top.end(), [](const auto& a, const auto& b) { return a.second != b.second ? a.second > b.second : a.first < b.first; });
long long total = 0;
for (const auto& entry : top) total += entry.second;
std::printf("%s: %lld bytes of well-formed XML (%s)\n", path, bytes, XML_ExpatVersion());
std::printf("%lld element%s, %lld attribute%s, nested %d deep\n", total, plural(total), stats.attributes, plural(stats.attributes), stats.maxDepth);
for (size_t i = 0; i < top.size() && i < 5; i += 1) std::printf("%s %lld\n", top[i].first.c_str(), top[i].second);
return true;
}
 
} // namespace
 
int main(int argc, char** argv) {
if (argc < 2) {
std::fprintf(stderr, "usage: xml-stats <file>...\n");
return 2;
}
bool ok = true;
for (int i = 1; i < argc; i += 1) ok = summarize(argv[i]) && ok;
return ok ? 0 : 1;
}
shell
npx crossbind build -p wasi -b release
wasmtime run --dir=. .crossbind/build/xml-stats-wasi-wasm32-st-release.wasm sitemap.xml
output
sitemap.xml: 4389004 bytes of well-formed XML (expat_2.8.5)
150001 elements, 1 attribute, nested 3 deep
lastmod 50000
loc 50000
url 50000
urlset 1

Command-line tools

One upstream command ships as npm executables built for wasm32-wasip3. They need wasmtime on PATH and no compiler - see WASI commands.

shell
npm install --global @crossbind/port-expat-bin-wasi@beta
xmlwf-wasi --help
  • xmlwf-wasi

What is different on WASI

  • There are no JavaScript bindings: src/native provides main(int, char**), and the build is a single .wasm.
  • Files come from the host through --dir preopens; --dir=. gives the program the current directory.
  • crossbind build -p wasi -b release writes the program to .crossbind/build/<name>-wasi-wasm32-st-release.wasm.
  • WASI 0.3's wasi:cli/exit carries success or failure only, so any non-zero return from main reaches the shell as exit code 1.
  • The build needs wasi-sdk 34 or newer (WASI_SDK_PATH) or the crossbind Docker image; running needs wasmtime 47 or newer. See WASI.

Other platforms

Facts on this page come from the port manifests in the repository and from what npm served on beta when the site was built. See the Libraries guide for the full consumer flow.

MORE LIBRARIES
cURLGDALGEOSGeoTIFFiconvLERClibjpeg-turbolibTIFFOpenSSLPROJSpatiaLiteSQLiteWebPzlibZstandard
Type to search every guide page and section.
↑↓ navigate↵ openesc close