mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Added importing for DingsBums?! decks
This commit is contained in:
parent
7e2fd90963
commit
4971069856
4 changed files with 287 additions and 1 deletions
|
@ -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),
|
||||
)
|
||||
|
|
224
anki/importing/dingsbums.py
Normal file
224
anki/importing/dingsbums.py
Normal file
|
@ -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', "<br>".join(frontStrings), "<br>".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)
|
50
tests/importing/dingsbums.xml
Normal file
50
tests/importing/dingsbums.xml
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<vocabulary version="3">
|
||||
<info><title>Deutsch - Italienisch</title><author>Rick Gruber-Riemer</author><notes>Klett Grund- und Aufbauwortschatz</notes><copyright></copyright><licence></licence><blabel>Deutsch</blabel><tlabel>Italienisch</tlabel><alabel>Attributes</alabel><ulabel>Lektion</ulabel><clabel>Kategori</clabel><olabel>Others</olabel><explabel>Erklärung</explabel><exlabel>Unregelmässig</exlabel><bor>en_US</bor><tor>en_US</tor><aor>en_US</aor><uor>en_US</uor><cor>en_US</cor><expor>en_US</expor><exor>en_US</exor><por>en_US</por><ror>en_US</ror><visa>1</visa><visu>0</visu><viscat>0</viscat><visexp>0</visexp><visex>2</visex><vispro>2</vispro><visrel>2</visrel><syllb>false</syllb><syllt>false</syllt></info>
|
||||
<etattributes>
|
||||
<eta eid="ETA4" n="Steigerung" di="ETAI10" lu="20061222 07:56:27 CET"><etai eid="ETAI10" ir="false">Regelmässig</etai><etai eid="ETAI11" ir="false">Unregelmässig</etai></eta>
|
||||
<eta eid="ETA5" n="Perfekt" di="ETAI12" lu="20070210 13:20:09 CET"><etai eid="ETAI12" ir="false">avere</etai><etai eid="ETAI13" ir="false">essere</etai><etai eid="ETAI14" ir="false">avere oder essere</etai></eta>
|
||||
<eta eid="ETA1" n="Konjugation" di="ETAI1" lu="20070211 12:35:19 CET"><etai eid="ETAI1" ir="false">-are (regelmässig)</etai><etai eid="ETAI16" ir="false">-ere (regelmässig)</etai><etai eid="ETAI15" ir="false">-ire (regelmässig)</etai><etai eid="ETAI2" ir="false">Unregelmässig</etai></eta>
|
||||
<eta eid="ETA2" n="Geschlecht" di="ETAI3" lu="20070210 21:08:17 CET"><etai eid="ETAI3" ir="false">il</etai><etai eid="ETAI4" ir="false">la</etai></eta>
|
||||
<eta eid="ETA3" n="Mehrzahl" di="ETAI6" lu="20070212 10:03:56 CET"><etai eid="ETAI6" ir="false">Regelmässig</etai><etai eid="ETAI7" ir="false">Unregelmässig</etai><etai eid="ETAI8" ir="false">Nur Einzahl</etai><etai eid="ETAI9" ir="false">Nur Mehrzahl</etai></eta>
|
||||
</etattributes>
|
||||
<entrytypes>
|
||||
<entrytype eid="ET8" n="Sätze und Redewendungen" lu="20070310 20:16:30 CET"></entrytype>
|
||||
<entrytype eid="ET7" n="Slang" lu="20070210 20:58:29 CET"></entrytype>
|
||||
<entrytype eid="ET4" n="Adjektiv / Adverb" a1="ETA4" lu="20061222 07:58:14 CET"></entrytype>
|
||||
<entrytype eid="ET3" n="Substantiv" a1="ETA2" a2="ETA3" lu="20061222 07:55:39 CET"></entrytype>
|
||||
<entrytype eid="ET6" n="Modi di dire" lu="20070210 13:29:14 CET"></entrytype>
|
||||
<entrytype eid="ET5" n="Konjugation" lu="20070210 13:20:36 CET"></entrytype>
|
||||
<entrytype eid="ET2" n="Anderes" lu="20061222 07:52:31 CET"></entrytype>
|
||||
<entrytype eid="ET1" n="Verb" a1="ETA1" a2="ETA5" lu="20061222 07:57:41 CET"></entrytype>
|
||||
</entrytypes>
|
||||
<units>
|
||||
<unit eid="U4" lu="20070217 20:14:02 CET"><name>Rest</name><desc></desc></unit>
|
||||
<unit eid="U3" lu="20070217 20:03:30 CET"><name>Harenberg Kalender Italienisch 2007</name><desc></desc></unit>
|
||||
<unit eid="U5" lu="20070310 20:15:52 CET"><name>50. Restaurant, Café, Hotel</name><desc></desc></unit>
|
||||
<unit eid="U2" lu="20070210 13:31:47 CET"><name>Berlitz Kalender 2005</name><desc></desc></unit>
|
||||
<unit eid="U1" lu="20061222 07:48:58 CET"><name>A</name><desc></desc></unit>
|
||||
</units>
|
||||
<categories>
|
||||
<category eid="C1" lu="20061222 07:46:40 CET"><name>Default</name><desc></desc></category>
|
||||
</categories>
|
||||
<entries>
|
||||
<e et="ET1" eid="E113" u="U1" c="C1" lv="1" st="true" lu="20070211 14:18:49 CET" ll="19700101 01:00:00 CET" a1="ETAI1" a2="ETAI12"><o>entfernen, beseitigen</o><d>allontanare</d></e>
|
||||
<e et="ET2" eid="E114" u="U1" c="C1" lv="2" st="true" lu="20070211 14:20:31 CET" ll="19700101 01:00:00 CET"><o>dann; damals, also; früher</o><d>allora</d></e>
|
||||
<e et="ET3" eid="E112" u="U1" c="C1" lv="3" st="true" lu="20070211 14:17:19 CET" ll="19700101 01:00:00 CET" a1="ETAI3" a2="ETAI6"><o>Schüler, Zögling</o><d>allievo</d></e>
|
||||
<e et="ET4" eid="E110" u="U1" c="C1" lv="4" st="true" lu="20070211 14:10:56 CET" ll="19700101 01:00:00 CET" a1="ETAI10"><o>lustig, heiter</o><d>allegro</d></e>
|
||||
<e et="ET6" eid="E8" u="U2" c="C1" lv="5" st="true" lu="20070210 13:31:58 CET" ll="19700101 01:00:00 CET"><o>sich in einer unbequemen Situation befinden</o><d>essere un pesche four d' aqua</d></e>
|
||||
<e et="ET7" eid="E49" u="U2" c="C1" lv="6" st="true" lu="20070210 20:59:34 CET" ll="19700101 01:00:00 CET"><o>das ist mir egal</o><d>me ne frego</d><ep>Geste: unter dem Kinn mit der Hand vonhinten nach vorne reiben</ep></e>
|
||||
<e et="ET3" eid="E251" u="U5" c="C1" lv="7" st="true" lu="20070310 20:29:49 CET" ll="19700101 01:00:00 CET" a1="ETAI4" a2="ETAI6"><o>Wirtin</o><d>ostessa</d></e>
|
||||
</entries>
|
||||
<stats>
|
||||
<sset ts="20070310 21:16:36 CET"><sne lv="1">236</sne><sne lv="2">19</sne><sne lv="3">1</sne><sne lv="4">5</sne><sne lv="5">3</sne><sne lv="6">1</sne><sne lv="7">2</sne></sset>
|
||||
<sset ts="20070217 20:37:22 CET"><sne lv="1">196</sne><sne lv="2">19</sne><sne lv="3">1</sne><sne lv="4">5</sne><sne lv="5">3</sne><sne lv="6">1</sne><sne lv="7">2</sne></sset>
|
||||
<sset ts="20070212 10:13:05 CET"><sne lv="1">125</sne><sne lv="2">12</sne><sne lv="3">5</sne><sne lv="4">1</sne><sne lv="5">1</sne><sne lv="6">0</sne><sne lv="7">0</sne></sset>
|
||||
<sset ts="20070228 21:44:04 CET"><sne lv="1">202</sne><sne lv="2">19</sne><sne lv="3">1</sne><sne lv="4">5</sne><sne lv="5">3</sne><sne lv="6">1</sne><sne lv="7">2</sne></sset>
|
||||
<sset ts="20070217 19:10:49 CET"><sne lv="1">188</sne><sne lv="2">12</sne><sne lv="3">5</sne><sne lv="4">1</sne><sne lv="5">1</sne><sne lv="6">0</sne><sne lv="7">0</sne></sset>
|
||||
<sset ts="20070211 20:55:08 CET"><sne lv="1">124</sne><sne lv="2">18</sne><sne lv="3">1</sne><sne lv="4">1</sne><sne lv="5">0</sne><sne lv="6">0</sne><sne lv="7">0</sne></sset>
|
||||
<sset ts="20070217 19:14:04 CET"><sne lv="1">176</sne><sne lv="2">19</sne><sne lv="3">1</sne><sne lv="4">5</sne><sne lv="5">3</sne><sne lv="6">1</sne><sne lv="7">2</sne></sset>
|
||||
<sset ts="20070211 20:57:10 CET"><sne lv="1">124</sne><sne lv="2">18</sne><sne lv="3">1</sne><sne lv="4">1</sne><sne lv="5">0</sne><sne lv="6">0</sne><sne lv="7">0</sne></sset>
|
||||
</stats>
|
||||
</vocabulary>
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue