Skip to main content

aviation_wx_notam/
lib.rs

1use aviation_wx_core::{ finalize_issues, issue_from_legacy,
2    DecodeResponse, DetailLevel, MessageType, NotamNormalized, NotamParsed,
3};
4use regex::Regex;
5
6pub fn parse_notam(raw: &str) -> (NotamParsed, Vec<String>) {
7    let mut warnings = Vec::new();
8    let normalized_raw = raw.replace("\r\n", "\n").replace('\r', "\n");
9    let raw_lines: Vec<String> = normalized_raw
10        .lines()
11        .map(|line| line.trim().to_string())
12        .filter(|line| !line.is_empty())
13        .collect();
14
15    let upper = normalized_raw.to_ascii_uppercase();
16    let tag_re = Regex::new(r"(Q\)|A\)|B\)|C\)|D\)|E\)|F\)|G\))").unwrap();
17    let mut positions: Vec<(usize, String)> = tag_re
18        .find_iter(&upper)
19        .map(|m| (m.start(), m.as_str().to_string()))
20        .collect();
21    positions.sort_by_key(|(pos, _)| *pos);
22
23    let mut q_line = None;
24    let mut a = None;
25    let mut b = None;
26    let mut c = None;
27    let mut d = None;
28    let mut e = None;
29    let mut f = None;
30    let mut g = None;
31
32    if positions.is_empty() {
33        warnings.push("No NOTAM tags found; parsed as raw lines.".to_string());
34    }
35
36    for idx in 0..positions.len() {
37        let (start, tag) = &positions[idx];
38        let tag_len = tag.len();
39        let end = if idx + 1 < positions.len() {
40            positions[idx + 1].0
41        } else {
42            upper.len()
43        };
44        let content = normalized_raw
45            .get(start + tag_len..end)
46            .unwrap_or("")
47            .trim()
48            .to_string();
49        match tag.as_str() {
50            "Q)" => q_line = if content.is_empty() { None } else { Some(content) },
51            "A)" => a = if content.is_empty() { None } else { Some(content) },
52            "B)" => b = if content.is_empty() { None } else { Some(content) },
53            "C)" => c = if content.is_empty() { None } else { Some(content) },
54            "D)" => d = if content.is_empty() { None } else { Some(content) },
55            "E)" => e = if content.is_empty() { None } else { Some(content) },
56            "F)" => f = if content.is_empty() { None } else { Some(content) },
57            "G)" => g = if content.is_empty() { None } else { Some(content) },
58            _ => {}
59        }
60    }
61
62    (
63        NotamParsed {
64            q_line,
65            a,
66            b,
67            c,
68            d,
69            e,
70            f,
71            g,
72            raw_lines,
73        },
74        warnings,
75    )
76}
77
78pub fn normalize_notam(parsed: &NotamParsed) -> NotamNormalized {
79    NotamNormalized {
80        q_line: parsed.q_line.clone(),
81        a: parsed.a.clone(),
82        b: parsed.b.clone(),
83        c: parsed.c.clone(),
84        d: parsed.d.clone(),
85        e: parsed.e.clone(),
86        f: parsed.f.clone(),
87        g: parsed.g.clone(),
88    }
89}
90
91pub fn translate_notam(normalized: &NotamNormalized, detail: DetailLevel, lang: &str) -> String {
92    if lang != "zh-CN" {
93        return "Translation only supports zh-CN for now.".to_string();
94    }
95
96    let mut parts = Vec::new();
97    if let Some(a) = &normalized.a {
98        parts.push(format!("地点 {}", a));
99    }
100    if let Some(b) = &normalized.b {
101        parts.push(format!("生效 {}", b));
102    }
103    if let Some(c) = &normalized.c {
104        parts.push(format!("终止 {}", c));
105    }
106    if let Some(e) = &normalized.e {
107        parts.push(format!("内容 {}", e));
108    }
109    if detail == DetailLevel::Full {
110        if let Some(q) = &normalized.q_line {
111            parts.push(format!("Q 行 {}", q));
112        }
113        if let Some(d) = &normalized.d {
114            parts.push(format!("D {}", d));
115        }
116        if let Some(f) = &normalized.f {
117            parts.push(format!("F {}", f));
118        }
119        if let Some(g) = &normalized.g {
120            parts.push(format!("G {}", g));
121        }
122    }
123
124    if parts.is_empty() {
125        "未提取到常见 NOTAM 字段。".to_string()
126    } else {
127        parts.join(",")
128    }
129}
130
131pub fn decode_notam(raw: &str, detail: DetailLevel, lang: &str) -> DecodeResponse {
132    let (parsed, warnings_legacy_raw) = parse_notam(raw);
133    let normalized = normalize_notam(&parsed);
134    let explain = translate_notam(&normalized, detail, lang);
135
136    let mut warnings: Vec<aviation_wx_core::Issue> =
137        warnings_legacy_raw.iter().map(|item| issue_from_legacy(item)).collect();
138    let mut errors = Vec::new();
139    let (warnings_legacy, errors_legacy) = finalize_issues(&mut warnings, &mut errors);
140
141    DecodeResponse {
142        schema_version: "1.0".to_string(),
143        message_type: MessageType::Notam,
144        requested_type: MessageType::Notam,
145        detected_type: MessageType::Notam,
146        final_type: MessageType::Notam,
147        raw: raw.trim().to_string(),
148        parsed: Some(aviation_wx_core::ParsedMessage::Notam(parsed)),
149        normalized: Some(aviation_wx_core::NormalizedMessage::Notam(normalized)),
150        explain: Some(explain),
151        warnings,
152        errors,
153        warnings_legacy,
154        errors_legacy,
155    }
156}
157