///|
/// Lossless URDF XML parsing.
///|
/// An XML element together with its source location, kept internally so that
/// conversion errors can point at the offending line and column.
priv struct XNode {
tag : String
attrs : Array[(String, String)]
attr_pos : Array[(Int32, Int32)]
text : String
children : Array[XNode]
line : Int32
col : Int32
}
///|
priv struct XmlParser {
text : String
pos : Array[Int32]
line : Array[Int32]
col : Array[Int32]
}
///|
fn xp_new(text : String) -> XmlParser {
{ text, pos: [0], line: [1], col: [1], }
}
///|
fn xp_len(p : XmlParser) -> Int32 {
p.text.length()
}
///|
fn xp_at(p : XmlParser, i : Int32) -> Int32 {
if i < 0 || i >= xp_len(p) {
-1
} else {
UInt16::to_int(String::code_unit_at(p.text, i))
}
}
///|
fn xp_peek(p : XmlParser) -> Int32 {
xp_at(p, p.pos[0])
}
///|
fn xp_advance(p : XmlParser) -> Unit {
let c = xp_peek(p)
if c < 0 {
return
}
p.pos[0] = p.pos[0] + 1
if c == Char::to_int('\n') {
p.line[0] = p.line[0] + 1
p.col[0] = 1
} else {
p.col[0] = p.col[0] + 1
}
}
///|
fn xp_starts(p : XmlParser, s : String) -> Bool {
let n = s.length()
if p.pos[0] + n > xp_len(p) {
return false
}
for i in 0.. String {
String::unsafe_substring(p.text, start~, end~)
}
///|
fn xp_snippet(p : XmlParser) -> String {
let n = xp_len(p)
let i = p.pos[0]
let mut start = i
while start > 0 && xp_at(p, start - 1) != Char::to_int('\n') {
start = start - 1
}
let mut end = i
while end < n && xp_at(p, end) != Char::to_int('\n') {
end = end + 1
}
xp_substr(p, start, end)
}
///|
fn xp_error(p : XmlParser, message : String) -> Unit raise UrdfParseError {
raise UrdfParseError({
message,
line: Some(p.line[0]),
col: Some(p.col[0]),
snippet: Some(xp_snippet(p)),
})
}
///|
fn is_space_code(c : Int32) -> Bool {
c == Char::to_int(' ') ||
c == Char::to_int('\t') ||
c == Char::to_int('\r') ||
c == Char::to_int('\n')
}
///|
fn xp_skip_ws(p : XmlParser) -> Unit {
while is_space_code(xp_peek(p)) {
xp_advance(p)
}
}
///|
fn is_name_char(c : Int32) -> Bool {
c >= 0 &&
c != Char::to_int(' ') &&
c != Char::to_int('\t') &&
c != Char::to_int('\r') &&
c != Char::to_int('\n') &&
c != Char::to_int('<') &&
c != Char::to_int('>') &&
c != Char::to_int('/') &&
c != Char::to_int('=')
}
///|
fn xp_skip_pi(p : XmlParser) -> Unit raise UrdfParseError {
// Consume "", then everything up to "?>".
xp_advance(p)
xp_advance(p)
while true {
if p.pos[0] >= xp_len(p) {
xp_error(p, "unterminated processing instruction")
}
if xp_starts(p, "?>") {
xp_advance(p)
xp_advance(p)
return
}
xp_advance(p)
}
}
///|
fn xp_skip_comment(p : XmlParser) -> String raise UrdfParseError {
// Consume "") {
let content = xp_substr(p, start, p.pos[0])
xp_advance(p)
xp_advance(p)
xp_advance(p)
return content
}
xp_advance(p)
}
""
}
///|
fn xp_skip_misc(p : XmlParser) -> Unit raise UrdfParseError {
while true {
xp_skip_ws(p)
if xp_starts(p, "