From 49710698563c72895a7f03a9ebf1d9cb4fa7b669 Mon Sep 17 00:00:00 2001 From: Rick Gruber-Riemer Date: Thu, 5 Nov 2009 21:59:33 +0100 Subject: [PATCH] Added importing for DingsBums?! decks --- anki/importing/__init__.py | 2 + anki/importing/dingsbums.py | 224 ++++++++++++++++++++++++++++++++++ tests/importing/dingsbums.xml | 50 ++++++++ tests/test_importing.py | 12 +- 4 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 anki/importing/dingsbums.py create mode 100644 tests/importing/dingsbums.xml diff --git a/anki/importing/__init__.py b/anki/importing/__init__.py index b178a8d5a..83ce7495f 100644 --- a/anki/importing/__init__.py +++ b/anki/importing/__init__.py @@ -262,6 +262,7 @@ from anki.importing.anki10 import Anki10Importer from anki.importing.mnemosyne10 import Mnemosyne10Importer from anki.importing.wcu import WCUImporter from anki.importing.supermemo_xml import SupermemoXmlImporter +from anki.importing.dingsbums import DingsBumsImporter Importers = ( (_("Text separated by tabs or semicolons (*)"), TextImporter), @@ -269,4 +270,5 @@ Importers = ( (_("Mnemosyne Deck (*.mem)"), Mnemosyne10Importer), (_("CueCard Deck (*.wcu)"), WCUImporter), (_("Supermemo XML export (*.xml)"), SupermemoXmlImporter), + (_("DingsBums?! Deck (*.xml)"), DingsBumsImporter), ) diff --git a/anki/importing/dingsbums.py b/anki/importing/dingsbums.py new file mode 100644 index 000000000..7bb19619a --- /dev/null +++ b/anki/importing/dingsbums.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +# Copyright: rick@vanosten.net +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +"""\ +Importing DingsBums?! decks (see dingsbums.vanosten.net) +======================================================== + +GENERAL: +* DingsBums?! files are xml with relational content. +* DingsBums?!'s data format is more relational than Anki's. Therefore some of the relations are denormalized. + +* A stack in DingsBums?! is a deck in Anki +* An entry type in DingsBums?! is a model in Anki +* An entry type attribute in DingsBums?! is a field in Anki +* An entry type attribute item in DingsBums?! does not exist in Anki. It is just the contents of a field denormalized. +* There is not concept of units and categories in Anki. +* An entry in DingsBums?! is basically a fact in Anki +* There are no cards in DingsBums?! +* There is a special plugin in Anki for Pinyin. Therefore syllable settings from DingsBums?! are ignored. +* The locale settings in DingsBums?! have never been active and are therefore ignored. +* All statistics will get lost - i.e. no historic informaiton about progress will be migrated to Anki. +* The DingsBums?! stack needs to end with *.xml in order to be recognizable in Anki import. +* The learning levels from DingsBums?! are not taken into account because they do not really match spaced repetition. + +DESIGN OF MAPPING FROM DingsBums?! TO Anki +* +* The contents of units and categories are transferred as tags to Anki: unit/category label + "_" + unit/category name. +* If unit/category name has space, then it is replaced by "_" +* The fields "base", "target", explanation", example", "pronounciation" and "relation" are created as fields in Anki +* The fields are only created and used in Anki, if they were visible in DingsBums?!, i.e. < 3: +VISIBILITY_ALWAYS = 0; +VISIBILITY_QUERY = 1; +VISIBILITY_SOLUTION = 2; +VISIBILITY_NEVER = 3; + +* The name of the fields in Anki is taken from the labels defined in the stack properties +* The description field of Anki is not used/displayed. Therefore there is not much sense to transfer the contents of title, author, notes, copyright and license. +* The visibility options in DingsBums?! are used as hints to make cards in Anki: + + Two card templates are made for each model and then applied to each fact when importing. + + "Forward": Base -> "Question", target -> "Answer"; if "always", then part of question; if "solution" or "part of query" then part of answer + + "Reverse": Target -> "Answer", base -> "Question" + + Unit and category are not shown, as they are tags and there is no possibility to distinguish between visibility settings in this case. + +CHANGES MADE TO LIBANKI: +* Added libanki/anki/import/dingsbums.py +* Added DingsBumsImporter to importers at end of file libanki/anki/import/__init__.py +* Added libanki/tests/importing/dingsbums.xml +* Added method test_dingsbums() to libanki/anki/tests/test_importing.py +""" + +from anki.importing import Importer +from anki import DeckStorage +from anki.facts import Fact +from anki.models import FieldModel +from anki.models import CardModel +from anki.models import Model +from anki.lang import _ + +from xml.sax import make_parser +from xml.sax.handler import ContentHandler +import sys + +class DingsBumsHandler(ContentHandler): + + def __init__(self, deck): + self.eid = "0" + self.attributeItems = {} + self.unitCategories = {} + self.attributes = {} + self.currentContent = "" + self.labels = {} + self.labels["pro"] = u"Pronunciation" # the user cannot change this label and therefore not in xml-file + self.labels["rel"] = u"Relation" + self.visibility = {} + self.models = {} + self.typeAttributes = {} # mapping of entry type and attribute name (e.g. "ET8_A1", "ET8_A2", ...) + self.deck = deck + self.f = None # the current fact + self.countFacts = 0 + + def startElement(self, name, attrs): + """Implements SAX interface""" + if name in ["etai", "unit", "category"]: + self.eid = attrs["eid"] + elif "eta" == name: + self.attributes[attrs["eid"]] = attrs["n"] + elif "entrytype" == name: + self.createModel(attrs) + elif "e" == name: + self.createFact(attrs) + + def endElement(self, name): + """Implements SAX interface""" + if "vocabulary" == name: + self.deck.updateProgress() + elif name.endswith("label"): + self.labels[name.replace("label", "")] = self.currentContent + elif name.startswith("vis"): + self.visibility[name.replace("vis", "")] = self.currentContent + elif "etai" == name: + self.attributeItems[self.eid] = self.currentContent + elif "etattributes" == name: + self.deck.updateProgress() + elif "entrytypes" == name: + self.deck.updateProgress() + elif "name" == name: + self.unitCategories[self.eid] = self.prepareTag(self.currentContent) + elif "units" == name: + self.deck.updateProgress() + elif "categories" == name: + self.deck.updateProgress() + elif "entries" == name: + self.deck.updateProgress() + elif "e" == name: + self.deck.addFact(self.f) + self.countFacts += 1 + # there is a not logical mapping between the tags for fields and names in VocabInfo + # See net.vanosten.dings.consts.Constants.XML_* + elif "o" == name: + self.f.__setitem__(self.labels["b"], self.currentContent) + elif "d" == name: + self.f.__setitem__(self.labels["t"], self.currentContent) + elif "ep" == name: + self.f.__setitem__(self.labels["exp"], self.currentContent) + elif "ea" == name: + self.f.__setitem__(self.labels["ex"], self.currentContent) + elif "p" == name: + self.f.__setitem__(self.labels["pro"], self.currentContent) + elif "r" == name: + self.f.__setitem__(self.labels["rel"], self.currentContent) + + def characters(self, content): + """Implements SAX interface""" + self.currentContent = content.strip() + + def createModel(self, attrs): + """Makes a new Anki (fact) model from an entry type. + The card models are made each time from scratch in order that evt. model specific fields (attributes) can make part.""" + m = Model(attrs["n"]) + # field model for standard fields + m.addFieldModel(FieldModel(self.labels["b"], True, False)) #there is no uniqueness check in DingsBums?! + m.addFieldModel(FieldModel(self.labels["t"], True, False)) + for aField in ["exp", "ex", "pro", "rel"]: + if self.visibility[aField] in "012": + m.addFieldModel(FieldModel(self.labels[aField], False, False)) + # field models for attributes + for attr in ["a1", "a2" "a3", "a4"]: + if attr in attrs: + m.addFieldModel(FieldModel(self.attributes[attrs[attr]], False, False)) + self.typeAttributes[attrs["eid"] + "_" + attr] = self.attributes[attrs[attr]] + + # card model for front + frontStrings = ["%(" + self.labels["b"] + ")s"] + backStrings = ["%(" + self.labels["t"] + ")s"] + for aField in ["exp", "ex", "pro", "rel"]: + if self.visibility[aField] in "01": + frontStrings.append("%(" + self.labels[aField] + ")s") + if self.visibility[aField] in "02": + backStrings.append("%(" + self.labels[aField] + ")s") + m.addCardModel(CardModel(u'Forward', "
".join(frontStrings), "
".join(backStrings))) + # card model for back + m.addCardModel(CardModel(u'Reverse', unicode("%(" + self.labels["t"] + ")s"), unicode("%(" + self.labels["b"] + ")s"))) + # tags is just the name without spaces + m.tags = self.prepareTag(m.name) + + # link + self.models[attrs["eid"]] = m + self.deck.addModel(m) + + def createFact(self, attrs): + """Makes a new Anki fact from an entry.""" + model = self.models[attrs["et"]] + self.f = Fact(model) + # process attributes + for attr in ["a1", "a2" "a3", "a4"]: + if attr in attrs: + self.f.__setitem__(self.typeAttributes[attrs["et"] + "_" + attr], self.attributeItems[attrs[attr]]) + # process tags. Unit, Category plus entry type name + tagString = unicode(self.unitCategories[attrs["u"]] + " " + self.unitCategories[attrs["c"]] + " " + model.tags) + self.f.tags = tagString + + def prepareTag(self, stringWithSpace): + parts = stringWithSpace.split() + return "_".join(parts) + +class DingsBumsImporter(Importer): + needMapper = False # needs to overwrite default in Importer - otherwise Mapping dialog is shown in GUI + + def __init__(self, deck, file): + Importer.__init__(self, deck, file) + self.deck = deck + self.file = file + self.total = 0 + + def doImport(self): + """Totally overrides the method in Importer""" + num = 7 # the number of updates to progress bar (see references in method endElement in DingsBumsHandler + self.deck.startProgress(num) + self.deck.updateProgress(_("Importing...")) + + # parse the DingsBums?! xml file + handler = DingsBumsHandler(self.deck) + saxparser = make_parser( ) + saxparser.setContentHandler(handler) + saxparser.parse(self.file) + self.total = handler.countFacts + self.deck.finishProgress() + self.deck.setModified() + +if __name__ == '__main__': + print "Starting ..." + + # for testing you can start it standalone. Use an argument to specify the file to import + filename = str(sys.argv[1]) + + mydeck = DeckStorage.Deck() + i = DingsBumsImporter(mydeck, filename) + i.doImport() + assert 7 == i.total + mydeck.s.close() + + print "... Finished" + sys.exit(1) \ No newline at end of file diff --git a/tests/importing/dingsbums.xml b/tests/importing/dingsbums.xml new file mode 100644 index 000000000..a00bd17df --- /dev/null +++ b/tests/importing/dingsbums.xml @@ -0,0 +1,50 @@ + + +Deutsch - ItalienischRick Gruber-RiemerKlett Grund- und AufbauwortschatzDeutschItalienischAttributesLektionKategoriOthersErklärungUnregelmässigen_USen_USen_USen_USen_USen_USen_USen_USen_US1000222falsefalse + +RegelmässigUnregelmässig +avereessereavere oder essere +-are (regelmässig)-ere (regelmässig)-ire (regelmässig)Unregelmässig +illa +RegelmässigUnregelmässigNur EinzahlNur Mehrzahl + + + + + + + + + + + + +Rest +Harenberg Kalender Italienisch 2007 +50. Restaurant, Café, Hotel +Berlitz Kalender 2005 +A + + +Default + + +entfernen, beseitigenallontanare +dann; damals, also; früherallora +Schüler, Zöglingallievo +lustig, heiterallegro +sich in einer unbequemen Situation befindenessere un pesche four d' aqua +das ist mir egalme ne fregoGeste: unter dem Kinn mit der Hand vonhinten nach vorne reiben +Wirtinostessa + + +2361915312 +1961915312 +1251251100 +2021915312 +1881251100 +1241811000 +1761915312 +1241811000 + + diff --git a/tests/test_importing.py b/tests/test_importing.py index d79384d2f..6b986cda8 100644 --- a/tests/test_importing.py +++ b/tests/test_importing.py @@ -5,7 +5,7 @@ from tests.shared import assertException from anki.errors import * from anki import DeckStorage -from anki.importing import anki10, csvfile, mnemosyne10, supermemo_xml +from anki.importing import anki10, csvfile, mnemosyne10, supermemo_xml, dingsbums from anki.stdmodels import BasicModel from anki.facts import Fact from anki.sync import SyncClient, SyncServer @@ -131,3 +131,13 @@ def test_anki10_modtime(): assert deck2.s.scalar("select count(*) from cards") == 2 assert deck2.s.scalar("select count(*) from facts") == 2 assert deck2.s.scalar("select count(*) from models") == 2 + +def test_dingsbums(): + deck = DeckStorage.Deck() + deck.addModel(BasicModel()) + startNumberOfFacts = deck.factCount + file = unicode(os.path.join(testDir, "importing/dingsbums.xml")) + i = dingsbums.DingsBumsImporter(deck, file) + i.doImport() + assert 7 == i.total + deck.s.close()