29 #include "dateparser_p.h"
33 #include <QtCore/QRegExp>
34 #include <QtCore/QSet>
35 #include <QtCore/QMutex>
36 #include <QtCore/QMutexLocker>
45 #include <Soprano/Node>
46 #include <Soprano/Model>
47 #include <Soprano/QueryResultIterator>
48 #include <Soprano/Vocabulary/RDFS>
49 #include <Soprano/Vocabulary/RDF>
50 #include <Soprano/Vocabulary/NAO>
52 using namespace Soprano::Vocabulary;
53 using namespace Nepomuk2::Vocabulary;
55 using namespace Nepomuk2::Query;
62 else if ( s ==
":" ) {
65 else if ( s ==
">" ) {
68 else if ( s ==
"<" ) {
71 else if ( s ==
">=" ) {
74 else if ( s ==
"<=" ) {
78 kDebug() <<
"FIXME: Unsupported relation:" << s;
83 QString stripQuotes(
const QString& s,
bool* hadQuotes = 0 ) {
88 return s.mid( 1 ).left( s.length()-2 );
97 QUrl tryToBeIntelligentAboutParsingUrl(
const QString& s ) {
98 if ( s.contains(
'%' ) && !s.contains(
'/' ) ) {
99 return QUrl::fromEncoded( s.toAscii() );
106 Soprano::LiteralValue createLiteral(
const QString& s,
bool globbing ) {
108 QString clearString(s);
109 clearString.remove(QLatin1Char(
'\''));
110 clearString.remove(QLatin1Char(
'"'));
112 int i = clearString.toInt( &b );
114 return Soprano::LiteralValue( i );
115 double d = clearString.toDouble( &b );
117 return Soprano::LiteralValue( d );
122 if ( s[0] == QLatin1Char(
'\'') ||
123 s[0] == QLatin1Char(
'\"') ) {
130 if( globbing && s.length() > 3 && !s.endsWith(
'*') && !s.endsWith(
'?') )
131 return QString(s +
'*');
136 bool positiveTerm(
const QString& s_ ) {
137 const QString s = s_.toLower().simplified();
157 bool isFilenamePattern(
const QString& s )
159 return( !s.contains(
' ') &&
161 s.count(
'*') + s.count(
'?') > 0 );
166 QString regex = QRegExp::escape(s);
167 regex.replace(
"\\*", QLatin1String(
".*" ) );
168 regex.replace(
"\\?", QLatin1String(
"." ) );
169 regex.replace(
"\\",
"\\\\");
190 QStringList fullTextTerms;
247 if( year >= 1000 && year <= 9999) {
248 QDate date(year, 1, 1);
254 if(ct.
comparator() == ComparisonTerm::Greater ||
255 ct.
comparator() == ComparisonTerm::SmallerOrEqual) {
256 date.setYMD(date.year(), 12, 31);
257 time.setHMS(23,59,59,999);
272 #warning Make the parser handle different data, time, and datetime encodings as well as suffixes like MB or GB
275 QDateTime parseDateTime(
const Soprano::LiteralValue& literal )
278 Nepomuk2::Search::DateParser date( literal.toString() );
279 if( date.hasDate() ) {
280 return QDateTime( date.getDate() );
283 Nepomuk2::Search::TimeParser time( literal.toString() );
285 return QDateTime(QDate::currentDate(), time.next() );
292 Soprano::LiteralValue parseSizeType(
const Soprano::LiteralValue& literal )
294 const double KiB = 1024.0;
295 const double MiB = KiB * 1024.0;
296 const double GiB = MiB * 1024.0;
297 const double TiB = GiB * 1024.0;
299 const double KB = 1000.0;
300 const double MB = KB * 1000.0;
301 const double GB = MB * 1000.0;
302 const double TB = GB * 1000.0;
305 sizes.insert(
"KiB", KiB );
306 sizes.insert(
"MiB", MiB );
307 sizes.insert(
"GiB", GiB );
308 sizes.insert(
"TiB", TiB );
309 sizes.insert(
"KB", KB );
310 sizes.insert(
"MB", MB );
311 sizes.insert(
"GB", GB );
312 sizes.insert (
"TB", TB );
315 i != sizes.constEnd(); ++i ) {
316 QRegExp cur( QString(
"^([\\d]+.?[\\d]*)[\\s]*%1$").arg( i.key() ) );
317 if( cur.indexIn( literal.toString() ) != -1 ) {
318 double value = cur.cap( 1 ).toDouble();
319 double newValue = value * i.value();
320 kDebug() <<
"Found value" << value << i.key() <<
"->" << newValue;
321 return Soprano::LiteralValue( newValue );
330 switch( term.
type() ) {
334 if ( p.literalRangeType().isValid() ) {
339 newTerm.setProperty( QUrl(cterm.
property()) );
344 if ( lt.
dataType() == QVariant::DateTime &&
345 !subTerm.
value().isDateTime() ) {
346 QDateTime dateTime = parseDateTime( subTerm.
value() );
347 if ( dateTime.isValid() ) {
352 else if ( lt.
dataType() == QVariant::Int &&
353 !subTerm.
value().isInt() ) {
364 QList<Nepomuk2::Query::Term> newSubTerms;
365 foreach(
const Nepomuk2::Query::Term& t, static_cast<const Nepomuk2::Query::GroupTerm&>( term ).subTerms() ) {
366 newSubTerms << resolveLiteralValues(t);
381 const char* s_fieldNamePattern =
"([^\\s\"':=<>]+|(?:([\"'])[^\"':=<>]+\\%1))";
382 const char* s_plainTermPattern =
"([^-][^\\s\"':=<>]*|(?:([\"'])[^\"']+\\%1))";
383 const char* s_inExclusionPattern =
"((?:[\\+\\-\\!]\\s*|[nN][oO][tT]\\s+)?)";
384 const char* s_uriPattern =
"<([^<>]+)>";
385 const char* s_comparatorPattern =
"(:|\\<=|\\>=|=|\\<|\\>)";
391 class QueryParserRegExpPool
394 QueryParserRegExpPool()
395 : plainTermRx( QLatin1String(s_inExclusionPattern)
396 + QString::fromLatin1(s_plainTermPattern).arg( 3 ) ),
397 fieldRx( QLatin1String(s_inExclusionPattern)
398 + QString::fromLatin1(s_fieldNamePattern).arg( 3 )
399 + QLatin1String(s_comparatorPattern)
400 + QString::fromLatin1(s_plainTermPattern).arg( 6 ) ),
401 propertyRx( QLatin1String(s_inExclusionPattern)
402 + QLatin1String(s_uriPattern)
403 + QLatin1String(s_comparatorPattern)
404 + QString::fromLatin1(s_plainTermPattern).arg( 5 ) ),
405 resourceRx( QLatin1String(s_inExclusionPattern)
406 + QLatin1String(s_uriPattern)
407 + QLatin1String(
"(?::|=)")
408 + QLatin1String(s_uriPattern) ),
409 fieldFieldRx( QLatin1String(s_inExclusionPattern)
410 + QString::fromLatin1(s_fieldNamePattern).arg( 3 )
411 + QLatin1String(s_comparatorPattern)
412 + QLatin1String(
"\\(")
413 + QString::fromLatin1(s_fieldNamePattern).arg( 6 )
414 + QLatin1String(s_comparatorPattern)
415 + QString::fromLatin1(s_plainTermPattern).arg( 9 )
416 + QLatin1String(
"\\)") )
445 QRegExp fieldFieldRx;
449 K_GLOBAL_STATIC( QueryParserRegExpPool, s_regExpPool )
453 class Nepomuk2::Query::QueryParser::Private
458 QSet<QString> andKeywords;
459 QSet<QString> orKeywords;
461 QMutex fieldMatchCacheMutex;
477 Term QueryParser::Private::resolveFields(
const Term &term)
479 switch( term.
type() ) {
482 QList<Nepomuk2::Query::Term> newSubTerms;
483 foreach(
const Nepomuk2::Query::Term& t, static_cast<const Nepomuk2::Query::GroupTerm&>( term ).subTerms() ) {
486 newSubTerms << resolvedTerm;
510 if ( newTerm.
property().
uri().scheme().isEmpty() ) {
516 for(
int i = 0; i < properties.count() && orTerm.
subTerms().count() < 4; ++i ) {
519 t.setProperty( property );
520 if(setupComparisonTermSubTerm(t)) {
525 m_invalidQuery =
true;
543 QString andListStr = i18nc(
"Boolean AND keyword in desktop search strings. "
544 "You can add several variants separated by spaces, "
545 "e.g. retain the English one alongside the translation; "
546 "keywords are not case sensitive. Make sure there is "
547 "no conflict with the OR keyword.",
549 foreach (
const QString &andKeyword, andListStr.split(
' ', QString::SkipEmptyParts ) ) {
550 d->andKeywords.insert( andKeyword.toLower() );
552 QString orListStr = i18nc(
"Boolean OR keyword in desktop search strings. "
553 "You can add several variants separated by spaces, "
554 "e.g. retain the English one alongside the translation; "
555 "keywords are not case sensitive. Make sure there is "
556 "no conflict with the AND keyword.",
558 foreach (
const QString &orKeyword, orListStr.split(
' ', QString::SkipEmptyParts ) ) {
559 d->orKeywords.insert( orKeyword.toLower() );
565 d->fieldMatchCache.insert( QLatin1String(
"hastag"), QList<Types::Property>() <<
Types::Property(NAO::hasTag()) );
566 d->fieldMatchCache.insert( QLatin1String(
"rating"), QList<Types::Property>() <<
Types::Property(NAO::numericRating()) );
567 d->fieldMatchCache.insert( QLatin1String(
"comment"), QList<Types::Property>() <<
Types::Property(NAO::description()) );
568 d->fieldMatchCache.insert( QLatin1String(
"mimetype"), QList<Types::Property>() <<
Types::Property(NIE::mimeType()) );
580 kDebug() << fieldName;
582 QMutexLocker lock( &d->fieldMatchCacheMutex );
585 if( it != d->fieldMatchCache.constEnd() ) {
591 QList<Nepomuk2::Types::Property> results;
598 QString query = QString(
"select distinct ?p where { "
602 "FILTER(REGEX(STR(?l),'%3*','i') || REGEX(STR(?p),'%3*','i')) . "
605 .arg( Soprano::Node::resourceToN3( Soprano::Vocabulary::RDF::Property() ) )
606 .arg( Soprano::Node::resourceToN3( Soprano::Vocabulary::RDFS::label() ) )
608 kDebug() <<
"Match query:" << query;
610 Soprano::QueryResultIterator labelHits
613 while ( labelHits.next() ) {
614 QUrl
property = labelHits.binding(
"p" ).uri();
616 kDebug() <<
"Found property match" << property;
620 d->fieldMatchCache.insert( fieldName, results );
628 return parse( query, NoParserFlags );
640 bool inOrBlock =
false;
641 bool inAndBlock =
false;
646 const QRegExp resourceRx = s_regExpPool->resourceRx;
647 const QRegExp propertyRx = s_regExpPool->propertyRx;
648 const QRegExp fieldFieldRx = s_regExpPool->fieldFieldRx;
649 const QRegExp fieldRx = s_regExpPool->fieldRx;
650 const QRegExp plainTermRx = s_regExpPool->plainTermRx;
652 while ( pos < query.length() ) {
654 while ( pos < query.length() && query[pos].isSpace() ) {
655 kDebug() <<
"Skipping space at" << pos;
661 if ( pos < query.length() ) {
662 if ( resourceRx.indexIn( query, pos ) == pos ) {
663 kDebug() <<
"matched resource term at" << pos << resourceRx.cap( 0 );
664 term =
ComparisonTerm( tryToBeIntelligentAboutParsingUrl( resourceRx.cap( 2 ) ),
665 ResourceTerm( tryToBeIntelligentAboutParsingUrl( resourceRx.cap( 3 ) ) ),
667 if ( !positiveTerm( resourceRx.cap( 1 ) ) ) {
670 pos += resourceRx.matchedLength();
673 else if ( propertyRx.indexIn( query, pos ) == pos ) {
674 kDebug() <<
"matched property term at" << pos << propertyRx.cap( 0 );
676 ct.
setProperty( tryToBeIntelligentAboutParsingUrl( propertyRx.cap( 2 ) ) );
678 QString comparator = propertyRx.cap( 3 );
679 ct.
setComparator( fieldTypeRelationFromString( comparator ) );
680 pos += propertyRx.matchedLength();
682 if ( !positiveTerm(propertyRx.cap( 1 ) ) ) {
690 else if ( fieldFieldRx.indexIn( query, pos ) == pos ) {
691 kDebug() <<
"matched field field term at" << pos
692 << fieldFieldRx.cap( 0 )
693 << fieldFieldRx.cap( 2 )
694 << fieldFieldRx.cap( 4 )
695 << fieldFieldRx.cap( 5 )
696 << fieldFieldRx.cap( 7 )
697 << fieldFieldRx.cap( 8 );
699 ct.
setProperty( QUrl(stripQuotes( fieldFieldRx.cap( 2 ) )) );
700 QString comparator = fieldFieldRx.cap( 4 );
701 ct.
setComparator( fieldTypeRelationFromString( comparator ) );
703 LiteralTerm( createLiteral( fieldFieldRx.cap( 8 ), flags&QueryTermGlobbing ) ),
704 fieldTypeRelationFromString( fieldFieldRx.cap( 7 ) ) ) );
705 pos += fieldFieldRx.matchedLength();
707 if ( !positiveTerm( fieldFieldRx.cap( 1 ) ) ) {
715 else if ( fieldRx.indexIn( query, pos ) == pos ) {
716 kDebug() <<
"matched field term at" << pos << fieldRx.cap( 0 ) << fieldRx.cap( 2 ) << fieldRx.cap( 4 ) << fieldRx.cap( 5 );
717 if( stripQuotes ( fieldRx.cap( 2 ) ).compare( QString(
"inFolder" ), Qt::CaseInsensitive ) == 0 ) {
718 KUrl url( fieldRx.cap( 5 ) );
719 kDebug() <<
"found include path" << url;
721 if ( positiveTerm( fieldRx.cap( 1 ) ) )
726 pos += fieldRx.matchedLength();
730 ct.
setProperty( QUrl( stripQuotes( fieldRx.cap( 2 ) ) ) );
732 QString comparator = fieldRx.cap( 4 );
733 ct.
setComparator( fieldTypeRelationFromString( comparator ) );
734 pos += fieldRx.matchedLength();
735 if ( !positiveTerm(fieldRx.cap( 1 ) ) ) {
746 else if ( plainTermRx.indexIn( query, pos ) == pos ) {
747 QString value = plainTermRx.cap( 2 );
748 if ( d->orKeywords.contains( value.toLower() ) ) {
751 else if ( d->andKeywords.contains( value.toLower() ) ) {
755 kDebug() <<
"matched literal at" << pos << value;
756 if( flags&DetectFilenamePattern && isFilenamePattern(value) ) {
757 term = createFilenamePatternTerm( value );
760 term =
LiteralTerm( createLiteral( value, flags&QueryTermGlobbing ) );
762 if ( !positiveTerm(plainTermRx.cap( 1 ) ) ) {
766 pos += plainTermRx.matchedLength();
770 kDebug() <<
"Invalid query at" << pos << query;
775 if ( inOrBlock && !terms.isEmpty() ) {
779 terms.append( orTerm );
781 else if ( inAndBlock && !terms.isEmpty() ) {
785 terms.append( andTerm );
788 terms.append( term );
794 if ( terms.count() == 1 ) {
795 final.setTerm( terms[0] );
797 else if ( terms.count() > 0 ) {
803 d->m_invalidQuery =
false;
804 final.setTerm( mergeLiteralTerms( d->resolveFields(
final.term() ) ) );
805 if(d->m_invalidQuery) {
818 return parser.
parse( query );
826 return parser.
parse( query, flags );
Class range()
The range of the property.
void setComparator(Comparator)
Set the comparator.
A LiteralTerm sub-term is matched to smaller literal values.
QueryParser()
Create a new query parser.
LiteralTerm toLiteralTerm() const
Interpret this term as a LiteralTerm.
void setProperty(const Types::Property &)
Set the property for ComparisonTerm Terms.
Match resource that match all sub terms.
void addExcludeFolder(const KUrl &folder)
Add a folder to exclude from the search.
A negation term inverts the meaning of its sub term.
QUrl uri() const
The URI of the resource.
A LiteralTerm sub-term is matched to greater literal values.
The base class for all term types.
void setSubTerm(const Term &term)
Set the sub term to match against.
A LiteralTerm sub-term is matched to greater or equal literal values.
static Term negateTerm(const Term &term)
Negate term.
Term subTerm() const
The sub term to match against.
QList< Term > subTerms() const
The sub terms that are combined in this group.
Term optimized() const
Optimizes the term without changing its meaning.
Match all resources that match all sub terms.
A sub-term is matched one-to-one.
Literal literalRangeType()
If the rage of this property is a literal (i.e.
A LiteralTerm sub-term is matched against a string literal value using the literal term's value as a ...
Negate an arbitrary term.
ComparisonTerm toComparisonTerm() const
Interpret this term as a ComparisonTerm.
void setSubTerms(const QList< Term > &terms)
Set the sub terms that are combined in this group.
QVariant::Type dataType() const
The type converted to a QVariant::Type.
A LiteralTerm sub-term is matched to smaller or equal literal values.
A Nepomuk desktop query specialized for file searches.
A term matching the value of a property.
bool isLiteralTerm() const
Match resource that match at least one of the sub terms.
Comparator
ComparisonTerm supports different ways to compare values.
A property is a resource of type rdf:Property which relates a domain with a range.
Defines a literal type based on XML Schema.
static ResourceManager * instance()
Query parse(const QString &query) const
Parse a user query.
~QueryParser()
Destructor.
QList< Types::Property > matchProperty(const QString &fieldName) const
Try to match a field name as used in a query string to actual properties.
static Query parseQuery(const QString &query)
Convenience method to quickly parse a query without creating an object.
Comparator comparator() const
The Comparator used by ComparisonTerm Terms.
Parser for desktop user queries.
void addSubTerm(const Term &term)
Add a sub term to the list of terms that are combined in this group.
AndTerm toAndTerm() const
Interpret this term as a AndTerm.
void addIncludeFolder(const KUrl &folder)
Add a folder to include in the search.
A LiteralTerm sub-term is matched against string literal values.
bool isValid() const
Is this a valid Entity, i.e.
Soprano::Model * mainModel()
Retrieve the main data storage model.
Match all resources that match one of the sub terms.
Types::Property property() const
A property used for ComparisonTerm Terms.
Match literal properties via full text.
Matches exactly one resource.
Soprano::LiteralValue value() const
The value this LiteralTerm should match to.
NegationTerm toNegationTerm() const
Interpret this term as a NegationTerm.