问题描述
IOS/Mac有MarginNote,Windows有BookxNote他们都支持将笔记导出为apkg格式,他们是如何实现的呢?
Anki 代码是开源的,题主学计算机的可以自己看一下哦[滑稽]
代码如下
# Packaged Anki decks
######################################################################
class AnkiPackageExporter(AnkiExporter):
key = lambda: _("Anki Deck Package")
ext = ".apkg"
def __init__(self, col: Collection) -> None:
AnkiExporter.__init__(self, col)
def exportInto(self, path: str) -> None:
# open a zip file
z = zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED, allowZip64=True)
media = self.doExport(z, path)
# media map
z.writestr("media", json.dumps(media))
z.close()
def doExport(self, z: ZipFile, path: str) -> Dict[str, str]: # type: ignore
# export into the anki2 file
colfile = path.replace(".apkg", ".anki2")
AnkiExporter.exportInto(self, colfile)
if not self._v2sched:
z.write(colfile, "collection.anki2")
else:
# prevent older clients from accessing
# pylint: disable=unreachable
self._addDummyCollection(z)
z.write(colfile, "collection.anki21")
# and media
self.prepareMedia()
media = self._exportMedia(z, self.mediaFiles, self.mediaDir)
# tidy up intermediate files
os.unlink(colfile)
p = path.replace(".apkg", ".media.db2")
if os.path.exists(p):
os.unlink(p)
os.chdir(self.mediaDir)
shutil.rmtree(path.replace(".apkg", ".media"))
return media
def _exportMedia(self, z: ZipFile, files: List[str], fdir: str) -> Dict[str, str]:
media = {}
for c, file in enumerate(files):
cStr = str(c)
mpath = os.path.join(fdir, file)
if os.path.isdir(mpath):
continue
if os.path.exists(mpath):
if re.search(r"\.svg$", file, re.IGNORECASE):
z.write(mpath, cStr, zipfile.ZIP_DEFLATED)
else:
z.write(mpath, cStr, zipfile.ZIP_STORED)
media[cStr] = unicodedata.normalize("NFC", file)
hooks.media_files_did_export(c)
return media
def prepareMedia(self) -> None:
# chance to move each file in self.mediaFiles into place before media
# is zipped up
pass
# create a dummy collection to ensure older clients don't try to read
# data they don't understand
def _addDummyCollection(self, zip) -> None:
path = namedtmp("dummy.anki2")
c = Collection(path)
n = c.newNote()
n[_("Front")] = "This file requires a newer version of Anki."
c.addNote(n)
c.save()
c.close(downgrade=True)
zip.write(path, "collection.anki2")
os.unlink(path)
分析代码我们就能看出,其实 apkg 文件是一个压缩包,里面有关于卡片信息的数据库和媒体文件。
我们用 7z 来打开一下 .apkg 文件:
其中,.anki2 的文件就是一个 SQLite 数据库,我们可以用 DB Browser for SQLite 打开看看:
其中,cards 这个表就是卡片信息,notes 表是笔记信息:
然后我们回归正题,如何不用 Anki 创建 apkg 格式的牌组呢?
首先需要创建一个和 Anki2 格式相对应的 SQLite 的数据库,然后用 zip 格式压缩,最后把后缀改成 apkg 即可。
(当然,这是比较笼统的说法,具体做法请研究 Anki 的源码。
2020/8/4 更新:
https://github.com/ankidroid/Anki-Android/wiki/Database-Structure最近在研究 Anki 学习数据,这个数据库的文档帮大忙了。 @小石子 感兴趣可以来看一下。