///|
pub fn parse_gs1(input : String) -> GS1Message raise ParseError {
let trimmed = input.trim().to_owned()
if trimmed.is_empty() {
raise ParseError::InvalidSyntax("empty GS1 payload")
}
if trimmed.has_prefix("(") {
parse_hri(trimmed)
} else {
parse_raw(trimmed)
}
}
///|
pub fn parse_gs1_with_options(
input : String,
options : ParseOptions,
) -> GS1Message raise ParseError {
if input.length() > options.max_length {
raise ParseError::Truncated("payload", options.max_length, input.length())
}
let trimmed = input.trim().to_owned()
if trimmed.is_empty() {
raise ParseError::InvalidSyntax("empty GS1 payload")
}
let hri = trimmed.has_prefix("(")
match options.mode {
HRI if !hri =>
raise ParseError::InvalidSyntax("raw payload requires raw mode")
Raw if hri =>
raise ParseError::InvalidSyntax("HRI payload requires HRI mode")
_ => ()
}
if !options.allow_group_separator && contains_group_separator(trimmed) {
raise ParseError::InvalidSyntax("group separator is disabled")
}
if hri && trimmed.get_char(trimmed.length() - 1) == Some('\u{1D}') {
raise ParseError::InvalidSyntax(
"HRI payload cannot end with a group separator",
)
}
let message = if hri { parse_hri(trimmed) } else { parse_raw(trimmed) }
if !options.allow_empty_variable {
for element in message.elements {
if element.value.is_empty() {
raise ParseError::InvalidSyntax("AI {element.code()} requires a value")
}
}
}
message
}
///|
fn contains_group_separator(input : String) -> Bool {
for ch in input {
if is_group_separator(ch) {
return true
}
} nobreak {
false
}
}
///|
fn parse_hri(input : String) -> GS1Message raise ParseError {
let elements : Array[GS1Element] = []
let len = input.length()
for pos = 0; pos < len; {
guard input.get_char(pos) is Some('(') else {
raise ParseError::InvalidSyntax("expected '(' at offset \{pos}")
}
let close = find_close(input, pos + 1)
let code = input.unsafe_substring(start=pos + 1, end=close)
let spec = lookup_ai_exact(code)
let value_start = close + 1
let (value, next) = read_value(input, value_start, spec, hri=true)
elements.push({ ai: spec, value })
continue next
} nobreak {
{ elements, }
}
}
///|
fn parse_raw(input : String) -> GS1Message raise ParseError {
let elements : Array[GS1Element] = []
let len = input.length()
for pos = 0; pos < len; {
match input.get_char(pos) {
Some(ch) if is_group_separator(ch) => continue pos + 1
_ => ()
}
let (spec, value_start) = find_ai(input, pos)
let (value, next) = read_value(input, value_start, spec, hri=false)
elements.push({ ai: spec, value })
continue next
} nobreak {
{ elements, }
}
}
///|
fn lookup_ai_exact(code : String) -> ApplicationIdentifier raise ParseError {
for item in known_ais() {
if item.code == code {
return item
}
}
raise ParseError::UnknownAI(code)
}
///|
fn find_close(input : String, pos : Int) -> Int raise ParseError {
let len = input.length()
for i = pos; i < len; i = i + 1 {
if input.get_char(i) == Some(')') {
if i == pos {
raise ParseError::InvalidSyntax("empty application identifier")
}
return i
}
}
raise ParseError::InvalidSyntax("missing ')' after offset \{pos - 1}")
}
///|
fn read_value(
input : String,
start : Int,
spec : ApplicationIdentifier,
hri~ : Bool,
) -> (String, Int) raise ParseError {
match spec.fixed_length {
Some(size) => {
let end = start + size
if end > input.length() {
raise ParseError::Truncated(spec.code, size, input.length() - start)
}
(input.unsafe_substring(start~, end~), end)
}
None => read_variable_value(input, start, spec, hri~)
}
}
///|
fn read_variable_value(
input : String,
start : Int,
spec : ApplicationIdentifier,
hri~ : Bool,
) -> (String, Int) raise ParseError {
let limit = if start + spec.max_length < input.length() {
start + spec.max_length
} else {
input.length()
}
for pos = start; pos < limit; pos = pos + 1 {
match input.get_char(pos) {
Some(ch) if is_group_separator(ch) => {
let value = input.unsafe_substring(start~, end=pos)
return (value, pos + 1)
}
Some('(') if hri => {
let value = input.unsafe_substring(start~, end=pos)
return (value, pos)
}
_ => ()
}
}
let value = input.unsafe_substring(start~, end=limit)
if value.is_empty() {
raise ParseError::InvalidSyntax("AI \{spec.code} requires a value")
}
(value, limit)
}