]> gitweb.fperrin.net Git - DictionaryPC.git/blob - src/com/hughes/android/dictionary/parser/wiktionary/EnParser.java
Major en refactoring underway.
[DictionaryPC.git] / src / com / hughes / android / dictionary / parser / wiktionary / EnParser.java
1 // Copyright 2012 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.wiktionary;
16
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.Collection;
20 import java.util.LinkedHashMap;
21 import java.util.LinkedHashSet;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.concurrent.atomic.AtomicInteger;
26 import java.util.regex.Pattern;
27
28 import com.hughes.android.dictionary.engine.EntryTypeName;
29 import com.hughes.android.dictionary.engine.IndexBuilder;
30 import com.hughes.android.dictionary.engine.IndexedEntry;
31 import com.hughes.android.dictionary.engine.PairEntry;
32 import com.hughes.android.dictionary.engine.PairEntry.Pair;
33 import com.hughes.android.dictionary.parser.WikiTokenizer;
34 import com.hughes.util.ListUtil;
35
36 public abstract class EnParser extends AbstractWiktionaryParser {
37
38   // TODO: process {{ttbc}} lines
39   
40   static final Pattern partOfSpeechHeader = Pattern.compile(
41       "Noun|Verb|Adjective|Adverb|Pronoun|Conjunction|Interjection|" +
42       "Preposition|Proper noun|Article|Prepositional phrase|Acronym|" +
43       "Abbreviation|Initialism|Contraction|Prefix|Suffix|Symbol|Letter|" +
44       "Ligature|Idiom|Phrase|\\{\\{acronym\\}\\}|\\{\\{initialism\\}\\}|" +
45       "\\{\\{abbreviation\\}\\}|" +
46       // These are @deprecated:
47       "Noun form|Verb form|Adjective form|Nominal phrase|Noun phrase|" +
48       "Verb phrase|Transitive verb|Intransitive verb|Reflexive verb|" +
49       // These are extras I found:
50       "Determiner|Numeral|Number|Cardinal number|Ordinal number|Proverb|" +
51       "Particle|Interjection|Pronominal adverb" +
52       "Han character|Hanzi|Hanja|Kanji|Katakana character|Syllable");
53   
54   // Might only want to remove "lang" if it's equal to "zh", for example.
55   static final Set<String> USELESS_WIKI_ARGS = new LinkedHashSet<String>(
56       Arrays.asList(
57           "lang",
58           "sc",
59           "sort",
60           "cat",
61           "cat2",
62           "xs",
63           "nodot"));
64
65   static boolean isIgnorableTitle(final String title) {
66     return title.startsWith("Wiktionary:") ||
67         title.startsWith("Template:") ||
68         title.startsWith("Appendix:") ||
69         title.startsWith("Category:") ||
70         title.startsWith("Index:") ||
71         title.startsWith("MediaWiki:") ||
72         title.startsWith("TransWiki:") ||
73         title.startsWith("Citations:") ||
74         title.startsWith("Concordance:") ||
75         title.startsWith("Help:");
76   }
77   
78   final IndexBuilder enIndexBuilder;
79   final IndexBuilder foreignIndexBuilder;
80   final Pattern langPattern;
81   final Pattern langCodePattern;
82   final boolean swap;
83   
84   // State used while parsing.
85   enum State {
86     TRANSLATION_LINE,
87     ENGLISH_DEF_OF_FOREIGN,
88     ENGLISH_EXAMPLE,
89     FOREIGN_EXAMPLE,
90   }
91   State state = null;
92
93   public boolean entryIsFormOfSomething = false;
94   final Collection<String> wordForms = new ArrayList<String>();
95   boolean titleAppended = false;
96
97
98   final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback = new AppendAndIndexWikiCallback<EnParser>(this);
99   {
100     appendAndIndexWikiCallback.functionCallbacks.putAll(FunctionCallbacks.DEFAULT);
101   }
102   
103   EnParser(final IndexBuilder enIndexBuilder, final IndexBuilder otherIndexBuilder, final Pattern langPattern, final Pattern langCodePattern, final boolean swap) {
104     this.enIndexBuilder = enIndexBuilder;
105     this.foreignIndexBuilder = otherIndexBuilder;
106     this.langPattern = langPattern;
107     this.langCodePattern = langCodePattern;
108     this.swap = swap;
109   }
110   
111   // --------------------------------------------------------------------
112
113   static final class EnToTranslationParser extends EnParser {
114
115     EnToTranslationParser(final IndexBuilder enIndexBuilder,
116         final IndexBuilder otherIndexBuilder, final Pattern langPattern,
117         final Pattern langCodePattern, final boolean swap) {
118       super(enIndexBuilder, otherIndexBuilder, langPattern, langCodePattern, swap);
119     }
120
121     @Override
122     void parseSection(String heading, String text) {
123       if (isIgnorableTitle(title)) {
124         return;
125       }
126       heading = heading.replaceAll("=", "").trim(); 
127       if (!heading.contains("English")) {
128         return;
129       }
130
131       String pos = null;
132       int posDepth = -1;
133
134       final WikiTokenizer wikiTokenizer = new WikiTokenizer(text);
135       while (wikiTokenizer.nextToken() != null) {
136         
137         if (wikiTokenizer.isHeading()) {
138           final String headerName = wikiTokenizer.headingWikiText();
139           
140           if (wikiTokenizer.headingDepth() <= posDepth) {
141             pos = null;
142             posDepth = -1;
143           }
144           
145           if (partOfSpeechHeader.matcher(headerName).matches()) {
146             posDepth = wikiTokenizer.headingDepth();
147             pos = wikiTokenizer.headingWikiText();
148             // TODO: if we're inside the POS section, we should handle the first title line...
149             
150           } else if (headerName.equals("Translations")) {
151             if (pos == null) {
152               LOG.info("Translations without POS (but using anyway): " + title);
153             }
154             doTranslations(wikiTokenizer, pos);
155           } else if (headerName.equals("Pronunciation")) {
156             //doPronunciation(wikiLineReader);
157           }
158         } else if (wikiTokenizer.isFunction()) {
159           final String name = wikiTokenizer.functionName();
160           if (name.equals("head") && pos == null) {
161             LOG.warning("{{head}} without POS: " + title);
162           }
163         }
164       }
165     }
166
167     private void doTranslations(final WikiTokenizer wikiTokenizer, final String pos) {
168       if (title.equals("absolutely")) {
169         //System.out.println();
170       }
171       
172       String topLevelLang = null;
173       String sense = null;
174       boolean done = false;
175       while (wikiTokenizer.nextToken() != null) {
176         if (wikiTokenizer.isHeading()) {
177           wikiTokenizer.returnToLineStart();
178           return;
179         }
180         if (done) {
181           continue;
182         }
183         
184         // Check whether we care about this line:
185         
186         if (wikiTokenizer.isFunction()) {
187           final String functionName = wikiTokenizer.functionName();
188           final List<String> positionArgs = wikiTokenizer.functionPositionArgs();
189           
190           if (functionName.equals("trans-top")) {
191             sense = null;
192             if (wikiTokenizer.functionPositionArgs().size() >= 1) {
193               sense = positionArgs.get(0);
194               // TODO: could emphasize words in [[brackets]] inside sense.
195               sense = WikiTokenizer.toPlainText(sense);
196               //LOG.info("Sense: " + sense);
197             }
198           } else if (functionName.equals("trans-bottom")) {
199             sense = null;
200           } else if (functionName.equals("trans-mid")) {
201           } else if (functionName.equals("trans-see")) {
202            incrementCount("WARNING:trans-see");
203           } else if (functionName.startsWith("picdic")) {
204           } else if (functionName.startsWith("checktrans")) {
205             done = true;
206           } else if (functionName.startsWith("ttbc")) {
207             wikiTokenizer.nextLine();
208             // TODO: would be great to handle ttbc
209             // TODO: Check this: done = true;
210           } else {
211             LOG.warning("Unexpected translation wikifunction: " + wikiTokenizer.token() + ", title=" + title);
212           }
213         } else if (wikiTokenizer.isListItem()) {
214           final String line = wikiTokenizer.listItemWikiText();
215           // This line could produce an output...
216           
217 //          if (line.contains("ich hoan dich gear")) {
218 //            //System.out.println();
219 //          }
220           
221           // First strip the language and check whether it matches.
222           // And hold onto it for sub-lines.
223           final int colonIndex = line.indexOf(":");
224           if (colonIndex == -1) {
225             continue;
226           }
227           
228           final String lang = trim(WikiTokenizer.toPlainText(line.substring(0, colonIndex)));
229           incrementCount("tCount:" + lang);
230           final boolean appendLang;
231           if (wikiTokenizer.listItemPrefix().length() == 1) {
232             topLevelLang = lang;
233             final boolean thisFind = langPattern.matcher(lang).find();
234             if (!thisFind) {
235               continue;
236             }
237             appendLang = !langPattern.matcher(lang).matches();
238           } else if (topLevelLang == null) {
239             continue;
240           } else {
241             // Two-level -- the only way we won't append is if this second level matches exactly.
242             if (!langPattern.matcher(lang).matches() && !langPattern.matcher(topLevelLang).find()) {
243               continue;
244             }
245             appendLang = !langPattern.matcher(lang).matches();
246           }
247           
248           String rest = line.substring(colonIndex + 1).trim();
249           if (rest.length() > 0) {
250             doTranslationLine(line, appendLang ? lang : null, pos, sense, rest);
251           }
252           
253         } else if (wikiTokenizer.remainderStartsWith("''See''")) {
254           wikiTokenizer.nextLine();
255           incrementCount("WARNING: ''See''" );
256           LOG.fine("Skipping See line: " + wikiTokenizer.token());
257         } else if (wikiTokenizer.isWikiLink()) {
258           final String wikiLink = wikiTokenizer.wikiLinkText();
259           if (wikiLink.contains(":") && wikiLink.contains(title)) {
260           } else if (wikiLink.contains("Category:")) {
261           } else  {
262             incrementCount("WARNING: Unexpected wikiLink" );
263             LOG.warning("Unexpected wikiLink: " + wikiTokenizer.token() + ", title=" + title);
264           }
265         } else if (wikiTokenizer.isNewline() || wikiTokenizer.isMarkup() || wikiTokenizer.isComment()) {
266         } else {
267           final String token = wikiTokenizer.token();
268           if (token.equals("----")) { 
269           } else {
270             LOG.warning("Unexpected translation token: " + wikiTokenizer.token() + ", title=" + title);
271             incrementCount("WARNING: Unexpected translation token" );
272           }
273         }
274         
275       }
276     }
277     
278     private void doTranslationLine(final String line, final String lang, final String pos, final String sense, final String rest) {
279       state = State.TRANSLATION_LINE;
280       // Good chance we'll actually file this one...
281       final PairEntry pairEntry = new PairEntry(entrySource);
282       final IndexedEntry indexedEntry = new IndexedEntry(pairEntry);
283       
284       final StringBuilder foreignText = new StringBuilder();
285       appendAndIndexWikiCallback.reset(foreignText, indexedEntry);
286       appendAndIndexWikiCallback.dispatch(rest, foreignIndexBuilder, EntryTypeName.WIKTIONARY_TRANSLATION_OTHER_TEXT);
287       
288       if (foreignText.length() == 0) {
289         LOG.warning("Empty foreignText: " + line);
290         incrementCount("WARNING: Empty foreignText" );
291         return;
292       }
293       
294       if (lang != null) {
295         foreignText.insert(0, String.format("(%s) ", lang));
296       }
297       
298       StringBuilder englishText = new StringBuilder();
299       
300       englishText.append(title);
301       if (sense != null) {
302         englishText.append(" (").append(sense).append(")");
303         enIndexBuilder.addEntryWithString(indexedEntry, sense, EntryTypeName.WIKTIONARY_TRANSLATION_SENSE);
304       }
305       if (pos != null) {
306         englishText.append(" (").append(pos.toLowerCase()).append(")");
307       }
308       enIndexBuilder.addEntryWithString(indexedEntry, title, EntryTypeName.WIKTIONARY_TITLE_MULTI);
309       
310       final Pair pair = new Pair(trim(englishText.toString()), trim(foreignText.toString()), swap);
311       pairEntry.pairs.add(pair);
312       if (!pairsAdded.add(pair.toString())) {
313         LOG.warning("Duplicate pair: " + pair.toString());
314         incrementCount("WARNING: Duplicate pair" );
315       }
316     }
317   }  // EnToTranslationParser
318   
319   // -----------------------------------------------------------------------
320   
321   
322   static final class ForeignParser extends EnParser {
323
324     ForeignParser(final IndexBuilder enIndexBuilder,
325         final IndexBuilder otherIndexBuilder, final Pattern langPattern,
326         final Pattern langCodePattern, final boolean swap) {
327       super(enIndexBuilder, otherIndexBuilder, langPattern, langCodePattern, swap);
328     }
329
330     @Override
331     void parseSection(String heading, String text) {
332       if (isIgnorableTitle(title)) {
333         return;
334       }
335       final String lang = heading.replaceAll("=", "").trim();
336       if (!langPattern.matcher(lang).find()){
337         return;
338       }
339       
340       final WikiTokenizer wikiTokenizer = new WikiTokenizer(text);
341       while (wikiTokenizer.nextToken() != null) {
342         if (wikiTokenizer.isHeading()) {
343           final String headingName = wikiTokenizer.headingWikiText();
344           if (headingName.equals("Translations")) {
345             LOG.warning("Translations not in English section: " + title);
346           } else if (headingName.equals("Pronunciation")) {
347             //doPronunciation(wikiLineReader);
348           } else if (partOfSpeechHeader.matcher(headingName).matches()) {
349             doForeignPartOfSpeech(lang, headingName, wikiTokenizer.headingDepth(), wikiTokenizer);
350           }
351         } else {
352           // It's not a heading.
353           // TODO: optimization: skip to next heading.
354         }
355       }
356     }
357     
358     static final class ListSection {
359       final String firstPrefix;
360       final String firstLine;
361       final List<String> nextPrefixes = new ArrayList<String>();
362       final List<String> nextLines = new ArrayList<String>();
363       
364       public ListSection(String firstPrefix, String firstLine) {
365         this.firstPrefix = firstPrefix;
366         this.firstLine = firstLine;
367       }
368
369       @Override
370       public String toString() {
371         return firstPrefix + firstLine + "{ " + nextPrefixes + "}";
372       }
373     }
374
375     int foreignCount = 0;
376     private void doForeignPartOfSpeech(final String lang, String posHeading, final int posDepth, WikiTokenizer wikiTokenizer) {
377       if (++foreignCount % 1000 == 0) {
378         LOG.info("***" + lang + ", " + title + ", pos=" + posHeading + ", foreignCount=" + foreignCount);
379       }
380       if (title.equals("6")) {
381         System.out.println();
382       }
383       
384       final StringBuilder foreignBuilder = new StringBuilder();
385       final List<ListSection> listSections = new ArrayList<ListSection>();
386       
387       appendAndIndexWikiCallback.reset(foreignBuilder, null);
388       this.state = State.ENGLISH_DEF_OF_FOREIGN;  // TODO: this is wrong, need new category....
389       titleAppended = false;
390       wordForms.clear();
391       
392       try {
393       
394       ListSection lastListSection = null;
395       
396       int currentHeadingDepth = posDepth;
397       while (wikiTokenizer.nextToken() != null) {
398         if (wikiTokenizer.isHeading()) {
399           currentHeadingDepth = wikiTokenizer.headingDepth();
400           
401           if (currentHeadingDepth <= posDepth) {
402             wikiTokenizer.returnToLineStart();
403             return;
404           }
405         }
406         
407         if (currentHeadingDepth > posDepth) {
408           // TODO: deal with other neat info sections
409           continue;
410         }
411         
412         if (wikiTokenizer.isFunction()) {
413           final String name = wikiTokenizer.functionName();
414           final List<String> args = wikiTokenizer.functionPositionArgs();
415           final Map<String,String> namedArgs = wikiTokenizer.functionNamedArgs();
416           // First line is generally a repeat of the title with some extra information.
417           // We need to build up the left side (foreign text, tokens) separately from the
418           // right side (English).  The left-side may get paired with multiple right sides.
419           // The left side should get filed under every form of the word in question (singular, plural).
420           
421           // For verbs, the conjugation comes later on in a deeper section.
422           // Ideally, we'd want to file every English entry with the verb
423           // under every verb form coming from the conjugation.
424           // Ie. under "fa": see: "make :: fare" and "do :: fare"
425           // But then where should we put the conjugation table?
426           // 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!)
427           // for the conjugation table from "fa".
428           // Would like to be able to link to a lang#token.
429           
430           appendAndIndexWikiCallback.onFunction(wikiTokenizer, name, args, namedArgs);
431           
432         } else if (wikiTokenizer.isListItem()) {
433           final String prefix = wikiTokenizer.listItemPrefix();
434           if (lastListSection != null && 
435               prefix.startsWith(lastListSection.firstPrefix) && 
436               prefix.length() > lastListSection.firstPrefix.length()) {
437             lastListSection.nextPrefixes.add(prefix);
438             lastListSection.nextLines.add(wikiTokenizer.listItemWikiText());
439           } else {
440             lastListSection = new ListSection(prefix, wikiTokenizer.listItemWikiText());
441             listSections.add(lastListSection);
442           }
443         } else if (lastListSection != null) {
444           // Don't append anything after the lists, because there's crap.
445         } else if (wikiTokenizer.isWikiLink()) {
446           // Unindexed!
447           foreignBuilder.append(wikiTokenizer.wikiLinkText());
448           
449         } else if (wikiTokenizer.isPlainText()) {
450           // Unindexed!
451           foreignBuilder.append(wikiTokenizer.token());
452           
453         } else if (wikiTokenizer.isMarkup() || wikiTokenizer.isNewline() || wikiTokenizer.isComment()) {
454           // Do nothing.
455         } else {
456           LOG.warning("Unexpected token: " + wikiTokenizer.token());
457         }
458       }
459       
460       } finally {
461         // Here's where we exit.
462         // Should we make an entry even if there are no foreign list items?
463         String foreign = foreignBuilder.toString().trim();
464         if (!titleAppended && !foreign.toLowerCase().startsWith(title.toLowerCase())) {
465           foreign = String.format("%s %s", title, foreign);
466         }
467         if (!langPattern.matcher(lang).matches()) {
468           foreign = String.format("(%s) %s", lang, foreign);
469         }
470         for (final ListSection listSection : listSections) {
471           doForeignListSection(foreign, title, wordForms, listSection);
472         }
473       }
474     }
475     
476     
477
478     private void doForeignListSection(final String foreignText, String title, final Collection<String> forms, final ListSection listSection) {
479       state = State.ENGLISH_DEF_OF_FOREIGN;
480       final String prefix = listSection.firstPrefix;
481       if (prefix.length() > 1) {
482         // Could just get looser and say that any prefix longer than first is a sublist.
483         LOG.warning("Prefix too long: " + listSection);
484         return;
485       }
486       
487       final PairEntry pairEntry = new PairEntry(entrySource);
488       final IndexedEntry indexedEntry = new IndexedEntry(pairEntry);
489
490       entryIsFormOfSomething = false;
491       final StringBuilder englishBuilder = new StringBuilder();
492       final String mainLine = listSection.firstLine;
493       appendAndIndexWikiCallback.reset(englishBuilder, indexedEntry);
494       appendAndIndexWikiCallback.dispatch(mainLine, enIndexBuilder, EntryTypeName.WIKTIONARY_ENGLISH_DEF);
495
496       final String english = trim(englishBuilder.toString());
497       if (english.length() > 0) {
498         final Pair pair = new Pair(english, trim(foreignText), this.swap);
499         pairEntry.pairs.add(pair);
500         foreignIndexBuilder.addEntryWithString(indexedEntry, title, entryIsFormOfSomething ? EntryTypeName.WIKTIONARY_IS_FORM_OF_SOMETHING_ELSE : EntryTypeName.WIKTIONARY_TITLE_MULTI);
501         for (final String form : forms) {
502           foreignIndexBuilder.addEntryWithString(indexedEntry, form, EntryTypeName.WIKTIONARY_INFLECTED_FORM_MULTI);
503         }
504       }
505       
506       // Do examples.
507       String lastForeign = null;
508       for (int i = 0; i < listSection.nextPrefixes.size(); ++i) {
509         final String nextPrefix = listSection.nextPrefixes.get(i);
510         final String nextLine = listSection.nextLines.get(i);
511
512         // TODO: This splitting is not sensitive to wiki code.
513         int dash = nextLine.indexOf("&mdash;");
514         int mdashLen = 7;
515         if (dash == -1) {
516           dash = nextLine.indexOf("—");
517           mdashLen = 1;
518         }
519         if (dash == -1) {
520           dash = nextLine.indexOf(" - ");
521           mdashLen = 3;
522         }
523         
524         if ((nextPrefix.equals("#:") || nextPrefix.equals("##:")) && dash != -1) {
525           final String foreignEx = nextLine.substring(0, dash);
526           final String englishEx = nextLine.substring(dash + mdashLen);
527           final Pair pair = new Pair(formatAndIndexExampleString(englishEx, enIndexBuilder, indexedEntry), formatAndIndexExampleString(foreignEx, foreignIndexBuilder, indexedEntry), swap);
528           if (pair.lang1 != "--" && pair.lang1 != "--") {
529             pairEntry.pairs.add(pair);
530           }
531           lastForeign = null;
532         } else if (nextPrefix.equals("#:") || nextPrefix.equals("##:")){
533           final Pair pair = new Pair("--", formatAndIndexExampleString(nextLine, null, indexedEntry), swap);
534           lastForeign = nextLine;
535           if (pair.lang1 != "--" && pair.lang1 != "--") {
536             pairEntry.pairs.add(pair);
537           }
538         } else if (nextPrefix.equals("#::") || nextPrefix.equals("#**")) {
539           if (lastForeign != null && pairEntry.pairs.size() > 0) {
540             pairEntry.pairs.remove(pairEntry.pairs.size() - 1);
541             final Pair pair = new Pair(formatAndIndexExampleString(nextLine, enIndexBuilder, indexedEntry), formatAndIndexExampleString(lastForeign, foreignIndexBuilder, indexedEntry), swap);
542             if (pair.lang1 != "--" || pair.lang2 != "--") {
543               pairEntry.pairs.add(pair);
544             }
545             lastForeign = null;
546           } else {
547             LOG.warning("TODO: English example with no foreign: " + title + ", " + nextLine);
548             final Pair pair = new Pair("--", formatAndIndexExampleString(nextLine, null, indexedEntry), swap);
549             if (pair.lang1 != "--" || pair.lang2 != "--") {
550               pairEntry.pairs.add(pair);
551             }
552           }
553         } else if (nextPrefix.equals("#*")) {
554           // Can't really index these.
555           final Pair pair = new Pair("--", formatAndIndexExampleString(nextLine, null, indexedEntry), swap);
556           lastForeign = nextLine;
557           if (pair.lang1 != "--" || pair.lang2 != "--") {
558             pairEntry.pairs.add(pair);
559           }
560         } else if (nextPrefix.equals("#::*") || nextPrefix.equals("##") || nextPrefix.equals("#*:") || nextPrefix.equals("#:*") || true) {
561           final Pair pair = new Pair("--", formatAndIndexExampleString(nextLine, null, indexedEntry), swap);
562           if (pair.lang1 != "--" || pair.lang2 != "--") {
563             pairEntry.pairs.add(pair);
564           }
565 //        } else {
566 //          assert false;
567         }
568       }
569     }
570     
571     private String formatAndIndexExampleString(final String example, final IndexBuilder indexBuilder, final IndexedEntry indexedEntry) {
572       // TODO:
573 //      if (wikiTokenizer.token().equals("'''")) {
574 //        insideTripleQuotes = !insideTripleQuotes;
575 //      }
576       final StringBuilder builder = new StringBuilder();
577       appendAndIndexWikiCallback.reset(builder, indexedEntry);
578       appendAndIndexWikiCallback.entryTypeName = EntryTypeName.WIKTIONARY_EXAMPLE;
579       appendAndIndexWikiCallback.entryTypeNameSticks = true;
580       try {
581         // TODO: this is a hack needed because we don't safely split on the dash.
582         appendAndIndexWikiCallback.dispatch(example, indexBuilder, EntryTypeName.WIKTIONARY_EXAMPLE);
583       } catch (AssertionError e) {
584         return "--";
585       }
586       final String result = trim(builder.toString());
587       return result.length() > 0 ? result : "--";
588     }
589
590
591     private void itConjAre(List<String> args, Map<String, String> namedArgs) {
592       final String base = args.get(0);
593       final String aux = args.get(1);
594       
595       putIfMissing(namedArgs, "inf", base + "are");
596       putIfMissing(namedArgs, "aux", aux);
597       putIfMissing(namedArgs, "ger", base + "ando");
598       putIfMissing(namedArgs, "presp", base + "ante");
599       putIfMissing(namedArgs, "pastp", base + "ato");
600       // Present
601       putIfMissing(namedArgs, "pres1s", base + "o");
602       putIfMissing(namedArgs, "pres2s", base + "i");
603       putIfMissing(namedArgs, "pres3s", base + "a");
604       putIfMissing(namedArgs, "pres1p", base + "iamo");
605       putIfMissing(namedArgs, "pres2p", base + "ate");
606       putIfMissing(namedArgs, "pres3p", base + "ano");
607       // Imperfect
608       putIfMissing(namedArgs, "imperf1s", base + "avo");
609       putIfMissing(namedArgs, "imperf2s", base + "avi");
610       putIfMissing(namedArgs, "imperf3s", base + "ava");
611       putIfMissing(namedArgs, "imperf1p", base + "avamo");
612       putIfMissing(namedArgs, "imperf2p", base + "avate");
613       putIfMissing(namedArgs, "imperf3p", base + "avano");
614       // Passato remoto
615       putIfMissing(namedArgs, "prem1s", base + "ai");
616       putIfMissing(namedArgs, "prem2s", base + "asti");
617       putIfMissing(namedArgs, "prem3s", base + "ò");
618       putIfMissing(namedArgs, "prem1p", base + "ammo");
619       putIfMissing(namedArgs, "prem2p", base + "aste");
620       putIfMissing(namedArgs, "prem3p", base + "arono");
621       // Future
622       putIfMissing(namedArgs, "fut1s", base + "erò");
623       putIfMissing(namedArgs, "fut2s", base + "erai");
624       putIfMissing(namedArgs, "fut3s", base + "erà");
625       putIfMissing(namedArgs, "fut1p", base + "eremo");
626       putIfMissing(namedArgs, "fut2p", base + "erete");
627       putIfMissing(namedArgs, "fut3p", base + "eranno");
628       // Conditional
629       putIfMissing(namedArgs, "cond1s", base + "erei");
630       putIfMissing(namedArgs, "cond2s", base + "eresti");
631       putIfMissing(namedArgs, "cond3s", base + "erebbe");
632       putIfMissing(namedArgs, "cond1p", base + "eremmo");
633       putIfMissing(namedArgs, "cond2p", base + "ereste");
634       putIfMissing(namedArgs, "cond3p", base + "erebbero");
635       // Subjunctive / congiuntivo
636       putIfMissing(namedArgs, "sub123s", base + "i");
637       putIfMissing(namedArgs, "sub1p", base + "iamo");
638       putIfMissing(namedArgs, "sub2p", base + "iate");
639       putIfMissing(namedArgs, "sub3p", base + "ino");
640       // Imperfect subjunctive
641       putIfMissing(namedArgs, "impsub12s", base + "assi");
642       putIfMissing(namedArgs, "impsub3s", base + "asse");
643       putIfMissing(namedArgs, "impsub1p", base + "assimo");
644       putIfMissing(namedArgs, "impsub2p", base + "aste");
645       putIfMissing(namedArgs, "impsub3p", base + "assero");
646       // Imperative
647       putIfMissing(namedArgs, "imp2s", base + "a");
648       putIfMissing(namedArgs, "imp3s", base + "i");
649       putIfMissing(namedArgs, "imp1p", base + "iamo");
650       putIfMissing(namedArgs, "imp2p", base + "ate");
651       putIfMissing(namedArgs, "imp3p", base + "ino");
652
653
654       itConj(args, namedArgs);
655     }
656
657
658     private void itConj(List<String> args, Map<String, String> namedArgs) {
659       // TODO Auto-generated method stub
660       
661     }
662
663
664     private static void putIfMissing(final Map<String, String> namedArgs, final String key,
665         final String value) {
666       final String oldValue = namedArgs.get(key);
667       if (oldValue == null || oldValue.length() == 0) {
668         namedArgs.put(key, value);
669       }
670     }
671     
672     // TODO: check how ='' and =| are manifested....
673     // TODO: get this right in -are
674     private static void putOrNullify(final Map<String, String> namedArgs, final String key,
675         final String value) {
676       final String oldValue = namedArgs.get(key);
677       if (oldValue == null/* || oldValue.length() == 0*/) {
678         namedArgs.put(key, value);
679       } else {
680         if (oldValue.equals("''")) {
681           namedArgs.put(key, "");
682         }
683       }
684     }
685
686   }  // ForeignParser
687   
688   // -----------------------------------------------------------------------
689   
690   static class FunctionCallbacks {
691   
692   static final Map<String,FunctionCallback<EnParser>> DEFAULT = new LinkedHashMap<String, FunctionCallback<EnParser>>();
693   
694   static {
695     FunctionCallback<EnParser> callback = new TranslationCallback();
696     DEFAULT.put("t", callback);
697     DEFAULT.put("t+", callback);
698     DEFAULT.put("t-", callback);
699     DEFAULT.put("tø", callback);
700     DEFAULT.put("apdx-t", callback);
701     
702     callback = new EncodingCallback();
703     Set<String> encodings = new LinkedHashSet<String>(Arrays.asList(
704         "zh-ts", "zh-tsp",
705         "sd-Arab", "ku-Arab", "Arab", "unicode", "Laoo", "ur-Arab", "Thai", 
706         "fa-Arab", "Khmr", "Cyrl", "IPAchar", "ug-Arab", "ko-inline", 
707         "Jpan", "Kore", "Hebr", "rfscript", "Beng", "Mong", "Knda", "Cyrs",
708         "yue-tsj", "Mlym", "Tfng", "Grek", "yue-yue-j"));
709     for (final String encoding : encodings) {
710       DEFAULT.put(encoding, callback);
711     }
712     
713     callback = new l_term();
714     DEFAULT.put("l", callback);
715     DEFAULT.put("term", callback);
716
717     callback = new Gender();
718     DEFAULT.put("m", callback);
719     DEFAULT.put("f", callback);
720     DEFAULT.put("n", callback);
721     DEFAULT.put("p", callback);
722     DEFAULT.put("g", callback);
723     
724     callback = new AppendArg0();
725
726     callback = new Ignore();
727     DEFAULT.put("trreq", callback);
728     DEFAULT.put("t-image", callback);
729     DEFAULT.put("defn", callback);
730     DEFAULT.put("rfdef", callback);
731     DEFAULT.put("rfdate", callback);
732     DEFAULT.put("rfex", callback);
733     DEFAULT.put("rfquote", callback);
734     DEFAULT.put("attention", callback);
735     DEFAULT.put("zh-attention", callback);
736
737
738     callback = new FormOf();
739     DEFAULT.put("form of", callback);
740     DEFAULT.put("conjugation of", callback);
741     DEFAULT.put("participle of", callback);
742     DEFAULT.put("present participle of", callback);
743     DEFAULT.put("past participle of", callback);
744     DEFAULT.put("feminine past participle of", callback);
745     DEFAULT.put("gerund of", callback);
746     DEFAULT.put("feminine of", callback);
747     DEFAULT.put("plural of", callback);
748     DEFAULT.put("feminine plural of", callback);
749     DEFAULT.put("inflected form of", callback);
750     DEFAULT.put("alternative form of", callback);
751     DEFAULT.put("dated form of", callback);
752     DEFAULT.put("apocopic form of", callback);
753     
754     callback = new InflOrHead();
755     DEFAULT.put("infl", callback);
756     DEFAULT.put("head", callback);
757     
758     callback = new AppendName();
759     DEFAULT.put("...", callback);
760     
761     DEFAULT.put("qualifier", new QualifierCallback());
762     DEFAULT.put("italbrac", new italbrac());
763     DEFAULT.put("gloss", new gloss());
764     DEFAULT.put("not used", new not_used());
765     DEFAULT.put("wikipedia", new wikipedia());
766   }
767   
768   static final NameAndArgs<EnParser> NAME_AND_ARGS = new NameAndArgs<EnParser>();
769
770   // ------------------------------------------------------------------
771
772   static final class TranslationCallback implements FunctionCallback<EnParser> {
773     @Override
774     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
775         final Map<String, String> namedArgs, final EnParser parser,
776         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
777
778       final String transliteration = namedArgs.remove("tr");
779       final String alt = namedArgs.remove("alt");
780       namedArgs.keySet().removeAll(USELESS_WIKI_ARGS);
781       if (args.size() < 2) {
782         LOG.warning("{{t...}} with wrong args: title=" + parser.title);
783         return false;
784       }
785       final String langCode = ListUtil.get(args, 0);
786       if (!appendAndIndexWikiCallback.langCodeToTCount.containsKey(langCode)) {
787         appendAndIndexWikiCallback.langCodeToTCount.put(langCode, new AtomicInteger());
788       }
789       appendAndIndexWikiCallback.langCodeToTCount.get(langCode).incrementAndGet();
790       final String word = ListUtil.get(args, 1);
791       appendAndIndexWikiCallback.dispatch(alt != null ? alt : word, EntryTypeName.WIKTIONARY_TITLE_MULTI);
792
793       // Genders...
794       if (args.size() > 2) {
795         appendAndIndexWikiCallback.builder.append(" {");
796         for (int i = 2; i < args.size(); ++i) {
797           if (i > 2) {
798             appendAndIndexWikiCallback.builder.append("|");
799           }
800           appendAndIndexWikiCallback.builder.append(args.get(i));
801         }
802         appendAndIndexWikiCallback.builder.append("}");
803       }
804
805       if (transliteration != null) {
806         appendAndIndexWikiCallback.builder.append(" (");
807         appendAndIndexWikiCallback.dispatch(transliteration, EntryTypeName.WIKTIONARY_TRANSLITERATION);
808         appendAndIndexWikiCallback.builder.append(")");
809       }
810       
811       if (alt != null) {
812         // If alt wasn't null, we appended alt instead of the actual word
813         // we're filing under..
814         appendAndIndexWikiCallback.builder.append(" (");
815         appendAndIndexWikiCallback.dispatch(word, EntryTypeName.WIKTIONARY_TITLE_MULTI);
816         appendAndIndexWikiCallback.builder.append(")");
817       }
818
819       // Catch-all for anything else...
820       if (!namedArgs.isEmpty()) {
821         appendAndIndexWikiCallback.builder.append(" {");
822         appendNamedArgs(namedArgs, appendAndIndexWikiCallback);
823         appendAndIndexWikiCallback.builder.append("}");
824       }
825       
826       return true;
827     }
828   }
829
830   // ------------------------------------------------------------------
831   
832   static final class QualifierCallback implements FunctionCallback<EnParser> {
833     @Override
834     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
835         final Map<String, String> namedArgs,
836         final EnParser parser,
837         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
838       if (args.size() != 1 || !namedArgs.isEmpty()) {
839         LOG.warning("weird qualifier: ");
840         return false;
841       }
842       String qualifier = args.get(0);
843       appendAndIndexWikiCallback.builder.append("(");
844       appendAndIndexWikiCallback.dispatch(qualifier, null);
845       appendAndIndexWikiCallback.builder.append(")");
846       return true;
847     }
848   }
849
850   // ------------------------------------------------------------------
851   
852   static final class EncodingCallback implements FunctionCallback<EnParser> {
853     @Override
854     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
855         final Map<String, String> namedArgs,
856         final EnParser parser,
857         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
858       if (!namedArgs.isEmpty()) {
859         LOG.warning("weird encoding: " + wikiTokenizer.token());
860       }
861       if (args.size() == 0) {
862         // Things like "{{Jpan}}" exist.
863         return true;
864       }
865       
866       for (int i = 0; i < args.size(); ++i) {
867         if (i > 0) {
868           appendAndIndexWikiCallback.builder.append(", ");
869         }
870         final String arg = args.get(i);
871 //        if (arg.equals(parser.title)) {
872 //          parser.titleAppended = true;
873 //        }
874         appendAndIndexWikiCallback.dispatch(arg, appendAndIndexWikiCallback.entryTypeName);
875       }
876       
877       return true;
878     }
879   }
880
881   // ------------------------------------------------------------------
882   
883   static final class Gender implements FunctionCallback<EnParser> {
884     @Override
885     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
886         final Map<String, String> namedArgs,
887         final EnParser parser,
888         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
889       if (!namedArgs.isEmpty()) {
890         return false;
891       }
892       appendAndIndexWikiCallback.builder.append("{");
893       appendAndIndexWikiCallback.builder.append(name);
894       for (int i = 0; i < args.size(); ++i) {
895         appendAndIndexWikiCallback.builder.append("|").append(args.get(i));
896       }
897       appendAndIndexWikiCallback.builder.append("}");
898       return true;
899     }
900   }
901
902   // ------------------------------------------------------------------
903   
904   static final class l_term implements FunctionCallback<EnParser> {
905     @Override
906     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
907         final Map<String, String> namedArgs,
908         final EnParser parser,
909         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
910       
911       // for {{l}}, lang is arg 0, but not for {{term}}
912       if (name.equals("term")) {
913         args.add(0, "");
914       }
915       
916       final EntryTypeName entryTypeName;
917       switch (parser.state) {
918       case TRANSLATION_LINE: entryTypeName = EntryTypeName.WIKTIONARY_TRANSLATION_OTHER_TEXT; break;
919       case ENGLISH_DEF_OF_FOREIGN: entryTypeName = EntryTypeName.WIKTIONARY_ENGLISH_DEF_WIKI_LINK; break;
920       default: throw new IllegalStateException("Invalid enum value: " + parser.state);
921       }
922       
923       final String langCode = args.get(0);
924       final IndexBuilder indexBuilder;
925       if ("".equals(langCode)) {
926         indexBuilder = parser.foreignIndexBuilder;
927       } else if ("en".equals(langCode)) {
928         indexBuilder = parser.enIndexBuilder;
929       } else {
930         indexBuilder = parser.foreignIndexBuilder;
931       }
932       
933       String displayText = ListUtil.get(args, 2, "");
934       if (displayText.equals("")) {
935         displayText = ListUtil.get(args, 1, null);
936       }
937       
938       if (displayText != null) {
939         appendAndIndexWikiCallback.dispatch(displayText, indexBuilder, entryTypeName);
940       } else {
941         LOG.warning("no display text: " + wikiTokenizer.token());
942       }
943       
944       final String tr = namedArgs.remove("tr");
945       if (tr != null) {
946         appendAndIndexWikiCallback.builder.append(" (");
947         appendAndIndexWikiCallback.dispatch(tr, indexBuilder, EntryTypeName.WIKTIONARY_TRANSLITERATION);
948         appendAndIndexWikiCallback.builder.append(")");
949       }
950       
951       final String gloss = ListUtil.get(args, 3, "");
952       if (!gloss.equals("")) {
953         appendAndIndexWikiCallback.builder.append(" (");
954         appendAndIndexWikiCallback.dispatch(gloss, parser.enIndexBuilder, EntryTypeName.WIKTIONARY_ENGLISH_DEF);
955         appendAndIndexWikiCallback.builder.append(")");
956       }
957       
958       namedArgs.keySet().removeAll(USELESS_WIKI_ARGS);
959       if (!namedArgs.isEmpty()) {
960         appendAndIndexWikiCallback.builder.append(" {").append(name);
961         appendNamedArgs(namedArgs, appendAndIndexWikiCallback);
962         appendAndIndexWikiCallback.builder.append("}");
963       }
964
965       return true;
966     }
967   }
968
969   // ------------------------------------------------------------------
970   
971   static final class AppendArg0 implements FunctionCallback<EnParser> {
972     @Override
973     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
974         final Map<String, String> namedArgs,
975         final EnParser parser,
976         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
977       if (args.size() != 1 || !namedArgs.isEmpty()) {
978         return false;
979       }
980       appendAndIndexWikiCallback.dispatch(args.get(0), EntryTypeName.WIKTIONARY_TRANSLATION_OTHER_TEXT);
981       // TODO: transliteration
982       return true;
983     }
984   }
985
986   // ------------------------------------------------------------------
987   
988   static final class italbrac implements FunctionCallback<EnParser> {
989     @Override
990     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
991         final Map<String, String> namedArgs,
992         final EnParser parser,
993         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
994       if (args.size() != 1 || !namedArgs.isEmpty()) {
995         return false;
996       }
997       appendAndIndexWikiCallback.builder.append("(");
998       appendAndIndexWikiCallback.dispatch(args.get(0), EntryTypeName.WIKTIONARY_TRANSLATION_OTHER_TEXT);
999       appendAndIndexWikiCallback.builder.append(")");
1000       return true;
1001     }
1002   }
1003
1004   // ------------------------------------------------------------------
1005   
1006   static final class gloss implements FunctionCallback<EnParser> {
1007     @Override
1008     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
1009         final Map<String, String> namedArgs,
1010         final EnParser parser,
1011         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
1012       if (args.size() != 1 || !namedArgs.isEmpty()) {
1013         return false;
1014       }
1015       appendAndIndexWikiCallback.builder.append("(");
1016       appendAndIndexWikiCallback.dispatch(args.get(0), EntryTypeName.WIKTIONARY_TRANSLATION_OTHER_TEXT);
1017       appendAndIndexWikiCallback.builder.append(")");
1018       return true;
1019     }
1020   }
1021   
1022   // ------------------------------------------------------------------
1023   
1024   static final class Ignore implements FunctionCallback<EnParser> {
1025     @Override
1026     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
1027         final Map<String, String> namedArgs,
1028         final EnParser parser,
1029         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
1030       return true;
1031     }
1032   }
1033
1034   // ------------------------------------------------------------------
1035   
1036   static final class not_used implements FunctionCallback<EnParser> {
1037     @Override
1038     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
1039         final Map<String, String> namedArgs,
1040         final EnParser parser,
1041         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
1042       appendAndIndexWikiCallback.builder.append("(not used)");
1043       return true;
1044     }
1045   }
1046
1047
1048   // ------------------------------------------------------------------
1049   
1050   static final class AppendName implements FunctionCallback<EnParser> {
1051     @Override
1052     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
1053         final Map<String, String> namedArgs,
1054         final EnParser parser,
1055         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
1056       if (!args.isEmpty() || !namedArgs.isEmpty()) {
1057         return false;
1058       }
1059       appendAndIndexWikiCallback.builder.append(name);
1060       return true;
1061     }
1062   }
1063
1064   // --------------------------------------------------------------------
1065   // --------------------------------------------------------------------
1066   
1067
1068   static final class FormOf implements FunctionCallback<EnParser> {
1069     @Override
1070     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
1071         final Map<String, String> namedArgs,
1072         final EnParser parser,
1073         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
1074       parser.entryIsFormOfSomething = true;
1075       String formName = name;
1076       if (name.equals("form of")) {
1077         formName = ListUtil.remove(args, 0, null);
1078       }
1079       if (formName == null) {
1080         LOG.warning("Missing form name: " + parser.title);
1081         formName = "form of";
1082       }
1083       String baseForm = ListUtil.get(args, 1, "");
1084       if ("".equals(baseForm)) {
1085         baseForm = ListUtil.get(args, 0, null);
1086         ListUtil.remove(args, 1, "");
1087       } else {
1088         ListUtil.remove(args, 0, null);
1089       }
1090       namedArgs.keySet().removeAll(USELESS_WIKI_ARGS);
1091       
1092       appendAndIndexWikiCallback.builder.append("{");
1093       NAME_AND_ARGS.onWikiFunction(wikiTokenizer, formName, args, namedArgs, parser, appendAndIndexWikiCallback);
1094       appendAndIndexWikiCallback.builder.append("}");
1095       if (baseForm != null && appendAndIndexWikiCallback.indexedEntry != null) {
1096         parser.foreignIndexBuilder.addEntryWithString(appendAndIndexWikiCallback.indexedEntry, baseForm, EntryTypeName.WIKTIONARY_BASE_FORM_MULTI);
1097       } else {
1098         // null baseForm happens in Danish.
1099         LOG.warning("Null baseform: " + parser.title);
1100       }
1101       return true;
1102     }
1103   }
1104   
1105   static final FormOf FORM_OF = new FormOf();
1106   
1107
1108   // --------------------------------------------------------------------
1109   // --------------------------------------------------------------------
1110   
1111   static final class wikipedia implements FunctionCallback<EnParser> {
1112     @Override
1113     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
1114         final Map<String, String> namedArgs,
1115         final EnParser parser,
1116         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
1117       namedArgs.remove("lang");
1118       if (args.size() > 1 || !namedArgs.isEmpty()) {
1119         // Unindexed!
1120         return false;
1121       } else if (args.size() == 1) {
1122         return false;
1123       } else {
1124         return true;
1125       }
1126     }
1127   }
1128
1129   static final class InflOrHead implements FunctionCallback<EnParser> {
1130     @Override
1131     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
1132         final Map<String, String> namedArgs,
1133         final EnParser parser,
1134         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
1135       // See: http://en.wiktionary.org/wiki/Template:infl
1136       final String langCode = ListUtil.get(args, 0);
1137       String head = namedArgs.remove("head");
1138       if (head == null) {
1139         head = namedArgs.remove("title"); // Bug
1140       }
1141       if (head == null) {
1142         head = parser.title;
1143       }
1144       parser.titleAppended = true;
1145       
1146       namedArgs.keySet().removeAll(USELESS_WIKI_ARGS);
1147
1148       final String tr = namedArgs.remove("tr");
1149       String g = namedArgs.remove("g");
1150       if (g == null) {
1151         g = namedArgs.remove("gender");
1152       }
1153       final String g2 = namedArgs.remove("g2");
1154       final String g3 = namedArgs.remove("g3");
1155
1156       appendAndIndexWikiCallback.dispatch(head, EntryTypeName.WIKTIONARY_TITLE_MULTI);
1157
1158       if (g != null) {
1159         appendAndIndexWikiCallback.builder.append(" {").append(g);
1160         if (g2 != null) {
1161           appendAndIndexWikiCallback.builder.append("|").append(g2);
1162         }
1163         if (g3 != null) {
1164           appendAndIndexWikiCallback.builder.append("|").append(g3);
1165         }
1166         appendAndIndexWikiCallback.builder.append("}");
1167       }
1168
1169       if (tr != null) {
1170         appendAndIndexWikiCallback.builder.append(" (");
1171         appendAndIndexWikiCallback.dispatch(tr, EntryTypeName.WIKTIONARY_TITLE_MULTI);
1172         appendAndIndexWikiCallback.builder.append(")");
1173         parser.wordForms.add(tr);
1174       }
1175
1176       final String pos = ListUtil.get(args, 1);
1177       if (pos != null) {
1178         appendAndIndexWikiCallback.builder.append(" (").append(pos).append(")");
1179       }
1180       for (int i = 2; i < args.size(); i += 2) {
1181         final String inflName = ListUtil.get(args, i);
1182         final String inflValue = ListUtil.get(args, i + 1);
1183         appendAndIndexWikiCallback.builder.append(", ");
1184         appendAndIndexWikiCallback.dispatch(inflName, null, null);
1185         if (inflValue != null && inflValue.length() > 0) {
1186           appendAndIndexWikiCallback.builder.append(": ");
1187           appendAndIndexWikiCallback.dispatch(inflValue, null, null);
1188           parser.wordForms.add(inflValue);
1189         }
1190       }
1191       for (final String key : namedArgs.keySet()) {
1192         final String value = WikiTokenizer.toPlainText(namedArgs.get(key));
1193         appendAndIndexWikiCallback.builder.append(" ");
1194         appendAndIndexWikiCallback.dispatch(key, null, null);
1195         appendAndIndexWikiCallback.builder.append("=");
1196         appendAndIndexWikiCallback.dispatch(value, null, null);
1197         parser.wordForms.add(value);
1198       }
1199       return true;
1200     }
1201   }
1202   
1203
1204   static {
1205     DEFAULT.put("it-noun", new it_noun());
1206   } 
1207   static final class it_noun implements FunctionCallback<EnParser> {
1208     @Override
1209     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
1210         final Map<String, String> namedArgs,
1211         final EnParser parser,
1212         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
1213       parser.titleAppended = true;
1214       final String base = ListUtil.get(args, 0);
1215       final String gender = ListUtil.get(args, 1);
1216       final String singular = base + ListUtil.get(args, 2, null);
1217       final String plural = base + ListUtil.get(args, 3, null);
1218       appendAndIndexWikiCallback.builder.append(" ");
1219       appendAndIndexWikiCallback.dispatch(singular, null, null);
1220       appendAndIndexWikiCallback.builder.append(" {").append(gender).append("}, ");
1221       appendAndIndexWikiCallback.dispatch(plural, null, null);
1222       appendAndIndexWikiCallback.builder.append(" {pl}");
1223       parser.wordForms.add(singular);
1224       parser.wordForms.add(plural);
1225       if (!namedArgs.isEmpty() || args.size() > 4) {
1226         LOG.warning("Invalid it-noun: " + wikiTokenizer.token());
1227       }
1228       return true;
1229     }
1230   }
1231
1232   static {
1233     DEFAULT.put("it-proper noun", new it_proper_noun());
1234   } 
1235   static final class it_proper_noun implements FunctionCallback<EnParser> {
1236     @Override
1237     public boolean onWikiFunction(final WikiTokenizer wikiTokenizer, final String name, final List<String> args,
1238         final Map<String, String> namedArgs,
1239         final EnParser parser,
1240         final AppendAndIndexWikiCallback<EnParser> appendAndIndexWikiCallback) {
1241       return false;
1242     }
1243   }
1244
1245   }
1246
1247
1248 }