예제 #1
0
QString LayerListModel::getAvailableLayerName(QString basename) const
{
	// Return a layer name of format "basename n" where n is one bigger than the
	// biggest suffix number of layers named "basename n".

	// First, strip suffix number from the basename (if it exists)

	QRegularExpression suffixNumRe("(\\d+)$");
	{
		auto m = suffixNumRe.match(basename);
		if(m.hasMatch()) {
			basename = basename.mid(0, m.capturedStart()).trimmed();
		}
	}

	// Find the biggest suffix in the layer stack
	int suffix = 0;
	for(const LayerListItem &l : _items) {
		auto m = suffixNumRe.match(l.title);
		if(m.hasMatch()) {
			if(l.title.startsWith(basename)) {
				suffix = qMax(suffix, m.captured(1).toInt());
			}
		}
	}

	// Make unique name
	return QString("%2 %1").arg(suffix+1).arg(basename);
}
예제 #2
0
void FrostEdit::parseCompileOut(QString line) {

	auto match = mFrostCompilerErrorRegEx.match(line);
	while(match.hasMatch()) {

		ui->consoleTabs->setCurrentIndex(0);
		QStringList captures = match.capturedTexts();

		QString wholeMsg = captures[0];
		QString file = captures[1];
		int row = captures[2].toInt();
		int col = captures[3].toInt();
		QString type = captures[4];
		int code = captures[5].toInt();
		QString explanation = captures[6];
		QString prevfile = file;

		if(file != mCompiledFile && file.right(4) == ".tmp") {
			file = mCompiledFile;
			wholeMsg.replace(prevfile, file);
		}

		if(type.toLower() == "warning")
			mIssueList->addWarning(wholeMsg, file, explanation, row, col);
		else if(type.toLower() == "error")
			mIssueList->addError(wholeMsg, file, explanation, row, col);
		match = mFrostCompilerErrorRegEx.match(line, match.capturedEnd());
	}
}
예제 #3
0
static QString getXdgValue(const QString& contents, const QString& key, const QString& accessor = "")
{
    QString fullkey = accessor.length() ? key + "[" + accessor + "]" : key;
    QRegularExpression re("^" + fullkey + "=(.*)$", QRegularExpression::MultilineOption);
    auto match = re.match(contents);
    return match.hasMatch() ? match.captured(1) : "";
}
예제 #4
0
// Delete all temporary directories for an application
int PathUtils::removeTemporaryApplicationDirs(QString appName) {
    if (appName.isNull()) {
        appName = qApp->applicationName();
    }

    auto dirName = TEMP_DIR_FORMAT.arg(appName).arg("*").arg("*");

    QDir rootTempDir = QDir::tempPath();
    auto dirs = rootTempDir.entryInfoList({ dirName }, QDir::Dirs);
    int removed = 0;
    for (auto& dir : dirs) {
        auto dirName = dir.fileName();
        auto absoluteDirPath = QDir(dir.absoluteFilePath());
        QRegularExpression re { "^" + QRegularExpression::escape(appName) + "\\-(?<pid>\\d+)\\-(?<timestamp>\\d+)$" };

        auto match = re.match(dirName);
        if (match.hasMatch()) {
            auto pid = match.capturedRef("pid").toLongLong();
            auto timestamp = match.capturedRef("timestamp");
            if (!processIsRunning(pid)) {
                qDebug() << "  Removing old temporary directory: " << dir.absoluteFilePath();
                absoluteDirPath.removeRecursively();
                removed++;
            } else {
                qDebug() << "  Not removing (process is running): " << dir.absoluteFilePath();
            }
        }
    }

    return removed;
}
예제 #5
0
TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities) {
	TextWithTags::Tags result;
	if (entities.isEmpty()) {
		return result;
	}

	result.reserve(entities.size());
	for (const auto &entity : entities) {
		const auto push = [&](const QString &tag) {
			result.push_back({ entity.offset(), entity.length(), tag });
		};
		switch (entity.type()) {
		case EntityInTextMentionName: {
			auto match = QRegularExpression("^(\\d+\\.\\d+)$").match(entity.data());
			if (match.hasMatch()) {
				push(kMentionTagStart + entity.data());
			}
		} break;
		case EntityInTextCustomUrl: {
			const auto url = entity.data();
			if (Ui::InputField::IsValidMarkdownLink(url)
				&& !IsMentionLink(url)) {
				push(url);
			}
		} break;
		case EntityInTextBold: push(Ui::InputField::kTagBold); break;
		case EntityInTextItalic: push(Ui::InputField::kTagItalic); break;
		case EntityInTextCode: push(Ui::InputField::kTagCode); break;
		case EntityInTextPre: push(Ui::InputField::kTagPre); break;
		}
	}
	return result;
}
예제 #6
0
QStringList KateProjectWorker::filesFromDarcs(const QDir &dir, bool recursive)
{
    QStringList files;

    const QString cmd = QStringLiteral("darcs");
    QString root;

    {
        QProcess darcs;
        darcs.setWorkingDirectory(dir.absolutePath());
        QStringList args;
        args << QStringLiteral("list") << QStringLiteral("repo");

        darcs.start(cmd, args);

        if (!darcs.waitForStarted() || !darcs.waitForFinished())
            return files;

        auto str = QString::fromLocal8Bit(darcs.readAllStandardOutput());
        QRegularExpression exp(QStringLiteral("Root: ([^\\n\\r]*)"));
        auto match = exp.match(str);

        if(!match.hasMatch())
            return files;

        root = match.captured(1);
    }

    QStringList relFiles;
    {
        QProcess darcs;
        QStringList args;
        darcs.setWorkingDirectory(dir.absolutePath());
        args << QStringLiteral("list") << QStringLiteral("files")
             << QStringLiteral("--no-directories") << QStringLiteral("--pending");

        darcs.start(cmd, args);

        if(!darcs.waitForStarted() || !darcs.waitForFinished())
            return files;

        relFiles = QString::fromLocal8Bit(darcs.readAllStandardOutput())
            .split(QRegularExpression(QStringLiteral("[\n\r]")), QString::SkipEmptyParts);
    }

    for (const QString &relFile: relFiles) {
        const QString path = dir.relativeFilePath(root + QStringLiteral("/") + relFile);

        if ((!recursive && (relFile.indexOf(QStringLiteral("/")) != -1)) ||
            (recursive && (relFile.indexOf(QStringLiteral("..")) == 0))
        ) {
            continue;
        }

        files.append(dir.absoluteFilePath(path));
    }

    return files;
}
예제 #7
0
void LoginHandler::handleHostMessage(const QString &message)
{
	Q_ASSERT(!_client->username().isEmpty());

	if(_server->sessionCount() >= _server->sessionLimit()) {
		send("ERROR CLOSED");
		_client->disconnectError("login error");
		return;
	}

	const QRegularExpression re("\\AHOST (\\*|[a-zA-Z0-9:-]{1,64}) (\\d+) (\\d+)\\s*(?:;(.+))?\\z");
	auto m = re.match(message);
	if(!m.hasMatch()) {
		send("ERROR SYNTAX");
		_client->disconnectError("login error");
		return;
	}

	QString sessionId = m.captured(1);
	int minorVersion = m.captured(2).toInt();
	int userId = m.captured(3).toInt();

	// Check if session ID is available
	if(sessionId == "*") {
		// Generated session ID
		sessionId = QUuid::createUuid().toString();
		sessionId = sessionId.mid(1, sessionId.length()-2); // strip the { and } chars
	}

	if(!_server->getSessionDescriptionById(sessionId).id.isEmpty()) {
		send("ERROR SESSIONIDINUSE");
		_client->disconnectError("login error");
		return;
	}

	QString password = m.captured(4);
	if(password != _server->hostPassword() && !_hostPrivilege) {
		send("ERROR BADPASS");
		_client->disconnectError("login error");
		return;
	}

	_client->setId(userId);

	// Mark login phase as complete. No more login messages will be sent to this user
	send(QString("OK %1 %2").arg(sessionId).arg(userId));
	_complete = true;

	// Create a new session
	SessionState *session = _server->createSession(sessionId, minorVersion, _client->username());

	session->joinUser(_client, true);

	deleteLater();
}
예제 #8
0
std::vector<BankAccount> OfxParser::parse()
{
    std::vector<BankAccount> result;
    QTextStream stream(m_file);
    QString line;
    QRegularExpression newTagMatcher("^<(\\w+)>$");
    QRegularExpression endTagMatcher("^</(\\w+)>$");
    QRegularExpression dataTagMatcher("^<(\\w+)>(.*)$");

    QStringList tagList;
    QMap<QString, QString> dataMap;
    while (stream.readLineInto(&line)) {
        if (line.isEmpty())
            continue;
        auto match = newTagMatcher.match(line);
        if (match.hasMatch()) {
            if (!dataMap.isEmpty()) {
                parsedData(result, tagList.join('/'), dataMap);
                dataMap.clear();
            }
            tagList.append(match.captured(1));
            continue;
        }
        match = endTagMatcher.match(line);
        if (match.hasMatch()) {
            if (!dataMap.isEmpty()) {
                parsedData(result, tagList.join('/'), dataMap);
                dataMap.clear();
            }
            tagList.pop_back();
            continue;
        }
        match = dataTagMatcher.match(line);
        if (match.hasMatch()) {
            dataMap.insert(match.captured(1), match.captured(2));
            continue;
        }
        qFatal("Invalid content");
    }
    return result;
}
예제 #9
0
StringList RegExpMatch::captured() const
{
	StringList r;

	if (hasMatch()) {
		iterator it = begin();
		iterator itEnd = end();

		while (it != itEnd) {
			r.append(*it++);
		}
	}
	return r;
}
예제 #10
0
QByteArray LSHttpdRequest::extractOption(QByteArray headerValue, QByteArray optionTag)
{
    if(headerValue.isEmpty() || optionTag.isEmpty())
    {
        return QByteArray();
    }
    QString pattern = QStringLiteral("%1=\"([^\"]+)\"");
    QRegularExpression rx(pattern.arg(QString::fromLocal8Bit(optionTag)));
    auto rxMatch = rx.match(QString::fromLocal8Bit(headerValue));
    if(rxMatch.hasMatch())
    {
        return rxMatch.captured(1).toLocal8Bit();
    }
    return "";
}
예제 #11
0
/**
 * @brief prepend the match string with specify symbol
 * @param input string need to prepend symbol
 * @param number_length the length of the number, will prepend symbol
 * if the length smaller than this number
 * @param symbol symbol prepand before the number
 * @return the name after alter
 * @example ex : number_length == 3, reg = (\\d+), symbol = '0'
 * then "1" == "001", "10" == "010"
 * ex : number_length == 2, reg = (\\d+), symbol = '0'
 * then "1" == "01", "10" == "10"
 */
QString prepend_symbol_on_match(QString const &input, int number_length,
                                QRegularExpression const &reg,
                                QChar symbol)
{    
    auto new_file_name = input;
    auto match = reg.match(new_file_name);

    if(match.hasMatch()){
        auto const Captured = match.captured(0);
        if(Captured.size() < number_length){
            new_file_name.insert(match.capturedStart(0),
                                 QString(number_length - match.capturedLength(1),
                                         symbol));
        }
    }

    return new_file_name;
}
예제 #12
0
KTextEditor::Cursor KTextEditorHelpers::extractCursor(const QString& input, int* pathLength)
{
    static const QRegularExpression pattern(QStringLiteral(":(\\d+)(?::(\\d+))?$"));
    const auto match = pattern.match(input);
    if (!match.hasMatch()) {
        if (pathLength)
            *pathLength = input.length();
        return KTextEditor::Cursor::invalid();
    }

    int line = match.capturedRef(1).toInt() - 1;
    // don't use an invalid column when the line is valid
    int column = qMax(0, match.captured(2).toInt() - 1);

    if (pathLength)
        *pathLength = match.capturedStart(0);
    return {line, column};
}
예제 #13
0
const QString clearHTMLTags(const QString &text)
{
    const QRegularExpression regex("<(\\w+)[^>]*>([^<]*)</\\1>");

    QString ret = text;
    do
    {
        const auto match = regex.match(ret);
        if (!match.isValid() || !match.hasMatch())
            break;

        const int start = match.capturedStart();
        const int len = match.capturedLength();
        const QString &cap = match.captured(2);

        ret.replace(start, len, cap);
    } while (true);

    return ret;
}
예제 #14
0
파일: mrl.cpp 프로젝트: akhilo/cmplayer
QString Mrl::displayName() const {
	if (isLocalFile())
		return fileName();
	QString disc;
	if (isDvd())
		disc = qApp->translate("Mrl", "DVD");
	else if (isBluray())
		disc = qApp->translate("Mrl", "Blu-ray");
	if (disc.isEmpty())
		return location();
	auto dev = device();
	if (dev.isEmpty())
		return disc;
	if (!dev.startsWith(_L("/dev/"))) {
		QRegularExpression regex("/([^/]+)/*$");
		auto match = regex.match(dev);
		if (match.hasMatch())
			dev = match.captured(1);
	}
	return disc % _L(" (") % dev % _L(')');
}
예제 #15
0
bool PathUtils::deleteMyTemporaryDir(QString dirName) {
    QDir rootTempDir = QDir::tempPath();

    QString appName = qApp->applicationName();
    QRegularExpression re { "^" + QRegularExpression::escape(appName) + "\\-(?<pid>\\d+)\\-(?<timestamp>\\d+)$" };

    auto match = re.match(dirName);
    auto pid = match.capturedRef("pid").toLongLong();

    if (match.hasMatch() && rootTempDir.exists(dirName) && pid == qApp->applicationPid()) {
        auto absoluteDirPath = QDir(rootTempDir.absoluteFilePath(dirName));

        bool success = absoluteDirPath.removeRecursively();
        if (success) {
            qDebug() << "  Removing temporary directory: " << absoluteDirPath.absolutePath();
        } else {
            qDebug() << "  Failed to remove temporary directory: " << absoluteDirPath.absolutePath();
        }
        return success;
    }

    return false;
}
예제 #16
0
 /**
  * @brief toBool
  * @param in
  * @param ok
  * @return
  */
 bool toBool(const QString &in, bool &ok)
 {
     auto match = QRegularExpression("^(true|false)$").match(in);
     return (ok = match.hasMatch()) ? match.captured(1) == "true" ? true : false : ok;
 }
예제 #17
0
bool Headline::isMatch(const QRegularExpression &pattern) const
{
    auto const match = pattern.match(caption());
    //qDebug() << caption() << "matches" << pattern << "?" << (match.hasMatch() ? "yes" : "no");
    return match.hasMatch();
}
예제 #18
0
void LoginHandler::handleJoinMessage(const QString &message)
{
	Q_ASSERT(!_client->username().isEmpty());

	const QRegularExpression re("\\AJOIN ([a-zA-Z0-9:-]{1,64})\\s*(?:;(.+))?\\z");
	auto m = re.match(message);
	if(!m.hasMatch()) {
		send("ERROR SYNTAX");
		_client->disconnectError("login error");
		return;
	}

	QString sessionId = m.captured(1);
	SessionDescription sessiondesc = _server->getSessionDescriptionById(sessionId);
	if(sessiondesc.id==0) {
		send("ERROR NOSESSION");
		_client->disconnectError("login error");
		return;
	}

	if(sessiondesc.closed && !_client->isModerator()) {
		send("ERROR CLOSED");
		_client->disconnectError("login error");
		return;
	}

	QString password = m.captured(2);

	if(!passwordhash::check(password, sessiondesc.passwordHash) && !_client->isModerator()) {
		send("ERROR BADPASS");
		_client->disconnectError("login error");
		return;
	}

	// Just the username uniqueness check to go, we can wake up the session now
	// A freshly de-hibernated session will not have any users, so the last check
	// will never fail in that case.
	SessionState *session = _server->getSessionById(sessionId);
	if(!session) {
		// The session was just deleted from under us! (or de-hibernation failed)
		send("ERROR NOSESSION");
		_client->disconnectError("login error");
		return;
	}

	if(session->getClientByUsername(_client->username())) {
#ifdef NDEBUG
		send("ERROR NAMEINUSE");
		_client->disconnectError("login error");
		return;
#else
		// Allow identical usernames in debug builds, so I don't have to keep changing
		// the username when testing. There is no technical requirement for unique usernames;
		// the limitation is solely for the benefit of the human users.
		logger::warning() << "Username clash" << _client->username() << "for" << session << "ignored because this is a debug build.";
#endif
	}

	// Ok, join the session
	session->assignId(_client);

	send(QString("OK %1 %2").arg(session->id()).arg(_client->id()));
	_complete = true;

	session->joinUser(_client, false);

	deleteLater();
}
예제 #19
0
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 &raquo;</a></p>"));
			wiki.replace("<h6>", "<span class=\"title\">").replace("</h6>", "</span>");
			ret.wiki = wiki;
		}
	}

	return ret;
}
예제 #20
0
void LoginHandler::handleIdentMessage(const QString &message)
{
	const QRegularExpression re("\\IDENT \"([^\"]+)\"\\s*(?:;(.+))?\\z");
	auto m = re.match(message);
	if(!m.hasMatch()) {
		send("ERROR SYNTAX");
		_client->disconnectError("login error");
		return;
	}

	QString username = m.captured(1);
	QString password;
	if(m.lastCapturedIndex() == 2)
		password = m.captured(2);

	if(!validateUsername(username)) {
		send("ERROR BADNAME");
		_client->disconnectError("login error");
		return;
	}

	if(_server->identityManager()) {
		_state = WAIT_FOR_IDENTITYMANAGER_REPLY;
		IdentityResult *result = _server->identityManager()->checkLogin(username, password);
		connect(result, &IdentityResult::resultAvailable, [this, username, password](IdentityResult *result) {
			QString error;
			Q_ASSERT(result->status() != IdentityResult::INPROGRESS);
			switch(result->status()) {
			case IdentityResult::INPROGRESS: /* can't happen */ break;
			case IdentityResult::NOTFOUND:
				if(!_server->identityManager()->isAuthorizedOnly()) {
					guestLogin(username);
					break;
				}
				// fall through to badpass if guest logins are disabled
			case IdentityResult::BADPASS:
				if(password.isEmpty()) {
					// No password: tell client that guest login is not possible (for this username)
					_state = WAIT_FOR_IDENT;
					send("NEEDPASS");
					return;
				}
				error = "BADPASS";
				break;
			case IdentityResult::BANNED: error = "BANNED"; break;
			case IdentityResult::OK: {
				// Yay, username and password were valid!
				QString okstr = "IDENTIFIED USER ";
				if(result->flags().isEmpty())
					okstr += "-";
				else
					okstr += result->flags().join(",");

				if(validateUsername(result->canonicalName())) {
					_client->setUsername(result->canonicalName());

				} else {
					logger::warning() << "Identity manager gave us an invalid username:"******"MOD"));
				_hostPrivilege = result->flags().contains("HOST");
				_state = WAIT_FOR_LOGIN;
				send(okstr);
				announceServerInfo();
				} break;
			}

			if(!error.isEmpty()) {
				send("ERROR " + error);
				_client->disconnectError("login error");
			}
		});

	} else {
		if(!password.isNull()) {
			// if we have no identity manager, we can't accept passwords
			send("ERROR NOIDENT");
			_client->disconnectError("login error");
			return;
		}
		guestLogin(username);
	}
}
예제 #21
0
void MessageLinksParser::parse() {
	const auto &textWithTags = _field->getTextWithTags();
	const auto &text = textWithTags.text;
	const auto &tags = textWithTags.tags;
	const auto &markdownTags = _field->getMarkdownTags();
	if (text.isEmpty()) {
		_list = QStringList();
		return;
	}

	auto ranges = QVector<LinkRange>();

	auto tag = tags.begin();
	const auto tagsEnd = tags.end();
	const auto processTag = [&] {
		Expects(tag != tagsEnd);

		if (Ui::InputField::IsValidMarkdownLink(tag->id)
			&& !IsMentionLink(tag->id)) {
			ranges.push_back({ tag->offset, tag->length, tag->id });
		}
		++tag;
	};
	const auto processTagsBefore = [&](int offset) {
		while (tag != tagsEnd && tag->offset + tag->length <= offset) {
			processTag();
		}
	};
	const auto hasTagsIntersection = [&](int till) {
		if (tag == tagsEnd || tag->offset >= till) {
			return false;
		}
		while (tag != tagsEnd && tag->offset < till) {
			processTag();
		}
		return true;
	};

	auto markdownTag = markdownTags.begin();
	const auto markdownTagsEnd = markdownTags.end();
	const auto markdownTagsAllow = [&](int from, int length) {
		while (markdownTag != markdownTagsEnd
			&& (markdownTag->adjustedStart
				+ markdownTag->adjustedLength <= from
				|| !markdownTag->closed)) {
			++markdownTag;
			continue;
		}
		if (markdownTag == markdownTagsEnd
			|| markdownTag->adjustedStart >= from + length) {
			return true;
		}
		// Ignore http-links that are completely inside some tags.
		// This will allow sending http://test.com/__test__/test correctly.
		return (markdownTag->adjustedStart > from)
			|| (markdownTag->adjustedStart
				+ markdownTag->adjustedLength < from + length);
	};

	const auto len = text.size();
	const QChar *start = text.unicode(), *end = start + text.size();
	for (auto offset = 0, matchOffset = offset; offset < len;) {
		auto m = TextUtilities::RegExpDomain().match(text, matchOffset);
		if (!m.hasMatch()) break;

		auto domainOffset = m.capturedStart();

		auto protocol = m.captured(1).toLower();
		auto topDomain = m.captured(3).toLower();
		auto isProtocolValid = protocol.isEmpty() || TextUtilities::IsValidProtocol(protocol);
		auto isTopDomainValid = !protocol.isEmpty() || TextUtilities::IsValidTopDomain(topDomain);

		if (protocol.isEmpty() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) {
			auto forMailName = text.mid(offset, domainOffset - offset - 1);
			auto mMailName = TextUtilities::RegExpMailNameAtEnd().match(forMailName);
			if (mMailName.hasMatch()) {
				offset = matchOffset = m.capturedEnd();
				continue;
			}
		}
		if (!isProtocolValid || !isTopDomainValid) {
			offset = matchOffset = m.capturedEnd();
			continue;
		}

		QStack<const QChar*> parenth;
		const QChar *domainEnd = start + m.capturedEnd(), *p = domainEnd;
		for (; p < end; ++p) {
			QChar ch(*p);
			if (chIsLinkEnd(ch)) break; // link finished
			if (chIsAlmostLinkEnd(ch)) {
				const QChar *endTest = p + 1;
				while (endTest < end && chIsAlmostLinkEnd(*endTest)) {
					++endTest;
				}
				if (endTest >= end || chIsLinkEnd(*endTest)) {
					break; // link finished at p
				}
				p = endTest;
				ch = *p;
			}
			if (ch == '(' || ch == '[' || ch == '{' || ch == '<') {
				parenth.push(p);
			} else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') {
				if (parenth.isEmpty()) break;
				const QChar *q = parenth.pop(), open(*q);
				if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) {
					p = q;
					break;
				}
			}
		}
		if (p > domainEnd) { // check, that domain ended
			if (domainEnd->unicode() != '/' && domainEnd->unicode() != '?') {
				matchOffset = domainEnd - start;
				continue;
			}
		}
		const auto range = LinkRange {
			domainOffset,
			static_cast<int>(p - start - domainOffset),
			QString()
		};
		processTagsBefore(domainOffset);
		if (!hasTagsIntersection(range.start + range.length)) {
			if (markdownTagsAllow(range.start, range.length)) {
				ranges.push_back(range);
			}
		}
		offset = matchOffset = p - start;
	}
	processTagsBefore(QFIXED_MAX);

	apply(text, ranges);
}
bool OsmAnd::CoreResourcesEmbeddedBundle_P::loadResources()
{
    typedef const void* (*GetPointerFunctionPtr)();
    typedef const char* NamePtr;
    typedef const uint8_t* DataPtr;

    // Regular expressions to extract pure resource name or qualifiers
    const QRegularExpression resourceNameWithQualifiersRegExp("(?:\\[(.*)\\])(.*)");

    // Find out what number of resources there is in the bundle
    const auto pGetResourcesCount = reinterpret_cast<GetPointerFunctionPtr>(loadSymbol("__get____CoreResourcesEmbeddedBundle__ResourcesCount"));
    if (pGetResourcesCount == nullptr)
        return false;
    const auto resourcesCount = *reinterpret_cast<const uint32_t*>(pGetResourcesCount());

    for (auto resourceIdx = 0u; resourceIdx < resourcesCount; resourceIdx++)
    {
        ResourceData resourceData;

        const auto pGetResourceName = reinterpret_cast<GetPointerFunctionPtr>(loadSymbol(
            QString(QLatin1String("__get____CoreResourcesEmbeddedBundle__ResourceName_%1")).arg(resourceIdx).toLatin1()));
        if (pGetResourceName == nullptr)
            return false;
        const auto resourceName = reinterpret_cast<const char*>(pGetResourceName());

        const auto pGetResourceSize = reinterpret_cast<GetPointerFunctionPtr>(loadSymbol(
            QString(QLatin1String("__get____CoreResourcesEmbeddedBundle__ResourceSize_%1")).arg(resourceIdx).toLatin1()));
        if (pGetResourceSize == nullptr)
            return false;
        resourceData.size = *reinterpret_cast<const size_t*>(pGetResourceSize());

        const auto pGetResourceData = reinterpret_cast<GetPointerFunctionPtr>(loadSymbol(
            QString(QLatin1String("__get____CoreResourcesEmbeddedBundle__ResourceData_%1")).arg(resourceIdx).toLatin1()));
        if (pGetResourceData == nullptr)
            return false;
        resourceData.data = reinterpret_cast<const uint8_t*>(pGetResourceData());

        // Process resource name
        QStringList qualifiers;
        QString pureResourceName;
        const auto resourceNameComponents = resourceNameWithQualifiersRegExp.match(QLatin1String(resourceName));
        if (resourceNameComponents.hasMatch())
        {
            qualifiers = resourceNameComponents.captured(1).split(QLatin1Char(';'), QString::SkipEmptyParts);
            pureResourceName = resourceNameComponents.captured(2);
        }
        else
        {
            pureResourceName = QLatin1String(resourceName);
        }

        // Get resource entry for this resource
        auto& resourceEntry = _resources[pureResourceName];
        if (qualifiers.isEmpty())
        {
            resourceEntry.defaultVariant = resourceData;
        }
        else
        {
            for (const auto& qualifier : constOf(qualifiers))
            {
                const auto qualifierComponents = qualifier.trimmed().split(QLatin1Char('='), QString::SkipEmptyParts);

                bool ok = false;
                if (qualifierComponents.size() == 2 && qualifierComponents.first() == QLatin1String("ddf"))
                {
                    const auto ddfValue = qualifierComponents.last().toFloat(&ok);
                    if (!ok)
                    {
                        LogPrintf(LogSeverityLevel::Warning,
                            "Unsupported value '%s' for DDF qualifier",
                            qPrintable(qualifierComponents.last()));
                    }
                    resourceEntry.variantsByDisplayDensityFactor.insert(ddfValue, resourceData);
                }
                else
                {
                    LogPrintf(LogSeverityLevel::Warning,
                        "Unsupported qualifier '%s'",
                        qPrintable(qualifier.trimmed()));
                }
            }
        }
    }

    return true;
}