Enhanced transaction parsing by integrating QDomElement for XML handling and adding functions to parse transaction details, history, and metadata. Replaced raw QString operations with structured data extraction and improved formatting for date, amount, and bank information parsing.
726 lines
28 KiB
C++
726 lines
28 KiB
C++
#include "ScreenXmlParser.h"
|
||
|
||
#include <QRegularExpression>
|
||
#include <QMap>
|
||
#include <QSet>
|
||
#include <QList>
|
||
#include <QXmlStreamReader>
|
||
#include <QDomDocument>
|
||
#include <QFile>
|
||
#include <QDateTime>
|
||
#include <QTimeZone>
|
||
|
||
#include "adb/AdbUtils.h"
|
||
#include "android/xml/Node.h"
|
||
#include "db/TransactionInfo.h"
|
||
|
||
ScreenXmlParser::ScreenXmlParser(QObject *parent) : QObject(parent) {
|
||
}
|
||
|
||
ScreenXmlParser::~ScreenXmlParser() = default;
|
||
|
||
static const QRegularExpression boundsRegex(R"(\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\])");
|
||
static const QRegularExpression onlyDigitsRegex(R"([^0-9])");
|
||
|
||
static const QRegularExpression rePhone(R"raw((\+7\(?\d{3}\)?[- ]?\d{3}[- ]?\d{2}[- ]?\d{2}))raw");
|
||
static const QRegularExpression reFromBank(R"raw(из (.+?) на)raw");
|
||
static const QRegularExpression reToBank(R"raw(в (.+?) через)raw");
|
||
static const QRegularExpression reSbpId(R"(СБП(?: С2С)? ([A-Z0-9]{30,}))");
|
||
static const QRegularExpression reIdAlt(R"(ID перевода (\d+))");
|
||
static const QRegularExpression reDate(R"((?:Дата операции|дата) (\d{2}\.\d{2}\.\d{4}(?: \d{2}:\d{2}:\d{2})?))");
|
||
static const QRegularExpression reSender(R"(от ([^\(]+))");
|
||
static const QRegularExpression reNameAlt(R"(Перевод ([А-Яа-яЁё\s\*]+) в)");
|
||
|
||
Node ScreenXmlParser::tryToFindBannerOrDialog(const int width, const int height) {
|
||
// 1. Ищем push banner - он перекрывает весь экран
|
||
Node first = m_nodes[0];
|
||
for (const Node &node: m_nodes) {
|
||
// Если это однозначно, что пуш-баннер
|
||
if (node.resourceId == "aft-popup-pushNotification-iconClose" && node.className == "android.widget.Button") {
|
||
return node;
|
||
}
|
||
// Если есть кнопка "Позже"
|
||
if (node.text.trimmed() == "Позже" && node.className == "android.widget.Button") {
|
||
return node;
|
||
}
|
||
// Если нашли пустую кнпоку
|
||
if (node.className == "android.widget.Button" && node.clickable && node.text.isEmpty()) {
|
||
// <node class="android.widget.Button" package="ru.rshb.dbo" clickable="true" bounds="[630,142][691,205]"/>
|
||
if (node.x() > width / 2 && node.y() < width / 2) {
|
||
return node;
|
||
}
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
/**
|
||
* Смотрим что есть текс "Мы заботимся о вашей безопасности" и
|
||
* кнопка "Отложить"
|
||
* @return координаты кнопки для закрытия
|
||
*/
|
||
Node ScreenXmlParser::tryToFindSecurityBanner() {
|
||
Node text;
|
||
Node closeBtn;
|
||
for (const Node &node: m_nodes) {
|
||
if (node.text.trimmed() == "Мы заботимся о вашей безопасности" && node.className == "android.app.Dialog") {
|
||
text = node;
|
||
}
|
||
if (node.text.trimmed() == "Отложить" && node.className == "android.widget.Button") {
|
||
closeBtn = node;
|
||
}
|
||
}
|
||
|
||
// Не нужно проверять все, если хотя бы одна координата есть
|
||
if (!text.isEmpty() && !closeBtn.isEmpty()) {
|
||
return closeBtn;
|
||
}
|
||
return {};
|
||
}
|
||
|
||
Node ScreenXmlParser::findTextNode(const QString &text) {
|
||
for (const Node &node: m_nodes) {
|
||
if (node.className == "android.widget.TextView" && node.text.trimmed() == text) {
|
||
return node;
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
Node ScreenXmlParser::findTextNode(const QString &text, const int x1, const int y1, const int x2, const int y2) {
|
||
for (const Node &node: m_nodes) {
|
||
if (node.className == "android.widget.TextView" && node.text.trimmed() == text
|
||
&& node.x() >= x1 && node.x() <= x2 && node.y() >= y1 && node.y() <= y2) {
|
||
return node;
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
Node ScreenXmlParser::findFirstEditText() {
|
||
for (const Node &node: m_nodes) {
|
||
if (node.className == "android.widget.EditText" && node.focusable) {
|
||
return node;
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
|
||
Node ScreenXmlParser::findButtonNode(const QString &text, const bool contains) {
|
||
for (const Node &node: m_nodes) {
|
||
if (contains) {
|
||
if (node.className == "android.widget.Button" && node.text.contains(text)) {
|
||
return node;
|
||
}
|
||
} else {
|
||
if (node.className == "android.widget.Button" && node.text.trimmed() == text) {
|
||
return node;
|
||
}
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
|
||
bool ScreenXmlParser::isHomeScreen() {
|
||
// Есть заголовок "Счета и карты" и кнопка "Все продукты"
|
||
Node text;
|
||
Node allProductBtn;
|
||
for (const Node &node: m_nodes) {
|
||
if (node.text.trimmed() == "Счета и карты" && node.className == "android.widget.TextView") {
|
||
text = node;
|
||
}
|
||
if (node.text.trimmed() == "Все продукты" && node.className == "android.widget.Button") {
|
||
allProductBtn = node;
|
||
}
|
||
}
|
||
return !text.isEmpty() && !allProductBtn.isEmpty();
|
||
}
|
||
|
||
QList<Node> ScreenXmlParser::findPinCodeNodes(const QString &pinCode) {
|
||
QMap<QString, Node> buttons;
|
||
const QSet<QString> numberSet = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
|
||
|
||
for (const Node &node: m_nodes) {
|
||
const QString &text = node.text.trimmed();
|
||
if (node.className == "android.widget.Button" && numberSet.contains(text)) {
|
||
buttons[text] = node;
|
||
}
|
||
}
|
||
|
||
QList<Node> nodes;
|
||
for (int i = 0; i < pinCode.length(); ++i) {
|
||
nodes.append(buttons[QString(pinCode.at(i))]);
|
||
}
|
||
return nodes;
|
||
}
|
||
|
||
QList<Node> ScreenXmlParser::tryToFindPinCode(const QString &pinCode, const QString &title) {
|
||
for (const Node &node: m_nodes) {
|
||
// если там есть такая кнопка значит банер есть, и пин-код не прожать
|
||
if (node.text.trimmed() == "Отложить" && node.className == "android.widget.Button") {
|
||
return {};
|
||
}
|
||
}
|
||
for (const Node &node: m_nodes) {
|
||
// FIXME определение пин-кода лучше сделать
|
||
if (node.text.trimmed() == title) {
|
||
return findPinCodeNodes(pinCode);
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
QList<Node> ScreenXmlParser::getLast2DaysTransactions() {
|
||
QList<Node> nodes;
|
||
for (const Node &node: m_nodes) {
|
||
if (node.className == "android.widget.Button" && node.text.contains("рубл")
|
||
&& (node.text.contains("Минус") || node.text.contains("Плюс"))) {
|
||
nodes.append(node);
|
||
}
|
||
}
|
||
return nodes;
|
||
}
|
||
|
||
/**
|
||
* <node NAF="true" index="1" text="" resource-id=""
|
||
class="android.widget.Button" package="ru.rshb.dbo"
|
||
content-desc="" checkable="false" checked="false"
|
||
clickable="true" enabled="true" focusable="false"
|
||
focused="false" scrollable="false"
|
||
long-clickable="false" password="false"
|
||
selected="false" bounds="[952,159][1039,248]"/>
|
||
*/
|
||
void ScreenXmlParser::parseAndSaveXml(const QString &xml) {
|
||
QDomDocument doc;
|
||
doc.setContent(xml);
|
||
m_xml = doc.documentElement();
|
||
m_nodes.clear();
|
||
|
||
QXmlStreamReader reader(xml);
|
||
while (!reader.atEnd()) {
|
||
reader.readNext();
|
||
if (reader.isStartElement() && reader.name() == "node") {
|
||
Node node = Node();
|
||
if (reader.attributes().hasAttribute("bounds")) {
|
||
QString bounds = reader.attributes().value("bounds").toString();
|
||
if (QRegularExpressionMatch match = boundsRegex.match(bounds); match.hasMatch()) {
|
||
node.setBounds(
|
||
match.captured(1).toInt(),
|
||
match.captured(2).toInt(),
|
||
match.captured(3).toInt(),
|
||
match.captured(4).toInt()
|
||
);
|
||
}
|
||
}
|
||
if (reader.attributes().hasAttribute("text")) {
|
||
node.text = reader.attributes().value("text").toString();
|
||
}
|
||
|
||
if (reader.attributes().hasAttribute("class")) {
|
||
node.className = reader.attributes().value("class").toString();
|
||
}
|
||
|
||
if (reader.attributes().hasAttribute("clickable")) {
|
||
node.clickable = reader.attributes().value("clickable").toString() == "true";
|
||
}
|
||
if (reader.attributes().hasAttribute("focusable")) {
|
||
node.focusable = reader.attributes().value("focusable").toString() == "true";
|
||
}
|
||
if (reader.attributes().hasAttribute("resource-id")) {
|
||
node.resourceId = reader.attributes().value("resource-id").toString();
|
||
}
|
||
|
||
m_nodes.append(node);
|
||
}
|
||
}
|
||
}
|
||
|
||
QString getDayOfWeekAsString(const int day_of_week) {
|
||
switch (day_of_week) {
|
||
case 1: return "понедельник";
|
||
case 2: return "вторник";
|
||
case 3: return "среда";
|
||
case 4: return "четверг";
|
||
case 5: return "пятница";
|
||
case 6: return "суббота";
|
||
case 7: return "воскресенье";
|
||
default: return "";
|
||
}
|
||
}
|
||
|
||
QString getMonthAsString(const int month) {
|
||
switch (month) {
|
||
case 1: return "января";
|
||
case 2: return "февраля";
|
||
case 3: return "марта";
|
||
case 4: return "апреля";
|
||
case 5: return "мая";
|
||
case 6: return "июня";
|
||
case 7: return "июля";
|
||
case 8: return "августа";
|
||
case 9: return "сентября";
|
||
case 10: return "октября";
|
||
case 11: return "ноября";
|
||
case 12: return "декабря";
|
||
default: return "";
|
||
}
|
||
}
|
||
|
||
QString findSubInfo(const QDomElement &elem) {
|
||
if (elem.hasChildNodes()) {
|
||
QDomNodeList subInfoList = elem.childNodes();
|
||
for (QDomNode subInfoNode: subInfoList) {
|
||
if (!subInfoNode.isElement()) continue;
|
||
QDomElement subInfo = subInfoNode.toElement();
|
||
if (subInfo.attribute("index") == "1") {
|
||
return subInfo.attribute("text");
|
||
}
|
||
}
|
||
}
|
||
return "";
|
||
}
|
||
|
||
bool hasTimeComponent(const QString &datetimeStr) {
|
||
QDateTime dt = QDateTime::fromString(datetimeStr, "dd.MM.yyyy HH:mm:ss");
|
||
if (!dt.isValid()) {
|
||
return false; // Строка не соответствует формату даты и времени
|
||
}
|
||
return dt.time() != QTime(0, 0, 0); // Проверка, что время не равно 00:00:00
|
||
}
|
||
|
||
void parseTransferInfo(const QString &text, TransactionInfo &info) {
|
||
if (const auto m = reSender.match(text); m.hasMatch()) {
|
||
info.name = m.captured(1).trimmed();
|
||
} else if (const auto m1 = reNameAlt.match(text); m1.hasMatch()) {
|
||
info.name = m1.captured(1).trimmed();
|
||
}
|
||
|
||
if (const auto m = rePhone.match(text); m.hasMatch()) {
|
||
info.phone = m.captured(1).trimmed().remove(onlyDigitsRegex);
|
||
}
|
||
|
||
if (const auto m = reFromBank.match(text); m.hasMatch()) {
|
||
if (const QString bank = m.captured(1).trimmed(); !bank.isEmpty()) {
|
||
info.bankName = bank;
|
||
}
|
||
} else if (const auto m1 = reToBank.match(text); m1.hasMatch()) {
|
||
if (const QString bank = m1.captured(1).trimmed(); !bank.isEmpty()) {
|
||
info.bankName = bank;
|
||
}
|
||
}
|
||
|
||
if (const auto m = reSbpId.match(text); m.hasMatch()) {
|
||
info.trExternalId = m.captured(1);
|
||
} else if (const auto m1 = reIdAlt.match(text); m1.hasMatch()) {
|
||
info.trExternalId = m1.captured(1);
|
||
}
|
||
|
||
if (const auto m = reDate.match(text); m.hasMatch()) {
|
||
const QString date = m.captured(1);
|
||
// если не пустая и дата у нас с 00:00
|
||
if (hasTimeComponent(date)) {
|
||
info.bankTime = date;
|
||
}
|
||
}
|
||
}
|
||
|
||
void traverseNodes(const QDomNode &node) {
|
||
if (node.isElement()) {
|
||
QDomElement elem = node.toElement();
|
||
|
||
if (node.isElement()) {
|
||
QDomElement elem = node.toElement();
|
||
|
||
if (elem.tagName() == "node" &&
|
||
elem.attribute("class") == "android.widget.Button" &&
|
||
elem.attribute("text") == "Детали операции") {
|
||
// Все что ниже деталей операции
|
||
QDomNodeList detailInfo = elem.parentNode().childNodes();
|
||
|
||
|
||
for (int j = 0; j < detailInfo.count(); ++j) {
|
||
QDomNode info = detailInfo.at(j);
|
||
if (!info.isElement()) continue;
|
||
QDomElement infoElement = info.toElement();
|
||
QString index = infoElement.attribute("index");
|
||
switch (index.toInt()) {
|
||
case 2:
|
||
qDebug() << "Дата операции:" << findSubInfo(infoElement);
|
||
continue;
|
||
case 5:
|
||
qDebug() << "Название:" << findSubInfo(infoElement);
|
||
qDebug() << "-------------------------------------";
|
||
// parseTransfer(findSubInfo(infoElement));
|
||
continue;
|
||
case 7:
|
||
qDebug() << "Комиссия:" << findSubInfo(infoElement);;
|
||
continue;
|
||
default:
|
||
qDebug() << "Прочее:" << findSubInfo(infoElement);;
|
||
continue;
|
||
}
|
||
|
||
/**
|
||
Node: "2" ""
|
||
... Node: "0" "Дата операции"
|
||
... Node: "1" "30.04.2025 17:46:29"
|
||
Node: "5" ""
|
||
... Node: "0" "Название"
|
||
... Node: "1" "DBO TRANSFER RSHB INTERNET-BANK"
|
||
Node: "7" ""
|
||
... Node: "0" "Комиссия"
|
||
... Node: "1" "50 ₽"
|
||
*/
|
||
|
||
// if (sibElem.hasChildNodes()) {
|
||
// QDomNodeList sibChildren = sibElem.childNodes();
|
||
// for (int k = 0; k < sibChildren.count(); ++k) {
|
||
// QDomNode sibChild = sibChildren.at(k);
|
||
// if (!sibChild.isElement()) continue;
|
||
// QDomElement sibChildElem = sibChild.toElement();
|
||
// qDebug() << "... Node:"
|
||
// << sibChildElem.attribute("index")
|
||
// << sibChildElem.attribute("text");
|
||
// // << sibChildElem.attribute("class");
|
||
//
|
||
//
|
||
// if (sibChildElem.hasChildNodes()) {
|
||
// QDomNodeList sibChildren2 = sibChildElem.childNodes();
|
||
// for (int k = 0; k < sibChildren2.count(); ++k) {
|
||
// QDomNode sibChild2 = sibChildren2.at(k);
|
||
// if (!sibChild2.isElement()) continue;
|
||
// QDomElement sibChildElem2 = sibChild2.toElement();
|
||
//
|
||
// qDebug() << "...... Node:"
|
||
// << sibChildElem2.attribute("index")
|
||
// << sibChildElem2.attribute("text");
|
||
// // << sibChildElem2.attribute("class");
|
||
//
|
||
// if (sibChildElem2.hasChildNodes()) {
|
||
// QDomNodeList sibChildren3 = sibChildElem2.childNodes();
|
||
// for (int k = 0; k < sibChildren3.count(); ++k) {
|
||
// QDomNode sibChild3 = sibChildren3.at(k);
|
||
// if (!sibChild3.isElement()) continue;
|
||
// QDomElement sibChildElem3 = sibChild3.toElement();
|
||
//
|
||
// qDebug() << "......... Node:"
|
||
// << sibChildElem3.attribute("index")
|
||
// << sibChildElem3.attribute("text");
|
||
// // << sibChildElem2.attribute("class");
|
||
// }
|
||
// }
|
||
// }
|
||
// }
|
||
// }
|
||
// }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Рекурсивно обходим всех детей
|
||
QDomNode child = node.firstChild();
|
||
while (!child.isNull()) {
|
||
traverseNodes(child);
|
||
child = child.nextSibling();
|
||
}
|
||
}
|
||
|
||
QDomNode findNode(const QDomNode &node, const QString &text) {
|
||
if (node.isElement()) {
|
||
QString nodeText = node.toElement().attribute("text");
|
||
if (nodeText.toLower().startsWith(text)) {
|
||
return node;
|
||
}
|
||
}
|
||
|
||
|
||
QDomNode child = node.firstChild();
|
||
while (!child.isNull()) {
|
||
QDomNode result = findNode(child, text);
|
||
if (!result.isNull()) {
|
||
return result;
|
||
}
|
||
child = child.nextSibling();
|
||
}
|
||
return {};
|
||
}
|
||
|
||
float extractAmounts(const QString &text) {
|
||
// Разрешаем пробелы внутри чисел, захватываем "рубль", "рубля", "рублей" и т.д.
|
||
// QRegularExpression re(R"((Плюс|Минус)\s+([\d\s]+)\s+рубл\w*\s+(\d+)\s+копе\w*)");
|
||
QRegularExpression re(
|
||
R"((Плюс|Минус)\s+([\d\s]+)\s+рубл\w*\s+(\d+)\s+копе\w*)",
|
||
QRegularExpression::UseUnicodePropertiesOption
|
||
);
|
||
|
||
QRegularExpressionMatch match = re.match(text);
|
||
if (match.hasMatch()) {
|
||
QString signStr = match.captured(1); // "Плюс" или "Минус"
|
||
QString rublesStr = match.captured(2).remove(' '); // "1 000" → "1000"
|
||
int rubles = rublesStr.toInt();
|
||
int kopecks = match.captured(3).toInt();
|
||
|
||
int totalKopecks = rubles * 100 + kopecks;
|
||
if (signStr == "Минус") {
|
||
totalKopecks *= -1;
|
||
}
|
||
|
||
return totalKopecks / 100.0f;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
QString beforeAmount(const QString &text) {
|
||
int index = text.indexOf(QRegularExpression("Плюс|Минус"));
|
||
if (index != -1) {
|
||
return text.left(index).trimmed();
|
||
}
|
||
return text; // если не найдено — вернуть всю строку
|
||
}
|
||
|
||
QList<TransactionInfo> ScreenXmlParser::parseTodayAndYesterdayHistory() {
|
||
QTimeZone tz("Europe/Moscow");
|
||
QDate today = QDateTime::currentDateTime(tz).date();
|
||
QDate yesterday = today.addDays(-1);
|
||
QDate beforeYesterday = today.addDays(-2);
|
||
|
||
|
||
QString todayTitle = QString("сегодня, %1").arg(getDayOfWeekAsString(today.dayOfWeek()));
|
||
QString yesterdayTitle = QString("вчера, %1").arg(getDayOfWeekAsString(yesterday.dayOfWeek()));
|
||
QString beforeYesterdayTitle = QString("%1 %2, %3").arg(
|
||
QString::number(beforeYesterday.day()),
|
||
getMonthAsString(beforeYesterday.month()),
|
||
getDayOfWeekAsString(beforeYesterday.dayOfWeek())
|
||
);
|
||
|
||
QDomNode todayNode = findNode(m_xml, todayTitle);
|
||
QDomNode yesterdayNode = findNode(m_xml, yesterdayTitle);
|
||
|
||
QDomNode parentNode;
|
||
if (!yesterdayNode.isNull()) {
|
||
parentNode = yesterdayNode.parentNode();
|
||
} else if (!todayNode.isNull()) {
|
||
parentNode = todayNode.parentNode();
|
||
}
|
||
|
||
|
||
QList<TransactionInfo> transactions;
|
||
if (!parentNode.isNull()) {
|
||
for (QDomNode transactionNode: parentNode.childNodes()) {
|
||
QString transactionShortText = transactionNode.toElement().attribute("text");
|
||
|
||
const QString titleLowerCase = transactionShortText.toLower();
|
||
if (titleLowerCase.startsWith(beforeYesterdayTitle)) {
|
||
// только сегодня и вчера парсим, позавчера не трогаем
|
||
break;
|
||
}
|
||
|
||
QDate date = {};
|
||
if (titleLowerCase.startsWith(todayTitle)) {
|
||
date = today;
|
||
transactionShortText = transactionShortText.mid(todayTitle.length() + 1);
|
||
}
|
||
if (titleLowerCase.startsWith(yesterdayTitle)) {
|
||
date = yesterday;
|
||
transactionShortText = transactionShortText.mid(yesterdayTitle.length() + 1);
|
||
}
|
||
|
||
if (transactionShortText.contains("ЕСПП")) {
|
||
continue;
|
||
}
|
||
|
||
float amount = extractAmounts(transactionShortText);
|
||
QString shortDesc = beforeAmount(transactionShortText);
|
||
|
||
TransactionInfo info;
|
||
info.amount = amount;
|
||
info.bankTime = date.toString("dd.MM.yyyy");
|
||
info.shortDescription = shortDesc;
|
||
|
||
transactions.append(info);
|
||
}
|
||
}
|
||
return transactions;
|
||
}
|
||
|
||
void parse2daysHistory(const QDomNode &node) {
|
||
QTimeZone tz("Europe/Moscow");
|
||
QDate today = QDateTime::currentDateTime(tz).date();
|
||
QDate tomorrow = today.addDays(-1);
|
||
QDate after2Days = today.addDays(-2);
|
||
|
||
QString todayTitle = QString("сегодня, %1").arg(getDayOfWeekAsString(today.dayOfWeek()));
|
||
QDomNode todayNode = findNode(node, todayTitle);
|
||
if (todayNode.isNull()) {
|
||
} else {
|
||
qDebug() << "todayNode: " << todayNode.toElement().attribute("text");
|
||
}
|
||
|
||
QString tomorrowTitle = QString("вчера, %1").arg(getDayOfWeekAsString(tomorrow.dayOfWeek()));
|
||
QDomNode tomorrowNode = findNode(node, tomorrowTitle);
|
||
if (tomorrowNode.isNull()) {
|
||
} else {
|
||
qDebug() << "tomorrowNode: " << tomorrowNode.toElement().attribute("text");
|
||
}
|
||
|
||
// 30 апреля, Среда
|
||
QString after2DaysTitle = QString("%1 %2, %3").arg(
|
||
QString::number(after2Days.day()),
|
||
getMonthAsString(after2Days.month()),
|
||
getDayOfWeekAsString(after2Days.dayOfWeek()));
|
||
QDomNode after2DaysNode = findNode(node, after2DaysTitle);
|
||
if (after2DaysNode.isNull()) {
|
||
qDebug() << "No after2DaysNode";
|
||
} else {
|
||
qDebug() << "after2DaysNode: " << after2DaysNode.toElement().attribute("text");
|
||
}
|
||
|
||
QDomNode parentNode;
|
||
if (!todayNode.isNull() || !tomorrowNode.isNull() || !after2DaysNode.isNull()) {
|
||
if (!todayNode.isNull()) {
|
||
parentNode = todayNode.parentNode();
|
||
}
|
||
if (!tomorrowNode.isNull()) {
|
||
parentNode = tomorrowNode.parentNode();
|
||
}
|
||
if (!after2DaysNode.isNull()) {
|
||
parentNode = after2DaysNode.parentNode();
|
||
}
|
||
}
|
||
|
||
|
||
if (!parentNode.isNull()) {
|
||
for (QDomNode child_node: parentNode.childNodes()) {
|
||
QString child_text = child_node.toElement().attribute("text");
|
||
if (child_text.toLower().startsWith(todayTitle)) {
|
||
qDebug() << "=== Today: " << todayTitle;
|
||
qDebug() << "node: " << beforeAmount(child_text.mid(todayTitle.length() + 1));
|
||
qDebug() << "--> sum: " << QString::number(extractAmounts(child_text), 'f', 2);
|
||
// qDebug() << "";
|
||
continue;
|
||
}
|
||
if (child_text.toLower().startsWith(tomorrowTitle)) {
|
||
qDebug() << "=== Yesterday: " << todayTitle;
|
||
qDebug() << "node: " << beforeAmount(child_text.mid(tomorrowTitle.length() + 1));
|
||
qDebug() << "--> sum: " << QString::number(extractAmounts(child_text), 'f', 2);
|
||
// qDebug() << "";
|
||
continue;
|
||
}
|
||
if (child_text.toLower().startsWith(after2DaysTitle)) {
|
||
qDebug() << "=== Before yesterday: " << todayTitle;
|
||
qDebug() << "node: " << beforeAmount(child_text.mid(after2DaysTitle.length() + 1));
|
||
qDebug() << "--> sum: " << QString::number(extractAmounts(child_text), 'f', 2);
|
||
// qDebug() << "";
|
||
continue;
|
||
}
|
||
|
||
qDebug() << "node: " << beforeAmount(child_node.toElement().attribute("text"));
|
||
qDebug() << "--> sum: " <<
|
||
QString::number(extractAmounts(child_node.toElement().attribute("text")), 'f', 2);
|
||
// qDebug() << "";
|
||
}
|
||
}
|
||
|
||
|
||
// QString d = today.day() > 9 ? QString::number(today.day()) : "0" + QString::number(today.day());
|
||
// QString m = today.month() > 9 ? QString::number(today.month()) : "0" + QString::number(today.month());
|
||
// QString y = QString::number(today.year());
|
||
// QString w = getDayOfWeekAsString(today.dayOfWeek());
|
||
// QString mm = getMonthAsString(today.month());
|
||
// qDebug() << "Date:" << d << m << y << w << mm;
|
||
|
||
|
||
// QString d2 = after2Days.day() > 9 ? QString::number(after2Days.day()) : "0" + QString::number(after2Days.day());
|
||
// QString m2 = after2Days.month() > 9
|
||
// ? QString::number(after2Days.month())
|
||
// : "0" + QString::number(after2Days.month());
|
||
// QString y2 = QString::number(after2Days.year());
|
||
// QString w2 = getDayOfWeekAsString(after2Days.dayOfWeek());
|
||
// QString mm2 = getMonthAsString(after2Days.month());
|
||
// qDebug() << "Date:" << d2 << m2 << y2 << w2 << mm2;
|
||
}
|
||
|
||
TransactionInfo parseExpandedInfoHistory(const QDomElement &root) {
|
||
if (root.isElement()) {
|
||
QDomElement elem = root.toElement();
|
||
if (elem.tagName() == "node"
|
||
&& elem.attribute("class") == "android.widget.Button"
|
||
&& elem.attribute("text") == "Детали операции"
|
||
) {
|
||
TransactionInfo transaction;
|
||
// Все что ниже деталей операции
|
||
QDomNodeList detailInfo = elem.parentNode().childNodes();
|
||
|
||
for (int i = 0; i < detailInfo.count(); ++i) {
|
||
QDomNode info = detailInfo.at(i);
|
||
if (!info.isElement()) continue;
|
||
|
||
switch (QDomElement e = info.toElement(); e.attribute("index").toInt()) {
|
||
case 2:
|
||
// Дата операции
|
||
transaction.bankTime = findSubInfo(e);
|
||
break;
|
||
case 5:
|
||
transaction.description = findSubInfo(e);
|
||
parseTransferInfo(transaction.description, transaction);
|
||
break;
|
||
case 7:
|
||
// Комиссия
|
||
transaction.fee = findSubInfo(e).remove(onlyDigitsRegex).toFloat();
|
||
break;
|
||
default:
|
||
// qDebug() << "Прочее:" << findSubInfo(e);
|
||
break;
|
||
}
|
||
}
|
||
return transaction;
|
||
}
|
||
}
|
||
|
||
// Рекурсивно обходим всех детей
|
||
QDomNode child = root.firstChild();
|
||
while (!child.isNull()) {
|
||
if (TransactionInfo info = parseExpandedInfoHistory(child.toElement()); !info.bankTime.isEmpty()) {
|
||
return info;
|
||
}
|
||
child = child.nextSibling();
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
TransactionInfo ScreenXmlParser::parseTransactionInfoHistory(int screenWidth, int screenHeight) {
|
||
// 1. Распарсить описание транзакции
|
||
TransactionInfo transaction = parseExpandedInfoHistory(m_xml);
|
||
// 1. Найти статус: Исполнен | Операция исполнена
|
||
transaction.status = TransactionStatus::Wait;
|
||
Node status1 = findTextNode("Исполнен", 0, 0, screenWidth, screenHeight / 2);
|
||
Node status2 = findTextNode("Операция исполнена", 0, 0, screenWidth, screenHeight / 2);
|
||
if (!status1.isEmpty() || !status2.isEmpty()) {
|
||
transaction.status = TransactionStatus::Complete;
|
||
}
|
||
return transaction;
|
||
}
|
||
|
||
void ScreenXmlParser::test() {
|
||
return;
|
||
QFile file("assets/rshb/test/spb_test_send.xml");
|
||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
qDebug() << "Cannot open file";
|
||
}
|
||
|
||
QDomDocument doc;
|
||
if (!doc.setContent(&file)) {
|
||
qDebug() << "Failed to parse XML";
|
||
}
|
||
file.close();
|
||
|
||
qDebug() << "Start parsing XML:";
|
||
|
||
QDomElement root = doc.documentElement();
|
||
// ЕСПП
|
||
}
|