// 数据段解析与序列化。
//
// 数据段是 `QN=...;ST=...;CN=...;PW=...;MN=...;Flag=...;PNUM=...;PNO=...;CP=&&...&&`。
//
// 这里有一个容易踩的坑:CP 的取值本身也用 `;` 分隔字段,所以不能把整个
// 数据段直接按 `;` 切开再找 `CP`——那样会把数据区撕成碎片。正确做法是先
// 定位 `CP=`,它之前的部分按 `;` 切字段,它之后的部分整体交给数据区解析。
///|
pub fn Segment::parse(text : String) -> Result[Segment, DecodeError] {
let cs = trim_chars(chars_of(text))
if cs.length() == 0 {
return Err(EmptySegment)
}
let cp_pos = find_seq(cs, "CP=", 0)
let (head_cs, cp_text) = if cp_pos >= 0 {
(chars_slice(cs, 0, cp_pos), slice_chars(cs, cp_pos + 3, cs.length()))
} else {
(chars_slice(cs, 0, cs.length()), "")
}
let mut qn = ""
let mut st = ""
let mut cn = ""
let mut pw = ""
let mut mn = ""
let mut flag_raw : Int32? = None
let mut pnum : Int32? = None
let mut pno : Int32? = None
let extra : Array[DataItem] = []
let seen_cp = cp_pos >= 0
// 核心字段只允许出现一次。现场报文偶尔会把 MN 重复贴两遍,
// 若静默取后者,出问题时很难查,所以宁可报错。
let seen_fields : Array[String] = []
let mut start = 0
let mut i = 0
while i <= head_cs.length() {
let at_end = i == head_cs.length()
if at_end || head_cs[i] == ';' {
let piece = trim_chars(chars_slice(head_cs, start, i))
if piece.length() > 0 {
let eq = find_char(piece, '=', 0)
if eq < 0 {
return Err(BadField(slice_chars(piece, 0, piece.length())))
}
let name = trim_text(slice_chars(piece, 0, eq))
let value = trim_text(slice_chars(piece, eq + 1, piece.length()))
let is_core = name == "QN" ||
name == "ST" ||
name == "CN" ||
name == "PW" ||
name == "MN" ||
name == "Flag" ||
name == "PNUM" ||
name == "PNO"
if is_core {
if seen_fields.contains(name) {
return Err(DuplicateField(name))
}
seen_fields.push(name)
}
match name {
"QN" => qn = value
"ST" => st = value
"CN" => cn = value
"PW" => pw = value
"MN" => mn = value
"Flag" =>
match parse_uint_all(value) {
Some(v) => flag_raw = Some(v & 0xFF)
None => return Err(BadFieldValue(name, value))
}
"PNUM" =>
match parse_uint_all(value) {
Some(v) => pnum = Some(v)
None => return Err(BadFieldValue(name, value))
}
"PNO" =>
match parse_uint_all(value) {
Some(v) => pno = Some(v)
None => return Err(BadFieldValue(name, value))
}
_ => extra.push({ name, values: [value], })
}
}
start = i + 1
}
i = i + 1
}
if qn == "" {
return Err(MissingField("QN"))
}
if st == "" {
return Err(MissingField("ST"))
}
if cn == "" {
return Err(MissingField("CN"))
}
if mn == "" {
return Err(MissingField("MN"))
}
let flag = match flag_raw {
Some(v) => Flag::from_raw(v)
None => return Err(MissingField("Flag"))
}
let cp = if seen_cp {
match DataArea::parse(cp_text) {
Ok(area) => area
Err(e) => return Err(e)
}
} else {
return Err(MissingField("CP"))
}
Ok({ qn, st, cn, pw, mn, flag, pnum, pno, cp, extra, })
}
///|
/// 渲染成规范的数据段文本。
pub fn Segment::text(self : Segment) -> String {
let sb = StringBuilder()
sb.write_string("QN=")
sb.write_string(self.qn)
sb.write_string(";ST=")
sb.write_string(self.st)
sb.write_string(";CN=")
sb.write_string(self.cn)
if self.pw != "" {
sb.write_string(";PW=")
sb.write_string(self.pw)
}
sb.write_string(";MN=")
sb.write_string(self.mn)
sb.write_string(";Flag=")
sb.write_string(self.flag.text())
match self.pnum {
Some(v) => {
sb.write_string(";PNUM=")
sb.write_string(v.to_string())
}
None => ()
}
match self.pno {
Some(v) => {
sb.write_string(";PNO=")
sb.write_string(v.to_string())
}
None => ()
}
let mut i = 0
while i < self.extra.length() {
sb.write_string(";")
sb.write_string(self.extra[i].name)
sb.write_string("=")
let values = self.extra[i].values
let mut j = 0
while j < values.length() {
if j > 0 {
sb.write_char(',')
}
sb.write_string(values[j])
j = j + 1
}
i = i + 1
}
sb.write_string(";CP=")
sb.write_string(self.cp.wrapped_text())
sb.to_string()
}
///|
/// 语义检查。
///
/// 结构性问题在 `parse` 阶段就会失败;这里报的是「能解析但不符合行业惯例」
/// 的情况,例如 MN 长度与版本不匹配、Flag 声明分包却没有包号。
/// 返回空数组表示没有发现问题。
pub fn Segment::validate(self : Segment) -> Array[String] {
let warnings : Array[String] = []
let qn_cs = chars_of(self.qn)
if qn_cs.length() < 14 || qn_cs.length() > 20 {
warnings.push(
"QN 长度 \{qn_cs.length()},通常为 17 位(YYYYMMDDhhmmsszzz)",
)
}
let mut all_digit = true
for c in qn_cs {
if !is_digit_char(c) {
all_digit = false
}
}
if !all_digit {
warnings.push("QN 含非数字字符")
}
match QnTime::parse(self.qn) {
Some(_) => ()
None => warnings.push("QN 无法解析成时间戳")
}
let cn_cs = chars_of(self.cn)
if cn_cs.length() != 4 {
warnings.push("CN 长度 \{cn_cs.length()},标准为 4 位")
}
match self.flag.version() {
Unknown(n) => warnings.push("Flag 版本位 \{n} 未登记")
_ => ()
}
if self.flag.is_split() {
if self.pnum is None || self.pno is None {
warnings.push("Flag 声明分包,但缺少 PNUM 或 PNO")
}
} else if self.pnum is Some(_) || self.pno is Some(_) {
warnings.push("Flag 声明不分包,但出现了 PNUM 或 PNO")
}
let mn_len = chars_of(self.mn).length()
match self.flag.version() {
V2005 =>
if mn_len != 14 {
warnings.push("2005 版 MN 通常为 14 位,实际 \{mn_len} 位")
}
V2017 =>
if mn_len != 24 {
warnings.push("2017 版 MN 通常为 24 位,实际 \{mn_len} 位")
}
V2025 =>
if mn_len != 24 {
warnings.push("2025 版 MN 通常为 24 位,实际 \{mn_len} 位")
}
Unknown(_) => ()
}
warnings
}