mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 22:42:25 -04:00
foreign card importing; mnemosyne 2.0 importer
This commit is contained in:
parent
66534db987
commit
1d29c7bcc0
5 changed files with 172 additions and 1 deletions
|
@ -7,6 +7,7 @@ from anki.importing.apkg import AnkiPackageImporter
|
||||||
from anki.importing.anki2 import Anki2Importer
|
from anki.importing.anki2 import Anki2Importer
|
||||||
from anki.importing.anki1 import Anki1Importer
|
from anki.importing.anki1 import Anki1Importer
|
||||||
from anki.importing.supermemo_xml import SupermemoXmlImporter
|
from anki.importing.supermemo_xml import SupermemoXmlImporter
|
||||||
|
from anki.importing.mnemo import MnemosyneImporter
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
|
||||||
Importers = (
|
Importers = (
|
||||||
|
|
134
anki/importing/mnemo.py
Normal file
134
anki/importing/mnemo.py
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
import time, re
|
||||||
|
from anki.db import DB
|
||||||
|
from anki.importing.base import Importer
|
||||||
|
from anki.importing.noteimp import NoteImporter, ForeignNote, ForeignCard
|
||||||
|
from anki.utils import checksum, base91
|
||||||
|
from anki.stdmodels import addBasicModel
|
||||||
|
|
||||||
|
class MnemosyneImporter(NoteImporter):
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
db = DB(self.file)
|
||||||
|
ver = db.scalar(
|
||||||
|
"select value from global_variables where key='version'")
|
||||||
|
assert ver.startswith('Mnemosyne SQL 1')
|
||||||
|
# gather facts into temp objects
|
||||||
|
curid = None
|
||||||
|
notes = {}
|
||||||
|
note = None
|
||||||
|
for _id, id, k, v in db.execute("""
|
||||||
|
select _id, id, key, value from facts f, data_for_fact d where
|
||||||
|
f._id=d._fact_id"""):
|
||||||
|
if id != curid:
|
||||||
|
if note:
|
||||||
|
notes[note['_id']] = note
|
||||||
|
note = {'_id': _id}
|
||||||
|
curid = id
|
||||||
|
note[k] = v
|
||||||
|
if note:
|
||||||
|
notes[note['_id']] = note
|
||||||
|
# gather cards
|
||||||
|
front = []
|
||||||
|
frontback = []
|
||||||
|
vocabulary = []
|
||||||
|
for row in db.execute("""
|
||||||
|
select _fact_id, fact_view_id, tags, next_rep, last_rep, easiness,
|
||||||
|
acq_reps+ret_reps, lapses from cards"""):
|
||||||
|
# categorize note
|
||||||
|
note = notes[row[0]]
|
||||||
|
if row[1] == "1.1":
|
||||||
|
front.append(note)
|
||||||
|
elif row[1] == "2.1":
|
||||||
|
frontback.append(note)
|
||||||
|
elif row[1] == "3.1":
|
||||||
|
vocabulary.append(note)
|
||||||
|
# merge tags into note
|
||||||
|
tags = row[2].replace(", ", "\x1f").replace(" ", "_")
|
||||||
|
tags = tags.replace("\x1f", " ")
|
||||||
|
if "tags" not in note:
|
||||||
|
note['tags'] = []
|
||||||
|
note['tags'] += self.col.tags.split(tags)
|
||||||
|
note['tags'] = self.col.tags.canonify(note['tags'])
|
||||||
|
# if it's a new card we can go with the defaults
|
||||||
|
if row[3] == -1:
|
||||||
|
continue
|
||||||
|
# add the card
|
||||||
|
c = ForeignCard()
|
||||||
|
c.factor = row[5]
|
||||||
|
c.reps = row[6]
|
||||||
|
c.lapses = row[7]
|
||||||
|
# ivl is inferred in mnemosyne
|
||||||
|
next, prev = row[3:5]
|
||||||
|
c.ivl = max(1, (next - prev)/86400)
|
||||||
|
# work out how long we've got left
|
||||||
|
rem = int((next - time.time())/86400)
|
||||||
|
c.due = max(0, self.col.sched.today+rem)
|
||||||
|
# get ord
|
||||||
|
m = re.match("\d+\.(\d+)", row[1])
|
||||||
|
ord = int(m.group(1))-1
|
||||||
|
if 'cards' not in note:
|
||||||
|
note['cards'] = {}
|
||||||
|
note['cards'][ord] = c
|
||||||
|
self._addFronts(front)
|
||||||
|
self._addFrontBacks(frontback)
|
||||||
|
self._addVocabulary(vocabulary)
|
||||||
|
|
||||||
|
def fields(self):
|
||||||
|
return self._fields
|
||||||
|
|
||||||
|
def _addFronts(self, notes, model=None, fields=("f", "b")):
|
||||||
|
data = []
|
||||||
|
for orig in notes:
|
||||||
|
# create a foreign note object
|
||||||
|
n = ForeignNote()
|
||||||
|
n.fields = []
|
||||||
|
for f in fields:
|
||||||
|
n.fields.append(orig.get(f, ''))
|
||||||
|
n.tags = orig['tags']
|
||||||
|
n.cards = orig.get('cards', {})
|
||||||
|
data.append(n)
|
||||||
|
# add a basic model
|
||||||
|
if not model:
|
||||||
|
model = addBasicModel(self.col)
|
||||||
|
model['name'] = "Mnemosyne-FrontOnly"
|
||||||
|
mm = self.col.models
|
||||||
|
mm.save(model)
|
||||||
|
mm.setCurrent(model)
|
||||||
|
self.model = model
|
||||||
|
self._fields = len(model['flds'])
|
||||||
|
self.initMapping()
|
||||||
|
# import
|
||||||
|
self.importNotes(data)
|
||||||
|
|
||||||
|
def _addFrontBacks(self, notes):
|
||||||
|
m = addBasicModel(self.col)
|
||||||
|
m['name'] = "Mnemosyne-FrontBack"
|
||||||
|
mm = self.col.models
|
||||||
|
t = mm.newTemplate("Back")
|
||||||
|
t['qfmt'] = "{{Back}}"
|
||||||
|
t['afmt'] = t['qfmt'] + "\n\n<hr id=answer>\n\n{{Front}}"
|
||||||
|
mm.addTemplate(m, t)
|
||||||
|
self._addFronts(notes, m)
|
||||||
|
|
||||||
|
def _addVocabulary(self, notes):
|
||||||
|
mm = self.col.models
|
||||||
|
m = mm.new("Mnemosyne-Vocabulary")
|
||||||
|
for f in "Expression", "Pronunciation", "Meaning", "Notes":
|
||||||
|
fm = mm.newField(f)
|
||||||
|
mm.addField(m, fm)
|
||||||
|
t = mm.newTemplate("Recognition")
|
||||||
|
t['qfmt'] = "{{Expression}}"
|
||||||
|
t['afmt'] = t['qfmt'] + """\n\n<hr id=answer>\n\n\
|
||||||
|
{{Pronunciation}}<br>\n{{Meaning}}<br>\n{{Notes}}"""
|
||||||
|
mm.addTemplate(m, t)
|
||||||
|
t = mm.newTemplate("Production")
|
||||||
|
t['qfmt'] = "{{Meaning}}"
|
||||||
|
t['afmt'] = t['qfmt'] + """\n\n<hr id=answer>\n\n\
|
||||||
|
{{Expression}}<br>\n{{Pronunciation}}<br>\n{{Notes}}"""
|
||||||
|
mm.addTemplate(m, t)
|
||||||
|
mm.add(m)
|
||||||
|
self._addFronts(notes, m, fields=("f", "p_1", "m_1", "n"))
|
|
@ -18,6 +18,15 @@ class ForeignNote(object):
|
||||||
self.fields = []
|
self.fields = []
|
||||||
self.tags = []
|
self.tags = []
|
||||||
self.deck = None
|
self.deck = None
|
||||||
|
self.cards = {} # map of ord -> card
|
||||||
|
|
||||||
|
class ForeignCard(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.due = 0
|
||||||
|
self.ivl = 1
|
||||||
|
self.factor = 2.5
|
||||||
|
self.reps = 0
|
||||||
|
self.lapses = 0
|
||||||
|
|
||||||
# Base class for CSV and similar text-based imports
|
# Base class for CSV and similar text-based imports
|
||||||
######################################################################
|
######################################################################
|
||||||
|
@ -84,6 +93,7 @@ class NoteImporter(Importer):
|
||||||
updates = []
|
updates = []
|
||||||
new = []
|
new = []
|
||||||
self._ids = []
|
self._ids = []
|
||||||
|
self._cards = []
|
||||||
for n in notes:
|
for n in notes:
|
||||||
fld0 = n.fields[fld0idx]
|
fld0 = n.fields[fld0idx]
|
||||||
csum = fieldChecksum(fld0)
|
csum = fieldChecksum(fld0)
|
||||||
|
@ -122,7 +132,10 @@ class NoteImporter(Importer):
|
||||||
self.addNew(new)
|
self.addNew(new)
|
||||||
self.addUpdates(updates)
|
self.addUpdates(updates)
|
||||||
self.col.updateFieldCache(self._ids)
|
self.col.updateFieldCache(self._ids)
|
||||||
|
# generate cards
|
||||||
assert not self.col.genCards(self._ids)
|
assert not self.col.genCards(self._ids)
|
||||||
|
# apply scheduling updates
|
||||||
|
self.updateCards()
|
||||||
# make sure to update sflds, etc
|
# make sure to update sflds, etc
|
||||||
self.total = len(self._ids)
|
self.total = len(self._ids)
|
||||||
|
|
||||||
|
@ -133,6 +146,10 @@ class NoteImporter(Importer):
|
||||||
if not self.processFields(n):
|
if not self.processFields(n):
|
||||||
print "no cards generated"
|
print "no cards generated"
|
||||||
return
|
return
|
||||||
|
# note id for card updates later
|
||||||
|
for ord, c in n.cards.items():
|
||||||
|
self._cards.append((id, ord, c))
|
||||||
|
self.col.tags.register(n.tags)
|
||||||
return [id, guid64(), self.model['id'], self.didForNote(n),
|
return [id, guid64(), self.model['id'], self.didForNote(n),
|
||||||
intTime(), self.col.usn(), self.col.tags.join(n.tags),
|
intTime(), self.col.usn(), self.col.tags.join(n.tags),
|
||||||
n.fieldsStr, "", "", 0, ""]
|
n.fieldsStr, "", "", 0, ""]
|
||||||
|
@ -148,6 +165,7 @@ class NoteImporter(Importer):
|
||||||
if not self.processFields(n):
|
if not self.processFields(n):
|
||||||
print "no cards generated"
|
print "no cards generated"
|
||||||
return
|
return
|
||||||
|
self.col.tags.register(n.tags)
|
||||||
tags = self.col.tags.join(n.tags)
|
tags = self.col.tags.join(n.tags)
|
||||||
return [intTime(), self.col.usn(), n.fieldsStr, tags,
|
return [intTime(), self.col.usn(), n.fieldsStr, tags,
|
||||||
id, n.fieldsStr, tags]
|
id, n.fieldsStr, tags]
|
||||||
|
@ -178,3 +196,12 @@ where id = ? and (flds != ? or tags != ?)""", rows)
|
||||||
fields[sidx] = note.fields[c]
|
fields[sidx] = note.fields[c]
|
||||||
note.fieldsStr = joinFields(fields)
|
note.fieldsStr = joinFields(fields)
|
||||||
return self.col.models.availOrds(self.model, note.fieldsStr)
|
return self.col.models.availOrds(self.model, note.fieldsStr)
|
||||||
|
|
||||||
|
def updateCards(self):
|
||||||
|
data = []
|
||||||
|
for nid, ord, c in self._cards:
|
||||||
|
data.append((c.ivl, c.due, c.factor, c.reps, c.lapses, nid, ord))
|
||||||
|
# we assume any updated cards are reviews
|
||||||
|
self.col.db.executemany("""
|
||||||
|
update cards set type = 2, queue = 2, ivl = ?, due = ?,
|
||||||
|
factor = ?, reps = ?, lapses = ? where nid = ? and ord = ?""", data)
|
||||||
|
|
BIN
tests/support/mnemo.db
Normal file
BIN
tests/support/mnemo.db
Normal file
Binary file not shown.
|
@ -6,7 +6,7 @@ from anki.upgrade import Upgrader
|
||||||
from anki.utils import ids2str
|
from anki.utils import ids2str
|
||||||
from anki.errors import *
|
from anki.errors import *
|
||||||
from anki.importing import Anki1Importer, Anki2Importer, TextImporter, \
|
from anki.importing import Anki1Importer, Anki2Importer, TextImporter, \
|
||||||
SupermemoXmlImporter
|
SupermemoXmlImporter, MnemosyneImporter
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
|
|
||||||
from anki.db import *
|
from anki.db import *
|
||||||
|
@ -142,3 +142,12 @@ def test_updating():
|
||||||
i.mapping[1] = 0
|
i.mapping[1] = 0
|
||||||
i.run()
|
i.run()
|
||||||
deck.close()
|
deck.close()
|
||||||
|
|
||||||
|
def test_mnemo():
|
||||||
|
deck = getEmptyDeck()
|
||||||
|
file = unicode(os.path.join(testDir, "support/mnemo.db"))
|
||||||
|
i = MnemosyneImporter(deck, file)
|
||||||
|
i.run()
|
||||||
|
assert deck.cardCount() == 7
|
||||||
|
assert "a_longer_tag" in deck.tags.all()
|
||||||
|
assert deck.db.scalar("select count() from cards where type = 0") == 1
|
||||||
|
|
Loading…
Reference in a new issue