]> 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 package com.hughes.android.dictionary.parser;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.LinkedHashSet;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Set;
11 import java.util.regex.Pattern;
12
13 import javax.xml.parsers.ParserConfigurationException;
14 import javax.xml.parsers.SAXParser;
15 import javax.xml.parsers.SAXParserFactory;
16
17 import org.xml.sax.Attributes;
18 import org.xml.sax.SAXException;
19
20 import com.hughes.android.dictionary.engine.DictionaryBuilder;
21 import com.hughes.android.dictionary.engine.IndexBuilder;
22 import com.hughes.android.dictionary.parser.WikiWord.FormOf;
23 import com.hughes.android.dictionary.parser.WikiWord.Translation;
24 import com.hughes.util.ListUtil;
25 import com.hughes.util.StringUtil;
26 import com.sun.tools.internal.ws.wsdl.document.jaxws.Exception;
27
28 public class EnWiktionaryXmlParser extends org.xml.sax.helpers.DefaultHandler implements WikiCallback {
29   
30   static final Pattern partOfSpeechHeader = Pattern.compile(
31       "Noun|Verb|Adjective|Adverb|Pronoun|Conjunction|Interjection|" +
32       "Preposition|Proper noun|Article|Prepositional phrase|Acronym|" +
33       "Abbreviation|Initialism|Contraction|Prefix|Suffix|Symbol|Letter|" +
34       "Ligature|Idiom|Phrase|" +
35       // These are @deprecated:
36       "Noun form|Verb form|Adjective form|Nominal phrase|Noun phrase|" +
37       "Verb phrase|Transitive verb|Intransitive verb|Reflexive verb|" +
38       // These are extras I found:
39       "Determiner|Numeral|Number|Cardinal number|Ordinal number|Proverb|" +
40       "Particle|Interjection|Pronominal adverb" +
41       "Han character|Hanzi|Hanja|Kanji|Katakana character|Syllable");
42
43   static final Pattern wikiMarkup =  Pattern.compile("\\[\\[|\\]\\]|''+");
44
45   final DictionaryBuilder dictBuilder;
46   
47   final IndexBuilder[] indexBuilders;
48   final Pattern[] langPatterns;
49   final int enIndexBuilder;
50
51   StringBuilder titleBuilder;
52   StringBuilder textBuilder;
53   StringBuilder currentBuilder = null;
54   
55   static void assertTrue(final boolean condition) {
56     assertTrue(condition, "");
57   }
58
59   static void assertTrue(final boolean condition, final String message) {
60     if (!condition) {
61       System.err.println("Assertion failed, message: " + message);
62       new RuntimeException().printStackTrace(System.err);
63     }
64   }
65
66   public EnWiktionaryXmlParser(final DictionaryBuilder dictBuilder, final Pattern[] langPatterns, final int enIndexBuilder) {
67     assertTrue(langPatterns.length == 2);
68     this.dictBuilder = dictBuilder;
69     this.indexBuilders = dictBuilder.indexBuilders.toArray(new IndexBuilder[0]);
70     this.langPatterns = langPatterns;
71     this.enIndexBuilder = enIndexBuilder;
72   }
73
74   @Override
75   public void startElement(String uri, String localName, String qName,
76       Attributes attributes) {
77     currentBuilder = null;
78     if ("page".equals(qName)) {
79       titleBuilder = new StringBuilder();
80       
81       // Start with "\n" to better match certain strings.
82       textBuilder = new StringBuilder("\n");
83     } else if ("title".equals(qName)) {
84       currentBuilder = titleBuilder;
85     } else if ("text".equals(qName)) {
86       currentBuilder = textBuilder;
87     }
88   }
89
90   @Override
91   public void characters(char[] ch, int start, int length) throws SAXException {
92     if (currentBuilder != null) {
93       currentBuilder.append(ch, start, length);
94     }
95   }
96
97   @Override
98   public void endElement(String uri, String localName, String qName)
99       throws SAXException {
100     currentBuilder = null;
101     if ("page".equals(qName)) {
102       endPage();
103     }
104   }
105   
106
107   public void parse(final File file) throws ParserConfigurationException,
108       SAXException, IOException {
109     final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
110     parser.parse(file, this);
111   }
112   
113   int pageCount = 0;
114   private void endPage() {
115     title = titleBuilder.toString();
116     ++pageCount;
117     if (pageCount % 1000 == 0) {
118       System.out.println("pageCount=" + pageCount);
119     }
120     if (title.startsWith("Wiktionary:") ||
121         title.startsWith("Template:") ||
122         title.startsWith("Appendix:") ||
123         title.startsWith("Category:") ||
124         title.startsWith("Index:") ||
125         title.startsWith("MediaWiki:") ||
126         title.startsWith("TransWiki:") ||
127         title.startsWith("Citations:") ||
128         title.startsWith("Concordance:") ||
129         title.startsWith("Help:")) {
130       return;
131     }
132     currentDepth = 0;
133     words.clear();
134     currentHeading = null;
135     insidePartOfSpeech = false;
136 //    System.err.println("Working on page: " + title);
137     try {
138       WikiParser.parse(textBuilder.toString(), this);
139     } catch (Throwable e) {
140       System.err.println("Failure on page: " + title);
141       e.printStackTrace(System.err); 
142     }
143
144    for (final WikiWord word : words) {
145      word.wikiWordToQuickDic(dictBuilder, enIndexBuilder);
146    }  // WikiWord
147    
148   }  // endPage()
149
150
151   // ------------------------------------------------------------------------
152   // ------------------------------------------------------------------------
153   // ------------------------------------------------------------------------
154   // ------------------------------------------------------------------------
155
156   /**
157    * Two things can happen:
158    * 
159    * We can be in a ==German== section.  There we will see English definitions.
160    * Each POS should get its own QuickDic entry.  Pretty much everything goes
161    * in.
162    * 
163    * Or we can be in an ==English== section with English definitions
164    * and maybe see translations for languages we care about.
165    * 
166    * In either case, we need to differentiate the subsections (Noun, Verb, etc.)
167    * into separate QuickDic entries, but that's tricky--how do we know when we
168    * found a subsection?  Just ignore anything containing pronunciation and
169    * etymology?
170    * 
171    * How do we decide when to seal the deal on an entry?
172    * 
173    * Would be nice if the parser told us about leaving sections....
174    * 
175    * 
176    */
177
178   String title;
179   String currentHeading;
180   int currentDepth;
181   final List<WikiWord> words = new ArrayList<WikiWord>();
182   WikiWord currentWord;
183   WikiWord.PartOfSpeech currentPartOfSpeech;
184   WikiWord.TranslationSense currentTranslationSense;
185   boolean insidePartOfSpeech;
186   
187   StringBuilder wikiBuilder = null;
188   
189   @Override
190   public void onWikiLink(String[] args) {
191     if (wikiBuilder == null) {
192       return;
193     }
194     wikiBuilder.append(args[args.length - 1]);
195   }
196   
197   // ttbc: translations to be checked.
198   static final Set<String> useRemainingArgTemplates = new LinkedHashSet<String>(Arrays.asList(
199       "Arab", "Cyrl", "fa-Arab", "italbrac", "Khmr", "ku-Arab", "IPAchar", "Laoo", 
200       "sd-Arab", "Thai", "ttbc", "unicode", "ur-Arab", "yue-yue-j", "zh-ts", 
201       "zh-tsp", "zh-zh-p", "ug-Arab", "ko-inline", "Jpan", "Kore", "rfscript", "Latinx"));
202   static final Set<String> ignoreTemplates = new LinkedHashSet<String>(Arrays.asList("audio", "rhymes", "hyphenation", "homophones", "wikipedia", "rel-top", "rel-bottom", "sense", "wikisource1911Enc", "g"));
203   static final Set<String> grammarTemplates = new LinkedHashSet<String>(Arrays.asList("impf", "pf", "pf.", "indeclinable"));
204   static final Set<String> passThroughTemplates = new LinkedHashSet<String>(Arrays.asList("zzzzzzzzzzzzzzz"));
205
206   @Override
207   public void onTemplate(final List<String> positionalArgs, final Map<String,String> namedArgs) {
208     if (positionalArgs.isEmpty()) {
209       // This happens very rarely with special templates.
210       return;
211     }
212     final String name = positionalArgs.get(0);
213     
214     namedArgs.remove("lang");
215     namedArgs.remove("nocat");
216     namedArgs.remove("nocap");
217     namedArgs.remove("sc");
218
219     // Pronunciation
220     if (currentWord != null) {
221       if (name.equals("a")) {
222         // accent tag
223         currentWord.currentPronunciation = new StringBuilder();
224         currentWord.accentToPronunciation.put(positionalArgs.get(1), currentWord.currentPronunciation);
225         return;
226       }
227       
228       if (name.equals("IPA") || name.equals("SAMPA") || name.equals("X-SAMPA") || name.equals("enPR")) {
229         namedArgs.remove("lang");
230         for (int i = 0; i < 100 && !namedArgs.isEmpty(); ++i) {
231           final String pron = namedArgs.remove("" + i);
232           if (pron != null) {
233             positionalArgs.add(pron);
234           } else {
235             if (i > 10) {
236               break;
237             }
238           }
239         }
240         if (!(positionalArgs.size() >= 2 && namedArgs.isEmpty())) {
241           System.err.println("Invalid pronunciation: " + positionalArgs.toString() + namedArgs.toString());
242         }
243         if (currentWord.currentPronunciation == null) {
244           currentWord.currentPronunciation = new StringBuilder();
245           currentWord.accentToPronunciation.put("", currentWord.currentPronunciation);
246         }
247         if (currentWord.currentPronunciation.length() > 0) {
248           currentWord.currentPronunciation.append("; ");
249         }
250         for (int i = 1; i < positionalArgs.size(); ++i) {
251           if (i > 1) {
252             currentWord.currentPronunciation.append(",");
253           }
254           final String pron = wikiMarkup.matcher(positionalArgs.get(1)).replaceAll("");
255           currentWord.currentPronunciation.append(pron).append("");
256         }
257         currentWord.currentPronunciation.append(" (").append(name).append(")");
258         return;
259       }
260       
261       if (name.equals("qualifier")) {
262         //assertTrue(positionalArgs.size() == 2 && namedArgs.isEmpty() : positionalArgs.toString() + namedArgs.toString());
263         if (wikiBuilder == null) {
264           return;
265         }
266         wikiBuilder.append(" (").append(positionalArgs.get(1)).append(")");
267         return;
268       }
269       
270       if (name.equals("...")) {
271         // Skipping any elided text for brevity.
272         wikiBuilder.append("...");
273         return;
274       }
275       
276       if (passThroughTemplates.contains(name)) {
277         assertTrue(positionalArgs.size() == 1 && namedArgs.isEmpty(), positionalArgs.toString() + namedArgs);
278         wikiBuilder.append(name);
279         return;
280       }
281       
282       if (ignoreTemplates.contains(name)) {
283         return;
284       }
285       
286       if ("Pronunciation".equals(currentHeading)) {
287         System.err.println("Unhandled pronunciation template: " + positionalArgs + namedArgs);
288         return;
289       }
290     }  // Pronunciation
291     
292     // Part of speech
293     if (insidePartOfSpeech) {
294       
295       // form of
296       if (name.equals("form of")) {
297         namedArgs.remove("sc");
298         if (positionalArgs.size() < 3 || positionalArgs.size() > 4) {
299           System.err.println("Invalid form of.");
300         }
301         final String token = positionalArgs.get(positionalArgs.size() == 3 ? 2 : 3);
302         final String grammarForm = WikiParser.simpleParse(positionalArgs.get(1));
303         currentPartOfSpeech.formOfs.add(new FormOf(grammarForm, token));
304         return;
305       }
306       
307       // The fallback plan: append the template!
308       if (wikiBuilder != null) {
309         wikiBuilder.append("{");
310         boolean first = true;
311         for (final String arg : positionalArgs) {
312           if (!first) {
313             wikiBuilder.append(", ");
314           }
315           first = false;
316           wikiBuilder.append(arg);
317         }
318         // This one isn't so useful.
319         for (final Map.Entry<String, String> entry : namedArgs.entrySet()) {
320           if (!first) {
321             wikiBuilder.append(", ");
322           }
323           first = false;
324           wikiBuilder.append(entry.getKey()).append("=").append(entry.getValue());
325         }
326         wikiBuilder.append("}");
327       }
328       
329       //System.err.println("Unhandled part of speech template: " + positionalArgs + namedArgs);
330       return;
331     }  // Part of speech
332
333     
334     // Translations
335     if (name.equals("trans-top")) {
336       assertTrue(positionalArgs.size() >= 1 && namedArgs.isEmpty(), positionalArgs.toString() + namedArgs + title);
337       
338       if (currentPartOfSpeech == null) {
339         assertTrue(currentWord != null && !currentWord.partsOfSpeech.isEmpty(),  title); 
340         System.err.println("Assuming last part of speech for non-nested translation section: " + title);
341         currentPartOfSpeech = ListUtil.getLast(currentWord.partsOfSpeech);
342       }
343       
344       currentTranslationSense = new WikiWord.TranslationSense();
345       currentPartOfSpeech.translationSenses.add(currentTranslationSense);
346       if (positionalArgs.size() > 1) {
347         currentTranslationSense.sense = positionalArgs.get(1);
348       }
349       return;
350     }  // Translations
351
352     if (wikiBuilder == null) {
353       return;
354     }    
355     if (name.equals("m") || name.equals("f") || name.equals("n") || name.equals("c")) {
356       assertTrue(positionalArgs.size() >= 1 && namedArgs.isEmpty(), positionalArgs.toString() + namedArgs.toString());
357       wikiBuilder.append("{");
358       for (int i = 1; i < positionalArgs.size(); ++i) {
359         wikiBuilder.append(i > 1 ? "," : "");
360         wikiBuilder.append(positionalArgs.get(i));
361       }
362       wikiBuilder.append(name).append("}");
363       
364     } else  if (name.equals("p")) {
365       assertTrue(positionalArgs.size() == 1 && namedArgs.isEmpty());
366       wikiBuilder.append("pl.");
367
368     } else  if (name.equals("s")) {
369       assertTrue(positionalArgs.size() == 1 && namedArgs.isEmpty() || title.equals("dobra"), title);
370       wikiBuilder.append("sg.");
371       
372     } else  if (grammarTemplates.contains(name)) {
373       assert positionalArgs.size() == 1 && namedArgs.isEmpty() : positionalArgs.toString() + namedArgs;
374       wikiBuilder.append(name).append(".");
375
376     } else  if (name.equals("l")) {
377       // This template is designed to generate a link to a specific language-section on the target page.
378       wikiBuilder.append(positionalArgs.size() >= 4 ? positionalArgs.get(3) : positionalArgs.get(2));
379       
380     } else if (name.equals("t") || name.equals("t+") || name.equals("t-") || name.equals("tø")) {
381       if (positionalArgs.size() > 2) {
382         wikiBuilder.append(positionalArgs.get(2));
383       }
384       for (int i = 3; i < positionalArgs.size(); ++i) {
385         wikiBuilder.append(i == 3 ? " {" : ",");
386         wikiBuilder.append(positionalArgs.get(i));
387         wikiBuilder.append(i == positionalArgs.size() - 1 ? "}" : "");
388       }
389       final String transliteration = namedArgs.remove("tr");
390       if (transliteration != null) {
391         wikiBuilder.append(" (").append(transliteration).append(")");
392       }
393       
394     } else  if (name.equals("trreq")) {
395       wikiBuilder.append("{{trreq}}");
396       
397     } else if (name.equals("qualifier")) {
398       //assert positionalArgs.size() == 2 && namedArgs.isEmpty() : positionalArgs.toString() + namedArgs.toString();
399       wikiBuilder.append(" (").append(positionalArgs.get(1)).append(")");
400       
401     } else if (useRemainingArgTemplates.contains(name)) {
402       for (int i = 1; i < positionalArgs.size(); ++i) {
403         if (i != 1) {
404           wikiBuilder.append(", ");
405         }
406         wikiBuilder.append(positionalArgs.get(i));
407       }
408     } else if (ignoreTemplates.contains(name)) {
409       // Do nothing.
410       
411     } else if (name.equals("initialism")) {
412       assert positionalArgs.size() <= 2 && namedArgs.isEmpty() : positionalArgs.toString() + namedArgs;
413       wikiBuilder.append("Initialism");
414     } else if (name.equals("abbreviation")) {
415       assert positionalArgs.size() <= 2 && namedArgs.isEmpty() : positionalArgs.toString() + namedArgs;
416       wikiBuilder.append("Abbreviation");
417     } else if (name.equals("acronym")) {
418       assert positionalArgs.size() <= 2 && namedArgs.isEmpty() : positionalArgs.toString() + namedArgs;
419       wikiBuilder.append("Acronym");
420     } else {
421       if (currentTranslationSense != null) {
422         System.err.println("Unhandled template: " + positionalArgs.toString() + namedArgs);
423       }
424     }
425   }
426
427   @Override
428   public void onText(String text) {
429     if (wikiBuilder != null) {
430       wikiBuilder.append(text);
431       return;
432     }
433   }
434
435   @Override
436   public void onHeadingStart(int depth) {
437     wikiBuilder = new StringBuilder();
438     currentDepth = depth;
439     if (currentPartOfSpeech != null && depth <= currentPartOfSpeech.depth) {
440       currentPartOfSpeech = null;
441       insidePartOfSpeech = false;
442     }
443     if (currentWord != null && depth <= currentWord.depth) {
444       currentWord = null;
445     }
446     
447     currentHeading = null;
448   }
449   
450   @Override
451   public void onHeadingEnd(int depth) {
452     final String name = wikiBuilder.toString().trim();
453     wikiBuilder = null;
454     currentTranslationSense = null;
455     currentHeading = name;
456     
457     final boolean lang0 = langPatterns[0].matcher(name).matches();
458     final boolean lang1 = langPatterns[1].matcher(name).matches();
459     if (name.equalsIgnoreCase("English") || lang0 || lang1 || name.equalsIgnoreCase("Translingual")) {
460       currentWord = new WikiWord(title, depth);
461       if (lang0 && lang1) {
462         System.err.println("Word is indexed in both index1 and index2: " + title);
463       }
464       currentWord.language = name;
465       currentWord.index = lang0 ? 0 : (lang1 ? 1 : -1);
466       words.add(currentWord);
467       return;
468     }
469     
470     if (currentWord == null) {
471       return;
472     }
473     
474     if (currentPartOfSpeech != null && depth <= currentPartOfSpeech.depth) {
475       currentPartOfSpeech = null;
476     }
477     
478     insidePartOfSpeech = false;
479     if (currentPartOfSpeech == null && partOfSpeechHeader.matcher(name).matches()) {
480       currentPartOfSpeech = new WikiWord.PartOfSpeech(depth, name);
481       currentWord.partsOfSpeech.add(currentPartOfSpeech);
482       insidePartOfSpeech = true;
483       return;
484     }
485     
486     if (name.equals("Translations")) {
487       if (currentWord == null || 
488           !currentWord.language.equals("English") || 
489           currentPartOfSpeech == null) {
490         System.err.println("Unexpected Translations section: " + title);
491         return;
492       }
493       currentTranslationSense = new WikiWord.TranslationSense();
494     }
495     
496   }
497
498   @Override
499   public void onListItemStart(String header, int[] section) {
500     wikiBuilder = new StringBuilder();
501     if (currentWord != null) {
502       currentWord.currentPronunciation = null;
503     }
504   }
505   
506
507   @Override
508   public void onListItemEnd(String header, int[] section) {
509     String item = wikiBuilder.toString().trim();
510     final String oldItem = item;
511     if (item.length() == 0) {
512       return;
513     }
514     item = WikiParser.simpleParse(item);
515     wikiBuilder = null;
516         
517     // Part of speech
518     if (insidePartOfSpeech) {
519       assert currentPartOfSpeech != null : title + item;
520       if (header.equals("#") || 
521           header.equals("##") || 
522           header.equals("###") || 
523           header.equals("####") || 
524           header.equals(":#") || 
525           header.equals("::") ||
526           header.equals(":::*")) {
527         // Definition.
528         // :: should append, probably.
529         currentPartOfSpeech.newMeaning().meaning = item;
530         
531       // Source
532       } else if (header.equals("#*") ||
533                  header.equals("##*") ||
534                  header.equals("###*")) {
535         currentPartOfSpeech.lastMeaning().newExample().source = item;
536         
537       // Example
538       } else if (header.equals("#:") || 
539                  header.equals("#*:") || 
540                  header.equals("#:*") || 
541                  header.equals("##:") || 
542                  header.equals("##*:") || 
543                  header.equals("#:*:") || 
544                  header.equals("#:*#") ||
545                  header.equals("#*:") ||
546                  header.equals("*:") || 
547                  header.equals("#:::") ||
548                  header.equals("#**") ||
549                  header.equals("#*:::") ||
550                  header.equals("#:#") ||
551                  header.equals(":::") ||
552                  header.equals("##:*") ||
553                  header.equals("###*:")) {
554         StringUtil.appendLine(currentPartOfSpeech.lastMeaning().newExample().example, item);
555         
556       // Example in English
557       } else if (header.equals("#::") || 
558                  header.equals("#*::") || 
559                  header.equals("#:**") ||
560                  header.equals("#*#") ||
561                  header.equals("##*::")) {
562         StringUtil.appendLine(currentPartOfSpeech.lastMeaning().lastExample().exampleInEnglish, item);
563         
564       // Skip
565       } else if (header.equals("*") ||
566                  header.equals("**") ||
567                  header.equals("***") || 
568                  header.equals("*#") ||
569                  header.equals(":") ||
570                  header.equals("::*") ||
571                  header.equals("#**") ||
572                  header.equals(":*") ||
573                  header.equals("#*:*") ||
574                  header.equals("#*:**") || 
575                  header.equals("#*:#") || 
576                  header.equals("#*:*:") || 
577                  header.equals("#*:*") || 
578                  header.equals(";")) {
579         // might have: * {{seeCites}}
580         // * [[w:Arabic numerals|Arabic numerals]]: 2
581         //assert item.trim().length() == 0;
582         System.err.println("Skipping meaning: " + header + " " + item);
583       } else {
584         if (title.equals("Yellowknife")) {
585           return;
586         }
587         System.err.println("Busted heading: " + title + "  "+ header + " " + item);
588       }
589       return;
590     }
591     // Part of speech
592     
593     // Translation
594     if (currentTranslationSense != null) {
595       if (item.indexOf("{{[trreq]{}}}") != -1) {
596         return;
597       }
598
599       if (currentPartOfSpeech.translationSenses.isEmpty()) {
600         currentPartOfSpeech.translationSenses.add(currentTranslationSense);
601       }
602
603       final int colonPos = item.indexOf(':');
604       if (colonPos == -1) {
605         System.err.println("Invalid translation: title=" + title +  ",  item=" + item);
606         return;
607       }
608       final String lang = item.substring(0, colonPos);
609       final String trans = item.substring(colonPos + 1).trim();
610       for (int i = 0; i < 2; ++i) {
611         if (langPatterns[i].matcher(lang).find()) {
612           currentTranslationSense.translations.get(i).add(new Translation(lang, trans));
613         }
614       }
615     } // Translation
616   }
617
618   @Override
619   public void onNewLine() {
620   }
621
622   @Override
623   public void onNewParagraph() {
624   }
625
626   // ----------------------------------------------------------------------
627   
628   @Override
629   public void onComment(String text) {
630   }
631
632   @Override
633   public void onFormatBold(boolean boldOn) {
634   }
635
636   @Override
637   public void onFormatItalic(boolean italicOn) {
638   }
639
640   @Override
641   public void onUnterminated(String start, String rest) {
642     System.err.printf("OnUnterminated: %s %s %s\n", title, start, rest);
643   }
644   @Override
645   public void onInvalidHeaderEnd(String rest) {
646     throw new RuntimeException(rest);
647   }
648
649 }