Stream oriented XML parser library with several useful features.
Website: https://libexpat.github.io
Platforms supported: Windows (32-bit and 64-bit), Linux, FreeBSD, Haiku
Headers to include: expat.bi
Header version: 2.1.0
Examples: in examples/xml/
Haiku R1 beta 6 amd64: `examples/xml/expat.bas` compiled and ran with
FreeBASIC 1.20.4-3 against HaikuPorts Expat 2.9.0, parsing a controlled XML
fixture through the streaming callbacks.
The callback example feeds binary input in 1,024-byte blocks and supplies a parser-owned context through
XML_SetUserData. Character data uses the C
int count (FreeBASIC
Long), is not terminated, and can arrive in several callbacks. Bounded copies are printed continuously, preserving long and mixed-content text instead of dropping data when a fixed aggregate buffer fills. Display indentation is limited to 64 spaces for deep input.
The final parse call is made even for an empty file, allowing Expat to report its syntax error. Input and parse errors return a failing process status after the file and parser are released. The examples are synchronous and use no external entity loader.
The Windows checks cover the installed narrow-character Expat 2.8.2 SDK.
XML_UNICODE requires a separately built, compatible
libexpatw with matching
XML_Char and
XML_LChar widths; that variant is not qualified by these narrow SDK checks. See the upstream
character callback and
parse contracts.
Example
'' FreeBASIC XML examples
'' File: expat.bas
'' Purpose: Display an XML file using Expat's streaming callbacks.
'' Responsibilities: Preserve text chunks, match C callback types, and release parser/file state.
'' This file contains no tree construction, external entity loading or background parsing.
#include Once "expat.bi"
#include Once "crt/string.bi"
'' Narrow mode uses libexpat. Define XML_UNICODE to use the compatible
'' wide-character libexpatw SDK instead; its XML_Char layout must match.
Const BUFFER_SIZE = 1024
Const MAX_INDENT = 64 '' Bound display indentation for deeply nested input.
Type Context
parser As XML_Parser
nesting As Integer
line_open As Boolean
End Type
Private Sub finish_text(ByRef ctx As Context)
If ctx.line_open Then Print
ctx.line_open = False
End Sub
Private Function indentation(ByRef ctx As Context) As Integer
If ctx.nesting > MAX_INDENT Then Return MAX_INDENT
Return ctx.nesting
End Function
Sub elementBegin cdecl(ByVal userdata As Any Ptr, ByVal element As Const XML_Char Ptr, _
ByVal attributes As Const XML_Char Ptr Ptr)
Dim ctx As Context Ptr = userdata
If ctx = NULL Or element = NULL Then Exit Sub
finish_text(*ctx)
Print Space(indentation(*ctx)); *element;
If Cast(Any Ptr, attributes) <> NULL Then
While attributes[0] <> NULL
If attributes[1] = NULL Then Exit While
Print " "; *attributes[0]; "='"; *attributes[1]; "'";
attributes += 2
Wend
End If
Print
ctx->nesting += 1
End Sub
Sub elementEnd cdecl(ByVal userdata As Any Ptr, ByVal element As Const XML_Char Ptr)
Dim ctx As Context Ptr = userdata
If ctx = NULL Then Exit Sub
finish_text(*ctx)
If ctx->nesting > 0 Then ctx->nesting -= 1
End Sub
Sub charData cdecl(ByVal userdata As Any Ptr, ByVal chars As Const XML_Char Ptr, ByVal length As Long)
Dim ctx As Context Ptr = userdata
If ctx = NULL Or chars = NULL Or length <= 0 Then Exit Sub
If Not ctx->line_open Then Print Space(indentation(*ctx));
ctx->line_open = True
'' Callback data is not terminated. Print bounded copies continuously,
'' retaining all character data rather than silently dropping long text.
'' XML forbids embedded zero characters in successfully parsed text.
Dim text As XML_Char * (BUFFER_SIZE+1)
Dim position As Long = 0
Do While position < length
Dim count As Long = length-position
If count > BUFFER_SIZE Then count = BUFFER_SIZE
#ifdef XML_UNICODE
'' Keep a UTF-16 surrogate pair in the same output conversion.
If SizeOf(XML_Char) = 2 And count < length-position Then
Dim last_unit As UInteger = chars[position+count-1]
If last_unit >= &hd800 And last_unit <= &hdbff Then count -= 1
End If
#endif
memcpy(@text[0], @chars[position], count * SizeOf(XML_Char))
text[count] = 0
Print text;
position += count
Loop
End Sub
Function main() As Integer
Dim filename As String = Command(1)
If Len(filename) = 0 Then
Print "Usage: expat <xmlfilename>"
Return 1
End If
Dim file_number As Integer = FreeFile()
If Open(filename, For Binary, Access Read, As #file_number) <> 0 Then
Print "Could not open file: '"; filename; "'"
Return 1
End If
Dim ctx As Context
ctx.parser = XML_ParserCreate(NULL)
If ctx.parser = NULL Then
Close #file_number
Print "XML_ParserCreate failed"
Return 1
End If
'' The local context outlives every synchronous XML_Parse callback.
XML_SetUserData(ctx.parser, @ctx)
XML_SetElementHandler(ctx.parser, @elementBegin, @elementEnd)
XML_SetCharacterDataHandler(ctx.parser, @charData)
Dim buffer(0 To BUFFER_SIZE-1) As UByte
Dim exit_status As Integer = 0
Do
Dim count As Integer
Dim input_status As Integer = Get(#file_number, , buffer(0), BUFFER_SIZE, count)
If input_status <> 0 Then
Print "File input error"
exit_status = 1
Exit Do
End If
Dim finished As Boolean = EOF(file_number) <> 0
'' Finalize even an empty input, so Expat reports its syntax error.
If XML_Parse(ctx.parser, @buffer(0), count, finished) = XML_STATUS_ERROR Then
finish_text(ctx)
Print filename & "(" & XML_GetCurrentLineNumber(ctx.parser) & "): Error from XML parser: "
Print *XML_ErrorString(XML_GetErrorCode(ctx.parser))
exit_status = 1
Exit Do
End If
If finished Then Exit Do
Loop
finish_text(ctx)
If Close(#file_number) <> 0 Then exit_status = 1
XML_ParserFree(ctx.parser)
Return exit_status
End Function
End main()
'' end of expat.bas