Skip to content

Conversation

@aruscher
Copy link

This PR adds Expat, a stream-oriented, event-driven XML parser. Unlike core/encoding/xml, which provides a higher-level, structure-based API, Expat processes XML incrementally and is better suited for large or streaming documents

@aruscher aruscher changed the title Implementation of vendor/expat Implementation of vendor/expat Jan 26, 2026
@aruscher
Copy link
Author

Here is a reimplementation the elements.c example by the original developers:

The books.xml used in the example can be downloaded from the Microsoft Webpage:

package main

import "base:runtime"
import "core:c"
import "core:fmt"
import "core:io"
import "core:mem"
import "core:os/os2"
import "vendor:expat"

BUFSIZ :: 1000


startElement: expat.XML_StartElementHandler = proc "c" (
	userdata: rawptr,
	name: ^expat.XML_Char,
	atts: [^]^expat.XML_Char,
) {
	context = runtime.default_context()
	depthPtr := cast(^int)userdata

	for i := 0; i < depthPtr^; i += 1 {
		fmt.print("\t")
	}
	fmt.printf("%s", cast(cstring)name)

	for i := 0; atts[i] != nil; i += 2 {
		fmt.printf(" %s = %s", cast(cstring)atts[i], cast(cstring)atts[i + 1])
	}
	fmt.print("\n")
	depthPtr^ += 1

}

endElement: expat.XML_EndElementHandler = proc "c" (userdata: rawptr, name: ^expat.XML_Char) {
	depthPtr := cast(^int)userdata
	depthPtr^ -= 1
}

main :: proc() {

	file, err := os2.open("books.xml")
	if (err != nil) {
		fmt.eprintfln("%s", os2.error_string(err))
		return
	}

	parser := expat.XML_ParserCreate(nil)
	defer expat.XML_ParserFree(parser)
	done: int
	depth: int = 0
	if (parser == nil) {
		fmt.eprintf("Can't allocate memory for the parser")
		return
	}
	expat.XML_SetUserData(parser, &depth)

	expat.XML_SetElementHandler(parser, startElement, endElement)

	for {
		buf := expat.XML_GetBuffer(parser, BUFSIZ)

		if (buf == nil) {
			fmt.eprintf("Can't allocate memory for the buffer")
			expat.XML_ParserFree(parser)
			return
		}
		slice := mem.slice_ptr(cast(^u8)buf, BUFSIZ)
		n, read_err := os2.read(file, slice)
		if (read_err != nil && read_err != io.Error.EOF) {
			fmt.eprintfln("%s", os2.error_string(read_err))
			return
		}
		done := read_err == io.Error.EOF

		if (expat.XML_ParseBuffer(parser, cast(c.int)n, cast(c.int)done) == expat.XML_STATUS_ERROR) {
			fmt.eprintfln(
				"Parse error at line %d: %s",
				expat.XML_GetCurrentLineNumber(parser),
				cast(cstring)expat.XML_ErrorString(expat.XML_GetErrorCode(parser)),
			)
			expat.XML_ParserFree(parser)
			return
		}

		if (done) {
			break
		}
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant