mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-04 22:58:46 +00:00
Update XML reader to normalize whitespace, part 1.
This commit is contained in:
@@ -56,38 +56,27 @@ CDATA_END :: "]]>"
|
|||||||
COMMENT_START :: "<!--"
|
COMMENT_START :: "<!--"
|
||||||
COMMENT_END :: "-->"
|
COMMENT_END :: "-->"
|
||||||
|
|
||||||
/*
|
// Default: CDATA and comments are passed through unchanged.
|
||||||
Default: CDATA and comments are passed through unchanged.
|
|
||||||
*/
|
|
||||||
XML_Decode_Option :: enum u8 {
|
XML_Decode_Option :: enum u8 {
|
||||||
/*
|
// Do not decode & entities. It decodes by default. If given, overrides `Decode_CDATA`.
|
||||||
Do not decode & entities. It decodes by default.
|
|
||||||
If given, overrides `Decode_CDATA`.
|
|
||||||
*/
|
|
||||||
No_Entity_Decode,
|
No_Entity_Decode,
|
||||||
|
|
||||||
/*
|
// CDATA is unboxed.
|
||||||
CDATA is unboxed.
|
|
||||||
*/
|
|
||||||
Unbox_CDATA,
|
Unbox_CDATA,
|
||||||
|
|
||||||
/*
|
// Unboxed CDATA is decoded as well. Ignored if `.Unbox_CDATA` is not given.
|
||||||
Unboxed CDATA is decoded as well.
|
|
||||||
Ignored if `.Unbox_CDATA` is not given.
|
|
||||||
*/
|
|
||||||
Decode_CDATA,
|
Decode_CDATA,
|
||||||
|
|
||||||
/*
|
// Comments are stripped.
|
||||||
Comments are stripped.
|
|
||||||
*/
|
|
||||||
Comment_Strip,
|
Comment_Strip,
|
||||||
|
|
||||||
|
// Normalize whitespace
|
||||||
|
Normalize_Whitespace,
|
||||||
}
|
}
|
||||||
XML_Decode_Options :: bit_set[XML_Decode_Option; u8]
|
XML_Decode_Options :: bit_set[XML_Decode_Option; u8]
|
||||||
|
|
||||||
/*
|
// Decode a string that may include SGML/XML/HTML entities.
|
||||||
Decode a string that may include SGML/XML/HTML entities.
|
// The caller has to free the result.
|
||||||
The caller has to free the result.
|
|
||||||
*/
|
|
||||||
decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := context.allocator) -> (decoded: string, err: Error) {
|
decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := context.allocator) -> (decoded: string, err: Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
@@ -100,14 +89,14 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
|||||||
t := Tokenizer{src=input}
|
t := Tokenizer{src=input}
|
||||||
in_data := false
|
in_data := false
|
||||||
|
|
||||||
|
prev: rune
|
||||||
|
|
||||||
loop: for {
|
loop: for {
|
||||||
advance(&t) or_return
|
advance(&t) or_return
|
||||||
if t.r < 0 { break loop }
|
if t.r < 0 { break loop }
|
||||||
|
|
||||||
/*
|
// Below here we're never inside a CDATA tag. At most we'll see the start of one,
|
||||||
Below here we're never inside a CDATA tag.
|
// but that doesn't affect the logic.
|
||||||
At most we'll see the start of one, but that doesn't affect the logic.
|
|
||||||
*/
|
|
||||||
switch t.r {
|
switch t.r {
|
||||||
case '<':
|
case '<':
|
||||||
/*
|
/*
|
||||||
@@ -126,9 +115,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
|||||||
in_data = _handle_xml_special(&t, &builder, options) or_return
|
in_data = _handle_xml_special(&t, &builder, options) or_return
|
||||||
|
|
||||||
case ']':
|
case ']':
|
||||||
/*
|
// If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
|
||||||
If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
|
|
||||||
*/
|
|
||||||
if in_data {
|
if in_data {
|
||||||
if t.read_offset + len(CDATA_END) < len(t.src) {
|
if t.read_offset + len(CDATA_END) < len(t.src) {
|
||||||
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
|
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
|
||||||
@@ -143,22 +130,16 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
|||||||
|
|
||||||
case:
|
case:
|
||||||
if in_data && .Decode_CDATA not_in options {
|
if in_data && .Decode_CDATA not_in options {
|
||||||
/*
|
// Unboxed, but undecoded.
|
||||||
Unboxed, but undecoded.
|
|
||||||
*/
|
|
||||||
write_rune(&builder, t.r)
|
write_rune(&builder, t.r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.r == '&' {
|
if t.r == '&' {
|
||||||
if entity, entity_err := _extract_xml_entity(&t); entity_err != .None {
|
if entity, entity_err := _extract_xml_entity(&t); entity_err != .None {
|
||||||
/*
|
// We read to the end of the string without closing the entity. Pass through as-is.
|
||||||
We read to the end of the string without closing the entity.
|
|
||||||
Pass through as-is.
|
|
||||||
*/
|
|
||||||
write_string(&builder, entity)
|
write_string(&builder, entity)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if .No_Entity_Decode not_in options {
|
if .No_Entity_Decode not_in options {
|
||||||
if decoded, ok := xml_decode_entity(entity); ok {
|
if decoded, ok := xml_decode_entity(entity); ok {
|
||||||
write_rune(&builder, decoded)
|
write_rune(&builder, decoded)
|
||||||
@@ -166,19 +147,27 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Literal passthrough because the decode failed or we want entities not decoded.
|
||||||
Literal passthrough because the decode failed or we want entities not decoded.
|
|
||||||
*/
|
|
||||||
write_string(&builder, "&")
|
write_string(&builder, "&")
|
||||||
write_string(&builder, entity)
|
write_string(&builder, entity)
|
||||||
write_string(&builder, ";")
|
write_string(&builder, ";")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
write_rune(&builder, t.r)
|
// https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-line-ends
|
||||||
|
switch t.r {
|
||||||
|
case '\n', 0x85, 0x2028:
|
||||||
|
write_rune(&builder, '\n')
|
||||||
|
case '\r': // Do nothing until next character
|
||||||
|
case:
|
||||||
|
if prev == '\r' { // Turn a single carriage return into a \n
|
||||||
|
write_rune(&builder, '\n')
|
||||||
|
}
|
||||||
|
write_rune(&builder, t.r)
|
||||||
|
}
|
||||||
|
prev = t.r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.clone(strings.to_string(builder), allocator), err
|
return strings.clone(strings.to_string(builder), allocator), err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,24 +242,18 @@ xml_decode_entity :: proc(entity: string) -> (decoded: rune, ok: bool) {
|
|||||||
return rune(val), true
|
return rune(val), true
|
||||||
|
|
||||||
case:
|
case:
|
||||||
/*
|
// Named entity.
|
||||||
Named entity.
|
|
||||||
*/
|
|
||||||
return named_xml_entity_to_rune(entity)
|
return named_xml_entity_to_rune(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Private XML helper to extract `&<stuff>;` entity.
|
||||||
Private XML helper to extract `&<stuff>;` entity.
|
|
||||||
*/
|
|
||||||
@(private="file")
|
@(private="file")
|
||||||
_extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
|
_extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
|
||||||
assert(t != nil && t.r == '&')
|
assert(t != nil && t.r == '&')
|
||||||
|
|
||||||
/*
|
// All of these would be in the ASCII range.
|
||||||
All of these would be in the ASCII range.
|
// Even if one is not, it doesn't matter. All characters we need to compare to extract are.
|
||||||
Even if one is not, it doesn't matter. All characters we need to compare to extract are.
|
|
||||||
*/
|
|
||||||
|
|
||||||
length := len(t.src)
|
length := len(t.src)
|
||||||
found := false
|
found := false
|
||||||
@@ -292,9 +275,7 @@ _extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
|
|||||||
return string(t.src[t.offset : t.read_offset]), .Invalid_Entity_Encoding
|
return string(t.src[t.offset : t.read_offset]), .Invalid_Entity_Encoding
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Private XML helper for CDATA and comments.
|
||||||
Private XML helper for CDATA and comments.
|
|
||||||
*/
|
|
||||||
@(private="file")
|
@(private="file")
|
||||||
_handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: XML_Decode_Options) -> (in_data: bool, err: Error) {
|
_handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: XML_Decode_Options) -> (in_data: bool, err: Error) {
|
||||||
assert(t != nil && t.r == '<')
|
assert(t != nil && t.r == '<')
|
||||||
@@ -304,20 +285,14 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X
|
|||||||
t.read_offset += len(CDATA_START) - 1
|
t.read_offset += len(CDATA_START) - 1
|
||||||
|
|
||||||
if .Unbox_CDATA in options && .Decode_CDATA in options {
|
if .Unbox_CDATA in options && .Decode_CDATA in options {
|
||||||
/*
|
// We're unboxing _and_ decoding CDATA
|
||||||
We're unboxing _and_ decoding CDATA
|
|
||||||
*/
|
|
||||||
return true, .None
|
return true, .None
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// CDATA is passed through.
|
||||||
CDATA is passed through.
|
|
||||||
*/
|
|
||||||
offset := t.offset
|
offset := t.offset
|
||||||
|
|
||||||
/*
|
// Scan until end of CDATA.
|
||||||
Scan until end of CDATA.
|
|
||||||
*/
|
|
||||||
for {
|
for {
|
||||||
advance(t) or_return
|
advance(t) or_return
|
||||||
if t.r < 0 { return true, .CDATA_Not_Terminated }
|
if t.r < 0 { return true, .CDATA_Not_Terminated }
|
||||||
@@ -341,14 +316,10 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X
|
|||||||
|
|
||||||
} else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START {
|
} else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START {
|
||||||
t.read_offset += len(COMMENT_START)
|
t.read_offset += len(COMMENT_START)
|
||||||
/*
|
// Comment is passed through by default.
|
||||||
Comment is passed through by default.
|
|
||||||
*/
|
|
||||||
offset := t.offset
|
offset := t.offset
|
||||||
|
|
||||||
/*
|
// Scan until end of Comment.
|
||||||
Scan until end of Comment.
|
|
||||||
*/
|
|
||||||
for {
|
for {
|
||||||
advance(t) or_return
|
advance(t) or_return
|
||||||
if t.r < 0 { return true, .Comment_Not_Terminated }
|
if t.r < 0 { return true, .Comment_Not_Terminated }
|
||||||
|
|||||||
@@ -218,9 +218,7 @@ scan_identifier :: proc(t: ^Tokenizer) -> string {
|
|||||||
for is_valid_identifier_rune(t.ch) {
|
for is_valid_identifier_rune(t.ch) {
|
||||||
advance_rune(t)
|
advance_rune(t)
|
||||||
if t.ch == ':' {
|
if t.ch == ':' {
|
||||||
/*
|
// A namespaced attr can have at most two parts, `namespace:ident`.
|
||||||
A namespaced attr can have at most two parts, `namespace:ident`.
|
|
||||||
*/
|
|
||||||
if namespaced {
|
if namespaced {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -268,14 +266,10 @@ scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) {
|
|||||||
return string(t.src[offset : t.offset - 1]), .None
|
return string(t.src[offset : t.offset - 1]), .None
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Skip CDATA
|
||||||
Skip CDATA
|
|
||||||
*/
|
|
||||||
skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
|
skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
|
||||||
if t.read_offset + len(CDATA_START) >= len(t.src) {
|
if t.read_offset + len(CDATA_START) >= len(t.src) {
|
||||||
/*
|
// Can't be the start of a CDATA tag.
|
||||||
Can't be the start of a CDATA tag.
|
|
||||||
*/
|
|
||||||
return .None
|
return .None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,9 +284,7 @@ skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
|
|||||||
return .Premature_EOF
|
return .Premature_EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Scan until the end of a CDATA tag.
|
||||||
Scan until the end of a CDATA tag.
|
|
||||||
*/
|
|
||||||
if t.read_offset + len(CDATA_END) < len(t.src) {
|
if t.read_offset + len(CDATA_END) < len(t.src) {
|
||||||
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
|
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
|
||||||
t.read_offset += len(CDATA_END)
|
t.read_offset += len(CDATA_END)
|
||||||
@@ -319,14 +311,10 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close
|
|||||||
case '<':
|
case '<':
|
||||||
if peek_byte(t) == '!' {
|
if peek_byte(t) == '!' {
|
||||||
if peek_byte(t, 1) == '[' {
|
if peek_byte(t, 1) == '[' {
|
||||||
/*
|
// Might be the start of a CDATA tag.
|
||||||
Might be the start of a CDATA tag.
|
|
||||||
*/
|
|
||||||
skip_cdata(t) or_return
|
skip_cdata(t) or_return
|
||||||
} else if peek_byte(t, 1) == '-' && peek_byte(t, 2) == '-' {
|
} else if peek_byte(t, 1) == '-' && peek_byte(t, 2) == '-' {
|
||||||
/*
|
// Comment start. Eat comment.
|
||||||
Comment start. Eat comment.
|
|
||||||
*/
|
|
||||||
t.read_offset += 3
|
t.read_offset += 3
|
||||||
_ = scan_comment(t) or_return
|
_ = scan_comment(t) or_return
|
||||||
}
|
}
|
||||||
@@ -342,17 +330,13 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close
|
|||||||
}
|
}
|
||||||
|
|
||||||
if t.ch == close {
|
if t.ch == close {
|
||||||
/*
|
// If it's not a CDATA or comment, it's the end of this body.
|
||||||
If it's not a CDATA or comment, it's the end of this body.
|
|
||||||
*/
|
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
advance_rune(t)
|
advance_rune(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Strip trailing whitespace.
|
||||||
Strip trailing whitespace.
|
|
||||||
*/
|
|
||||||
lit := string(t.src[offset : t.offset])
|
lit := string(t.src[offset : t.offset])
|
||||||
|
|
||||||
end := len(lit)
|
end := len(lit)
|
||||||
@@ -369,11 +353,6 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close
|
|||||||
if consume_close {
|
if consume_close {
|
||||||
advance_rune(t)
|
advance_rune(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
TODO: Handle decoding escape characters and unboxing CDATA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
return lit, err
|
return lit, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +363,7 @@ peek :: proc(t: ^Tokenizer) -> (token: Token) {
|
|||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
scan :: proc(t: ^Tokenizer) -> Token {
|
scan :: proc(t: ^Tokenizer, multiline_string := false) -> Token {
|
||||||
skip_whitespace(t)
|
skip_whitespace(t)
|
||||||
|
|
||||||
offset := t.offset
|
offset := t.offset
|
||||||
@@ -418,7 +397,7 @@ scan :: proc(t: ^Tokenizer) -> Token {
|
|||||||
case '"', '\'':
|
case '"', '\'':
|
||||||
kind = .Invalid
|
kind = .Invalid
|
||||||
|
|
||||||
lit, err = scan_string(t, t.offset, ch, true, false)
|
lit, err = scan_string(t, t.offset, ch, true, multiline_string)
|
||||||
if err == .None {
|
if err == .None {
|
||||||
kind = .String
|
kind = .String
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,9 +203,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
|
|||||||
|
|
||||||
doc.elements = make([dynamic]Element, 1024, 1024, allocator)
|
doc.elements = make([dynamic]Element, 1024, 1024, allocator)
|
||||||
|
|
||||||
// strings.intern_init(&doc.intern, allocator, allocator)
|
err = .Unexpected_Token
|
||||||
|
|
||||||
err = .Unexpected_Token
|
|
||||||
element, parent: Element_ID
|
element, parent: Element_ID
|
||||||
open: Token
|
open: Token
|
||||||
|
|
||||||
@@ -259,8 +257,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
|
|||||||
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
|
||||||
|
|
||||||
case:
|
case:
|
||||||
error(t, t.offset, "Expected close tag, got: %#v\n", end_token)
|
error(t, t.offset, "Expected close tag, got: %#v\n", end_token)
|
||||||
@@ -276,8 +274,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
|
|||||||
error(t, t.offset, "Mismatched Closing Tag. Expected %v, got %v\n", doc.elements[element].ident, ident.text)
|
error(t, t.offset, "Mismatched Closing Tag. Expected %v, got %v\n", doc.elements[element].ident, ident.text)
|
||||||
return doc, .Mismatched_Closing_Tag
|
return doc, .Mismatched_Closing_Tag
|
||||||
}
|
}
|
||||||
parent = doc.elements[element].parent
|
parent = doc.elements[element].parent
|
||||||
element = parent
|
element = parent
|
||||||
|
|
||||||
} else if open.kind == .Exclaim {
|
} else if open.kind == .Exclaim {
|
||||||
// <!
|
// <!
|
||||||
@@ -463,8 +461,8 @@ validate_options :: proc(options: Options) -> (validated: Options, err: Error) {
|
|||||||
return validated, .None
|
return validated, .None
|
||||||
}
|
}
|
||||||
|
|
||||||
expect :: proc(t: ^Tokenizer, kind: Token_Kind) -> (tok: Token, err: Error) {
|
expect :: proc(t: ^Tokenizer, kind: Token_Kind, multiline_string := false) -> (tok: Token, err: Error) {
|
||||||
tok = scan(t)
|
tok = scan(t, multiline_string=multiline_string)
|
||||||
if tok.kind == kind { return tok, .None }
|
if tok.kind == kind { return tok, .None }
|
||||||
|
|
||||||
error(t, t.offset, "Expected \"%v\", got \"%v\".", kind, tok.kind)
|
error(t, t.offset, "Expected \"%v\", got \"%v\".", kind, tok.kind)
|
||||||
@@ -480,7 +478,13 @@ parse_attribute :: proc(doc: ^Document) -> (attr: Attribute, offset: int, err: E
|
|||||||
offset = t.offset - len(key.text)
|
offset = t.offset - len(key.text)
|
||||||
|
|
||||||
_ = expect(t, .Eq) or_return
|
_ = expect(t, .Eq) or_return
|
||||||
value := expect(t, .String) or_return
|
value := expect(t, .String, multiline_string=true) or_return
|
||||||
|
|
||||||
|
normalized, normalize_err := entity.decode_xml(value.text, {.Normalize_Whitespace}, doc.allocator)
|
||||||
|
if normalize_err == .None {
|
||||||
|
append(&doc.strings_to_free, normalized)
|
||||||
|
value.text = normalized
|
||||||
|
}
|
||||||
|
|
||||||
attr.key = key.text
|
attr.key = key.text
|
||||||
attr.val = value.text
|
attr.val = value.text
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ xml_test_utf8_normal :: proc(t: ^testing.T) {
|
|||||||
},
|
},
|
||||||
expected_doctype = "恥ずべきフクロウ",
|
expected_doctype = "恥ずべきフクロウ",
|
||||||
},
|
},
|
||||||
crc32 = 0xe9b62f03,
|
crc32 = 0xefa55f27,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ xml_test_utf8_unbox_cdata :: proc(t: ^testing.T) {
|
|||||||
},
|
},
|
||||||
expected_doctype = "恥ずべきフクロウ",
|
expected_doctype = "恥ずべきフクロウ",
|
||||||
},
|
},
|
||||||
crc32 = 0x9c2643ed,
|
crc32 = 0x2dd27770,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ xml_test_entities_unbox :: proc(t: ^testing.T) {
|
|||||||
},
|
},
|
||||||
expected_doctype = "html",
|
expected_doctype = "html",
|
||||||
},
|
},
|
||||||
crc32 = 0x3b6d4a90,
|
crc32 = 0x350ca83e,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ xml_test_entities_unbox_decode :: proc(t: ^testing.T) {
|
|||||||
},
|
},
|
||||||
expected_doctype = "html",
|
expected_doctype = "html",
|
||||||
},
|
},
|
||||||
crc32 = 0x5be2ffdc,
|
crc32 = 0x7f58db7d,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ xml_test_unicode :: proc(t: ^testing.T) {
|
|||||||
expected_doctype = "",
|
expected_doctype = "",
|
||||||
},
|
},
|
||||||
err = .None,
|
err = .None,
|
||||||
crc32 = 0x0b6100ab,
|
crc32 = 0x73070b55,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user