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