mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-04-05 17:35:34 +08:00
ref #701, "improved countries"
* utility function for "accent free" strings * added 3 letter ISO, alias names * improved searching in countries
This commit is contained in:
@@ -28,8 +28,11 @@ namespace BlackGui
|
||||
CColumn col("country", CCountry::IndexIcon);
|
||||
col.setSortPropertyIndex(CCountry::IndexIsoCode);
|
||||
this->m_columns.addColumn(col);
|
||||
this->m_columns.addColumn(CColumn::standardString("ISO", CCountry::IndexIsoCode));
|
||||
this->m_columns.addColumn(CColumn::standardString("ISO2", CCountry::IndexIsoCode));
|
||||
this->m_columns.addColumn(CColumn::standardString("ISO3", CCountry::IndexIso3Code));
|
||||
this->m_columns.addColumn(CColumn::standardString("name", CCountry::IndexName));
|
||||
this->m_columns.addColumn(CColumn::standardString("alias 1", CCountry::IndexAlias1));
|
||||
this->m_columns.addColumn(CColumn::standardString("alias 2", CCountry::IndexAlias2));
|
||||
this->m_columns.addColumn(CColumn::standardString("changed", CCountry::IndexUtcTimestampFormattedYmdhms));
|
||||
|
||||
// default sort order
|
||||
@@ -39,7 +42,8 @@ namespace BlackGui
|
||||
// force strings for translation in resource files
|
||||
(void)QT_TRANSLATE_NOOP("ModelCountryList", "cty.");
|
||||
(void)QT_TRANSLATE_NOOP("ModelCountryList", "country");
|
||||
(void)QT_TRANSLATE_NOOP("ModelCountryList", "ISO");
|
||||
(void)QT_TRANSLATE_NOOP("ModelCountryList", "ISO2");
|
||||
(void)QT_TRANSLATE_NOOP("ModelCountryList", "ISO3");
|
||||
(void)QT_TRANSLATE_NOOP("ModelCountryList", "name");
|
||||
}
|
||||
} // ns
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "blackmisc/country.h"
|
||||
#include "blackmisc/icons.h"
|
||||
|
||||
#include "blackmisc/stringutils.h"
|
||||
#include <QJsonValue>
|
||||
#include <Qt>
|
||||
#include <QtGlobal>
|
||||
@@ -19,7 +19,9 @@ namespace BlackMisc
|
||||
CCountry::CCountry(const QString &iso, const QString &name) :
|
||||
IDatastoreObjectWithStringKey(iso.trimmed().toUpper()),
|
||||
m_name(name.trimmed())
|
||||
{ }
|
||||
{
|
||||
this->setSimplifiedNameIfNotSame();
|
||||
}
|
||||
|
||||
CIcon CCountry::toIcon() const
|
||||
{
|
||||
@@ -36,8 +38,14 @@ namespace BlackMisc
|
||||
|
||||
void CCountry::setIsoCode(const QString &iso)
|
||||
{
|
||||
m_dbKey = iso.trimmed().toUpper();
|
||||
Q_ASSERT_X(m_dbKey.length() == 2, Q_FUNC_INFO, "wromg ISO code");
|
||||
const QString code(iso.trimmed().toUpper());
|
||||
m_dbKey = (code.length() == 2) ? code : "";
|
||||
}
|
||||
|
||||
void CCountry::setIso3Code(const QString &iso)
|
||||
{
|
||||
const QString code(iso.trimmed().toUpper());
|
||||
m_iso3 = (code.length() == 3) ? code : "";
|
||||
}
|
||||
|
||||
bool CCountry::hasIsoCode() const
|
||||
@@ -45,6 +53,21 @@ namespace BlackMisc
|
||||
return m_dbKey.length() == 2;
|
||||
}
|
||||
|
||||
bool CCountry::hasIso3Code() const
|
||||
{
|
||||
return m_iso3.length() == 3;
|
||||
}
|
||||
|
||||
void CCountry::setAlias1(const QString &alias)
|
||||
{
|
||||
m_alias1 = alias.trimmed().toUpper();
|
||||
}
|
||||
|
||||
void CCountry::setAlias2(const QString &alias)
|
||||
{
|
||||
m_alias2 = alias.trimmed().toUpper();
|
||||
}
|
||||
|
||||
QString CCountry::getCombinedStringIsoName() const
|
||||
{
|
||||
if (!this->hasIsoCode()) { return QString(); }
|
||||
@@ -65,21 +88,29 @@ namespace BlackMisc
|
||||
void CCountry::setName(const QString &countryName)
|
||||
{
|
||||
m_name = countryName.trimmed();
|
||||
this->setSimplifiedNameIfNotSame();
|
||||
}
|
||||
|
||||
bool CCountry::matchesCountryName(const QString &name) const
|
||||
{
|
||||
if (name.isEmpty() || m_name.isEmpty()) { return false; }
|
||||
if (caseInsensitiveStringCompare(name, this->getDbKey())) { return true; } // exact ISO match
|
||||
if (caseInsensitiveStringCompare(name, this->getIso3Code())) { return true; } // exact ISO match
|
||||
if (name.length() < 5)
|
||||
{
|
||||
return m_name.length() == name.length() && (m_name.startsWith(name, Qt::CaseInsensitive) || name.startsWith(m_name, Qt::CaseInsensitive));
|
||||
return caseInsensitiveStringCompare(name, m_name) || caseInsensitiveStringCompare(name, m_simplifiedName);
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_name.contains(name, Qt::CaseInsensitive);
|
||||
return m_name.contains(name, Qt::CaseInsensitive) || m_simplifiedName.contains(name, Qt::CaseInsensitive);
|
||||
}
|
||||
}
|
||||
|
||||
bool CCountry::matchesAlias(const QString &alias) const
|
||||
{
|
||||
return caseInsensitiveStringCompare(alias, m_alias1) || caseInsensitiveStringCompare(alias, m_alias2);
|
||||
}
|
||||
|
||||
bool CCountry::isValid() const
|
||||
{
|
||||
return m_dbKey.length() == 2 && !m_name.isEmpty();
|
||||
@@ -99,12 +130,18 @@ namespace BlackMisc
|
||||
{
|
||||
case IndexIsoCode:
|
||||
return CVariant::fromValue(m_dbKey);
|
||||
case IndexIso3Code:
|
||||
return CVariant::fromValue(getIso3Code());
|
||||
case IndexName:
|
||||
return CVariant::fromValue(m_name);
|
||||
case IndexIsoName:
|
||||
return CVariant::fromValue(getCombinedStringIsoName());
|
||||
case IndexNameIso:
|
||||
return CVariant::fromValue(getCombinedStringNameIso());
|
||||
case IndexAlias1:
|
||||
return CVariant::fromValue(this->getAlias1());
|
||||
case IndexAlias2:
|
||||
return CVariant::fromValue(this->getAlias2());
|
||||
case IndexHistoric:
|
||||
return CVariant::fromValue(this->isHistoric());
|
||||
default:
|
||||
return (IDatastoreObjectWithStringKey::canHandleIndex(index)) ?
|
||||
IDatastoreObjectWithStringKey::propertyByIndex(index) :
|
||||
@@ -121,9 +158,21 @@ namespace BlackMisc
|
||||
case IndexIsoCode:
|
||||
this->setIsoCode(variant.toQString());
|
||||
break;
|
||||
case IndexIso3Code:
|
||||
this->setIso3Code(variant.toQString());
|
||||
break;
|
||||
case IndexName:
|
||||
this->setName(variant.toQString());
|
||||
break;
|
||||
case IndexAlias1:
|
||||
this->setAlias1(variant.toQString());
|
||||
break;
|
||||
case IndexAlias2:
|
||||
this->setAlias1(variant.toQString());
|
||||
break;
|
||||
case IndexHistoric:
|
||||
this->setHistoric(variant.toBool());
|
||||
break;
|
||||
default:
|
||||
IDatastoreObjectWithStringKey::canHandleIndex(index) ?
|
||||
IDatastoreObjectWithStringKey::setPropertyByIndex(index, variant) :
|
||||
@@ -141,8 +190,14 @@ namespace BlackMisc
|
||||
{
|
||||
case IndexIsoCode:
|
||||
return getIsoCode().compare(compareValue.getIsoCode(), Qt::CaseInsensitive);
|
||||
case IndexIso3Code:
|
||||
return getIso3Code().compare(compareValue.getIsoCode(), Qt::CaseInsensitive);
|
||||
case IndexName:
|
||||
return getName().compare(compareValue.getName(), Qt::CaseInsensitive);
|
||||
case IndexAlias1:
|
||||
return this->getAlias1().compare(compareValue.getAlias1(), Qt::CaseInsensitive);
|
||||
case IndexAlias2:
|
||||
return this->getAlias2().compare(compareValue.getAlias2(), Qt::CaseInsensitive);
|
||||
default:
|
||||
Q_ASSERT_X(false, Q_FUNC_INFO, "No comparison possible");
|
||||
}
|
||||
@@ -158,7 +213,15 @@ namespace BlackMisc
|
||||
}
|
||||
const QString iso(json.value(prefix + "id").toString());
|
||||
const QString name(json.value(prefix + "country").toString());
|
||||
const QString alias1(json.value(prefix + "alias1").toString());
|
||||
const QString alias2(json.value(prefix + "alias2").toString());
|
||||
const QString iso3(json.value(prefix + "iso3").toString());
|
||||
const QString historic(json.value(prefix + "historic").toString());
|
||||
CCountry country(iso, name);
|
||||
country.setAlias1(alias1);
|
||||
country.setAlias2(alias2);
|
||||
country.setIso3Code(iso3);
|
||||
country.setHistoric(stringToBool(historic));
|
||||
country.setKeyAndTimestampFromDatabaseJson(json, prefix);
|
||||
return country;
|
||||
}
|
||||
@@ -167,4 +230,10 @@ namespace BlackMisc
|
||||
{
|
||||
return isoCode.length() == 2;
|
||||
}
|
||||
|
||||
void CCountry::setSimplifiedNameIfNotSame()
|
||||
{
|
||||
const QString simplified = removeAccents(this->m_name);
|
||||
this->m_simplifiedName = this->m_name == simplified ? "" : simplified;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -40,9 +40,13 @@ namespace BlackMisc
|
||||
enum ColumnIndex
|
||||
{
|
||||
IndexIsoCode = BlackMisc::CPropertyIndex::GlobalIndexCCountry,
|
||||
IndexIso3Code,
|
||||
IndexName,
|
||||
IndexAlias1,
|
||||
IndexAlias2,
|
||||
IndexNameIso,
|
||||
IndexIsoName
|
||||
IndexIsoName,
|
||||
IndexHistoric
|
||||
};
|
||||
|
||||
//! Constructor
|
||||
@@ -60,15 +64,45 @@ namespace BlackMisc
|
||||
//! DB ISO code
|
||||
const QString &getIsoCode() const { return m_dbKey; }
|
||||
|
||||
//! Get 3 letter iso code
|
||||
const QString &getIso3Code() const { return m_iso3; }
|
||||
|
||||
//! Country ISO code (US, DE, GB, PL)
|
||||
void setIsoCode(const QString &iso);
|
||||
|
||||
//! Country ISO code (USA, DEU, GBR, POL)
|
||||
void setIso3Code(const QString &iso);
|
||||
|
||||
//! ISO code?
|
||||
bool hasIsoCode() const;
|
||||
|
||||
//! ISO 3 letter code?
|
||||
bool hasIso3Code() const;
|
||||
|
||||
//! Country name
|
||||
const QString &getName() const { return m_name; }
|
||||
|
||||
//! Country name (no accents ...)
|
||||
const QString &getSimplifiedName() const { return m_simplifiedName; }
|
||||
|
||||
//! Alias 1
|
||||
const QString &getAlias1() const { return m_alias1; }
|
||||
|
||||
//! Alias 1
|
||||
void setAlias1(const QString &alias);
|
||||
|
||||
//! Alias 2
|
||||
const QString &getAlias2() const { return m_alias2; }
|
||||
|
||||
//! Alias 2
|
||||
void setAlias2(const QString &alias);
|
||||
|
||||
//! Historic / non-existing country (e.g. Soviet Union)
|
||||
bool isHistoric() const { return m_historic; }
|
||||
|
||||
//! Historic country?
|
||||
void setHistoric(bool historic) { m_historic = historic; }
|
||||
|
||||
//! Combined string ISO/name
|
||||
QString getCombinedStringIsoName() const;
|
||||
|
||||
@@ -81,6 +115,9 @@ namespace BlackMisc
|
||||
//! Matches country name
|
||||
bool matchesCountryName(const QString &name) const;
|
||||
|
||||
//! Matches alias
|
||||
bool matchesAlias(const QString &alias) const;
|
||||
|
||||
//! \copydoc BlackMisc::Mixin::String::toQString
|
||||
QString convertToQString(bool i18n = false) const;
|
||||
|
||||
@@ -100,13 +137,26 @@ namespace BlackMisc
|
||||
static bool isValidIsoCode(const QString &isoCode);
|
||||
|
||||
private:
|
||||
QString m_name; //!< country name
|
||||
//! Set a simplified name if not equal to "official name"
|
||||
void setSimplifiedNameIfNotSame();
|
||||
|
||||
QString m_iso3; //!< 3 letter code
|
||||
QString m_name; //!< country name
|
||||
QString m_simplifiedName; //!< no accent characters
|
||||
QString m_alias1; //!< 1st alias
|
||||
QString m_alias2; //!< 2nd alias
|
||||
bool m_historic = false; //!< historic country
|
||||
|
||||
BLACK_METACLASS(
|
||||
CCountry,
|
||||
BLACK_METAMEMBER(dbKey),
|
||||
BLACK_METAMEMBER(timestampMSecsSinceEpoch),
|
||||
BLACK_METAMEMBER(name)
|
||||
BLACK_METAMEMBER(iso3),
|
||||
BLACK_METAMEMBER(name),
|
||||
BLACK_METAMEMBER(simplifiedName),
|
||||
BLACK_METAMEMBER(alias1),
|
||||
BLACK_METAMEMBER(alias2),
|
||||
BLACK_METAMEMBER(historic)
|
||||
);
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -33,13 +33,14 @@ namespace BlackMisc
|
||||
{
|
||||
if (countryName.isEmpty()) { return CCountry(); }
|
||||
|
||||
static const QRegExp reg("[^a-z]", Qt::CaseInsensitive);
|
||||
QString cn(countryName);
|
||||
cn.remove(reg);
|
||||
static const QRegExp reg("^[a-z]+", Qt::CaseInsensitive);
|
||||
int pos = reg.indexIn(countryName);
|
||||
const QString cn(pos >= 0 ? reg.cap(0) : countryName);
|
||||
CCountryList countries = this->findBy([&](const CCountry & country)
|
||||
{
|
||||
return country.matchesCountryName(cn);
|
||||
});
|
||||
if (countries.isEmpty()) { return this->findFirstByAlias(cn); }
|
||||
if (countries.size() < 2) { return countries.frontOrDefault(); }
|
||||
|
||||
// find best match by further reducing
|
||||
@@ -52,6 +53,17 @@ namespace BlackMisc
|
||||
return countries.front();
|
||||
}
|
||||
|
||||
CCountry CCountryList::findFirstByAlias(const QString &alias) const
|
||||
{
|
||||
if (alias.isEmpty()) { return CCountry(); }
|
||||
const QString a(alias.toUpper().trimmed());
|
||||
for (const CCountry &country : (*this))
|
||||
{
|
||||
if (country.matchesAlias(a)) { return country;}
|
||||
}
|
||||
return CCountry();
|
||||
}
|
||||
|
||||
QStringList CCountryList::toIsoNameList() const
|
||||
{
|
||||
QStringList sl;
|
||||
@@ -88,7 +100,6 @@ namespace BlackMisc
|
||||
return sl;
|
||||
}
|
||||
|
||||
|
||||
CCountryList CCountryList::fromDatabaseJson(const QJsonArray &array)
|
||||
{
|
||||
CCountryList countries;
|
||||
@@ -99,5 +110,4 @@ namespace BlackMisc
|
||||
}
|
||||
return countries;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -49,6 +49,9 @@ namespace BlackMisc
|
||||
//! Find "best match" by country
|
||||
CCountry findBestMatchByCountryName(const QString &countryName) const;
|
||||
|
||||
//! Find first by alias
|
||||
CCountry findFirstByAlias(const QString &alias) const;
|
||||
|
||||
//! ISO/name string list
|
||||
QStringList toIsoNameList() const;
|
||||
|
||||
|
||||
@@ -133,6 +133,35 @@ namespace BlackMisc
|
||||
if (mibNames) { return mib; }
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
// http://www.codegur.online/14009522/how-to-remove-accents-diacritic-marks-from-a-string-in-qt
|
||||
QString removeAccents(const QString &candidate)
|
||||
{
|
||||
static const QString diacriticLetters = QString::fromUtf8("ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ");
|
||||
static const QStringList noDiacriticLetters({"S", "OE", "Z", "s", "oe", "z", "Y", "Y", "u", "A", "A", "A", "A", "A", "A", "AE", "C", "E", "E", "E", "E", "I", "I", "I", "I", "D", "N", "O", "O", "O", "O", "O", "O", "U", "U", "U", "U", "Y", "s", "a", "a", "a", "a", "a", "a", "ae", "c", "e", "e", "e", "e", "i", "i", "i", "i", "o", "n", "o", "o", "o", "o", "o", "o", "u", "u", "u", "u", "y", "y"});
|
||||
|
||||
QString output = "";
|
||||
for (int i = 0; i < candidate.length(); i++)
|
||||
{
|
||||
const QChar c = candidate[i];
|
||||
int dIndex = diacriticLetters.indexOf(c);
|
||||
if (dIndex < 0)
|
||||
{
|
||||
output.append(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
const QString replacement = noDiacriticLetters[dIndex];
|
||||
output.append(replacement);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
bool caseInsensitiveStringCompare(const QString &c1, const QString &c2)
|
||||
{
|
||||
return c1.length() == c2.length() && c1.startsWith(c2, Qt::CaseInsensitive);
|
||||
}
|
||||
}
|
||||
|
||||
//! \endcond
|
||||
|
||||
@@ -72,6 +72,12 @@ namespace BlackMisc
|
||||
//! Strip a designator from a combined string
|
||||
BLACKMISC_EXPORT QStringList textCodecNames(bool simpleNames, bool mibNames);
|
||||
|
||||
//! Remove accents / diacritic marks from a string
|
||||
BLACKMISC_EXPORT QString removeAccents(const QString &candidate);
|
||||
|
||||
//! Case insensitive string compare
|
||||
BLACKMISC_EXPORT bool caseInsensitiveStringCompare(const QString &c1, const QString &c2);
|
||||
|
||||
namespace Mixin
|
||||
{
|
||||
/*!
|
||||
|
||||
Reference in New Issue
Block a user