wordpress 禁止注册,网站seo关键词优化排名,保定做公司网站的,python线上培训班学费一般多少在Anki中进行复习时#xff0c;每次只能打开一条笔记。如果积累了很多笔记#xff0c;有时候会有将它们集中输出成一个pdf进行阅读的想法。Anki插件Export deck to html#xff08;安装ID#xff1a;1897277426#xff09;就有这个功能。但是#xff0c;这个插件目前存在…在Anki中进行复习时每次只能打开一条笔记。如果积累了很多笔记有时候会有将它们集中输出成一个pdf进行阅读的想法。Anki插件Export deck to html安装ID1897277426就有这个功能。但是这个插件目前存在以下问题
1、Anki升级为版本 24.06.3 (d678e393)后插件无法正常运行也许更早的版本就这样我没试过
2、插件转pdf的效果不是很好但转html的效果不错。考虑到html转pdf非常容易word即可完成多数浏览器在插件支持下或无需插件也能完成所以插件的转pdf功能比较鸡肋
3、笔记中的img标签在转换为html后除了“src”属性得以保留其余的属性会全部丢失。
4、输出的html在每一条笔记前添加了没有用处的前缀“y”
鉴于上述问题所以对该插件的主文件ExportDeckToHtml.py进行了修改。具体修改的内容包括
1、将不再兼容新版Anki的几行代码进行修改和删除其中包括
1dialog.exec_()修改为dialog.exec()
2options QFileDialog.DontUseNativeDialog删除
3path QFileDialog.getSaveFileName( self, Save File, directory, All Files (*), optionsoptions) 修改为path QFileDialog.getSaveFileName( self, Save File, directory, All Files (*))
2、修改_setup_ui函数取消界面上的保存为pdf等元素。
3、修改_export_to_html函数在处理卡片的html中的img标签时只将src属性中的路径修改为绝对路径而src属性之外的其他属性保持不变。
4、修改每条笔记的html增加笔记序号信息删掉无用前缀。
修改后的ExportDeckToHtml.py文件内容如下
from aqt import mw, utils
from aqt.qt import *
from os.path import expanduser, join
from pickle import load, dumpimport os
import re
import unicodedata
from .pdfkit import from_stringdelimiter ####ascending Ascending
descending Descending
config_file export_decks_to_html_config.cfgclass AddonDialog(QDialog):Main Options dialogdef __init__(self):global config_fileQDialog.__init__(self, parentmw)self.path Noneself.deck Noneself.fields {}self.card_orders [ascending, descending]self.order_fn Noneself.advance_mode Falseif os.path.exists(config_file):try:self.config load(open(config_file, rb))except:self.config {}else:self.config {}self._setup_ui()def _handle_button(self):dialog OpenFileDialog()self.path dialog.filenameif self.path is not None:utils.showInfo(Choose file successful.)def _handle_load_template(self):dialog OpenFileDialog()self.advance_mode Falseself.template_path dialog.filenameif self.template_path is not None and len(self.template_path) 0:utils.showInfo(Choose file successful.)self.template_label.setText(self.template_path)def _setup_ui(self):Set up widgets and layoutslayout QGridLayout()layout.setSpacing(10)deck_label QLabel(Choose deck)# deck nameself.deck_selection QComboBox()deck_names sorted(mw.col.decks.allNames())current_deck mw.col.decks.current()[name]deck_names.insert(0, current_deck)for i in range(len(deck_names)):if deck_names[i] Default:deck_names.pop(i)breakself.deck_selection.addItems(deck_names)self.deck_selection.currentIndexChanged.connect(self._select_deck)layout.addWidget(deck_label, 1, 0, 1, 1)layout.addWidget(self.deck_selection, 1, 1, 1, 2)export_dir self.config.get(export_dir, expanduser(~/Desktop))self.export_dir QLineEdit(export_dir)field_label QLabel(Sort)self.field_selection QComboBox()fields self._select_fields(self.deck_selection.currentText())if self.deck_selection.currentText() in self.config:currentField self.config[self.deck_selection.currentText()].get(field_selection, )if len(currentField) 0:if currentField in fields:fields.remove(currentField)fields.insert(0, currentField)self.field_selection.addItems(fields)layout.addWidget(field_label, 2, 0, 1, 1)layout.addWidget(self.field_selection, 2, 1, 1, 2)template_path if self.deck_selection.currentText() in self.config:template_path self.config[self.deck_selection.currentText()].get(template_path, )self.template_label QLabel(template_path)# orderorder_label QLabel(Order)self.order_selection QComboBox()orders self.card_orders[:]if self.deck_selection.currentText() in self.config:currentOrder self.config[self.deck_selection.currentText()].get(order_selection, )if len(currentOrder) 0:orders.remove(currentOrder)orders.insert(0, currentOrder)self.order_selection.addItems(orders)self.order_selection.currentIndexChanged.connect(self._handle_order_card)layout.addWidget(order_label, 3, 0, 1, 1)layout.addWidget(self.order_selection, 3, 1, 1, 2)self.load_template_btn QPushButton(Load template)self.load_template_btn.clicked.connect(self._handle_load_template)layout.addWidget(self.load_template_btn, 4, 0, 1, 1)layout.addWidget(self.template_label, 4, 1, 1, 2)self.to_pdf Falselayout.addWidget(self.export_dir, 5, 1, 1, 2)export_dir_label QLabel(Export directory)layout.addWidget(export_dir_label, 5, 0, 1, 1)# Main button boxok_btn QPushButton(Export)save_btn QPushButton(Save)cancel_btn QPushButton(Cancel)button_box QHBoxLayout()ok_btn.clicked.connect(self._on_accept)save_btn.clicked.connect(self._on_save)cancel_btn.clicked.connect(self._on_reject)button_box.addWidget(ok_btn)button_box.addWidget(save_btn)button_box.addWidget(cancel_btn)# Main layoutmain_layout QVBoxLayout()main_layout.addLayout(layout)main_layout.addLayout(button_box)self.setLayout(main_layout)self.setMinimumWidth(360)self.setWindowTitle(Export deck to html)def _reset_advance_mode(self):self.advance_mode Falseself.csv_file_label.setText()def _to_pdf(self):self.to_pdf not self.to_pdfdef _handle_adv_mode(self):dialog OpenFileDialog(csv)self.path dialog.filenameif self.path is not None and len(self.path) 0:utils.showInfo(Choose file successful.)self.advance_mode Trueself.csv_file_label.setText(self.path)def _select_deck(self):current_deck self.deck_selection.currentText()fields self._select_fields(current_deck)if self.deck_selection.currentText() in self.config:currentField self.config[current_deck].get(field_selection, )if len(currentField) 0:fields.remove(currentField)fields.insert(0, currentField)self.field_selection.clear()self.field_selection.addItems(fields)orders self.card_orders[:]if current_deck in self.config:currentOrder self.config[current_deck].get(order_selection, )if len(currentOrder) 0:orders.remove(currentOrder)orders.insert(0, currentOrder)self.order_selection.clear()self.order_selection.addItems(orders)template_path if current_deck in self.config:template_path self.config[current_deck].get(template_path, )self.template_label.setText(template_path)def _on_save(self):global config_filecurrent_deck self.deck_selection.currentText()self.config[current_deck] {}self.config[current_deck][template_path] self.template_label.text()self.config[current_deck][field_selection] self.field_selection.currentText()self.config[current_deck][order_selection] self.order_selection.currentText()self.config[current_deck][to_pdf] self.to_pdfself.config[export_dir] self.export_dir.text()dump(self.config, open(config_file, wb))utils.showInfo(Config saved)def _convert_to_multiple_choices(self, value):choices value.split(|)letters ABCDEFGHIKLMNOPvalue divfor letter, choice in zip(letters, choices):value div \spanstrong( letter )nbsp/strong/span \choice.strip() /divreturn value /divdef _select_fields(self, deck):query deck:{}.format(deck)try:card_id mw.col.findCards(queryquery)[0]except:utils.showInfo(This deck has no cards.)return []card mw.col.getCard(card_id)fields card.note().keys()return [Due, ] fieldsdef _handle_order_card(self):self.order_fn self._order_card(self.order_selection.currentText())def _order_card(self, order_by):def f(field):def g(card):try:if field Due:return card.duereturn card.note()[field]except KeyError:return return gdef ascending_fn(cards, field):return sorted(cards, keyf(field))def descending_fn(cards, field):return sorted(cards, keyf(field), reverseTrue)if order_by ascending:return ascending_fnreturn descending_fndef _get_all_cards(self, deck_name, field, order_fn):deck_name deck_name.replace(, )deck_name unicodedata.normalize(NFC, deck_name)deck mw.col.decks.byName(deck_name)if deck None:returndecks [deck_name, ]if len(mw.col.decks.children(deck[id])) ! 0:decks [name for (name, _) in mw.col.decks.children(deck[id])]decks sorted(decks)all_cards []for deck in decks:query deck:{}.format(deck)cids mw.col.findCards(queryquery)cards []for cid in cids:card mw.col.getCard(cid)cards.append(card)all_cards.extend(cards)if order_fn is not None:return order_fn(all_cards, field)return all_cardsdef _export_to_html(self, output_path, deck_name, sort_by, order, template_path, export_to_pdfTrue):# html_path self.template_label.text()if template_path is None or len(template_path) 0:return Falseorder_fn self._order_card(order)cards self._get_all_cards(deck_name, sort_by, order_fn)if cards is None or len(cards) 0:return Falsehtml_template with open(template_path, r, encodingutf-8) as f:html_template f.read()header, body, has_table self._separate_header_and_body(html_template)collection_path mw.col.media.dir()path output_pathtry:html template bodyfields re.findall(\{\{[^\}]*\}\}, template)dedup set()for i, card in enumerate(cards):card_html templatecard_html card_html.replace({{id}}, str(i 1))key for field in fields:if field {{id}}:continuetry:value card.note()[field[2:-2]]key valueexcept KeyError:value ## field field not found ##card_html card_html.replace(field, value)# 将html中img标签的src属性中的相对路径全部替换为绝对路径pattern re.compile(rimg.*?src(.*?).*?, re.I | re.M)for match in re.finditer(pattern, card_html):relative_path match.group(1)absolute_path f{collection_path}\\{relative_path}card_html card_html.replace(relative_path, absolute_path)if key not in dedup:html span classred第 str(i 1) 条:/span card_html[2:]dedup.add(key)if not has_table:html header \nbody html /bodyelse:html header \nbody\n\ttable html \t/table\n/bodyif not export_to_pdf:with open(path, w, encodingutf8) as f:f.write(html)else:options {# header-left: [webpage],# header-right: [page]/[toPage],# header-line: ,# header-font-size: 10margin-bottom: 15,margin-left: 10,margin-right: 10,margin-top: 15,footer-center: [page],footer-font-size: 8,footer-spacing: 5,}from_string(html, path, options)except IOError as e:return Falsereturn Truedef _on_accept(self):if not self.advance_mode:dialog SaveFileDialog(self.deck_selection.currentText(), self.export_dir.text(), self.to_pdf)file_path dialog.filenameif file_path None:returnif type(file_path) is tuple:file_path file_path[0]template_path self.template_label.text()if template_path is None or len(template_path) 0:utils.showInfo(Cannot find template)returncan_export self._export_to_html(join(self.export_dir.text(), file_path),self.deck_selection.currentText(),self.field_selection.currentText(),self.order_selection.currentText(),template_path,self.to_pdf)if not can_export:utils.showInfo(Cannot export)else:utils.showInfo(Exported successfully)else:with open(self.path, r, encodingutf-8) as f:i 0non_exist_decks []non_exist_files []for line in f:if i 0:i 1continuedeck_name, output_dir, output_name, sort_by, order, template_path, to_pdf \line.split(,)[:7]if output_name is None and len(output_name) 0:output_name deck_nameif not os.path.isfile(template_path):non_exist_files.append(template_path)continueto_pdf True if standardize(to_pdf).lower() true else Falsecan_export self._export_to_html(join(standardize(output_dir),standardize(output_name)),standardize(deck_name),standardize(sort_by),standardize(order),standardize(template_path),to_pdf)if not can_export:non_exist_decks.append(deck_name)if len(non_exist_decks) 0:utils.showInfo(Non existing decks\n \n.join(non_exist_decks))returnif len(non_exist_files) 0:utils.showInfo(Non existing files\n \n.join(non_exist_files))returnutils.showInfo(Exported successfully)def _on_reject(self):self.close()def _separate_header_and_body(self, hl):last_header hl.find(/head)last_header len(/head)body hl[last_header:]first body.find(table)last body.rfind(/table)if first -1 or last -1:first body.find(table) len(body)last body.find(/body)has_table Falseelse:first first len(table)has_table Truereturn hl[:last_header][:], body[first:last], has_tableclass SaveFileDialog(QDialog):def __init__(self, filename, export_direxpanduser(~/Desktop/), to_pdfFalse):QDialog.__init__(self, mw)self.title Save Fileself.left 10self.top 10self.width 640self.height 480self.filename Noneself.default_filename filenameself.to_pdf to_pdfself.export_dir export_dirself._init_ui()def _init_ui(self):self.setWindowTitle(self.title)self.setGeometry(self.left, self.top, self.width, self.height)self.filename self._get_file()def _get_file(self):# options QFileDialog.Options()# 升级后QFileDialog不存在DontUseNativeDialog属性# options QFileDialog.DontUseNativeDialogdefault_filename self.default_filename.replace(::, _)if not self.to_pdf:directory join(self.export_dir, default_filename .html)else:directory join(self.export_dir, default_filename .pdf)try:path QFileDialog.getSaveFileName(# 取消options参数# self, Save File, directory, All Files (*), optionsoptions)self, Save File, directory, All Files (*))if path:return pathelse:utils.showInfo(Cannot open this file.)except:utils.showInfo(Cannot open this file.)return Noneclass OpenFileDialog(QDialog):def __init__(self, file_typehtml):QDialog.__init__(self, mw)self.title Open fileself.left 10self.top 10self.width 640self.height 480self.filename Noneself.file_type file_typeself._init_ui()def _init_ui(self):self.setWindowTitle(self.title)self.setGeometry(self.left, self.top, self.width, self.height)self.filename self._get_file()# self.exec_()def _get_file(self):# options QFileDialog.Options()# 升级后QFileDialog不存在DontUseNativeDialog属性# options QFileDialog.DontUseNativeDialogdirectory expanduser(~/Desktop)try:if self.file_type html:path QFileDialog.getOpenFileName(# 取消options参数# self, Save File, directory, All Files (*), optionsoptions)self, Save File, directory, All Files (*))elif self.file_type csv:path QFileDialog.getOpenFileName(# 取消options参数# self, Save File, directory, All Files (*), optionsoptions)self, Save File, directory, All Files (*))if path and path[0]:return path[0]else:utils.showInfo(Cannot open this file.)except:utils.showInfo(Cannot open this file.)return Nonedef display_dialog():dialog AddonDialog()dialog.exec()# 原来方法名exec_错误多了下划线# dialog.exec_()def standardize(word):return word.strip()action QAction(Export deck to html, mw)
action.setShortcut(CtrlM)
action.triggered.connect(display_dialog)
mw.form.menuTools.addAction(action)只需安装该插件然后打开插件文件夹编辑ExportDeckToHtml.py文件将其内容全部替换为以上代码即可。在使用此插件时需要提前准备一个html模板。我用于导出基于对兼容各操作系统的Anki选择题模板的更新——提供更方便的笔记修改功能-CSDN博客一文中的模板所编写的笔记牌组的html模板如下可供参考
!DOCTYPE html
html
headstylebody {font-size: 1.2em;width: 19.7cm;}table {border-collapse: collapse;}table tr:nth-child(2n1) {background-color: #eee;}td {padding: 5px;text-align: center;border: 2px solid green;vertical-align: middle;}td.left {text-align: left;}td.red {border-right: solid thick red;}hr {border: none;height: 5px;background-color: blue;}div {margin: 5px auto}a,a:visited,a:hover,a:link,a:active {color: #f90;font-weight: bold;font-family: Cambria-modify, 干就完事了简, 微软雅黑;}.pink {font-family: 黑体;font-weight: bold;font-size: 1.2em;}u,.red {color: #f00;font-weight: bold;text-decoration: none;font-family: Cambria-modify, 干就完事了简, 微软雅黑;}.green,i {font-weight: bold;font-style: normal;color: #3bb;font-family: Cambria-modify, Aa奇思胖丫儿, 微软雅黑;}.blue,b {font-weight: bold;font-style: normal;color: #39e;font-family: Cambria-modify, 微软雅黑;}img {display: block;object-fit: scale-down;}/style
/headbodydivspan classpink【题干】/span{{问题}}/divspan classpink【选项】/spandiv{{选项}}/divdivspan classpink【答案】/span{{答案}}/divspan classpink【解析】/spandiv{{解析}}/divhr
/body/html
基于以上模板输出html的操作过程如下 导出的html效果如下 顺便说一句在试用了十数个Anki插件后我只保留了两个Edit field during review和Export deck to html。如果有其他便于Anki使用的插件欢迎留言推荐如有改造相关插件的想法也欢迎留言我可能会试着帮你实现。