1 /*
2     This file is part of Kiten, a KDE Japanese Reference Tool
3     SPDX-FileCopyrightText: 2001 Jason Katz-Brown <jason@katzbrown.com>
4     SPDX-FileCopyrightText: 2006 Joseph Kerian <jkerian@gmail.com>
5     SPDX-FileCopyrightText: 2006 Eric Kjeldergaard <kjelderg@gmail.com>
6     SPDX-FileCopyrightText: 2011 Daniel E. Moctezuma <democtezuma@gmail.com>
7 
8     SPDX-License-Identifier: LGPL-2.0-or-later
9 */
10 
11 #include "entry.h"
12 
13 #include <KLocalizedString>
14 
15 
16 #include <iostream>
17 #include <cassert>
18 #include <sys/mman.h>
19 #include <stdio.h>
20 
21 /**
22  * The default constructor, unless you really know what you're doing,
23  * THIS SHOULD NOT BE USED. For general use, other entities will need
24  * to have the information provided by the other constructors
25  * (particularly the sourceDictionary).
26  */
Entry()27 Entry::Entry()
28 {
29   init();
30 }
31 
Entry(const QString & sourceDictionary)32 Entry::Entry( const QString &sourceDictionary )
33 : sourceDict( sourceDictionary )
34 {
35   init();
36 }
37 
Entry(const QString & sourceDictionary,const QString & word,const QStringList & reading,const QStringList & meanings)38 Entry::Entry( const QString &sourceDictionary, const QString &word,
39 	      const QStringList &reading, const QStringList &meanings )
40 : Word( word )
41 , Meanings( meanings )
42 , Readings( reading )
43 , sourceDict( sourceDictionary )
44 {
45   init();
46 }
47 
Entry(const Entry & src)48 Entry::Entry( const Entry &src )
49 : Word( src.Word )
50 , Meanings( src.Meanings )
51 , Readings( src.Readings )
52 , ExtendedInfo( src.ExtendedInfo )
53 , sourceDict( src.sourceDict )
54 {
55   outputListDelimiter = src.outputListDelimiter;
56 }
57 
~Entry()58 Entry::~Entry()
59 {
60 //   kdDebug() << "nuking : " << Word << endl;
61 }
62 
extendedItemCheck(const QString & key,const QString & value) const63 bool Entry::extendedItemCheck( const QString& key, const QString &value ) const
64 {
65   return getExtendedInfoItem( key ) == value;
66 }
67 
68 /**
69  * Get the dictionary name that generated this Entry. I can't think of a reason to be changing this
70  */
getDictName() const71 QString Entry::getDictName() const
72 {
73   return sourceDict;
74 }
75 
76 /**
77  * Get the word from this Entry. If the entry is of type kanji/kana/meaning/etc, this will return
78  * the kanji. If it is of kana/meaning/etc, it will return kana.
79  */
getWord() const80 QString Entry::getWord() const
81 {
82   return Word;
83 }
84 
85 /**
86  * Get a QString containing all of the meanings known, connected by the outputListDelimiter
87  */
getMeanings() const88 QString Entry::getMeanings() const
89 {
90   return Meanings.join(outputListDelimiter);
91 }
92 
93 /**
94  * Simple accessor
95  */
getMeaningsList() const96 QStringList Entry::getMeaningsList() const
97 {
98   return Meanings;
99 }
100 
101 /**
102  * Simple accessor
103  */
getReadings() const104 QString Entry::getReadings() const
105 {
106   return Readings.join( outputListDelimiter );
107 }
108 
109 /**
110  * Simple accessor
111  */
getReadingsList() const112 QStringList Entry::getReadingsList() const
113 {
114   return Readings;
115 }
116 
117 /**
118  * Simple accessor
119  */
getExtendedInfo() const120 QHash<QString,QString> Entry::getExtendedInfo() const
121 {
122   return ExtendedInfo;
123 }
124 
125 /**
126  * Simple accessor
127  *
128  * @param x the key for the extended info item to get
129  */
getExtendedInfoItem(const QString & x) const130 QString Entry::getExtendedInfoItem( const QString &x ) const
131 {
132   return ExtendedInfo[ x ];
133 }
134 
135 /**
136  * Prepares Meanings for output as HTML
137  */
HTMLMeanings() const138 inline QString Entry::HTMLMeanings() const
139 {
140   return QStringLiteral( "<span class=\"Meanings\">%1</span>" )
141              .arg( Meanings.join( outputListDelimiter ) );
142 }
143 
144 /* Prepares Readings for output as HTML */
HTMLReadings() const145 inline QString Entry::HTMLReadings() const
146 {
147   QStringList list;
148   foreach( const QString &it, Readings )
149   {
150     list += makeLink( it );
151   }
152 
153   return QStringLiteral( "<span class=\"Readings\">%1</span>" )
154              .arg( list.join( outputListDelimiter ) );
155 }
156 
157 /**
158  * Prepares Word for output as HTML
159  */
HTMLWord() const160 inline QString Entry::HTMLWord() const
161 {
162   return QStringLiteral( "<span class=\"Word\">%1</span>" ).arg( Word );
163 }
164 
init()165 void Entry::init()
166 {
167   outputListDelimiter = i18n( "; " );
168 }
169 
170 /**
171  * Determines whether @param character is a kanji character.
172  */
isKanji(const QChar & character) const173 bool Entry::isKanji( const QChar &character ) const
174 {
175   ushort value = character.unicode();
176   if( value < 255 )
177   {
178     return false;
179   }
180   if( 0x3040 <= value && value <= 0x30FF )
181   {
182     return false; //Kana
183   }
184 
185   return true; //Note our folly here... we assuming any non-ascii/kana is kanji
186 }
187 
188 /**
189  * Returns true if all members of test are in list
190  */
listMatch(const QStringList & list,const QStringList & test,DictQuery::MatchType type) const191 bool Entry::listMatch( const QStringList &list, const QStringList &test, DictQuery::MatchType type ) const
192 {
193   if( type == DictQuery::Exact )
194   {
195     foreach( const QString &it, test )
196     {
197       if( ! list.contains( it ) )
198       {
199         return false;
200       }
201     }
202   }
203   else if( type == DictQuery::Beginning )
204   {
205     foreach( const QString &it, test )
206     {
207       bool found = false;
208       foreach( const QString &it2, list )
209       {
210         if( it2.startsWith( it ) )
211         {
212           found = true;
213           break;
214         }
215       }
216       if( ! found )
217       {
218         return false;
219       }
220     }
221   }
222   else if( type == DictQuery::Ending )
223   {
224     foreach( const QString &it, test )
225     {
226       bool found = false;
227       foreach( const QString &it2, list )
228       {
229         if( it2.endsWith( it ) )
230         {
231           found = true;
232           break;
233         }
234       }
235       if( ! found )
236       {
237         return false;
238       }
239     }
240   }
241   else
242   {
243     foreach( const QString &it, test )
244     {
245       bool found = false;
246       foreach( const QString &it2, list )
247       {
248         if( it2.contains( it ) )
249         {
250           found = true;
251           break;
252         }
253       }
254       if( ! found )
255       {
256         return false;
257       }
258     }
259   }
260 
261   return true;
262 }
263 
264 /**
265  * New functions for Entry doing direct display.
266  *
267  * Creates a link for the given @p entryString.
268  */
makeLink(const QString & entryString) const269 inline QString Entry::makeLink( const QString &entryString ) const
270 {
271   return QStringLiteral( "<a href=\"%1\">%1</a>" ).arg( entryString );
272 }
273 
matchesQuery(const DictQuery & query) const274 bool Entry::matchesQuery( const DictQuery &query ) const
275 {
276   if( ! query.getWord().isEmpty() )
277   {
278     if( query.getMatchType() == DictQuery::Exact
279       && this->getWord() != query.getWord() )
280     {
281       return false;
282     }
283     if( query.getMatchType() == DictQuery::Beginning
284       && ! this->getWord().startsWith( query.getWord() ) )
285     {
286       return false;
287     }
288     if( query.getMatchType() == DictQuery::Ending
289       && ! this->getWord().endsWith( query.getWord() ) )
290     {
291       return false;
292     }
293     if( query.getMatchType() == DictQuery::Anywhere
294       && ! this->getWord().contains( query.getWord() ) )
295     {
296       return false;
297     }
298   }
299 
300   if( ! query.getPronunciation().isEmpty() && ! getReadings().isEmpty() )
301   {
302     if( ! listMatch( Readings, query.getPronunciation().split( DictQuery::mainDelimiter ),
303                             query.getMatchType() ) )
304     {
305       return false;
306     }
307   }
308 
309   if( ! query.getPronunciation().isEmpty() && getReadings().isEmpty() && ! getWord().isEmpty() )
310   {
311     switch ( query.getMatchType() )
312     {
313       case DictQuery::Exact:
314         if ( getWord() != query.getPronunciation() )
315         {
316           return false;
317         }
318         break;
319       case DictQuery::Beginning:
320         if ( ! getWord().startsWith( query.getPronunciation() ) )
321         {
322           return false;
323         }
324         break;
325       case DictQuery::Ending:
326         if ( ! getWord().endsWith( query.getPronunciation() ) )
327         {
328           return false;
329         } //fallthrough
330       case DictQuery::Anywhere:
331         if ( ! getWord().contains( query.getPronunciation() ) )
332         {
333           return false;
334         }
335         break;
336     }
337   }
338 
339   if( ! query.getMeaning().isEmpty() )
340   {
341     if( ! listMatch(   Meanings.join(QLatin1Char(' ') ).toLower().split( ' ' )
342                      , query.getMeaning().toLower().split( DictQuery::mainDelimiter )
343                      , query.getMatchType() ) )
344     {
345       return false;
346     }
347   }
348 
349   QList<QString> propList = query.listPropertyKeys();
350   foreach( const QString &key, propList )
351   {
352     if( ! extendedItemCheck( key, query.getProperty( key ) ) )
353     {
354       return false;
355     }
356   }
357 
358   return true;
359 }
360 
361 /**
362  * Main switching function for displaying to the user
363  */
toHTML() const364 QString Entry::toHTML() const
365 {
366   return QStringLiteral( "<div class=\"Entry\">%1%2%3</div>" )
367              .arg( HTMLWord() )
368              .arg( HTMLReadings() )
369              .arg( HTMLMeanings() );
370 }
371 
toKVTML() const372 inline QString Entry::toKVTML() const
373 {
374         /*
375    <e m="1" s="1">
376            <o width="414" l="en" q="t">(eh,) excuse me</o>
377            <t width="417" l="jp" q="o">(あのう、) すみません </t>
378    </e>
379    */
380   //TODO: en should not necessarily be the language here.
381   return QString( "<e>\n<o l=\"en\">%1</o>\n"
382                   "<t l=\"jp-kanji\">%2</t>\n"
383                   "<t l=\"jp-kana\">%3</t></e>\n\n" ).arg( getMeanings() )
384                                                      .arg( getWord() )
385                                                      .arg( getReadings() );
386 }
387 
388 /**
389  * This method should return the entry object in a simple QString format
390  * Brief form should be usable in quick summaries, for example
391  * Verbose form might be used to save a complete list of entries to a file, for example.
392  */
toString() const393 QString Entry::toString() const
394 {
395   return QStringLiteral( "%1 (%2) %3" ).arg( Word )
396                                 .arg( getReadings() )
397                                 .arg( getMeanings() );
398 }
399 
400 /**
401  * This version of sort only sorts dictionaries...
402  * This is a replacement for an operator\< function... so we return true if
403  * "this" should show up first on the list.
404  */
sort(const Entry & that,const QStringList & dictOrder,const QStringList & fields) const405 bool Entry::sort( const Entry &that, const QStringList &dictOrder, const QStringList &fields ) const
406 {
407   if( this->sourceDict != that.sourceDict )
408   {
409     foreach( const QString &dict, dictOrder )
410     {
411       if( dict == that.sourceDict )
412       {
413         return false;
414       }
415       if( dict == this->sourceDict )
416       {
417         return true;
418       }
419     }
420   }
421   else
422   {
423     foreach( const QString &field, fields )
424     {
425       if( field == QLatin1String( "Word/Kanji" ) )
426       {
427         return this->getWord() < that.getWord();
428       }
429       else if( field == QLatin1String( "Meaning" ) )
430       {
431         return listMatch( that.getMeaningsList(), this->getMeaningsList(), DictQuery::Exact )
432                && ( that.getMeaningsList().count() != this->getMeaningsList().count() );
433       }
434       else if( field == QLatin1String( "Reading" ) )
435       {
436         return listMatch( that.getReadingsList(), this->getReadingsList(), DictQuery::Exact )
437                && ( that.getReadingsList().count() != this->getReadingsList().count() );
438       }
439       else
440       {
441         const QString thisOne = this->getExtendedInfoItem( field );
442         const QString thatOne = that.getExtendedInfoItem( field );
443         //Only sort by this field if the values differ, otherwise move to the next field
444         if( thisOne != thatOne )
445         {
446           //If the second item does not have this field, sort this one first
447           if( thatOne.isEmpty() )
448           {
449             return true;
450           }
451           //If we don't have this field, sort "this" to second
452           if( thisOne.isEmpty() )
453           {
454             return false;
455           }
456           //Otherwise, send it to a virtual function (to allow dictionaries to override sorting)
457           return this->sortByField( that, field );
458         }
459       }
460     }
461   }
462   return false; //If we reach here, they match as much as possible
463 }
464 
sortByField(const Entry & that,const QString & field) const465 bool Entry::sortByField( const Entry &that, const QString &field ) const
466 {
467   return this->getExtendedInfoItem( field ) < that.getExtendedInfoItem( field );
468 }
469