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