anki-word-query/addons/fastwq/query/common.py
2018-08-09 19:34:49 +08:00

238 lines
7.3 KiB
Python

#-*- coding:utf-8 -*-
#
# Copyright (C) 2018 sthoo <sth201807@gmail.com>
#
# Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version; http://www.gnu.org/copyleft/gpl.html.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from collections import defaultdict
import os
import shutil
import unicodedata
from PyQt4 import QtGui
from aqt.utils import showInfo
from ..constants import Template
from ..context import config
from ..service import service_pool, QueryResult, copy_static_file
from ..service.base import LocalService
from ..utils import wrap_css
__all__ = [
'InvalidWordException', 'update_note_fields',
'update_note_field', 'promot_choose_css', 'add_to_tmpl',
'query_all_flds', 'inspect_note'
]
class InvalidWordException(Exception):
"""Invalid word exception"""
def inspect_note(note):
"""
inspect the note, and get necessary input parameters
return word_ord: field index of the word in current note
return word: the word
return maps: dicts map of current note
"""
conf = config.get_maps(note.model()['id'])
maps_list = {'list': [conf], 'def': 0} if isinstance(conf, list) else conf
maps = maps_list['list'][maps_list['def']]
for i, m in enumerate(maps):
if m.get('word_checked', False):
word_ord = i
break
else:
# if no field is checked to be the word field, default the
# first one.
word_ord = 0
def purify_word(word):
return word.strip() if word else ''
word = purify_word(note.fields[word_ord])
return word_ord, word, maps
def strip_combining(txt):
"Return txt with all combining characters removed."
norm = unicodedata.normalize('NFKD', txt)
return u"".join([c for c in norm if not unicodedata.combining(c)])
def update_note_fields(note, results):
"""
Update query result to note fields, return updated fields count.
"""
if not results or not note or len(results) == 0:
return 0
count = 0
for i, q in results.items():
if isinstance(q, QueryResult) and i < len(note.fields):
count += update_note_field(note, i, q)
return count
def update_note_field(note, fld_index, fld_result):
"""
Update single field, if result is valid then return 1, else return 0
"""
result, js, jsfile = fld_result.result, fld_result.js, fld_result.jsfile
# js process: add to template of the note model
add_to_tmpl(note, js=js, jsfile=jsfile)
# if not result:
# return
if not config.force_update and not result:
return 0
value = result if result else ''
if note.fields[fld_index] != value:
note.fields[fld_index] = value
return 1
return 0
def promot_choose_css(missed_css):
'''
Choose missed css file and copy to user folder
'''
checked = set()
for css in missed_css:
filename = u'_' + css['file']
if not os.path.exists(filename) and not css['file'] in checked:
checked.add(css['file'])
showInfo(
Template.miss_css.format(
dict=css['title'],
css=css['file']
)
)
try:
filepath = css['dict_path'][:css['dict_path'].rindex(
os.path.sep)+1]
filepath = QtGui.QFileDialog.getOpenFileName(
directory=filepath,
caption=u'Choose css file',
filter=u'CSS (*.css)'
)
if filepath:
shutil.copy(filepath, filename)
wrap_css(filename)
except KeyError:
pass
def add_to_tmpl(note, **kwargs):
# templates
'''
[{u'name': u'Card 1', u'qfmt': u'{{Front}}\n\n', u'did': None, u'bafmt': u'',
u'afmt': u'{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}\n\n{{12}}\n\n{{44}}\n\n', u'ord': 0, u'bqfmt': u''}]
'''
# showInfo(str(kwargs))
afmt = note.model()['tmpls'][0]['afmt']
if kwargs:
jsfile, js = kwargs.get('jsfile', None), kwargs.get('js', None)
if js and js.strip():
addings = js.strip()
if addings not in afmt:
if not addings.startswith(u'<script') and not addings.endswith(u'/script>'):
addings = u'\r\n<script>{}</script>'.format(addings)
afmt += addings
if jsfile:
new_jsfile = u'_' + \
jsfile if not jsfile.startswith(u'_') else jsfile
copy_static_file(jsfile, new_jsfile)
addings = u'\r\n<script src="{}"></script>'.format(new_jsfile)
afmt += addings
note.model()['tmpls'][0]['afmt'] = afmt
def query_all_flds(note):
"""
Query all fields of single note
"""
word_ord, word, maps = inspect_note(note)
if not word:
raise InvalidWordException
if config.ignore_accents:
word = strip_combining(unicode(word))
# progress.update_title(u'Querying [[ %s ]]' % word)
services = {}
tasks = []
for i, each in enumerate(maps):
if i == word_ord:
continue
if i == len(note.fields):
break
#ignore field
ignore = each.get('ignore', False)
if ignore:
continue
#skip valued
skip = each.get('skip_valued', False)
if skip and len(note.fields[i]) != 0:
continue
#normal
dict_unique = each.get('dict_unique', '').strip()
dict_fld_ord = each.get('dict_fld_ord', -1)
fld_ord = each.get('fld_ord', -1)
if dict_unique and dict_fld_ord != -1 and fld_ord != -1:
s = services.get(dict_unique, None)
if s is None:
s = service_pool.get(dict_unique)
if s and s.support:
services[dict_unique] = s
if s and s.support:
tasks.append({'k': dict_unique, 'w': word,
'f': dict_fld_ord, 'i': fld_ord})
success_num = 0
result = defaultdict(QueryResult)
for task in tasks:
#try:
service = services.get(task['k'], None)
qr = service.active(task['f'], task['w'])
if qr:
result.update({task['i']: qr})
success_num += 1
#except:
# showInfo(_("NO_QUERY_WORD"))
# pass
missed_css = list()
for service in services.values():
if isinstance(service, LocalService):
for css in service.missed_css:
missed_css.append({
'dict_path': service.dict_path,
'title': service.title,
'file': css
})
service_pool.put(service)
return result, -1 if len(tasks) == 0 else success_num, missed_css