#include "decodedtext.h" #include #include #include #include "qt_helpers.hpp" extern "C" { bool stdmsg_(char const * msg, fortran_charlen_t); } namespace { QRegularExpression tokens_re {R"( ^ (?:(?[A-Z0-9/]+)\sRR73;\s)? # dual reply DXpedition message (?: (? (?:CQ|DE|QRZ) (?:\s?DX|\s (?:[A-Z]{1,4}|\d{3}) # directional CQ ) | [A-Z0-9/]+ # DX call |\.{3} # unknown hash code )\s ) (?: (?[A-Z0-9/]+) # DE call (?:\s (?[-+A-Z0-9]+) # report (?:\s (? (?: OOO # EME | (?!RR73)[A-R]{2}[0-9]{2} # grid square (not RR73) | 5[0-9]{5} # EU VHF Contest report & serial ) ) (?:\s (?[A-R]{2}[0-9]{2}[A-X]{2}) # EU VHF Contest grid locator )? )? )? )? )" , QRegularExpression::ExtendedPatternSyntaxOption}; } DecodedText::DecodedText (QString const& the_string) : string_ {the_string.left (the_string.indexOf (QChar::Nbsp))} // discard appended info , clean_string_ {string_} , padding_ {string_.indexOf (" ") > 4 ? 2 : 0} // allow for // seconds , message_ {string_.mid (column_qsoText + padding_).trimmed ()} , is_standard_ {false} { // discard appended AP info clean_string_.replace (QRegularExpression {R"(^(.*?)(?:\?\s)?[aq][0-9].*$)"}, "\\1"); // qDebug () << "DecodedText: the_string:" << the_string << "Nbsp pos:" << the_string.indexOf (QChar::Nbsp); if (message_.length() >= 1) { message0_ = message_.left(37); message_ = message_.left(37).remove (QRegularExpression {"[<>]"}); int i1 = message_.indexOf ('\r'); if (i1 > 0) { message_ = message_.left (i1 - 1); } if (message_.contains (QRegularExpression {"^(CQ|QRZ)\\s"})) { // TODO this magic position 16 is guaranteed to be after the // last space in a decoded CQ or QRZ message but before any // appended DXCC entity name or worked before information auto eom_pos = message_.indexOf (' ', 16); // we always want at least the characters to position 16 if (eom_pos < 16) eom_pos = message_.size () - 1; // remove DXCC entity and worked B4 status. TODO need a better way to do this message_ = message_.left (eom_pos + 1); } // stdmsg is a Fortran routine that packs the text, unpacks it // and compares the result auto message_c_string = message0_.toLocal8Bit (); message_c_string += QByteArray {37 - message_c_string.size (), ' '}; is_standard_ = stdmsg_(message_c_string.constData(),37); } }; QStringList DecodedText::messageWords () const { if(is_standard_) { // extract up to the first four message words QString t=message_; if(t.left(4)=="TU; ") t=message_.mid(4,-1); return tokens_re.match(t).capturedTexts(); } // simple word split for free text messages auto words = message_.split (' ', SkipEmptyParts); // add whole message and two empty strings as item 0 & 1 to mimic RE // capture list words.prepend (QString {}); words.prepend (message_); return words; } QString DecodedText::CQersCall() const { QRegularExpression callsign_re {R"(^(CQ|DE|QRZ)(\s?DX|\s([A-Z]{1,4}|\d{3}))?\s(?[A-Z0-9/]{2,})(\s[A-R]{2}[0-9]{2})?)"}; return callsign_re.match (message_).captured ("callsign"); } bool DecodedText::isJT65() const { return string_.indexOf("#") == column_mode + padding_; } bool DecodedText::isJT9() const { return string_.indexOf("@") == column_mode + padding_; } bool DecodedText::isTX() const { int i = string_.indexOf("Tx"); return (i >= 0 && i < 15); // TODO guessing those numbers. Does Tx ever move? } bool DecodedText::isLowConfidence () const { return QChar {'?'} == string_.mid (padding_ + column_qsoText + 36, 1); } int DecodedText::frequencyOffset() const { return string_.mid(column_freq + padding_,4).toInt(); } int DecodedText::snr() const { int i1=string_.indexOf(" ")+1; return string_.mid(i1,3).toInt(); } float DecodedText::dt() const { return string_.mid(column_dt + padding_,5).toFloat(); } /* 2343 -11 0.8 1259 # YV6BFE F6GUU R-08 2343 -19 0.3 718 # VE6WQ SQ2NIJ -14 2343 -7 0.3 815 # KK4DSD W7VP -16 2343 -13 0.1 3627 @ CT1FBK IK5YZT R+02 0605 Tx 1259 # CQ VK3ACF QF22 */ // find and extract any report. Returns true if this is a standard message bool DecodedText::report(QString const& myBaseCall, QString const& dxBaseCall, /*mod*/QString& report) const { if (message_.size () < 1) return false; QStringList const& w = message_.split(" ", SkipEmptyParts); int offset {0}; if (w.size () > 2) { if ("RR73;" == w[1] && w.size () > 3) { offset = 2; } if (is_standard_ && (w[offset] == myBaseCall || w[offset].endsWith ("/" + myBaseCall) || w[offset].startsWith (myBaseCall + "/") || (w.size () > offset + 1 && !dxBaseCall.isEmpty () && (w[offset + 1] == dxBaseCall || w[offset + 1].endsWith ("/" + dxBaseCall) || w[offset + 1].startsWith (dxBaseCall + "/"))))) { bool ok; auto tt = w[offset + 2]; auto i1=tt.toInt(&ok); if (ok and i1>=-50 and i1<50) { report = tt; } else { if (tt.mid(0,1)=="R") { i1=tt.mid(1).toInt(&ok); if(ok and i1>=-50 and i1<50) { report = tt.mid(1); } } } } } return is_standard_; } // get the first text word, usually the call QString DecodedText::call() const { return tokens_re.match (message_).captured ("word1"); } // get the second word, most likely the de call and the third word, most likely grid void DecodedText::deCallAndGrid(/*out*/QString& call, QString& grid) const { auto msg = message_; auto p = msg.indexOf ("; "); if (p >= 0) { msg = msg.mid (p + 2); } auto const& match = tokens_re.match (msg); call = match.captured ("word2"); grid = match.captured ("word3"); if ("R" == grid) grid = match.captured ("word4"); } unsigned DecodedText::timeInSeconds() const { return 3600 * string_.mid (column_time, 2).toUInt () + 60 * string_.mid (column_time + 2, 2).toUInt() + (padding_ ? string_.mid (column_time + 2 + padding_, 2).toUInt () : 0U); } QString DecodedText::report() const // returns a string of the SNR field with a leading + or - followed by two digits { int sr = snr(); if (sr<-50) sr = -50; else if (sr > 49) sr = 49; QString rpt; rpt = rpt.asprintf("%d",abs(sr)); if (sr > 9) rpt = "+" + rpt; else if (sr >= 0) rpt = "+0" + rpt; else if (sr >= -9) rpt = "-0" + rpt; else rpt = "-" + rpt; return rpt; }