// Named-field token syntax and ordered field lookup
// (ISO 28500:2017 clauses 4 and 5).
//
// Field names follow the RFC 2616 token rule; comparison is
// case-insensitive while the original spelling is preserved. Lookup
// helpers operate on the ordered field array of a parsed record.
///|
/// Whether `b` is an RFC 2616 separator. Separators terminate a token
/// and can never appear inside a field name.
pub fn is_separator(b : Byte) -> Bool {
match b {
b'('
| b')'
| b'<'
| b'>'
| b'@'
| b','
| b';'
| b':'
| b'\\'
| b'"'
| b'/'
| b'['
| b']'
| b'?'
| b'='
| b'{'
| b'}'
| b' '
| b'\t' => true
_ => false
}
}
///|
/// Whether `b` is a legal token byte: US-ASCII CHAR (0..127), not a
/// control character, not a separator. Bytes above 127 are not tokens.
pub fn is_token_byte(b : Byte) -> Bool {
b < 0x80 && !is_ctl(b) && !is_separator(b)
}
///|
/// Whether `data[start:end]` forms a valid field name (1*token).
pub fn valid_field_name(data : Bytes, start : Int, end : Int) -> Bool {
if end - start == 0 {
return false
}
let mut i = start
while i < end {
if !is_token_byte(data[i]) {
return false
}
i = i + 1
}
true
}
///|
/// The value of the first field with a case-insensitive name match.
pub fn WarcRecord::field_first(self : WarcRecord, name : String) -> String? {
for i = 0; i < self.fields.length(); i = i + 1 {
if self.fields[i].name.equal_ignore_ascii_case(name) {
return Some(self.fields[i].value)
}
}
None
}
///|
/// All values of fields with a case-insensitive name match, in input
/// order (relevant for repeatable fields such as WARC-Concurrent-To).
pub fn WarcRecord::field_all(self : WarcRecord, name : String) -> Array[String] {
let out : Array[String] = []
for i = 0; i < self.fields.length(); i = i + 1 {
if self.fields[i].name.equal_ignore_ascii_case(name) {
out.push(self.fields[i].value)
}
}
out
}
///|
/// How many fields carry a case-insensitive name match.
pub fn WarcRecord::field_count(self : WarcRecord, name : String) -> Int {
let mut n = 0
for i = 0; i < self.fields.length(); i = i + 1 {
if self.fields[i].name.equal_ignore_ascii_case(name) {
n = n + 1
}
}
n
}
///|
/// Whether at least one field carries a case-insensitive name match.
pub fn WarcRecord::field_has(self : WarcRecord, name : String) -> Bool {
self.field_first(name) != None
}