Files
go-caatsm/internal/parsers/aviation_parser.go
T

275 lines
8.2 KiB
Go
Raw Normal View History

package parsers
import (
2024-07-20 22:21:12 +08:00
"caatsm/internal/config"
"caatsm/internal/domain"
2024-07-20 22:21:12 +08:00
"fmt"
2024-07-20 22:33:22 +08:00
"regexp"
"strings"
2024-07-20 22:25:28 +08:00
"time"
)
2024-07-20 18:55:28 +08:00
const (
StartIndicatorPrefix = "ZCZC"
EndHeaderMarker = "."
BeginPartMarker = "BEGIN PART"
)
2024-07-22 08:12:18 +08:00
var (
2024-07-22 12:42:30 +08:00
categoryRegex = regexp.MustCompile(`\((?P<category>[A-Z]+)-`)
2024-07-22 08:12:18 +08:00
emptyLineRemove = regexp.MustCompile(`(?m)^\s*$`)
2024-07-22 14:43:35 +08:00
bodyOnly = regexp.MustCompile(`(.|\n)?(ZCZC(.|\n)*)NNNN(.|\n)?$`)
2024-07-22 08:12:18 +08:00
)
2024-07-20 22:21:12 +08:00
type BodyParser struct {
2024-07-20 22:33:22 +08:00
bodyPatterns map[string]config.BodyConfig
2024-07-20 22:21:12 +08:00
}
2024-07-20 22:33:22 +08:00
// NewBodyParser initializes a BodyParser with the default body patterns.
func NewBodyParser() *BodyParser {
return &BodyParser{bodyPatterns: config.GetBodyPatterns()}
2024-07-20 22:21:12 +08:00
}
2024-07-20 22:33:22 +08:00
// GetBodyPatterns returns the body patterns used by the parser.
2024-07-20 22:21:12 +08:00
func (bp *BodyParser) GetBodyPatterns() map[string]config.BodyConfig {
2024-07-20 22:33:22 +08:00
return bp.bodyPatterns
2024-07-20 22:21:12 +08:00
}
2024-07-20 22:33:22 +08:00
// SetBodyPatterns sets the body patterns for the parser.
2024-07-20 22:21:12 +08:00
func (bp *BodyParser) SetBodyPatterns(patterns map[string]config.BodyConfig) {
2024-07-20 22:33:22 +08:00
bp.bodyPatterns = patterns
2024-07-20 22:21:12 +08:00
}
2024-07-20 22:33:22 +08:00
// Parse attempts to parse the body text using the configured patterns.
2024-07-20 22:21:12 +08:00
func (bp *BodyParser) Parse(body string) (interface{}, error) {
2024-07-22 12:42:30 +08:00
// log := utils.Logger
body = strings.TrimSpace(body)
2024-07-22 12:42:30 +08:00
// log.Info("Parsing body text", body)
category := findCategory(body)
if category == "" {
2024-07-22 12:42:30 +08:00
// log.Error("No category found in body text")
return nil, fmt.Errorf("no category found in body text")
}
patters := bp.GetBodyPatterns()
2024-07-22 12:42:30 +08:00
// log.Infof("body config [%s] %v\n", category, patters[category])
if patterConfig := patters[category]; patterConfig.Patterns != nil {
for _, p := range patterConfig.Patterns {
2024-07-22 12:42:30 +08:00
// log.Infof("Trying pattern %s\n%s\n", p.Comments, p.Pattern)
2024-07-20 22:21:12 +08:00
re := p.Expression
match := re.FindStringSubmatch(body)
2024-07-22 12:42:30 +08:00
// log.Info("Match: ", match)
2024-07-20 22:21:12 +08:00
if match != nil {
2024-07-22 12:42:30 +08:00
// log.Infof("Matched: %v\n", match)
2024-07-20 22:33:22 +08:00
data := extractData(match, re)
return createBodyData(data)
2024-07-20 22:21:12 +08:00
}
2024-07-22 12:42:30 +08:00
// log.Infof("No match for pattern %s\n", p.Comments)
2024-07-20 22:21:12 +08:00
}
2024-07-20 22:21:12 +08:00
}
return nil, fmt.Errorf(" no matching pattern found for body: %s", body)
}
func findCategory(body string) string {
match := categoryRegex.FindStringSubmatch(body)
2024-07-22 12:42:30 +08:00
// utils.Logger.Infof("Match: %v\n", match)
if match != nil {
2024-07-22 12:42:30 +08:00
groups := categoryRegex.SubexpNames()
for i, name := range groups {
if i != 0 && name == "category" {
return match[i]
}
}
}
return ""
2024-07-20 22:21:12 +08:00
}
2024-07-20 22:33:22 +08:00
// extractData extracts named groups from the regex match.
func extractData(match []string, re *regexp.Regexp) map[string]string {
data := make(map[string]string)
for i, name := range re.SubexpNames() {
if i != 0 && name != "" {
data[name] = match[i]
}
}
return data
}
// createBodyData creates the appropriate domain object based on the type of message.
func createBodyData(data map[string]string) (interface{}, error) {
2024-07-21 00:25:16 +08:00
switch data["category"] {
2024-07-20 22:21:12 +08:00
case "ARR":
return &domain.ARR{
2024-07-21 00:25:16 +08:00
Category: data["category"],
2024-07-20 22:21:12 +08:00
AircraftID: data["number"],
SSRModeAndCode: data["ssr"],
DepartureAirport: data["departure"],
ArrivalAirport: data["arrival"],
ArrivalTime: data["time"],
2024-07-20 22:21:12 +08:00
}, nil
case "DEP":
return &domain.DEP{
2024-07-21 00:25:16 +08:00
Category: data["category"],
2024-07-20 22:21:12 +08:00
AircraftID: data["number"],
SSRModeAndCode: data["ssr"],
DepartureAirport: data["departure"],
DepartureTime: data["departure_time"],
Destination: data["arrival"],
}, nil
case "FPL":
return &domain.FPL{
2024-07-21 00:25:16 +08:00
Category: data["category"],
2024-07-20 22:21:12 +08:00
FlightNumber: data["number"],
ReferenceData: data["reference_data"],
AircraftID: data["aircraft"],
SSRModeAndCode: data["surve"],
FlightRulesAndType: data["indicator"],
CruisingSpeedAndLevel: data["speed"] + data["level"],
DepartureAirport: data["departure"],
DepartureTime: data["departure_time"],
Route: data["route"],
DestinationAndTotalTime: data["destination"] + data["estt"],
AlternateAirport: data["alter"],
OtherInfo: fmt.Sprintf("%s %s REG/%s EET/%s SEL/%s PER/%s RIF/%s",
data["pbn"], data["nav"], data["reg"], data["eet"], data["sel"], data["performance"], data["rif"]),
SupplementaryInfo: "RMK/" + data["remark"],
EstimatedArrivalTime: data["estimated_arrival_time"],
PBN: data["pbn"],
NavigationEquipment: data["nav"],
EstimatedElapsedTime: data["eet"],
SELCALCode: data["sel"],
PerformanceCategory: data["performance"],
RerouteInformation: data["rif"],
Remarks: data["remark"],
}, nil
default:
2024-07-21 00:25:16 +08:00
return nil, fmt.Errorf("invalid message type: %s", data["category"])
2024-07-20 22:21:12 +08:00
}
}
2024-07-20 22:33:22 +08:00
// Parse parses the raw text message and returns a ParsedMessage.
2024-07-20 22:21:12 +08:00
func Parse(rawText string) (*domain.ParsedMessage, error) {
message, err := ParseHeader(rawText)
if err != nil {
return nil, err
}
2024-07-20 22:33:22 +08:00
bodyParser := NewBodyParser()
2024-07-20 22:21:12 +08:00
bodyData, err := bodyParser.Parse(message.BodyAndFooter)
if err != nil {
return nil, err
}
2024-07-20 22:25:28 +08:00
message.ParsedAt = time.Now()
2024-07-20 22:21:12 +08:00
message.BodyData = bodyData
return &message, nil
}
// removeEmptyLines removes empty lines from a given text.
2024-07-22 08:12:18 +08:00
func clean(text string) string {
cleanedText := emptyLineRemove.ReplaceAllString(text, "")
cleanText := strings.ReplaceAll(cleanedText, "\n\n", "\n")
2024-07-22 14:43:35 +08:00
// if bodyOnly != nil {
match := bodyOnly.FindStringSubmatch(cleanText)
if len(match) > 1 {
bodyOnly := match[2]
if bodyOnly[len(bodyOnly)-1] == '\n' {
return bodyOnly[:len(bodyOnly)-1]
}
2024-07-22 14:43:35 +08:00
return bodyOnly
2024-07-22 08:12:18 +08:00
}
2024-07-22 14:43:35 +08:00
// }
2024-07-22 08:12:18 +08:00
return ""
}
2024-07-20 22:33:22 +08:00
// parseHeader parses the header of the message and returns a ParsedMessage struct.
2024-07-20 18:55:28 +08:00
func ParseHeader(fullMessage string) (domain.ParsedMessage, error) {
2024-07-22 08:12:18 +08:00
fullMessage = clean(fullMessage)
// fullMessage = strings.TrimSpace(fullMessage)
2024-07-20 22:33:22 +08:00
lines := strings.Split(fullMessage, "\n")
2024-07-20 18:55:28 +08:00
startIndicator, messageID, dateTime, err := parseStartIndicator(lines[0])
if err != nil {
2024-07-20 22:33:22 +08:00
return domain.ParsedMessage{}, err
}
// priorityIndicator, primaryAddress, err := parsePriorityAndPrimary(lines[1])
// if err != nil {
// return domain.ParsedMessage{}, err
// }
priorityIndicator, primaryAddress := parsePriorityAndPrimary(lines[1])
2024-07-20 18:55:28 +08:00
secondaryAddresses, originator, originatorDateTime, bodyAndFooter := parseRemainingLines(lines[2:])
return domain.ParsedMessage{
StartIndicator: startIndicator,
MessageID: messageID,
DateTime: dateTime,
PriorityIndicator: priorityIndicator,
PrimaryAddress: primaryAddress,
SecondaryAddresses: secondaryAddresses,
Originator: originator,
OriginatorDateTime: originatorDateTime,
2024-07-20 18:55:28 +08:00
BodyAndFooter: bodyAndFooter,
2024-07-20 22:25:28 +08:00
ReceivedAt: time.Now(),
2024-07-20 18:55:28 +08:00
}, nil
}
2024-07-20 22:33:22 +08:00
// parseStartIndicator parses the start indicator line.
2024-07-20 18:55:28 +08:00
func parseStartIndicator(line string) (string, string, string, error) {
2024-07-20 22:33:22 +08:00
parts := strings.Fields(line)
if len(parts) >= 3 && strings.HasPrefix(parts[0], StartIndicatorPrefix) {
return parts[0], parts[1], parts[2], nil
}
2024-07-20 22:33:22 +08:00
return "", "", "", fmt.Errorf("invalid start indicator line format: %s", line)
2024-07-20 18:55:28 +08:00
}
2024-07-20 22:33:22 +08:00
// parsePriorityAndPrimary parses the priority indicator and primary address line.
func parsePriorityAndPrimary(line string) (string, string) {
2024-07-20 18:55:28 +08:00
parts := strings.Fields(line)
if len(parts) >= 2 {
return parts[0], parts[1]
2024-07-20 18:55:28 +08:00
}
return "", ""
2024-07-20 18:55:28 +08:00
}
2024-07-20 22:33:22 +08:00
// parseRemainingLines parses the remaining lines of the message.
2024-07-20 18:55:28 +08:00
func parseRemainingLines(lines []string) ([]string, string, string, string) {
var (
secondaryAddresses []string
originator string
originatorDateTime string
bodyAndFooter strings.Builder
headerEnded bool
)
for _, line := range lines {
line = strings.TrimSpace(line)
if headerEnded {
bodyAndFooter.WriteString(line + "\n")
} else {
switch {
case line == EndHeaderMarker:
2024-07-20 22:33:22 +08:00
// End header marker, do nothing
2024-07-20 18:55:28 +08:00
case strings.HasPrefix(line, "."):
originatorInfo := strings.Fields(line[1:])
if len(originatorInfo) >= 2 {
originator = originatorInfo[0]
originatorDateTime = originatorInfo[1]
}
headerEnded = true
case strings.HasPrefix(line, BeginPartMarker) || strings.HasPrefix(line, "("):
headerEnded = true
if strings.Index(line, "NNNN") > 0 {
break
}
2024-07-20 18:55:28 +08:00
bodyAndFooter.WriteString(line + "\n")
default:
secondaryAddresses = append(secondaryAddresses, line)
}
}
}
return secondaryAddresses, originator, originatorDateTime, bodyAndFooter.String()
}