]> gitweb.fperrin.net Git - DictionaryPC.git/blob - src/com/hughes/android/dictionary/parser/EnWiktionaryXmlParser.java
go
[DictionaryPC.git] / src / com / hughes / android / dictionary / parser / EnWiktionaryXmlParser.java
1 // Copyright 2011 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package com.hughes.android.dictionary.parser;
16
17 import java.io.BufferedInputStream;
18 import java.io.DataInputStream;
19 import java.io.EOFException;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.LinkedHashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.regex.Pattern;
32
33 import com.hughes.android.dictionary.engine.EntryTypeName;
34 import com.hughes.android.dictionary.engine.IndexBuilder;
35 import com.hughes.android.dictionary.engine.IndexedEntry;
36 import com.hughes.android.dictionary.engine.PairEntry;
37 import com.hughes.android.dictionary.engine.PairEntry.Pair;
38
39 public class EnWiktionaryXmlParser {
40   
41   // TODO: look for {{ and [[ and <adf> <!-- in output.
42   // TODO: process {{ttbc}} lines
43   
44   static final Pattern partOfSpeechHeader = Pattern.compile(
45       "Noun|Verb|Adjective|Adverb|Pronoun|Conjunction|Interjection|" +
46       "Preposition|Proper noun|Article|Prepositional phrase|Acronym|" +
47       "Abbreviation|Initialism|Contraction|Prefix|Suffix|Symbol|Letter|" +
48       "Ligature|Idiom|Phrase|" +
49       // These are @deprecated:
50       "Noun form|Verb form|Adjective form|Nominal phrase|Noun phrase|" +
51       "Verb phrase|Transitive verb|Intransitive verb|Reflexive verb|" +
52       // These are extras I found:
53       "Determiner|Numeral|Number|Cardinal number|Ordinal number|Proverb|" +
54       "Particle|Interjection|Pronominal adverb" +
55       "Han character|Hanzi|Hanja|Kanji|Katakana character|Syllable");
56   
57   final IndexBuilder enIndexBuilder;
58   final IndexBuilder otherIndexBuilder;
59   final Pattern langPattern;
60   final Pattern langCodePattern;
61   final boolean swap;
62
63   public EnWiktionaryXmlParser(final IndexBuilder enIndexBuilder, final IndexBuilder otherIndexBuilder, final Pattern langPattern, final Pattern langCodePattern, final boolean swap) {
64     this.enIndexBuilder = enIndexBuilder;
65     this.otherIndexBuilder = otherIndexBuilder;
66     this.langPattern = langPattern;
67     this.langCodePattern = langCodePattern;
68     this.swap = swap;
69   }
70
71   
72   public void parse(final File file, final int pageLimit) throws IOException {
73     int pageCount = 0;
74     final DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
75     while (true) {
76       if (pageLimit >= 0 && pageCount >= pageLimit) {
77         return;
78       }
79       
80       final String title;
81       try {
82         title = dis.readUTF();
83       } catch (EOFException e) {
84         dis.close();
85         return;
86       }
87       final String heading = dis.readUTF();
88       final int bytesLength = dis.readInt();
89       final byte[] bytes = new byte[bytesLength];
90       dis.readFully(bytes);
91       final String text = new String(bytes, "UTF8");
92       
93       parseSection(title, heading, text);
94
95       ++pageCount;
96       if (pageCount % 1000 == 0) {
97         System.out.println("pageCount=" + pageCount);
98       }
99     }
100   }
101   
102   private void parseSection(final String title, String heading, final String text) {
103     if (title.startsWith("Wiktionary:") ||
104         title.startsWith("Template:") ||
105         title.startsWith("Appendix:") ||
106         title.startsWith("Category:") ||
107         title.startsWith("Index:") ||
108         title.startsWith("MediaWiki:") ||
109         title.startsWith("TransWiki:") ||
110         title.startsWith("Citations:") ||
111         title.startsWith("Concordance:") ||
112         title.startsWith("Help:")) {
113       return;
114     }
115     
116     heading = heading.replaceAll("=", "").trim(); 
117     if (heading.equals("English")) {
118       doEnglishWord(title, text);
119     } else if (langPattern.matcher(heading).matches()){
120       doForeignWord(title, text);
121     }
122         
123   }  // endPage()
124   
125   // -------------------------------------------------------------------------
126   
127   String pos = null;
128   int posDepth = -1;
129
130   private void doEnglishWord(String title, String text) {
131     final WikiTokenizer wikiTokenizer = new WikiTokenizer(text);
132     while (wikiTokenizer.nextToken() != null) {
133       
134       if (wikiTokenizer.isHeading()) {
135         final String headerName = wikiTokenizer.headingWikiText();
136         
137         if (wikiTokenizer.headingDepth() <= posDepth) {
138           pos = null;
139           posDepth = -1;
140         }
141         
142         if (partOfSpeechHeader.matcher(headerName).matches()) {
143           posDepth = wikiTokenizer.headingDepth();
144           pos = wikiTokenizer.headingWikiText();
145         } else if (headerName.equals("Translations")) {
146           doTranslations(title, wikiTokenizer);
147         } else if (headerName.equals("Pronunciation")) {
148           //doPronunciation(wikiLineReader);
149         }
150       }
151     }
152   }
153
154
155   private static Set<String> encodings = new LinkedHashSet<String>(Arrays.asList("zh-ts",
156       "sd-Arab", "ku-Arab", "Arab", "unicode", "Laoo", "ur-Arab", "Thai", 
157       "fa-Arab", "Khmr", "zh-tsp", "Cyrl", "IPAchar", "ug-Arab", "ko-inline", 
158       "Jpan", "Kore", "Hebr", "rfscript", "Beng", "Mong", "Knda", "Cyrs",
159       "yue-tsj", "Mlym", "Tfng", "Grek", "yue-yue-j"));
160   
161   private void doTranslations(final String title, final WikiTokenizer wikiTokenizer) {
162     String sense = null;
163     boolean done = false;
164     while (wikiTokenizer.nextToken() != null) {
165       if (wikiTokenizer.isHeading()) {
166         wikiTokenizer.returnToLineStart();
167         return;
168       }
169       if (done) {
170         continue;
171       }
172       
173       // Check whether we care about this line:
174       
175       //line = WikiLineReader.removeSquareBrackets(line);
176       
177       if (wikiTokenizer.isFunction()) {
178         final String functionName = wikiTokenizer.functionName();
179         final List<String> positionArgs = wikiTokenizer.functionPositionArgs();
180         
181         if (functionName.equals("trans-top")) {
182           sense = null;
183           if (wikiTokenizer.functionPositionArgs().size() >= 1) {
184             sense = positionArgs.get(0);
185             // TODO: could emphasize words in [[brackets]] inside sense.
186             sense = WikiTokenizer.toPlainText(sense);
187             //System.out.println("Sense: " + sense);
188           }
189         } else if (functionName.equals("trans-bottom")) {
190           sense = null;
191         } else if (functionName.equals("trans-mid")) {
192         } else if (functionName.equals("trans-see")) {
193           // TODO
194         } else if (functionName.startsWith("picdic")) {
195         } else if (functionName.startsWith("checktrans")) {
196         } else if (functionName.startsWith("ttbc")) {
197           wikiTokenizer.nextLine();
198           // TODO: would be great to handle
199           //TODO: Check this: done = true;
200         } else {
201           System.err.println("Unexpected translation wikifunction: " + wikiTokenizer.token() + ", title=" + title);
202         }
203       } else if (wikiTokenizer.isListItem() && wikiTokenizer.listItemPrefix().startsWith("*")) {
204         final String line = wikiTokenizer.listItemWikiText();
205         // This line could produce an output...
206         
207         // First strip the language and check whether it matches.
208         // And hold onto it for sub-lines.
209         final int colonIndex = line.indexOf(":");
210         if (colonIndex == -1) {
211           continue;
212         }
213         
214         final String lang = line.substring(0, colonIndex);
215         if (!this.langPattern.matcher(lang).find()) {
216           continue;
217         }
218         
219         String rest = line.substring(colonIndex + 1).trim();
220         if (rest.length() > 0) {
221           doTranslationLine(line, title, sense, rest);
222         } else {
223           // TODO: do lines that are like Greek:
224         }
225         
226       } else if (wikiTokenizer.remainderStartsWith("''See''")) {
227         wikiTokenizer.nextLine();
228         System.out.println("Skipping line: " + wikiTokenizer.token());
229       } else if (wikiTokenizer.isWikiLink()) {
230         final String wikiLink = wikiTokenizer.wikiLinkText();
231         if (wikiLink.contains(":") && wikiLink.contains(title)) {
232         } else if (wikiLink.contains("Category:")) {
233         } else  {
234           System.err.println("Unexpected wikiLink: " + wikiTokenizer.token() + ", title=" + title);
235         }
236       } else if (wikiTokenizer.isNewline() || wikiTokenizer.isMarkup() || wikiTokenizer.isComment()) {
237       } else {
238         final String token = wikiTokenizer.token();
239         if (token.equals("----")) { 
240         } else {
241           System.err.println("Unexpected translation token: " + wikiTokenizer.token() + ", title=" + title);
242         }
243       }
244       
245     }
246   }
247   
248   private static <T> T get(final List<T> list, final int index) {
249     return index < list.size() ? list.get(index) : null;
250   }
251   
252   private void doTranslationLine(final String line, final String title, final String sense, final String rest) {
253     // Good chance we'll actually file this one...
254     final PairEntry pairEntry = new PairEntry();
255     final IndexedEntry indexedEntry = new IndexedEntry(pairEntry);
256     
257     final StringBuilder otherText = new StringBuilder();
258     final WikiTokenizer wikiTokenizer = new WikiTokenizer(rest, false);
259     while (wikiTokenizer.nextToken() != null) {
260       
261       if (wikiTokenizer.isPlainText()) {
262         final String plainText = wikiTokenizer.token(); 
263         otherText.append("").append(plainText);
264         otherIndexBuilder.addEntryWithString(indexedEntry, plainText, EntryTypeName.WIKTIONARY_TRANSLATION_OTHER_TEXT);
265         
266       } else if (wikiTokenizer.isWikiLink()) {
267         final String plainText = wikiTokenizer.wikiLinkText(); 
268         otherText.append("").append(plainText);
269         otherIndexBuilder.addEntryWithString(indexedEntry, plainText, EntryTypeName.WIKTIONARY_TRANSLATION_WIKI_TEXT);
270         
271       } else if (wikiTokenizer.isFunction()) {
272         final String functionName = wikiTokenizer.functionName();
273         final List<String> args = wikiTokenizer.functionPositionArgs();
274         final Map<String,String> namedArgs = wikiTokenizer.functionNamedArgs();
275         
276         if (functionName.equals("t") || functionName.equals("t+") || functionName.equals("t-") || functionName.equals("tø") || functionName.equals("apdx-t")) {
277           if (args.size() < 2) {
278             System.err.println("{{t}} with too few args: " + line + ", title=" + title);
279             continue;
280           }
281           final String langCode = get(args, 0);
282           //if (this.langCodePattern.matcher(langCode).matches()) {
283             final String word = get(args, 1);
284             final String gender = get(args, 2);
285             final String transliteration = namedArgs.get("tr");
286             if (otherText.length() > 0) {
287               otherText.append("");
288             }
289             otherText.append(word);
290             otherIndexBuilder.addEntryWithString(indexedEntry, word, EntryTypeName.WIKTIONARY_TITLE_SINGLE, EntryTypeName.WIKTIONARY_TITLE_MULTI);
291             if (gender != null) {
292               otherText.append(String.format(" {%s}", gender));
293             }
294             if (transliteration != null) {
295               otherText.append(String.format(" (tr. %s)", transliteration));
296               otherIndexBuilder.addEntryWithString(indexedEntry, transliteration, EntryTypeName.WIKTIONARY_TRANSLITERATION);
297             }
298           //}
299         } else if (functionName.equals("qualifier")) {
300           String qualifier = args.get(0);
301           if (!namedArgs.isEmpty() || args.size() > 1) {
302             System.err.println("weird qualifier: " + line);
303           }
304           otherText.append("(").append(qualifier).append(")");
305         } else if (encodings.contains(functionName)) {
306           otherText.append("").append(args.get(0));
307           otherIndexBuilder.addEntryWithString(indexedEntry, args.get(0), EntryTypeName.WIKTIONARY_TRANSLATION_OTHER_TEXT);
308         } else if (functionName.equals("m") || functionName.equals("f") || functionName.equals("n") || functionName.equals("p")) {
309           otherText.append("{");
310           otherText.append(functionName);
311           for (int i = 0; i < args.size(); ++i) {
312             otherText.append("|").append(args.get(i));
313           }
314           otherText.append("}");
315         } else if (functionName.equals("g")) {
316           otherText.append("{g}");
317         } else if (functionName.equals("l")) {
318           // encodes text in various langs.
319           // lang is arg 0.
320           otherText.append("").append(args.get(1));
321           otherIndexBuilder.addEntryWithString(indexedEntry, args.get(1), EntryTypeName.WIKTIONARY_TRANSLATION_OTHER_TEXT);
322           // TODO: transliteration
323         } else if (functionName.equals("term")) {
324           // cross-reference to another dictionary
325           otherText.append("").append(args.get(0));
326           otherIndexBuilder.addEntryWithString(indexedEntry, args.get(0), EntryTypeName.WIKTIONARY_TRANSLATION_OTHER_TEXT);
327           // TODO: transliteration
328         } else if (functionName.equals("italbrac") || functionName.equals("gloss")) {
329           // TODO: put this text aside to use it.
330           otherText.append("[").append(args.get(0)).append("]");
331           otherIndexBuilder.addEntryWithString(indexedEntry, args.get(0), EntryTypeName.WIKTIONARY_TRANSLATION_OTHER_TEXT);
332         } else if (functionName.equals("ttbc")) {
333         } else if (functionName.equals("trreq")) {
334         } else if (functionName.equals("not used")) {
335           otherText.append("(not used)");
336         } else if (functionName.equals("t-image")) {
337           // American sign language
338         } else if (args.isEmpty() && namedArgs.isEmpty()) {
339           otherText.append("{UNK. FUNC.: ").append(functionName).append("}");
340         } else {
341           System.err.println("Unexpected t+- wikifunction: " + line + ", title=" + title);
342         }
343         
344       } else if (wikiTokenizer.isNewline()) {
345         assert false;
346       } else if (wikiTokenizer.isComment()) {
347       } else if (wikiTokenizer.isMarkup()) {
348       } else {
349         System.err.println("Bad translation token: " + wikiTokenizer.token());
350       }
351     }
352     if (otherText.length() == 0) {
353       System.err.println("Empty otherText: " + line);
354       return;
355     }
356     
357     StringBuilder englishText = new StringBuilder();
358     
359     englishText.append(title);
360     if (sense != null) {
361       englishText.append(" (").append(sense).append(")");
362       enIndexBuilder.addEntryWithString(indexedEntry, sense, EntryTypeName.WIKTIONARY_TRANSLATION_SENSE, EntryTypeName.WIKTIONARY_TRANSLATION_SENSE);
363     }
364     if (pos != null) {
365       englishText.append(" (").append(pos.toLowerCase()).append(")");
366     }
367     enIndexBuilder.addEntryWithString(indexedEntry, title, EntryTypeName.WIKTIONARY_TITLE_SINGLE, EntryTypeName.WIKTIONARY_TITLE_MULTI);
368     
369     final Pair pair = new Pair(trim(englishText.toString()), trim(otherText.toString()), swap);
370     pairEntry.pairs.add(pair);
371     if (!pairsAdded.add(pair.toString())) {
372       System.err.println("Duplicate pair: " + pair.toString());
373     }
374     if (pair.toString().equals("libero {m} :: free (adjective)")) {
375       System.out.println();
376     }
377
378   }
379   
380   static final Pattern whitespace = Pattern.compile("\\s+");
381
382   static String trim(final String s) {
383     return whitespace.matcher(s).replaceAll(" ").trim();
384   }
385   
386   Set<String> pairsAdded = new LinkedHashSet<String>();
387   
388   // -------------------------------------------------------------------------
389   
390   private void doForeignWord(final String title, final String text) {
391     final WikiTokenizer wikiTokenizer = new WikiTokenizer(text);
392     while (wikiTokenizer.nextToken() != null) {
393       if (wikiTokenizer.isHeading()) {
394         final String headingName = wikiTokenizer.headingWikiText();
395         if (headingName.equals("Translations")) {
396           System.err.println("Translations not in English section: " + title);
397         } else if (headingName.equals("Pronunciation")) {
398           //doPronunciation(wikiLineReader);
399         } else if (partOfSpeechHeader.matcher(headingName).matches()) {
400           doPartOfSpeech(title, headingName, wikiTokenizer.headingDepth(), wikiTokenizer);
401         }
402       } else {
403       }
404     }
405   }
406
407
408   private void doPartOfSpeech(String title, final String posHeading, final int posDepth, WikiTokenizer wikiTokenizer) {
409     System.out.println("***" + title);
410     System.out.println(posHeading);
411     //final StringBuilder foreignBuilder = new StringBuilder();
412     
413     String side = null;
414     Collection<String> forms = Collections.emptyList();
415     
416     int currentHeadingDepth = posDepth;
417     while (wikiTokenizer.nextToken() != null) {
418       if (wikiTokenizer.isHeading()) {
419         currentHeadingDepth = wikiTokenizer.headingDepth();
420         
421         if (currentHeadingDepth <= posDepth) {
422           wikiTokenizer.returnToLineStart();
423           return;
424         }
425       }
426       
427       if (currentHeadingDepth > posDepth) {
428         // TODO
429         continue;
430       }
431       
432       if (wikiTokenizer.isFunction()) {
433         final String name = wikiTokenizer.functionName();
434         final List<String> args = wikiTokenizer.functionPositionArgs();
435         final Map<String,String> namedArgs = wikiTokenizer.functionNamedArgs();
436         // First line is generally a repeat of the title with some extra information.
437         // We need to build up the left side (foreign text, tokens) separately from the
438         // right side (English).  The left-side may get paired with multiple right sides.
439         // The left side should get filed under every form of the word in question (singular, plural).
440         
441         // For verbs, the conjugation comes later on in a deeper section.
442         // Ideally, we'd want to file every English entry with the verb
443         // under every verb form coming from the conjugation.
444         // Ie. under "fa": see: "make :: fare" and "do :: fare"
445         // But then where should we put the conjugation table?
446         // I think just under fare.  But then we need a way to link to the entry (actually the row, since entries doesn't show up!)
447         // for the conjugation table from "fa".
448         // Would like to be able to link to a lang#token.
449         if (name.equals("it-noun")) {
450           assert forms.isEmpty();
451           final String base = get(args, 0);
452           final String gender = get(args, 1);
453           final String singular = base + get(args, 2);
454           final String plural = base + get(args, 3);
455           side = String.format("%s {%s}, %s {pl}", singular, gender, plural, plural);
456           forms = Arrays.asList(singular, plural);
457         } else if (name.equals("it-proper noun")) {
458           // TODO
459         } else if (name.equals("it-adj")) {
460           // TODO
461         } else if (name.startsWith("it-conj")) {
462           if (name.equals("it-conj-are")) {
463             itConjAre(args, namedArgs);
464           } else if (name.equals("it-conj-ere")) {
465           } else if (name.equals("it-conj-ire")) {
466           } else {
467             System.err.println("Unknown conjugation: " + wikiTokenizer.token());
468           }
469           
470         } else {
471           System.err.println("Unknown function: " + wikiTokenizer.token());
472         }
473         
474       } else if (wikiTokenizer.isListItem()) {
475         handleForeignListItem(side != null ? side : title, title, forms, wikiTokenizer);
476
477       } else if (wikiTokenizer.isWikiLink()) {
478
479       } else {
480       }
481       
482     }
483   }
484
485   private void itConjAre(List<String> args, Map<String, String> namedArgs) {
486     final String base = args.get(0);
487     final String aux = args.get(1);
488     
489     putIfMissing(namedArgs, "inf", base + "are");
490     putIfMissing(namedArgs, "aux", aux);
491     putIfMissing(namedArgs, "ger", base + "ando");
492     putIfMissing(namedArgs, "presp", base + "ante");
493     putIfMissing(namedArgs, "pastp", base + "ato");
494     // Present
495     putIfMissing(namedArgs, "pres1s", base + "o");
496     putIfMissing(namedArgs, "pres2s", base + "i");
497     putIfMissing(namedArgs, "pres3s", base + "a");
498     putIfMissing(namedArgs, "pres1p", base + "iamo");
499     putIfMissing(namedArgs, "pres2p", base + "ate");
500     putIfMissing(namedArgs, "pres3p", base + "ano");
501     // Imperfect
502     putIfMissing(namedArgs, "imperf1s", base + "avo");
503     putIfMissing(namedArgs, "imperf2s", base + "avi");
504     putIfMissing(namedArgs, "imperf3s", base + "ava");
505     putIfMissing(namedArgs, "imperf1p", base + "avamo");
506     putIfMissing(namedArgs, "imperf2p", base + "avate");
507     putIfMissing(namedArgs, "imperf3p", base + "avano");
508     // Passato remoto
509     putIfMissing(namedArgs, "prem1s", base + "ai");
510     putIfMissing(namedArgs, "prem2s", base + "asti");
511     putIfMissing(namedArgs, "prem3s", base + "ò");
512     putIfMissing(namedArgs, "prem1p", base + "ammo");
513     putIfMissing(namedArgs, "prem2p", base + "aste");
514     putIfMissing(namedArgs, "prem3p", base + "arono");
515     // Future
516     putIfMissing(namedArgs, "fut1s", base + "erò");
517     putIfMissing(namedArgs, "fut2s", base + "erai");
518     putIfMissing(namedArgs, "fut3s", base + "erà");
519     putIfMissing(namedArgs, "fut1p", base + "eremo");
520     putIfMissing(namedArgs, "fut2p", base + "erete");
521     putIfMissing(namedArgs, "fut3p", base + "eranno");
522     // Conditional
523     putIfMissing(namedArgs, "cond1s", base + "erei");
524     putIfMissing(namedArgs, "cond2s", base + "eresti");
525     putIfMissing(namedArgs, "cond3s", base + "erebbe");
526     putIfMissing(namedArgs, "cond1p", base + "eremmo");
527     putIfMissing(namedArgs, "cond2p", base + "ereste");
528     putIfMissing(namedArgs, "cond3p", base + "erebbero");
529     // Subjunctive / congiuntivo
530     putIfMissing(namedArgs, "sub123s", base + "i");
531     putIfMissing(namedArgs, "sub1p", base + "iamo");
532     putIfMissing(namedArgs, "sub2p", base + "iate");
533     putIfMissing(namedArgs, "sub3p", base + "ino");
534     // Imperfect subjunctive
535     putIfMissing(namedArgs, "impsub12s", base + "assi");
536     putIfMissing(namedArgs, "impsub3s", base + "asse");
537     putIfMissing(namedArgs, "impsub1p", base + "assimo");
538     putIfMissing(namedArgs, "impsub2p", base + "aste");
539     putIfMissing(namedArgs, "impsub3p", base + "assero");
540     // Imperative
541     putIfMissing(namedArgs, "imp2s", base + "a");
542     putIfMissing(namedArgs, "imp3s", base + "i");
543     putIfMissing(namedArgs, "imp1p", base + "iamo");
544     putIfMissing(namedArgs, "imp2p", base + "ate");
545     putIfMissing(namedArgs, "imp3p", base + "ino");
546
547
548     itConj(args, namedArgs);
549   }
550
551
552   private void itConj(List<String> args, Map<String, String> namedArgs) {
553     // TODO Auto-generated method stub
554     
555   }
556
557
558   private static void putIfMissing(final Map<String, String> namedArgs, final String key,
559       final String value) {
560     final String oldValue = namedArgs.get(key);
561     if (oldValue == null || oldValue.length() == 0) {
562       namedArgs.put(key, value);
563     }
564   }
565   
566   // TODO: check how ='' and =| are manifested....
567   // TODO: get this right in -are
568   private static void putOrNullify(final Map<String, String> namedArgs, final String key,
569       final String value) {
570     final String oldValue = namedArgs.get(key);
571     if (oldValue == null/* || oldValue.length() == 0*/) {
572       namedArgs.put(key, value);
573     } else {
574       if (oldValue.equals("''")) {
575         namedArgs.put(key, "");
576       }
577     }
578   }
579
580   final List<String> listPrefixes = new ArrayList<String>(); 
581   final List<String> listLines = new ArrayList<String>(); 
582   
583 static final Pattern UNINDEXED_WIKI_TEXT = Pattern.compile(
584     "(first|second|third)-person (singular|plural)|" +
585     "present tense|" +
586     "imperative"
587     );
588
589   private void handleForeignListItem(final String foreignText, String title, final Collection<String> forms, final WikiTokenizer wikiTokenizer) {
590     
591     final String prefix = wikiTokenizer.listItemPrefix();
592     if (prefix.length() > 1) {
593       System.err.println("Prefix too long: " + wikiTokenizer.token());
594       return;
595     }
596     
597     listPrefixes.clear();
598     listLines.clear();
599     listPrefixes.add(prefix);
600     listLines.add(wikiTokenizer.listItemWikiText());
601     while(wikiTokenizer.nextToken() != null &&
602         wikiTokenizer.isNewline() || 
603         wikiTokenizer.isComment() ||
604         (wikiTokenizer.isListItem() && 
605             wikiTokenizer.listItemPrefix().length() > prefix.length() && 
606             wikiTokenizer.listItemPrefix().startsWith(prefix))) {
607       if (wikiTokenizer.isListItem()) {
608         listPrefixes.add(wikiTokenizer.listItemPrefix());
609         listLines.add(wikiTokenizer.listItemWikiText());
610       }
611     }
612     if (wikiTokenizer.nextToken() != null) {
613       wikiTokenizer.returnToLineStart();
614     }
615     System.out.println("list lines: " + listLines);
616     System.out.println("list prefixes: " + listPrefixes);
617     
618     final PairEntry pairEntry = new PairEntry();
619     final IndexedEntry indexedEntry = new IndexedEntry(pairEntry);
620     
621     final String foreign = trim(title);
622
623     final StringBuilder englishBuilder = new StringBuilder();
624
625     final String mainLine = listLines.get(0);
626     
627     final WikiTokenizer englishTokenizer = new WikiTokenizer(mainLine, false);
628     while (englishTokenizer.nextToken() != null) {
629       // TODO handle form of....
630       if (englishTokenizer.isPlainText()) {
631         englishBuilder.append(englishTokenizer.token());
632         enIndexBuilder.addEntryWithString(indexedEntry, englishTokenizer.token(), EntryTypeName.WIKTIONARY_ENGLISH_DEF);
633       } else if (englishTokenizer.isWikiLink()) {
634         final String text = englishTokenizer.wikiLinkText();
635         final String link = englishTokenizer.wikiLinkDest();
636         if (link != null) {
637           if (link.contains("#English")) {
638             englishBuilder.append(text);
639             enIndexBuilder.addEntryWithString(indexedEntry, text, EntryTypeName.WIKTIONARY_ENGLISH_DEF_WIKI_LINK);
640           } else if (link.contains("#") && this.langPattern.matcher(link).find()) {
641             englishBuilder.append(text);
642             otherIndexBuilder.addEntryWithString(indexedEntry, text, EntryTypeName.WIKTIONARY_ENGLISH_DEF_OTHER_LANG);
643           } else {
644             System.err.println("Special link: " + englishTokenizer.token());
645             // TODO: something here...
646           }
647         } else {
648           // link == null
649           englishBuilder.append(text);
650           if (!UNINDEXED_WIKI_TEXT.matcher(text).find()) {
651             enIndexBuilder.addEntryWithString(indexedEntry, text, EntryTypeName.WIKTIONARY_ENGLISH_DEF_WIKI_LINK);
652           }
653         }
654       } else if (englishTokenizer.isFunction()) {
655         final String name = englishTokenizer.functionName();
656         if (name.contains(" conjugation of ") || 
657             name.contains(" form of ") || 
658             name.contains(" feminine of ") || 
659             name.contains(" plural of ")) {
660           // Ignore these in the index, they're really annoying....
661           englishBuilder.append(englishTokenizer.token());
662         } else {
663           System.err.println("Unexpected function: " + englishTokenizer.token());
664         }
665       } else {
666         if (englishTokenizer.isComment() || englishTokenizer.isMarkup()) {
667         } else {
668           System.err.println("Unexpected definition text: " + englishTokenizer.token());
669         }
670       }
671     }
672     final String english = trim(englishBuilder.toString());
673     if (english.length() > 0) {
674       final Pair pair = new Pair(english, trim(foreignText), this.swap);
675       pairEntry.pairs.add(pair);
676       otherIndexBuilder.addEntryWithString(indexedEntry, title, EntryTypeName.WIKTIONARY_TITLE_SINGLE, EntryTypeName.WIKTIONARY_TITLE_MULTI);
677       for (final String form : forms) {
678         otherIndexBuilder.addEntryWithString(indexedEntry, form, EntryTypeName.WIKTIONARY_FORM_SINGLE, EntryTypeName.WIKTIONARY_FORM_MULTI);
679       }
680     }
681   }
682
683   
684 }