void Page::parseTags() { // Check redirection QUrl redir = m_replyTags->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (!redir.isEmpty()) { m_urlRegex = m_site->fixUrl(redir.toString(), m_urlRegex); loadTags(); return; } QString source = QString::fromUtf8(m_replyTags->readAll()); m_tags.clear(); if (m_site->contains("Regex/Tags")) { QRegExp rxtags(m_site->value("Regex/Tags")); rxtags.setMinimal(true); int p = 0; QStringList order = m_site->value("Regex/TagsOrder").split('|', QString::SkipEmptyParts); while ((p = rxtags.indexIn(source, p)) != -1) { p += rxtags.matchedLength(); QString type = "", tag = ""; int count = 1; if (order.empty()) { switch (rxtags.captureCount()) { case 4: order << "type" << "" << "count" << "tag"; break; case 3: order << "type" << "tag" << "count"; break; case 2: order << "type" << "tag"; break; case 1: order << "tag"; break; } } for (int o = 0; o < order.size(); o++) { if (order.at(o) == "tag" && tag.isEmpty()) { tag = rxtags.cap(o + 1).replace(" ", "_").replace("&", "&").trimmed(); } else if (order.at(o) == "type" && type.isEmpty()) { type = rxtags.cap(o + 1).toLower().trimmed(); if (type.contains(", ")) { type = type.split(", ").at(0).trimmed(); } if (type == "series") { type = "copyright"; } else if (type == "mangaka") { type = "artist"; } else if (type == "game") { type = "copyright"; } else if (type == "studio") { type = "circle"; } else if (type == "source") { type = "general"; } else if (type == "character group") { type = "general"; } else if (type.length() == 1) { int tpe = type.toInt(); if (tpe >= 0 && tpe <= 4) { QStringList types = QStringList() << "general" << "artist" << "unknown" << "copyright" << "character"; type = types[tpe]; } } } else if (order.at(o) == "count" && count != 0) { count = rxtags.cap(o + 1).toLower().endsWith('k') ? rxtags.cap(3).left(rxtags.cap(3).length() - 1).toInt() * 1000 : rxtags.cap(3).toInt(); } } if (type.isEmpty()) { type = "unknown"; } m_tags.append(Tag(tag, type, count)); } } // Getting last page if (m_site->contains("Regex/Count") && m_imagesCount < 1) { QRegExp rxlast(m_site->value("Regex/Count")); rxlast.indexIn(source, 0); m_imagesCount = rxlast.cap(1).remove(",").toInt(); } if (m_imagesCount < 1) { for (Tag tag : m_tags) { if (tag.text() == m_search.join(" ")) { m_imagesCount = tag.count(); if (m_pagesCount < 0) m_pagesCount = (int)ceil((m_imagesCount * 1.) / m_imagesPerPage); } } } if (m_site->contains("Regex/LastPage") && (m_imagesCount < 1 || m_imagesCount % 1000 == 0)) { QRegExp rxlast(m_site->value("Regex/LastPage")); rxlast.indexIn(source, 0); m_pagesCount = rxlast.cap(1).remove(",").toInt(); if (m_pagesCount != 0) m_imagesCount = m_pagesCount * m_imagesPerPage; } // Wiki m_wiki.clear(); if (m_site->contains("Regex/Wiki")) { QRegExp rxwiki(m_site->value("Regex/Wiki")); rxwiki.setMinimal(true); if (rxwiki.indexIn(source) != -1) { m_wiki = rxwiki.cap(1); m_wiki.remove("/wiki/show?title=").remove(QRegExp("<p><a href=\"([^\"]+)\">Full entry »</a></p>")).replace("<h6>", "<span class=\"title\">").replace("</h6>", "</span>"); } } m_replyTags->deleteLater(); m_replyTagsExists = false; emit finishedLoadingTags(this); }
// main logic of the component - a slot triggered upon data entering the socket // comments inline... void QwwSmtpClientPrivate::_q_readFromSocket() { while (socket->canReadLine()) { QString line = socket->readLine(); qDebug() << "SMTP <<<" << line.toUtf8().constData(); QRegExp rx("(\\d+)-(.*)\n"); // multiline response (aka 250-XYZ) QRegExp rxlast("(\\d+) (.*)\n"); // single or last line response (aka 250 XYZ) bool mid = rx.exactMatch(line); bool last = rxlast.exactMatch(line); // multiline if (mid){ int status = rx.cap(1).toInt(); SMTPCommand &cmd = commandqueue.head(); switch (cmd.type) { // trying to connect case SMTPCommand::Connect: { int stage = cmd.extra.toInt(); // stage 0 completed with success - socket is connected and EHLO was sent if(stage==1 && status==250){ QString arg = rx.cap(2).trimmed(); parseOption(arg); // we're probably receiving options } } break; // trying to establish deferred SSL handshake case SMTPCommand::StartTLS: { int stage = cmd.extra.toInt(); // stage 0 (negotiation) completed ok if(stage==1 && status==250){ QString arg = rx.cap(2).trimmed(); parseOption(arg); // we're probably receiving options } } default: break; } } else // single line if (last) { int status = rxlast.cap(1).toInt(); SMTPCommand &cmd = commandqueue.head(); switch (cmd.type) { // trying to connect case SMTPCommand::Connect: { int stage = cmd.extra.toInt(); // connection established, server sent its banner if (stage==0 && status==220) { sendEhlo(); // connect ok, send ehlo } // server responded to EHLO if (stage==1 && status==250){ // success (EHLO) parseOption(rxlast.cap(2).trimmed()); // we're probably receiving the last option errorString.clear(); setState(QwwSmtpClient::Connected); processNextCommand(); } // server responded to HELO (EHLO failed) if (state==2 && status==250) { // success (HELO) errorString.clear(); setState(QwwSmtpClient::Connected); processNextCommand(); } // EHLO failed, reason given in errorString if (stage==1 && (status==554 || status==501 || status==502 || status==421)) { errorString = rxlast.cap(2).trimmed(); sendHelo(); // ehlo failed, send helo cmd.extra = 2; } //abortDialog(); } break; // trying to establish a delayed SSL handshake case SMTPCommand::StartTLS: { int stage = cmd.extra.toInt(); // received an invitation from the server to enter TLS mode if (stage==0 && status==220) { qDebug() << "SMTP ** startClientEncruption"; socket->startClientEncryption(); } // TLS established, connection is encrypted, EHLO was sent else if (stage==1 && status==250) { setState(QwwSmtpClient::Connected); parseOption(rxlast.cap(2).trimmed()); // we're probably receiving options errorString.clear(); emit q->tlsStarted(); processNextCommand(); } // starttls failed else { qDebug() << "TLS failed at stage " << stage << ": " << line; errorString = "TLS failed"; emit q->done(false); } } break; // trying to authenticate the client to the server case SMTPCommand::Authenticate: { int stage = cmd.extra.toInt(); if (stage==0 && status==334) { // AUTH mode was accepted by the server, 1st challenge sent QwwSmtpClient::AuthMode authmode = (QwwSmtpClient::AuthMode)cmd.data.toList().at(0).toInt(); errorString.clear(); switch (authmode) { case QwwSmtpClient::AuthPlain: sendAuthPlain(cmd.data.toList().at(1).toString(), cmd.data.toList().at(2).toString()); break; case QwwSmtpClient::AuthLogin: sendAuthLogin(cmd.data.toList().at(1).toString(), cmd.data.toList().at(2).toString(), 1); break; default: qWarning("I shouldn't be here"); setState(QwwSmtpClient::Connected); processNextCommand(); break; } cmd.extra = stage+1; } else if (stage==1 && status==334) { // AUTH mode and user names were acccepted by the server, 2nd challenge sent QwwSmtpClient::AuthMode authmode = (QwwSmtpClient::AuthMode)cmd.data.toList().at(0).toInt(); errorString.clear(); switch (authmode) { case QwwSmtpClient::AuthPlain: // auth failed setState(QwwSmtpClient::Connected); processNextCommand(); break; case QwwSmtpClient::AuthLogin: sendAuthLogin(cmd.data.toList().at(1).toString(), cmd.data.toList().at(2).toString(), 2); break; default: qWarning("I shouldn't be here"); setState(QwwSmtpClient::Connected); processNextCommand(); break; } } else if (stage==2 && status==334) { // auth failed errorString = rxlast.cap(2).trimmed(); setState(QwwSmtpClient::Connected); processNextCommand(); } else if (status==235) { // auth ok errorString.clear(); emit q->authenticated(); setState(QwwSmtpClient::Connected); processNextCommand(); } else { errorString = rxlast.cap(2).trimmed(); setState(QwwSmtpClient::Connected); emit q->done(false); } } break; // trying to send mail case SMTPCommand::Mail: case SMTPCommand::MailBurl: { int stage = cmd.extra.toInt(); // temporary failure upon receiving the sender address (greylisting probably) if (status==421 && stage==0) { errorString = rxlast.cap(2).trimmed(); // temporary envelope failure (greylisting) setState(QwwSmtpClient::Connected); processNextCommand(false); } if (status==250 && stage==0) { // sender accepted errorString.clear(); sendRcpt(); } else if (status==250 && stage==1) { // all receivers accepted if (cmd.type == SMTPCommand::MailBurl) { errorString.clear(); QByteArray url = cmd.data.toList().at(2).toByteArray(); qDebug() << "SMTP >>> BURL" << url << "LAST"; socket->write("BURL " + url + " LAST\r\n"); cmd.extra=2; } else { errorString.clear(); qDebug() << "SMTP >>> DATA"; socket->write("DATA\r\n"); cmd.extra=2; } } else if ((cmd.type == SMTPCommand::Mail && status==354 && stage==2)) { // DATA command accepted errorString.clear(); QByteArray toBeWritten = cmd.data.toList().at(2).toString().toUtf8(); qDebug() << "SMTP >>>" << toBeWritten << "\r\n.\r\n"; socket->write(toBeWritten); // expecting data to be already escaped (CRLF.CRLF) socket->write("\r\n.\r\n"); // termination token - CRLF.CRLF cmd.extra=3; } else if ((cmd.type == SMTPCommand::MailBurl && status==354 && stage==2)) { // BURL succeeded setState(QwwSmtpClient::Connected); errorString.clear(); processNextCommand(); } else if ((cmd.type == SMTPCommand::Mail && status==250 && stage==3)) { // mail queued setState(QwwSmtpClient::Connected); errorString.clear(); processNextCommand(); } else { // something went wrong errorString = rxlast.cap(2).trimmed(); setState(QwwSmtpClient::Connected); emit q->done(false); processNextCommand(); } } default: break; } } else { qDebug() << "None of two regular expressions matched the input" << line; } } }
void Page::parse() { m_source = m_reply->readAll(); // Check redirection QUrl redir = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (!redir.isEmpty()) { m_url = m_site->fixUrl(redir.toString(), m_url); load(); return; } // Reading reply and resetting vars qDeleteAll(m_images); m_images.clear(); m_tags.clear(); /*m_imagesCount = -1; m_pagesCount = -1;*/ if (m_source.isEmpty()) { if (m_reply->error() != QNetworkReply::OperationCanceledError) { log("Loading error: "+m_reply->errorString()); fallback(); } return; } int first = m_smart ? ((m_page - 1) * m_imagesPerPage) % m_blim : 0; // XML if (m_format == "xml") { // Initializations QDomDocument doc; QString errorMsg; int errorLine, errorColumn; if (!doc.setContent(m_source, false, &errorMsg, &errorLine, &errorColumn)) { log(tr("Erreur lors de l'analyse du fichier XML : %1 (%2 - %3).").arg(errorMsg, QString::number(errorLine), QString::number(errorColumn))); fallback(); return; } QDomElement docElem = doc.documentElement(); // Getting last page int count = docElem.attributes().namedItem("count").nodeValue().toFloat(); QString database = docElem.attributes().namedItem("type").nodeValue(); if (count == 0 && database == "array") { count = docElem.elementsByTagName("total-count").at(0).toElement().text().toInt(); } if (count > 0) { m_imagesCount = count; } // Reading posts QDomNodeList nodeList = docElem.elementsByTagName("post"); if (nodeList.count() > 0) { for (int id = 0; id < nodeList.count(); id++) { QMap<QString,QString> d; if (database == "array") { QStringList infos, assoc; infos << "created_at" << "status" << "source" << "has_comments" << "file_url" << "sample_url" << "change" << "sample_width" << "has_children" << "preview_url" << "width" << "md5" << "preview_width" << "sample_height" << "parent_id" << "height" << "has_notes" << "creator_id" << "file_size" << "id" << "preview_height" << "rating" << "tags" << "author" << "score" << "tags_artist" << "tags_character" << "tags_copyright" << "tags_general" << "ext"; assoc << "created-at" << "status" << "source" << "has_comments" << "file-url" << "large-file-url" << "change" << "sample_width" << "has-children" << "preview-file-url" << "image-width" << "md5" << "preview_width" << "sample_height" << "parent-id" << "image-height" << "has_notes" << "uploader-id" << "file_size" << "id" << "preview_height" << "rating" << "tag-string" << "uploader-name" << "score" << "tag-string-artist" << "tag-string-character" << "tag-string-copyright" << "tag-string-general" << "file-ext"; for (int i = 0; i < infos.count(); i++) { d[infos.at(i)] = nodeList.at(id + first).namedItem(assoc.at(i)).toElement().text(); } } else { QStringList infos; infos << "created_at" << "status" << "source" << "has_comments" << "file_url" << "sample_url" << "change" << "sample_width" << "has_children" << "preview_url" << "width" << "md5" << "preview_width" << "sample_height" << "parent_id" << "height" << "has_notes" << "creator_id" << "file_size" << "id" << "preview_height" << "rating" << "tags" << "author" << "score"; for (int i = 0; i < infos.count(); i++) { d[infos.at(i)] = nodeList.at(id + first).attributes().namedItem(infos.at(i)).nodeValue().trimmed(); } } this->parseImage(d, id + first); } } } // RSS else if (m_format == "rss") { // Initializations QDomDocument doc; QString errorMsg; int errorLine, errorColumn; if (!doc.setContent(m_source, false, &errorMsg, &errorLine, &errorColumn)) { log(tr("Erreur lors de l'analyse du fichier RSS : %1 (%2 - %3).").arg(errorMsg, QString::number(errorLine), QString::number(errorColumn))); fallback(); return; } QDomElement docElem = doc.documentElement(); // Reading posts QDomNodeList nodeList = docElem.elementsByTagName("item"); if (nodeList.count() > 0) { for (int id = 0; id < nodeList.count(); id++) { QDomNodeList children = nodeList.at(id + first).childNodes(); QMap<QString,QString> d, dat; for (int i = 0; i < children.size(); i++) { QString content = children.at(i).childNodes().at(0).nodeValue(); if (!content.isEmpty()) { dat.insert(children.at(i).nodeName(), content.trimmed()); } else { dat.insert(children.at(i).nodeName(), children.at(i).attributes().namedItem("url").nodeValue().trimmed()); } } // QDateTime::fromString(date, "ddd, dd MMM yyyy hh:mm:ss +0000"); // shimmie date format d.insert("page_url", dat["link"]); d.insert("tags", dat["media:keywords"]); d.insert("preview_url", dat["media:thumbnail"]); d.insert("sample_url", dat["media:content"]); d.insert("file_url", dat["media:content"]); if (!d.contains("id")) { QRegExp rx("/(\\d+)"); rx.indexIn(d["page_url"]); d.insert("id", rx.cap(1)); } this->parseImage(d, id + first); } } } // Regexes else if (m_format == "regex") { // Getting tags if (m_site->contains("Regex/Tags")) { QRegExp rxtags(m_site->value("Regex/Tags")); rxtags.setMinimal(true); QStringList tags = QStringList(); int p = 0; while (((p = rxtags.indexIn(m_source, p)) != -1)) { if (!tags.contains(rxtags.cap(2))) { m_tags.append(Tag(rxtags.cap(2), rxtags.cap(1), rxtags.cap(3).toInt())); tags.append(rxtags.cap(2)); } p += rxtags.matchedLength(); } } // Getting images QRegExp rx(m_site->value("Regex/Image")); QStringList order = m_site->value("Regex/Order").split('|'); rx.setMinimal(true); int pos = 0, id = 0; while ((pos = rx.indexIn(m_source, pos)) != -1) { pos += rx.matchedLength(); QMap<QString,QString> d; for (int i = 0; i < order.size(); i++) { d[order.at(i)] = rx.cap(i+1); } // JSON elements if (order.contains("json") && !d["json"].isEmpty()) { QVariant src = Json::parse(d["json"]); if (!src.isNull()) { QMap<QString,QVariant> map = src.toMap(); for (int i = 0; i < map.size(); i++) { d[map.keys().at(i)] = map.values().at(i).toString(); } } } this->parseImage(d, id + first); id++; } } // JSON else if (m_format == "json") { QVariant src = Json::parse(m_source); if (!src.isNull()) { QMap<QString, QVariant> sc; QList<QVariant> sourc = src.toList(); if (sourc.isEmpty()) { sourc = src.toMap().value("images").toList(); } for (int id = 0; id < sourc.count(); id++) { sc = sourc.at(id + first).toMap(); QMap<QString,QString> d; if (sc.contains("tag_string")) { QStringList infos, assoc; infos << "created_at" << "status" << "source" << "has_comments" << "file_url" << "sample_url" << "change" << "sample_width" << "has_children" << "preview_url" << "width" << "md5" << "preview_width" << "sample_height" << "parent_id" << "height" << "has_notes" << "creator_id" << "file_size" << "id" << "preview_height" << "rating" << "tags" << "author" << "score" << "tags_artist" << "tags_character" << "tags_copyright" << "tags_general"; assoc << "created_at" << "status" << "source" << "has_comments" << "file_url" << "large_file_url" << "change" << "sample_width" << "has_children" << "preview_file_url" << "image_width" << "md5" << "preview_width" << "sample_height" << "parent_id" << "image_height" << "has_notes" << "uploader_id" << "file_size" << "id" << "preview_height" << "rating" << "tag_string" << "uploader_name" << "score" << "tag_string_artist" << "tag_string_character" << "tag_string_copyright" << "tag_string_general"; for (int i = 0; i < infos.count(); i++) { d[infos.at(i)] = sc.value(assoc.at(i)).toString().trimmed(); } } else if (sc.contains("tag_ids")) { QStringList infos, assoc; infos << "created_at" << "source" << "file_url" << "preview_url" << "width" << "md5" << "height" << "id" << "tags" << "author" << "score"; assoc << "created_at" << "source_url" << "image" << "image" << "width" << "id" << "height" << "id_number" << "tags" << "uploader" << "score"; for (int i = 0; i < infos.count(); i++) { d[infos.at(i)] = sc.value(assoc.at(i)).toString().trimmed(); } } else { QStringList infos; infos << "created_at" << "status" << "source" << "has_comments" << "file_url" << "sample_url" << "change" << "sample_width" << "has_children" << "preview_url" << "width" << "md5" << "preview_width" << "sample_height" << "parent_id" << "height" << "has_notes" << "creator_id" << "file_size" << "id" << "preview_height" << "rating" << "tags" << "author" << "score"; for (int i = 0; i < infos.count(); i++) { d[infos.at(i)] = sc.value(infos.at(i)).toString().trimmed(); } } this->parseImage(d, id + first); } } else { fallback(); return; } } // If tags have not been retrieved yet if (m_tags.isEmpty()) { QStringList tagsGot; for (int i = 0; i < m_images.count(); i++) { QList<Tag> tags = m_images.at(i)->tags(); for (int t = 0; t < tags.count(); t++) { if (tagsGot.contains(tags[t].text())) { m_tags[tagsGot.indexOf(tags[t].text())].setCount(m_tags[tagsGot.indexOf(tags[t].text())].count()+1); } else { m_tags.append(tags[t]); tagsGot.append(tags[t].text()); } } } } // Getting last page if (m_site->contains("LastPage") && m_pagesCount < 1) { m_pagesCount = m_site->value("LastPage").toInt(); } if (m_site->contains("Regex/Count") && m_imagesCount < 1) { QRegExp rxlast(m_site->value("Regex/Count")); rxlast.indexIn(m_source, 0); m_imagesCount = rxlast.cap(1).remove(",").toInt(); } if (m_site->contains("Regex/LastPage") && m_pagesCount < 1) { QRegExp rxlast(m_site->value("Regex/LastPage")); rxlast.indexIn(m_source, 0); m_pagesCount = rxlast.cap(1).remove(",").toInt(); } // Guess images count if (m_site->contains("Urls/"+QString::number(m_currentSource)+"/Limit") && m_pagesCount > 0) { m_imagesCount = m_pagesCount * m_site->value("Urls/"+QString::number(m_currentSource)+"/Limit").toInt(); } // Remove first n images (according to site settings) int skip = m_site->setting("ignore/always", 0).toInt(); if (m_page == m_site->value("FirstPage").toInt()) skip = m_site->setting("ignore/1", 0).toInt(); if (m_images.size() > m_imagesPerPage && m_images.size() > skip) for (int i = 0; i < skip; ++i) m_images.removeFirst(); // Virtual paging int firstImage = 0; int lastImage = m_smart ? m_imagesPerPage : m_images.size(); if (!m_originalUrl.contains("{page}")) { firstImage = m_imagesPerPage * (m_page - 1); lastImage = m_imagesPerPage; } while (firstImage > 0) { m_images.removeFirst(); firstImage--; } while (m_images.size() > lastImage) { m_images.removeLast(); } m_reply->deleteLater(); m_replyExists = false; QString t = m_search.join(" "); if (m_site->contains("DefaultTag") && t.isEmpty()) { t = m_site->value("DefaultTag"); } if (!m_search.isEmpty() && !m_site->value("Urls/"+QString::number(m_currentSource)+"/"+(t.isEmpty() && !m_site->contains("Urls/"+QString::number(m_currentSource)+"/Home") ? "Home" : "Tags")).contains("{tags}")) { m_errors.append(tr("La recherche par tags est impossible avec la source choisie (%1).").arg(m_format)); } emit finishedLoading(this); }
ParsedPage HtmlApi::parsePage(Page *parentPage, const QString &source, int first, int limit) const { ParsedPage ret; // Getting tags if (contains("Regex/Tags")) { QList<Tag> tgs = Tag::FromRegexp(value("Regex/Tags"), source); if (!tgs.isEmpty()) { ret.tags = tgs; } } // Getting images QRegularExpression rxImages(value("Regex/Image"), QRegularExpression::DotMatchesEverythingOption); auto matches = rxImages.globalMatch(source); int id = 0; while (matches.hasNext()) { auto match = matches.next(); QMap<QString, QString> d = multiMatchToMap(match, rxImages.namedCaptureGroups()); // JSON elements if (d.contains("json") && !d["json"].isEmpty()) { QVariant src = Json::parse(d["json"]); if (!src.isNull()) { QMap<QString, QVariant> map = src.toMap(); for (auto it = map.begin(); it != map.end(); ++it) { d[it.key()] = it.value().toString(); } } } QSharedPointer<Image> img = parseImage(parentPage, d, id + first); if (!img.isNull()) { ret.images.append(img); } id++; } // Navigation if (contains("Regex/NextPage")) { QRegularExpression rxNextPage(value("Regex/NextPage")); auto match = rxNextPage.match(source); if (match.hasMatch()) { ret.urlNextPage = QUrl(match.captured(1)); } } if (contains("Regex/PrevPage")) { QRegularExpression rxPrevPage(value("Regex/PrevPage")); auto match = rxPrevPage.match(source); if (match.hasMatch()) { ret.urlPrevPage = QUrl(match.captured(1)); } } // Last page if (contains("LastPage")) { ret.pageCount = value("LastPage").toInt(); } else if (contains("Regex/LastPage")) { QRegularExpression rxlast(value("Regex/LastPage")); auto match = rxlast.match(source); int cnt = match.hasMatch() ? match.captured(1).remove(",").toInt() : 0; if (cnt > 0) { int pagesCount = cnt; if (value("Urls/Tags").contains("{pid}") || (contains("Urls/PagePart") && value("Urls/PagePart").contains("{pid}"))) { int forced = forcedLimit(); int ppid = forced > 0 ? forced : limit; pagesCount = qFloor(static_cast<qreal>(pagesCount) / static_cast<qreal>(ppid)) + 1; } ret.pageCount = pagesCount; } } // Count images if (contains("Regex/Count")) { QRegularExpression rxlast(value("Regex/Count")); auto match = rxlast.match(source); int cnt = match.hasMatch() ? match.captured(1).remove(",").toInt() : 0; if (cnt > 0) { ret.imageCount = cnt; } } // Wiki if (contains("Regex/Wiki")) { QRegularExpression rxwiki(value("Regex/Wiki"), QRegularExpression::DotMatchesEverythingOption); auto match = rxwiki.match(source); if (match.hasMatch()) { QString wiki = match.captured(1); wiki.remove("/wiki/show?title="); wiki.remove(QRegularExpression("<p><a href=\"([^\"]+)\">Full entry »</a></p>")); wiki.replace("<h6>", "<span class=\"title\">").replace("</h6>", "</span>"); ret.wiki = wiki; } } return ret; }