Fix comments in xml_reader.odin for better rendering in documentation

This commit is contained in:
Kostas Tsiligkiris
2024-01-17 21:58:38 +02:00
parent a86cfa6e97
commit d54f34a162
+53 -130
View File
@@ -43,48 +43,32 @@ DEFAULT_OPTIONS :: Options{
} }
Option_Flag :: enum { Option_Flag :: enum {
/* // If the caller says that input may be modified, we can perform in-situ parsing.
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.
If this flag isn't provided, the XML parser first duplicates the input so that it can.
*/
Input_May_Be_Modified, Input_May_Be_Modified,
/* // Document MUST start with `<?xml` prologue.
Document MUST start with `<?xml` prologue.
*/
Must_Have_Prolog, Must_Have_Prolog,
/* // Document MUST have a `<!DOCTYPE`.
Document MUST have a `<!DOCTYPE`.
*/
Must_Have_DocType, Must_Have_DocType,
/* // By default we skip comments. Use this option to intern a comment on a parented Element.
By default we skip comments. Use this option to intern a comment on a parented Element.
*/
Intern_Comments, Intern_Comments,
/* // How to handle unsupported parts of the specification, like <! other than <!DOCTYPE and <![CDATA[
How to handle unsupported parts of the specification, like <! other than <!DOCTYPE and <![CDATA[
*/
Error_on_Unsupported, Error_on_Unsupported,
Ignore_Unsupported, Ignore_Unsupported,
/* // By default CDATA tags are passed-through as-is.
By default CDATA tags are passed-through as-is. // This option unwraps them when encountered.
This option unwraps them when encountered.
*/
Unbox_CDATA, Unbox_CDATA,
/* // By default SGML entities like `&gt;`, `&#32;` and `&#x20;` are passed-through as-is.
By default SGML entities like `&gt;`, `&#32;` and `&#x20;` are passed-through as-is. // This option decodes them when encountered.
This option decodes them when encountered.
*/
Decode_SGML_Entities, Decode_SGML_Entities,
/* // If a tag body has a comment, it will be stripped unless this option is given.
If a tag body has a comment, it will be stripped unless this option is given.
*/
Keep_Tag_Body_Comments, Keep_Tag_Body_Comments,
} }
Option_Flags :: bit_set[Option_Flag; u16] Option_Flags :: bit_set[Option_Flag; u16]
@@ -97,28 +81,20 @@ Document :: struct {
encoding: Encoding, encoding: Encoding,
doctype: struct { doctype: struct {
/* // We only scan the <!DOCTYPE IDENT part and skip the rest.
We only scan the <!DOCTYPE IDENT part and skip the rest.
*/
ident: string, ident: string,
rest: 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.
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.
Otherwise they'll be in the element tree.
*/
comments: [dynamic]string, comments: [dynamic]string,
/* // Internal
Internal
*/
tokenizer: ^Tokenizer, tokenizer: ^Tokenizer,
allocator: mem.Allocator, allocator: mem.Allocator,
/* // Input. Either the original buffer, or a copy if `.Input_May_Be_Modified` isn't specified.
Input. Either the original buffer, or a copy if `.Input_May_Be_Modified` isn't specified.
*/
input: []u8, input: []u8,
strings_to_free: [dynamic]string, strings_to_free: [dynamic]string,
} }
@@ -158,34 +134,24 @@ Encoding :: enum {
UTF_8, UTF_8,
ISO_8859_1, ISO_8859_1,
/* // Aliases
Aliases
*/
LATIN_1 = ISO_8859_1, LATIN_1 = ISO_8859_1,
} }
Error :: enum { Error :: enum {
/* // General return values.
General return values.
*/
None = 0, None = 0,
General_Error, General_Error,
Unexpected_Token, Unexpected_Token,
Invalid_Token, Invalid_Token,
/* // Couldn't find, open or read file.
Couldn't find, open or read file.
*/
File_Error, File_Error,
/* // File too short.
File too short.
*/
Premature_EOF, Premature_EOF,
/* // XML-specific errors.
XML-specific errors.
*/
No_Prolog, No_Prolog,
Invalid_Prolog, Invalid_Prolog,
Too_Many_Prologs, Too_Many_Prologs,
@@ -194,11 +160,9 @@ Error :: enum {
Too_Many_DocTypes, Too_Many_DocTypes,
DocType_Must_Preceed_Elements, DocType_Must_Preceed_Elements,
/* // If a DOCTYPE is present _or_ the caller
If a DOCTYPE is present _or_ the caller // asked for a specific DOCTYPE and the DOCTYPE
asked for a specific DOCTYPE and the DOCTYPE // and root tag don't match, we return `.Invalid_DocType`.
and root tag don't match, we return `.Invalid_DocType`.
*/
Invalid_DocType, Invalid_DocType,
Invalid_Tag_Value, Invalid_Tag_Value,
@@ -211,27 +175,20 @@ Error :: enum {
Unsupported_Version, Unsupported_Version,
Unsupported_Encoding, Unsupported_Encoding,
/* // <!FOO are usually skipped.
<!FOO are usually skipped.
*/
Unhandled_Bang, Unhandled_Bang,
Duplicate_Attribute, Duplicate_Attribute,
Conflicting_Options, Conflicting_Options,
} }
/*
Implementation starts here.
*/
parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) { parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) {
data := data data := data
context.allocator = allocator context.allocator = allocator
opts := validate_options(options) or_return opts := validate_options(options) or_return
/* // If `.Input_May_Be_Modified` is not specified, we duplicate the input so that we can modify it in-place.
If `.Input_May_Be_Modified` is not specified, we duplicate the input so that we can modify it in-place.
*/
if .Input_May_Be_Modified not_in opts.flags { if .Input_May_Be_Modified not_in opts.flags {
data = bytes.clone(data) data = bytes.clone(data)
} }
@@ -252,10 +209,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
element, parent: Element_ID element, parent: Element_ID
open: Token open: Token
/* // If a DOCTYPE is present, the root tag has to match.
If a DOCTYPE is present, the root tag has to match. // If an expected DOCTYPE is given in options (i.e. it's non-empty), the DOCTYPE (if present) and root tag have to match.
If an expected DOCTYPE is given in options (i.e. it's non-empty), the DOCTYPE (if present) and root tag have to match.
*/
expected_doctype := options.expected_doctype expected_doctype := options.expected_doctype
loop: for { loop: for {
@@ -263,17 +218,13 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
// NOTE(Jeroen): This is faster as a switch. // NOTE(Jeroen): This is faster as a switch.
switch t.ch { switch t.ch {
case '<': case '<':
/* // Consume peeked `<`
Consume peeked `<`
*/
advance_rune(t) advance_rune(t)
open = scan(t) open = scan(t)
// NOTE(Jeroen): We're not using a switch because this if-else chain ordered by likelihood is 2.5% faster at -o:size and -o:speed. // NOTE(Jeroen): We're not using a switch because this if-else chain ordered by likelihood is 2.5% faster at -o:size and -o:speed.
if likely(open.kind, Token_Kind.Ident) == .Ident { if likely(open.kind, Token_Kind.Ident) == .Ident {
/* // e.g. <odin - Start of new element.
e.g. <odin - Start of new element.
*/
element = new_element(doc) element = new_element(doc)
if element == 0 { // First Element if element == 0 { // First Element
parent = element parent = element
@@ -286,11 +237,9 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
parse_attributes(doc, &doc.elements[element].attribs) or_return parse_attributes(doc, &doc.elements[element].attribs) or_return
/* // If a DOCTYPE is present _or_ the caller
If a DOCTYPE is present _or_ the caller // asked for a specific DOCTYPE and the DOCTYPE
asked for a specific DOCTYPE and the DOCTYPE // and root tag don't match, we return .Invalid_Root_Tag.
and root tag don't match, we return .Invalid_Root_Tag.
*/
if element == 0 { // Root tag? if element == 0 { // Root tag?
if len(expected_doctype) > 0 && expected_doctype != open.text { if len(expected_doctype) > 0 && expected_doctype != open.text {
error(t, t.offset, "Root Tag doesn't match DOCTYPE. Expected: %v, got: %v\n", expected_doctype, open.text) error(t, t.offset, "Root Tag doesn't match DOCTYPE. Expected: %v, got: %v\n", expected_doctype, open.text)
@@ -298,23 +247,17 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
} }
} }
/* // One of these should follow:
One of these should follow: // - `>`, which means we've just opened this tag and expect a later element to close it.
- `>`, which means we've just opened this tag and expect a later element to close it. // - `/>`, which means this is an 'empty' or self-closing tag.
- `/>`, which means this is an 'empty' or self-closing tag.
*/
end_token := scan(t) end_token := scan(t)
#partial switch end_token.kind { #partial switch end_token.kind {
case .Gt: case .Gt:
/* // We're now the new parent.
We're now the new parent.
*/
parent = element parent = element
case .Slash: case .Slash:
/* // Empty tag. Close it.
Empty tag. Close it.
*/
expect(t, .Gt) or_return expect(t, .Gt) or_return
parent = doc.elements[element].parent parent = doc.elements[element].parent
element = parent element = parent
@@ -325,9 +268,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
} }
} else if open.kind == .Slash { } else if open.kind == .Slash {
/* // Close tag.
Close tag.
*/
ident := expect(t, .Ident) or_return ident := expect(t, .Ident) or_return
_ = expect(t, .Gt) or_return _ = expect(t, .Gt) or_return
@@ -339,9 +280,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
element = parent element = parent
} else if open.kind == .Exclaim { } else if open.kind == .Exclaim {
/* // <!
<!
*/
next := scan(t) next := scan(t)
#partial switch next.kind { #partial switch next.kind {
case .Ident: case .Ident:
@@ -370,10 +309,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
} }
case .Dash: case .Dash:
/* // Comment: <!-- -->.
Comment: <!-- -->. // The grammar does not allow a comment to end in --->
The grammar does not allow a comment to end in --->
*/
expect(t, .Dash) expect(t, .Dash)
comment := scan_comment(t) or_return comment := scan_comment(t) or_return
@@ -395,23 +332,17 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
} }
} else if open.kind == .Question { } else if open.kind == .Question {
/* // <?xml
<?xml
*/
next := scan(t) next := scan(t)
#partial switch next.kind { #partial switch next.kind {
case .Ident: case .Ident:
if len(next.text) == 3 && strings.equal_fold(next.text, "xml") { if len(next.text) == 3 && strings.equal_fold(next.text, "xml") {
parse_prologue(doc) or_return parse_prologue(doc) or_return
} else if len(doc.prologue) > 0 { } else if len(doc.prologue) > 0 {
/* // We've already seen a prologue.
We've already seen a prologue.
*/
return doc, .Too_Many_Prologs return doc, .Too_Many_Prologs
} else { } else {
/* // Could be `<?xml-stylesheet`, etc. Ignore it.
Could be `<?xml-stylesheet`, etc. Ignore it.
*/
skip_element(t) or_return skip_element(t) or_return
} }
case: case:
@@ -425,15 +356,11 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
} }
case -1: case -1:
/* // End of file.
End of file.
*/
break loop break loop
case: case:
/* // This should be a tag's body text.
This should be a tag's body text.
*/
body_text := scan_string(t, t.offset) or_return body_text := scan_string(t, t.offset) or_return
needs_processing := .Unbox_CDATA in opts.flags needs_processing := .Unbox_CDATA in opts.flags
needs_processing |= .Decode_SGML_Entities in opts.flags needs_processing |= .Decode_SGML_Entities in opts.flags
@@ -613,9 +540,7 @@ parse_prologue :: proc(doc: ^Document) -> (err: Error) {
doc.encoding = .LATIN_1 doc.encoding = .LATIN_1
case: case:
/* // Unrecognized encoding, assume UTF-8.
Unrecognized encoding, assume UTF-8.
*/
error(t, offset, "[parse_prologue] Warning: Unrecognized encoding: %v\n", attr.val) error(t, offset, "[parse_prologue] Warning: Unrecognized encoding: %v\n", attr.val)
} }
@@ -658,11 +583,11 @@ skip_element :: proc(t: ^Tokenizer) -> (err: Error) {
parse_doctype :: proc(doc: ^Document) -> (err: Error) { parse_doctype :: proc(doc: ^Document) -> (err: Error) {
/* /*
<!DOCTYPE greeting SYSTEM "hello.dtd"> <!DOCTYPE greeting SYSTEM "hello.dtd">
<!DOCTYPE greeting [ <!DOCTYPE greeting [
<!ELEMENT greeting (#PCDATA)> <!ELEMENT greeting (#PCDATA)>
]> ]>
*/ */
assert(doc != nil) assert(doc != nil)
context.allocator = doc.allocator context.allocator = doc.allocator
@@ -675,9 +600,7 @@ parse_doctype :: proc(doc: ^Document) -> (err: Error) {
offset := t.offset offset := t.offset
skip_element(t) or_return skip_element(t) or_return
/* // -1 because the current offset is that of the closing tag, so the rest of the DOCTYPE tag ends just before it.
-1 because the current offset is that of the closing tag, so the rest of the DOCTYPE tag ends just before it.
*/
doc.doctype.rest = string(t.src[offset : t.offset - 1]) doc.doctype.rest = string(t.src[offset : t.offset - 1])
return .None return .None
} }