1 /*
2     This file is part of Kiten, a KDE Japanese Reference Tool
3     SPDX-FileCopyrightText: 2006 Joseph Kerian <jkerian@gmail.com>
4     SPDX-FileCopyrightText: 2011 Daniel E. Moctezuma <democtezuma@gmail.com>
5 
6     SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 /*
10 TODO: Add features to limit the number of hits on a per-search basis.
11 
12 	Add a mechanism (either through subclassing, or directly) for use
13 		for marking "requested" fields for the dcop system.
14 */
15 
16 #include "dictquery.h"
17 
18 #include <QDebug>
19 
20 #include <QString>
21 
22 class DictQuery::Private
23 {
24   public:
Private()25     Private() : matchType( DictQuery::Exact )
26               , matchWordType( DictQuery::Any )
27               , filterType( DictQuery::NoFilter ) { }
28 
29     /** Stores the (english or otherwise non-japanese) meaning */
30     QString meaning;
31     /** Stores the pronunciation in kana */
32     QString pronunciation;
33     /** The main word, this usually contains kanji */
34     QString word;
35     /** Any amount of extended attributes, grade level, heisig/henshall/etc index numbers, whatever you want */
36     QHash<QString,QString> extendedAttributes;
37     /** The order that various attributes, meanings, and pronunciations were entered, so we can
38       * regenerate the list for the user if they need them again */
39     QStringList entryOrder;
40     /** A list of dictionaries to limit the search to, and empty list implies "all loaded dictionaries" */
41     QStringList targetDictionaries;
42     /** What MatchType is this set to */
43     MatchType matchType;
44     /** What MatchWordType is this set to */
45     MatchWordType matchWordType;
46     /** What FilterType is this set to */
47     FilterType filterType;
48 
49     /** Marker in the m_entryOrder for the location of the pronunciation element */
50     static const QString pronunciationMarker;
51     /** Marker in the m_entryOrder for the location of the translated meaning element */
52     static const QString meaningMarker;
53     /** Marker in the m_entryOrder for the location of the word (kanji) element */
54     static const QString wordMarker;
55 };
56 
57 const QString DictQuery::Private::pronunciationMarker( QStringLiteral("__@\\p") );
58 const QString DictQuery::Private::meaningMarker( QStringLiteral("__@\\m") );
59 const QString DictQuery::Private::wordMarker( QStringLiteral("_@\\w") );
60 
61 /*****************************************************************************
62 *	Constructors, Destructors, Initializers, and
63 *	Global Status Indicators.
64 *****************************************************************************/
DictQuery()65 DictQuery::DictQuery()
66 : d( new Private )
67 { }
68 
DictQuery(const QString & str)69 DictQuery::DictQuery( const QString& str )
70 : d( new Private )
71 {
72   this->operator=( (QString)str );
73 }
74 
DictQuery(const DictQuery & orig)75 DictQuery::DictQuery( const DictQuery& orig )
76 : d( new Private )
77 {
78   this->operator=( (DictQuery&)orig );
79 }
80 
clone() const81 DictQuery *DictQuery::clone() const
82 {
83   return new DictQuery( *this );
84 }
85 
operator QString() const86 DictQuery::operator QString() const
87 {
88   //kDebug() << "DictQuery toString operator called!";
89   return toString();
90 }
91 
~DictQuery()92 DictQuery::~DictQuery()
93 {
94   delete d;
95 }
96 
isEmpty() const97 bool DictQuery::isEmpty() const
98 {
99   // We're only empty if the two strings are empty too
100   return d->extendedAttributes.isEmpty() && d->meaning.isEmpty()
101            && d->pronunciation.isEmpty() && d->word.isEmpty();
102 }
103 
clear()104 void DictQuery::clear()
105 {
106   d->extendedAttributes.clear();
107   d->meaning = QLatin1String("");
108   d->pronunciation = QLatin1String("");
109   d->word = QLatin1String("");
110   d->entryOrder.clear();
111 }
112 
113 /*****************************************************************************
114 *	Methods that involve multiple instances of the class
115 *	(comparison, copy etc)
116 *****************************************************************************/
operator =(const DictQuery & old)117 DictQuery &DictQuery::operator=( const DictQuery &old )
118 {
119   if ( &old == this )
120   {
121     return *this;
122   }
123 
124   clear();
125   d->matchType          = old.d->matchType;
126   d->matchWordType      = old.d->matchWordType;
127   d->filterType         = old.d->filterType;
128   d->extendedAttributes = old.d->extendedAttributes;
129   d->meaning            = old.d->meaning;
130   d->pronunciation      = old.d->pronunciation;
131   d->word               = old.d->word;
132   d->entryOrder         = old.d->entryOrder;
133   return *this;
134 }
135 
operator +=(const DictQuery & old)136 DictQuery &DictQuery::operator+=( const DictQuery &old )
137 {
138   foreach( const QString &item, old.d->entryOrder )
139   {
140     if( item == d->meaningMarker )
141     {
142       if( d->entryOrder.removeAll( d->meaningMarker ) > 0 )
143       {
144         setMeaning( getMeaning() + mainDelimiter + old.getMeaning() );
145       }
146       else
147       {
148         setMeaning( old.getMeaning() );
149       }
150     }
151     else if( item == d->pronunciationMarker )
152     {
153       if( d->entryOrder.removeAll( d->pronunciationMarker ) > 0 )
154       {
155         setPronunciation( getPronunciation() + mainDelimiter + old.getPronunciation() );
156       }
157       else
158       {
159         setPronunciation( old.getPronunciation() );
160       }
161     }
162     else if( item == d->wordMarker )
163     {
164       d->entryOrder.removeAll( d->wordMarker );
165       //Only one of these allowed
166       setWord( old.getWord() );
167     }
168     else
169     {
170       setProperty( item, old.getProperty( item ) );
171     }
172   }
173 
174   return *this;
175 }
176 
operator +(const DictQuery & a,const DictQuery & b)177 DictQuery operator+( const DictQuery &a, const DictQuery &b )
178 {
179   DictQuery val( a );
180   val += b;
181   return val;
182 }
183 
operator ==(const DictQuery & a,const DictQuery & b)184 bool operator==( const DictQuery &a, const DictQuery &b )
185 {
186   if(   ( a.d->pronunciation      != b.d->pronunciation )
187      || ( a.d->meaning            != b.d->meaning )
188      || ( a.d->word               != b.d->word )
189      || ( a.d->entryOrder         != b.d->entryOrder )
190      || ( a.d->extendedAttributes != b.d->extendedAttributes )
191      || ( a.d->matchType          != b.d->matchType )
192      || ( a.d->matchWordType      != b.d->matchWordType )
193      || ( a.d->filterType         != b.d->filterType ) )
194   {
195     return false;
196   }
197 
198   return true;
199 }
200 
operator !=(const DictQuery & a,const DictQuery & b)201 bool operator!=( const DictQuery &a, const DictQuery &b )
202 {
203   return ! ( a == b );
204 }
205 
operator <(const DictQuery & a,const DictQuery & b)206 bool operator<( const DictQuery &a, const DictQuery &b )
207 {
208   QHash<QString,QString>::const_iterator it     = a.d->extendedAttributes.constBegin();
209   QHash<QString,QString>::const_iterator it_end = a.d->extendedAttributes.constEnd();
210   for( ; it != it_end; ++it )
211   {
212     QString B_version = b.d->extendedAttributes.value( it.key() );
213     if( a.d->extendedAttributes[ it.key() ] != B_version )
214     {
215       if( ! B_version.contains( QLatin1Char(',') ) && ! B_version.contains( QLatin1Char('-') ) )
216       {
217         return false;
218       }
219       //TODO: check for multi-values or ranges in DictQuery operator<
220     }
221   }
222 
223   if( ! a.d->pronunciation.isEmpty() )
224   {
225     QStringList aList = a.d->pronunciation.split( DictQuery::mainDelimiter );
226     QStringList bList = b.d->pronunciation.split( DictQuery::mainDelimiter );
227     foreach( const QString &str, aList )
228     {
229       if( bList.contains( str ) == 0 )
230       {
231         return false;
232       }
233     }
234   }
235 
236   if( ! a.d->meaning.isEmpty() )
237   {
238     QStringList aList = a.d->meaning.split( DictQuery::mainDelimiter );
239     QStringList bList = b.d->meaning.split( DictQuery::mainDelimiter );
240     foreach( const QString &str, aList )
241     {
242       if( bList.contains( str ) == 0 )
243       {
244         return false;
245       }
246     }
247   }
248 
249   //Assume only one entry for word
250   if( ! a.d->word.isEmpty() )
251   {
252     if( a.d->word != b.d->word )
253     {
254       return false;
255     }
256   }
257 
258   return true;
259 }
260 
261 /*****************************************************************************
262 *	Methods to extract from QStrings and recreate QStrings
263 *
264 *****************************************************************************/
toString() const265 const QString DictQuery::toString() const
266 {
267   if( isEmpty() )
268   {
269     return QString();
270   }
271 
272   QString reply;
273   foreach( const QString &it, d->entryOrder )
274   {
275     if( it == d->pronunciationMarker )
276     {
277       reply += d->pronunciation+mainDelimiter;
278     }
279     else if( it == d->meaningMarker )
280     {
281       reply += d->meaning+mainDelimiter;
282     }
283     else if( it == d->wordMarker )
284     {
285       reply += d->word+mainDelimiter;
286     }
287     else
288     {
289       reply += it + propertySeperator + d->extendedAttributes.value( it )
290                   + mainDelimiter;
291     }
292   }
293   reply.truncate( reply.length() - mainDelimiter.length() );
294 
295   return reply;
296 }
297 
operator =(const QString & str)298 DictQuery &DictQuery::operator=( const QString &str )
299 {
300   QStringList parts = str.split( mainDelimiter );
301   DictQuery result;
302   if( str.length() > 0 )
303   {
304     foreach( const QString &it, parts )
305     {
306       if( it.contains( propertySeperator ) )
307       {
308         QStringList prop = it.split( propertySeperator );
309         if( prop.count() != 2 )
310         {
311           break;
312         }
313         result.setProperty( prop[ 0 ], prop[ 1 ] );
314         //replace or throw an error with duplicates?
315       }
316       else
317       {
318         switch( stringTypeCheck( it ) )
319         {
320           case DictQuery::Latin:
321             if( result.d->entryOrder.removeAll( d->meaningMarker ) > 0 )
322             {
323               result.setMeaning( result.getMeaning() + mainDelimiter + it );
324             }
325             else
326             {
327               result.setMeaning( it );
328             }
329             break;
330 
331           case DictQuery::Kana:
332             if( result.d->entryOrder.removeAll( d->pronunciationMarker ) > 0 )
333             {
334               result.setPronunciation( result.getPronunciation() + mainDelimiter + it );
335             }
336             else
337             {
338               result.setPronunciation( it );
339             }
340             break;
341 
342           case DictQuery::Kanji:
343             result.d->entryOrder.removeAll( d->wordMarker );
344             result.setWord( it ); //Only one of these allowed
345             break;
346 
347           case DictQuery::Mixed:
348             qWarning() << "DictQuery: String parsing error - mixed type";
349             break;
350 
351           case DictQuery::ParseError:
352             qWarning() << "DictQuery: String parsing error";
353             break;
354         }
355       }
356     }
357   }
358   //kDebug() << "Query: ("<<result.getWord() << ") ["<<result.getPronunciation()<<"] :"<<
359   //	result.getMeaning()<<endl;
360   this->operator=( result );
361   return *this;
362 }
363 
364 /**
365  * Private utility method for the above... confirms that an entire string
366  * is either completely japanese or completely english
367  */
stringTypeCheck(const QString & in)368 DictQuery::StringTypeEnum DictQuery::stringTypeCheck( const QString &in )
369 {
370   StringTypeEnum firstType;
371   //Split into individual characters
372   if( in.size() <= 0 )
373   {
374     return DictQuery::ParseError;
375   }
376 
377   firstType = charTypeCheck( in.at( 0 ) );
378   for( int i = 1; i < in.size(); i++ )
379   {
380     StringTypeEnum newType = charTypeCheck( in.at( i ) );
381     if( newType != firstType )
382     {
383       if( firstType == Kana && newType == Kanji )
384       {
385         firstType = Kanji;
386       }
387       else if( firstType == Kanji && newType == Kana )
388         ; //That's okay
389       else
390       {
391         return DictQuery::Mixed;
392       }
393     }
394   }
395 
396   return firstType;
397 }
398 
399 /**
400  * Private utility method for the stringTypeCheck
401  * Just checks and returns the type of the first character in the string
402  * that is passed to it.
403  */
charTypeCheck(const QChar & ch)404 DictQuery::StringTypeEnum DictQuery::charTypeCheck( const QChar &ch )
405 {
406   if( ch.toLatin1() )
407   {
408     return Latin;
409   }
410   //The unicode character boundaries are:
411   // 3040 - 309F Hiragana
412   // 30A0 - 30FF Katakana
413   // 31F0 - 31FF Katakana phonetic expressions (wtf?)
414   if( 0x3040 <= ch.unicode() && ch.unicode() <= 0x30FF /*|| ch.unicode() & 0x31F0*/ )
415   {
416     return Kana;
417   }
418 
419   return Kanji;
420 }
421 
422 /*****************************************************************************
423 *	An array of Property List accessors and mutators
424 *
425 *****************************************************************************/
getProperty(const QString & key) const426 QString DictQuery::getProperty( const QString &key ) const
427 {
428   return ( *this )[ key ];
429 }
430 
listPropertyKeys() const431 const QList<QString> DictQuery::listPropertyKeys() const
432 {
433   return d->extendedAttributes.keys();
434 }
435 
operator [](const QString & key) const436 const QString DictQuery::operator[] ( const QString &key ) const
437 {
438   return d->extendedAttributes.value( key );
439 }
440 
operator [](const QString & key)441 QString DictQuery::operator[] ( const QString &key )
442 {
443   return d->extendedAttributes[ key ];
444 }
445 
hasProperty(const QString & key) const446 bool DictQuery::hasProperty( const QString &key ) const
447 {
448   return d->entryOrder.contains( key ) > 0;
449 }
450 
451 //TODO: Add i18n handling and alternate versions of property names
452 //TODO: further break down the barrier between different types
setProperty(const QString & key,const QString & value)453 bool DictQuery::setProperty( const QString& key, const QString& value )
454 {
455   if( key == d->pronunciationMarker || key == d->meaningMarker
456       || key.isEmpty() || value.isEmpty() )
457   {
458     return false;
459   }
460 
461   if ( ! d->extendedAttributes.contains( key ) )
462   {
463     d->entryOrder.append( key );
464   }
465 
466   d->extendedAttributes.insert( key, value );
467   return true;
468 }
469 
removeProperty(const QString & key)470 bool DictQuery::removeProperty( const QString &key )
471 {
472   if( d->extendedAttributes.contains( key ) )
473   {
474     return d->entryOrder.removeAll( key );
475   }
476   return false;
477 }
478 
takeProperty(const QString & key)479 QString DictQuery::takeProperty ( const QString & key )
480 {
481   d->entryOrder.removeAll( key );
482   return d->extendedAttributes.take( key );
483 }
484 
485 /*****************************************************************************
486 *	Meaning and Pronunciation Accessors and Mutators
487 ****************************************************************************/
getMeaning() const488 QString DictQuery::getMeaning() const
489 {
490   return d->meaning;
491 }
492 
setMeaning(const QString & newMeaning)493 bool DictQuery::setMeaning( const QString &newMeaning )
494 {
495   if ( newMeaning.isEmpty() )
496   {
497 #ifdef USING_QUERY_EXCEPTIONS
498     throw InvalidQueryException( newMeaning );
499 #else
500     return false;
501 #endif
502   }
503 
504   d->meaning = newMeaning;
505 
506   if( ! d->entryOrder.contains( d->meaningMarker ) )
507   {
508     d->entryOrder.append( d->meaningMarker );
509   }
510 
511   return true;
512 }
513 
getPronunciation() const514 QString DictQuery::getPronunciation() const
515 {
516   return d->pronunciation;
517 }
518 
setPronunciation(const QString & newPronunciation)519 bool DictQuery::setPronunciation( const QString &newPronunciation )
520 {
521   if( newPronunciation.isEmpty() )
522   {
523 #ifdef USING_QUERY_EXCEPTIONS
524     throw InvalidQueryException( newPro );
525 #else
526     return false;
527 #endif
528   }
529 
530   d->pronunciation = newPronunciation;
531 
532   if( ! d->entryOrder.contains( d->pronunciationMarker ) )
533   {
534     d->entryOrder.append( d->pronunciationMarker );
535   }
536 
537   return true;
538 }
539 
getWord() const540 QString DictQuery::getWord() const
541 {
542   return d->word;
543 }
544 
setWord(const QString & newWord)545 bool DictQuery::setWord( const QString &newWord )
546 {
547   if( newWord.isEmpty() )
548   {
549 #ifdef USING_QUERY_EXCEPTIONS
550     throw InvalidQueryException( newWord );
551 #else
552     return false;
553 #endif
554   }
555 
556   d->word = newWord;
557 
558   if( ! d->entryOrder.contains( d->wordMarker ) )
559   {
560     d->entryOrder.append( d->wordMarker );
561   }
562 
563   return true;
564 }
565 
566 /*************************************************************
567   Handlers for getting and setting dictionary types
568   *************************************************************/
getDictionaries() const569 QStringList DictQuery::getDictionaries() const
570 {
571   return d->targetDictionaries;
572 }
573 
setDictionaries(const QStringList & newDictionaries)574 void DictQuery::setDictionaries( const QStringList &newDictionaries )
575 {
576   d->targetDictionaries = newDictionaries;
577 }
578 
579 /**************************************************************
580   Match Type Accessors and Mutators
581   ************************************************************/
getFilterType() const582 DictQuery::FilterType DictQuery::getFilterType() const
583 {
584   return d->filterType;
585 }
586 
setFilterType(FilterType newType)587 void DictQuery::setFilterType( FilterType newType )
588 {
589   d->filterType = newType;
590 }
591 
getMatchType() const592 DictQuery::MatchType DictQuery::getMatchType() const
593 {
594   return d->matchType;
595 }
596 
setMatchType(MatchType newType)597 void DictQuery::setMatchType( MatchType newType )
598 {
599   d->matchType = newType;
600 }
601 
getMatchWordType() const602 DictQuery::MatchWordType DictQuery::getMatchWordType() const
603 {
604   return d->matchWordType;
605 }
606 
setMatchWordType(MatchWordType newType)607 void DictQuery::setMatchWordType( MatchWordType newType )
608 {
609   d->matchWordType = newType;
610 }
611 
612 /**************************************************************
613 *	Aliases to handle different forms of operator arguments
614 *	Disabled at the moment
615 *************************************************************
616 bool operator==( const QString &other, const DictQuery &query ) {
617 	DictQuery x(other); return x == query;
618 }
619 bool operator==( const DictQuery &query, const QString &other ) {
620 	return other==query;
621 }
622 bool operator!=( const DictQuery &q1, const DictQuery &q2 ) {
623 	return !(q1==q2);
624 }
625 bool operator!=( const QString &other, const DictQuery &query ) {
626 	return !(other==query);
627 }
628 bool operator!=( const DictQuery &query, const QString &other ) {
629 	return !(query==other);
630 }
631 inline bool operator<=( const DictQuery &a, const DictQuery &b) {
632 	return (a<b || a==b);
633 }
634 bool operator>=( const DictQuery &a, const DictQuery &b) {
635 	return (b>a || a==b);
636 }
637 bool operator>( const DictQuery &a, const DictQuery &b) {
638 	return b < a;
639 }
640 DictQuery &operator+( const DictQuery &a, const QString &b) {
641 	return (*(new DictQuery(a))) += b;
642 }
643 DictQuery &operator+( const QString &a,   const DictQuery &b)  {
644 	return (*(new DictQuery(a))) += b;
645 }
646 DictQuery    &DictQuery::operator+=(const QString &str) {
647 	DictQuery x(str);
648 	return operator+=(x);
649 }
650 #ifndef QT_NO_CAST_ASCII
651 DictQuery    &DictQuery::operator=(const char *str) {
652 	QString x(str);
653 	return operator=(x);
654 }
655 DictQuery    &DictQuery::operator+=(const char *str) {
656 	DictQuery x(str);
657 	return operator+=(x);
658 }
659 #endif
660 */
661 /**************************************************************
662 *	Set our constants declared in the class
663 **************************************************************/
664 const QString DictQuery::mainDelimiter( QStringLiteral(" ") );
665 const QString DictQuery::propertySeperator( QStringLiteral(":") );
666