core/encoding/xml
encoding_xml
Types
16Attribute
Attribute :: struct {
key: string,
val: string,
}SourceAttributes
Attributes :: [dynamic]AttributeSourceDocument
Document :: struct {
elements: [dynamic]Element,
element_count: Element_ID,
prologue: Attributes,
encoding: Encoding,
doctype: struct {
// We only scan the <!DOCTYPE IDENT part and skip the rest.
ident: string,
rest: string,
},
// If we encounter comments before the root node, and the option to intern comments is given, this is where they'll live.
// Otherwise they'll be in the element tree.
comments: [dynamic]string,
// Internal
tokenizer: ^Tokenizer,
allocator: mem.Allocator,
// Input. Either the original buffer, or a copy if `.Input_May_Be_Modified` isn't specified.
input: []u8,
strings_to_free: [dynamic]string,
}SourceElement
Element :: struct {
ident: string,
value: [dynamic]Value,
attribs: Attributes,
kind: enum,
parent: Element_ID,
}SourceElement_ID
Element_ID :: u32SourceEncoding
Encoding :: enum int {
Unknown = 0,
UTF_8 = 1,
ISO_8859_1 = 2,
// Aliases
LATIN_1 = ISO_8859_1,
}SourceError
Error :: enum int {
// General return values.
None = 0,
General_Error = 1,
Unexpected_Token = 2,
Invalid_Token = 3,
// Couldn't find, open or read file.
File_Error = 4,
// File too short.
Premature_EOF = 5,
// XML-specific errors.
No_Prolog = 6,
Invalid_Prolog = 7,
Too_Many_Prologs = 8,
No_DocType = 9,
Too_Many_DocTypes = 10,
DocType_Must_Preceed_Elements = 11,
// If a DOCTYPE is present _or_ the caller
// asked for a specific DOCTYPE and the DOCTYPE
// and root tag don't match, we return `.Invalid_DocType`.
Invalid_DocType = 12,
Invalid_Tag_Value = 13,
Mismatched_Closing_Tag = 14,
Unclosed_Comment = 15,
Comment_Before_Root_Element = 16,
Invalid_Sequence_In_Comment = 17,
Unsupported_Version = 18,
Unsupported_Encoding = 19,
// <!FOO are usually skipped.
Unhandled_Bang = 20,
Duplicate_Attribute = 21,
Conflicting_Options = 22,
}SourceError_Handler
Error_Handler :: proc(pos: Pos, fmt: string, args)SourceOption_Flag
Option_Flag :: enum int {
// If the caller says that input may be modified, we can perform in-situ parsing.
// If this flag isn't provided, the XML parser first duplicates the input so that it can.
Input_May_Be_Modified = 0,
// Document MUST start with `<?xml` prologue.
Must_Have_Prolog = 1,
// Document MUST have a `<!DOCTYPE`.
Must_Have_DocType = 2,
// By default we skip comments. Use this option to intern a comment on a parented Element.
Intern_Comments = 3,
// How to handle unsupported parts of the specification, like <! other than <!DOCTYPE and <![CDATA[
Error_on_Unsupported = 4,
Ignore_Unsupported = 5,
// By default CDATA tags are passed-through as-is.
// This option unwraps them when encountered.
Unbox_CDATA = 6,
// By default SGML entities like `>`, ` ` and ` ` are passed-through as-is.
// This option decodes them when encountered.
Decode_SGML_Entities = 7,
// If a tag body has a comment, it will be stripped unless this option is given.
Keep_Tag_Body_Comments = 8,
}SourceOption_Flags
Option_Flags :: bit_set[Option_Flag; u16]SourceOptions
Options :: struct {
flags: Option_Flags,
expected_doctype: string,
}SourcePos
Pos :: struct {
file: string,
offset: int,
line: int,
column: int,
}SourceToken
Token :: struct {
kind: Token_Kind,
text: string,
pos: Pos,
}SourceToken_Kind
Token_Kind :: enum int {
Invalid = 0,
Ident = 1,
Literal = 2,
Rune = 3,
String = 4,
Double_Quote = 5, // "
Single_Quote = 6, // '
Colon = 7, // :
Eq = 8, // =
Lt = 9, // <
Gt = 10, // >
Exclaim = 11, // !
Question = 12, // ?
Hash = 13, // #
Slash = 14, // /
Dash = 15, // -
Open_Bracket = 16, // [
Close_Bracket = 17, // ]
EOF = 18,
}SourceTokenizer
Tokenizer :: struct {
// Immutable data
path: string,
src: string,
err: Error_Handler,
// Tokenizing state
ch: rune,
offset: int,
read_offset: int,
line_offset: int,
line_count: int,
// Mutable data
error_count: int,
}SourceValue
Value :: union {
string,
Element_ID,
}SourceConstants
6CDATA_END
CDATA_END :: "]]>"SourceCDATA_START
CDATA_START :: "<![CDATA["SourceCOMMENT_END
COMMENT_END :: "-->"SourceCOMMENT_START
COMMENT_START :: "<!--"SourceDEFAULT_OPTIONS
DEFAULT_OPTIONS :: Options = Options{
flags = {.Ignore_Unsupported},
expected_doctype = "",
}Sourcelikely
likely :: intrinsics.expectSourceProcedures
32advance_rune
advance_rune :: proc(t: ^Tokenizer)Sourcecheck_duplicate_attributes
check_duplicate_attributes :: proc(t: ^Tokenizer, attribs: Attributes, attr: Attribute, offset: int) -> (err: Error)Sourcedefault_error_handler
default_error_handler :: proc(pos: Pos, msg: string, args)Sourcedestroy
destroy :: proc(doc: ^Document, allocator: mem.Allocator = context.allocator)Sourceerror
error :: proc(t: ^Tokenizer, offset: int, msg: string, args)Sourceexpect
expect :: proc(t: ^Tokenizer, kind: Token_Kind, multiline_string: untyped boolean = false) -> (tok: Token, err: Error)Sourcefind_attribute_val_by_key
find_attribute_val_by_key :: proc(doc: ^Document, parent_id: Element_ID, key: string) -> (val: string, found: bool)SourceFind an attribute by key.
find_child_by_ident
find_child_by_ident :: proc(doc: ^Document, parent_id: Element_ID, ident: string, nth: untyped integer = 0) -> (res: Element_ID, found: bool)SourceFind parent's nth child with a given ident.
init
init :: proc(t: ^Tokenizer, src: string, path: string, err: Error_Handler)Sourceis_letter
is_letter :: proc(r: rune) -> (bool)Sourceis_valid_identifier_rune
is_valid_identifier_rune :: proc(r: rune) -> (bool)Sourceload_from_file
load_from_file :: proc(filename: string, options = DEFAULT_OPTIONS, error_handler = default_error_handler, allocator: mem.Allocator = context.allocator) -> (doc: ^Document, err: Error)SourceLoad an XML file
new_element
new_element :: proc(doc: ^Document) -> (id: Element_ID)Sourceparse_attribute
parse_attribute :: proc(doc: ^Document) -> (attr: Attribute, offset: int, err: Error)Sourceparse_attributes
parse_attributes :: proc(doc: ^Document, attribs: ^Attributes) -> (err: Error)Sourceparse_body
parse_body :: proc(doc: ^Document, element: Element_ID, opts: Options) -> (err: Error)Sourceparse_bytes
parse_bytes :: proc(data: []u8, options = DEFAULT_OPTIONS, path: untyped string = "", error_handler = default_error_handler, allocator: mem.Allocator = context.allocator) -> (doc: ^Document, err: Error)Sourceparse_doctype
parse_doctype :: proc(doc: ^Document) -> (err: Error)Sourceparse_prologue
parse_prologue :: proc(doc: ^Document) -> (err: Error)Sourceparse_string
parse_string :: proc(data: string, options = DEFAULT_OPTIONS, path: untyped string = "", error_handler = default_error_handler, allocator: mem.Allocator = context.allocator) -> (doc: ^Document, err: Error)Sourcepeek
peek :: proc(t: ^Tokenizer) -> (token: Token)Sourcepeek_byte
peek_byte :: proc(t: ^Tokenizer, offset: untyped integer = 0) -> (u8)Sourceprint :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error)SourceJust for debug purposes.
print_element
print_element :: proc(writer: io.Writer, doc: ^Document, element_id: Element_ID, indent: untyped integer = 0) -> (written: int, err: io.Error)Sourcescan
scan :: proc(t: ^Tokenizer, multiline_string: untyped boolean = false) -> (Token)Sourcescan_comment
scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error)SourceA comment ends when we see -->, preceded by a character that's not a dash.
"For compatibility, the string "--" (double-hyphen) must not occur within comments."
See: https://www.w3.org/TR/2006/REC-xml11-20060816/#dt-comment
Thanks to the length (4) of the comment start, we also have enough lookback,
and the peek at the next byte asserts that there's at least one more character
that's a `>`.scan_identifier
scan_identifier :: proc(t: ^Tokenizer) -> (string)Sourcescan_string
scan_string :: proc(t: ^Tokenizer, offset: int, close: rune, consume_close: untyped boolean = false, multiline: untyped boolean = true) -> (value: string, err: Error)Sourceskip_cdata
skip_cdata :: proc(t: ^Tokenizer) -> (err: Error)SourceSkip CDATA
skip_element
skip_element :: proc(t: ^Tokenizer) -> (err: Error)Sourceskip_whitespace
skip_whitespace :: proc(t: ^Tokenizer)Sourcevalidate_options
validate_options :: proc(options: Options) -> (validated: Options, err: Error)SourceHelpers.
Procedure Groups
1parse
parse :: proc{parse_string, parse_bytes}Source