Anki/anki/exporting.py
Damien Elmes b69fd48768 more type handling updates; don't munge counts on sync
In various parts of the code we need to get all cards of a given category
(new, failed, etc) regardless of whether they're suspended, buried, etc. So we
store the true type in the obsolete relativeDelay column and add in index for
it, because it's cheaper than putting indices on reps & successive.
2010-11-13 18:39:24 +09:00

273 lines
8.1 KiB
Python

# -*- coding: utf-8 -*-
# Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
"""\
Exporting support
==============================
"""
__docformat__ = 'restructuredtext'
import itertools, time, re, os, HTMLParser
from operator import itemgetter
from anki import DeckStorage
from anki.cards import Card
from anki.sync import SyncClient, SyncServer, copyLocalMedia
from anki.lang import _
from anki.utils import findTag, parseTags, stripHTML, ids2str
from anki.tags import tagIds
from anki.db import *
class Exporter(object):
def __init__(self, deck):
self.deck = deck
self.limitTags = []
self.limitCardIds = []
def exportInto(self, path):
self._escapeCount = 0
file = open(path, "wb")
self.doExport(file)
file.close()
def escapeText(self, text, removeFields=False):
"Escape newlines and tabs, and strip Anki HTML."
from BeautifulSoup import BeautifulSoup as BS
text = text.replace("\n", "<br>")
text = text.replace("\t", " " * 8)
if removeFields:
# beautifulsoup is slow
self._escapeCount += 1
if self._escapeCount % 100 == 0:
self.deck.updateProgress()
try:
s = BS(text)
all = s('span', {'class': re.compile("fm.*")})
for e in all:
e.replaceWith("".join([unicode(x) for x in e.contents]))
text = unicode(s)
except HTMLParser.HTMLParseError:
pass
return text
def cardIds(self):
"Return all cards, limited by tags or provided ids."
if self.limitCardIds:
return self.limitCardIds
if not self.limitTags:
cards = self.deck.s.column0("select id from cards")
else:
d = tagIds(self.deck.s, self.limitTags, create=False)
cards = self.deck.s.column0(
"select cardId from cardTags where tagid in %s" %
ids2str(d.values()))
self.count = len(cards)
return cards
class AnkiExporter(Exporter):
key = _("Anki Deck (*.anki)")
ext = ".anki"
def __init__(self, deck):
Exporter.__init__(self, deck)
self.includeSchedulingInfo = False
self.includeMedia = True
def exportInto(self, path):
n = 3
if not self.includeSchedulingInfo:
n += 1
self.deck.startProgress(n)
self.deck.updateProgress(_("Exporting..."))
try:
os.unlink(path)
except (IOError, OSError):
pass
self.newDeck = DeckStorage.Deck(path)
client = SyncClient(self.deck)
server = SyncServer(self.newDeck)
server._mediaSupported = self.includeMedia
client.setServer(server)
client.localTime = self.deck.modified
client.remoteTime = 0
self.deck.s.flush()
# set up a custom change list and sync
lsum = self.localSummary()
rsum = server.summary(0)
self.deck.updateProgress()
payload = client.genPayload((lsum, rsum))
self.deck.updateProgress()
res = server.applyPayload(payload)
if not self.includeSchedulingInfo:
self.deck.updateProgress()
self.newDeck.s.statement("""
delete from reviewHistory""")
self.newDeck.s.statement("""
update cards set
interval = 0,
lastInterval = 0,
due = created,
lastDue = 0,
factor = 2.5,
firstAnswered = 0,
reps = 0,
successive = 0,
averageTime = 0,
reviewTime = 0,
youngEase0 = 0,
youngEase1 = 0,
youngEase2 = 0,
youngEase3 = 0,
youngEase4 = 0,
matureEase0 = 0,
matureEase1 = 0,
matureEase2 = 0,
matureEase3 = 0,
matureEase4 = 0,
yesCount = 0,
noCount = 0,
spaceUntil = 0,
type = 2,
relativeDelay = 2,
combinedDue = created,
modified = :now
""", now=time.time())
self.newDeck.s.statement("""
delete from stats""")
# media
copyLocalMedia(client.deck, server.deck)
# need to save manually
self.newDeck.rebuildCounts()
self.newDeck.updateAllPriorities()
self.exportedCards = self.newDeck.cardCount
self.newDeck.utcOffset = -1
self.newDeck.s.commit()
self.newDeck.close()
self.deck.finishProgress()
def localSummary(self):
cardIds = self.cardIds()
cStrIds = ids2str(cardIds)
cards = self.deck.s.all("""
select id, modified from cards
where id in %s""" % cStrIds)
facts = self.deck.s.all("""
select facts.id, facts.modified from cards, facts where
facts.id = cards.factId and
cards.id in %s""" % cStrIds)
models = self.deck.s.all("""
select models.id, models.modified from models, facts where
facts.modelId = models.id and
facts.id in %s""" % ids2str([f[0] for f in facts]))
media = self.deck.s.all("""
select id, created from media""")
return {
# cards
"cards": cards,
"delcards": [],
# facts
"facts": facts,
"delfacts": [],
# models
"models": models,
"delmodels": [],
# media
"media": media,
"delmedia": [],
}
class TextCardExporter(Exporter):
key = _("Text files (*.txt)")
ext = ".txt"
def __init__(self, deck):
Exporter.__init__(self, deck)
self.includeTags = False
def doExport(self, file):
ids = self.cardIds()
strids = ids2str(ids)
self.deck.startProgress((len(ids) + 1) / 50)
self.deck.updateProgress(_("Exporting..."))
cards = self.deck.s.all("""
select cards.question, cards.answer, cards.id from cards
where cards.id in %s
order by cards.created""" % strids)
self.deck.updateProgress()
if self.includeTags:
self.cardTags = dict(self.deck.s.all("""
select cards.id, facts.tags from cards, facts
where cards.factId = facts.id
and cards.id in %s
order by cards.created""" % strids))
out = u"\n".join(["%s\t%s%s" % (
self.escapeText(c[0], removeFields=True),
self.escapeText(c[1], removeFields=True),
self.tags(c[2]))
for c in cards])
if out:
out += "\n"
file.write(out.encode("utf-8"))
self.deck.finishProgress()
def tags(self, id):
if self.includeTags:
return "\t" + ", ".join(parseTags(self.cardTags[id]))
return ""
class TextFactExporter(Exporter):
key = _("Text files (*.txt)")
ext = ".txt"
def __init__(self, deck):
Exporter.__init__(self, deck)
self.includeTags = False
def doExport(self, file):
cardIds = self.cardIds()
self.deck.startProgress()
self.deck.updateProgress(_("Exporting..."))
facts = self.deck.s.all("""
select factId, value, facts.created from facts, fields
where
facts.id in
(select distinct factId from cards
where cards.id in %s)
and facts.id = fields.factId
order by factId, ordinal""" % ids2str(cardIds))
txt = ""
self.deck.updateProgress()
if self.includeTags:
self.factTags = dict(self.deck.s.all(
"select id, tags from facts where id in %s" %
ids2str([fact[0] for fact in facts])))
groups = itertools.groupby(facts, itemgetter(0))
groups = [[x for x in y[1]] for y in groups]
groups = [(group[0][2],
"\t".join([self.escapeText(x[1]) for x in group]) +
self.tags(group[0][0]))
for group in groups]
self.deck.updateProgress()
groups.sort(key=itemgetter(0))
out = [ret[1] for ret in groups]
self.count = len(out)
out = "\n".join(out)
file.write(out.encode("utf-8"))
self.deck.finishProgress()
def tags(self, id):
if self.includeTags:
return "\t" + self.factTags[id]
return ""
# Export modules
##########################################################################
def exporters():
return (
(_("Anki Deck (*.anki)"), AnkiExporter),
(_("Cards in tab-separated text file (*.txt)"), TextCardExporter),
(_("Facts in tab-separated text file (*.txt)"), TextFactExporter))