def dupeOrEmpty(self): "1 if first is empty; 2 if first is a duplicate, False otherwise." val = self.fields[0] if not val.strip(): return 1 csum = fieldChecksum(val) # find any matching csums and compare for flds in self.col.db.list( "select flds from notes where csum = ? and id != ? and mid = ?", csum, self.id or 0, self.mid ): if stripHTMLMedia(splitFields(flds)[0]) == stripHTMLMedia(self.fields[0]): return 2 return False
def dupeOrEmpty(self): "1 if first is empty; 2 if first is a duplicate, False otherwise." val = self.fields[0] if not val.strip(): return 1 csum = fieldChecksum(val) # find any matching csums and compare for flds in self.col.db.list( "select flds from notes where csum = ? and id != ? and mid = ?", csum, self.id or 0, self.mid): if stripHTMLMedia(splitFields(flds)[0]) == stripHTMLMedia( self.fields[0]): return 2 return False
def showDupes(self): contents = stripHTMLMedia(self.note.fields[0]) browser = aqt.dialogs.open("Browser", self.mw) browser.form.searchEdit.lineEdit().setText( '"dupe:%s,%s"' % (self.note.model()['id'], contents)) browser.onSearch()
def findDupes(col, fieldName, search=""): # limit search to notes with applicable field name if search: search = "("+search+") " search += "'%s:*'" % fieldName # go through notes vals = {} dupes = [] fields = {} def ordForMid(mid): if mid not in fields: model = col.models.get(mid) for c, f in enumerate(model['flds']): if f['name'].lower() == fieldName.lower(): fields[mid] = c break return fields[mid] for nid, mid, flds in col.db.all( "select id, mid, flds from notes where id in "+ids2str( col.findNotes(search))): flds = splitFields(flds) ord = ordForMid(mid) if ord is None: continue val = flds[ord] val = stripHTMLMedia(val) # empty does not count as duplicate if not val: continue if val not in vals: vals[val] = [] vals[val].append(nid) if len(vals[val]) == 2: dupes.append((val, vals[val])) return dupes
def findDupes(self, fieldName: str, search: str = "") -> List[Tuple[Any, list]]: nids = self.findNotes(search, SearchNode(field_name=fieldName)) # go through notes vals: Dict[str, List[int]] = {} dupes = [] fields: Dict[int, int] = {} def ordForMid(mid: int) -> int: if mid not in fields: model = self.models.get(mid) for c, f in enumerate(model["flds"]): if f["name"].lower() == fieldName.lower(): fields[mid] = c break return fields[mid] for nid, mid, flds in self.db.all( f"select id, mid, flds from notes where id in {ids2str(nids)}" ): flds = splitFields(flds) ord = ordForMid(mid) if ord is None: continue val = flds[ord] val = stripHTMLMedia(val) # empty does not count as duplicate if not val: continue vals.setdefault(val, []).append(nid) if len(vals[val]) == 2: dupes.append((val, vals[val])) return dupes
def showDupes(self): contents = stripHTMLMedia(self.note.fields[0]) browser = aqt.dialogs.open("Browser", self.mw) browser.form.searchEdit.lineEdit().setText( '"dupe:%s,%s"' % (self.note.model()['id'], contents)) browser.onSearchActivated()
def flush(self, mod=None): """If fields or tags have changed, write changes to disk. If there exists a note with same id, tags and fields, and mod is not set, do nothing. Change the mod to given argument or current time Change the USNk If the not is not new, according to _preFlush, generate the cards Add its tag to the collection Add the note in the db Keyword arguments: mod -- A modification timestamp""" assert self.scm == self.col.scm self._preFlush() sfld = stripHTMLMedia(self.fields[self.col.models.sortIdx(self._model)]) tags = self.stringTags() fields = self.joinedFields() if not mod and self.col.db.scalar( "select 1 from notes where id = ? and tags = ? and flds = ?", self.id, tags, fields): return csum = fieldChecksum(self.fields[0]) self.mod = mod if mod else intTime() self.usn = self.col.usn() res = self.col.db.execute(""" insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)""", self.id, self.guid, self.mid, self.mod, self.usn, tags, fields, sfld, csum, self.flags, self.data) self.col.tags.register(self.tags) self._postFlush()
def render_sections(self, template, context): """Expands sections.""" while 1: match = self.section_re.search(template) if match is None: break section, section_name, inner = match.group(0, 1, 2) section_name = section_name.strip() # check for cloze val = None m = re.match(r"c[qa]:(\d+):(.+)", section_name) if m: # get full field text txt = get_or_attr(context, m.group(2), None) m = re.search(clozeReg%m.group(1), txt) if m: val = m.group(1) else: val = get_or_attr(context, section_name, None) replacer = '' inverted = section[2] == "^" if val: val = stripHTMLMedia(val).strip() if (val and not inverted) or (not val and inverted): replacer = inner template = template.replace(section, replacer) return template
def render_sections(self, template, context) -> str: """Expands sections.""" while 1: match = self.section_re.search(template) if match is None: break section, section_name, inner = match.group(0, 1, 2) section_name = section_name.strip() # check for cloze val = None m = re.match(r"c[qa]:(\d+):(.+)", section_name) if m: # get full field text txt = get_or_attr(context, m.group(2), None) m = re.search(clozeReg % m.group(1), txt) if m: val = m.group(CLOZE_REGEX_MATCH_GROUP_TAG) else: val = get_or_attr(context, section_name, None) replacer = "" inverted = section[2] == "^" if val: val = stripHTMLMedia(val).strip() if (val and not inverted) or (not val and inverted): replacer = inner template = template.replace(section, replacer) return template
def flush(self, mod=None): "If fields or tags have changed, write changes to disk." assert self.scm == self.col.scm self._preFlush() sfld = stripHTMLMedia(self.fields[self.col.models.sortIdx(self._model)]) tags = self.stringTags() fields = self.joinedFields() if not mod and self.col.db.scalar( "select 1 from notes where id = ? and tags = ? and flds = ?", self.id, tags, fields ): return csum = fieldChecksum(self.fields[0]) self.mod = mod if mod else intTime() self.usn = self.col.usn() res = self.col.db.execute( """ insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)""", self.id, self.guid, self.mid, self.mod, self.usn, tags, fields, sfld, csum, self.flags, self.data, ) self.col.tags.register(self.tags) self._postFlush()
def findDupes(col, fieldName, search="") -> List[Tuple[Any, List]]: # limit search to notes with applicable field name if search: search = "(" + search + ") " search += "'%s:*'" % fieldName # go through notes vals: Dict[str, List[int]] = {} dupes = [] fields: Dict[int, int] = {} def ordForMid(mid): if mid not in fields: model = col.models.get(mid) for c, f in enumerate(model["flds"]): if f["name"].lower() == fieldName.lower(): fields[mid] = c break return fields[mid] for nid, mid, flds in col.db.all( "select id, mid, flds from notes where id in " + ids2str(col.findNotes(search)) ): flds = splitFields(flds) ord = ordForMid(mid) if ord is None: continue val = flds[ord] val = stripHTMLMedia(val) # empty does not count as duplicate if not val: continue vals.setdefault(val, []).append(nid) if len(vals[val]) == 2: dupes.append((val, vals[val])) return dupes
def showDupes(self): contents = html.escape(stripHTMLMedia(self.note.fields[0]), quote=False).replace('"', r"\"") browser = aqt.dialogs.open("Browser", self.mw) browser.form.searchEdit.lineEdit().setText( '"dupe:%s,%s"' % (self.note.model()["id"], contents)) browser.onSearchActivated()
def flush(self, mod: Optional[int] = None) -> None: "If fields or tags have changed, write changes to disk." assert self.scm == self.col.scm self._preFlush() sfld = stripHTMLMedia(self.fields[self.col.models.sortIdx( self._model)]) tags = self.stringTags() fields = self.joinedFields() if not mod and self.col.db.scalar( "select 1 from notes where id = ? and tags = ? and flds = ?", self.id, tags, fields, ): return csum = fieldChecksum(self.fields[0]) self.mod = mod if mod else intTime() self.usn = self.col.usn() res = self.col.db.execute( """ insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)""", self.id, self.guid, self.mid, self.mod, self.usn, tags, fields, sfld, csum, self.flags, self.data, ) self.col.tags.register(self.tags) self._postFlush()
def sub_section(self, match, context): section, section_name, inner = match.group(0, 1, 2) section_name = section_name.strip() # val will contain the content of the field considered # right now val = None m = re.match("c[qa]:(\d+):(.+)", section_name) if m: # get full field text txt = get_or_attr(context, m.group(2), None) m = re.search(clozeReg % m.group(1), txt) if m: val = m.group(1) else: val = get_or_attr(context, section_name, None) replacer = '' # Whether it's {{^ inverted = section[2] == "^" # Ensuring we don't consider whitespace in wval if val: val = stripHTMLMedia(val).strip() if (val and not inverted) or (not val and inverted): replacer = inner return replacer
def flush(self, mod=None): "If fields or tags have changed, write changes to disk." assert self.scm == self.col.scm self.newlyAdded = (self.id is None) sfld = stripHTMLMedia(self.fields[self.col.models.sortIdx( self._model)]) tags = self.stringTags() fields = self.joinedFields() if not mod and self.col.db.scalar( "select 1 from notes where id = ? and tags = ? and flds = ?", self.id, tags, fields): return csum = fieldChecksum(self.fields[0]) self.mod = mod if mod else intTime() self.usn = self.col.usn() if self.id is None: self.id = timestampID(self.col.db, "notes") self.col.db.execute( """insert into notes values (?,?,?,?,?,?,?,?,?,?,?)""", self.id, self.guid, self.mid, self.mod, self.usn, tags, fields, sfld, csum, self.flags, self.data) else: self.col.db.execute( """update notes set guid=?, mid=?, mod=?, usn=?, tags=?, flds=?, sfld=?, csum=?, flags=?, data=? where id = ?""", self.guid, self.mid, self.mod, self.usn, tags, fields, sfld, csum, self.flags, self.data, self.id) self.col.tags.register(self.tags) self._postFlush()
def showDupes(self): contents = stripHTMLMedia(self.note.fields[0]) browser = aqt.dialogs.open("Browser", self.mw) browser.form.searchEdit.lineEdit().setText( "'note:%s' '%s:%s'" % (self.note.model()['name'], self.note.model()['flds'][0]['name'], contents)) browser.onSearch()
def formatQA(self, txt): s = txt.replace("<br>", u" ") s = s.replace("<br />", u" ") s = s.replace("\n", u" ") s = re.sub("\[sound:[^]]+\]", "", s) s = stripHTMLMedia(s) s = s.strip() return s
def showDupes(self): contents = stripHTMLMedia(self.note.fields[0]) browser = aqt.dialogs.open("Browser", self.mw) browser.form.searchEdit.lineEdit().setText( "'note:%s' '%s:%s'" % ( self.note.model()['name'], self.note.model()['flds'][0]['name'], contents)) browser.onSearch()
def onGetSoundFile(editor): datasource.setConfig(get_config()['profiles'][mw.pm.name]) word = stripHTMLMedia(getWordToLookup(editor)) filename = datasource.lookup(word) if filename: if datasource.use_text_selection and editor.web.hasSelection(): editor.web.triggerPageAction(QWebEnginePage.Unselect) editor.addMedia(filename) else: showWarning('{0}: no sound data found'.format(word, title='Sound Files'))
def htmlToTextLine(s): s = s.replace("<br>", " ") s = s.replace("<br />", " ") s = s.replace("<div>", " ") s = s.replace("\n", " ") s = re.sub("\[sound:[^]]+\]", "", s) s = re.sub("\[\[type:[^]]+\]\]", "", s) s = stripHTMLMedia(s) s = s.strip() return s
def from_anki(cls, local_id): if is_remote_note(local_id): remote_id = get_remote_note_id(local_id) else: remote_id = None anki_note = mw.col.getNote(local_id) tags = anki_note.tags fields = anki_note.joinedFields() model_id = anki_note.mid sfld = stripHTMLMedia(anki_note.fields[anki_note.col.models.sortIdx(anki_note._model)]) return cls(tags, fields, model_id, sfld, remote_id, local_id)
def setModified(self, textChanged=False): "Mark modified and update cards." self.modified = time.time() if textChanged: d = {} for f in self.model.fieldModels: d[f.name] = (f.id, self[f.name]) self.spaceUntil = stripHTMLMedia(u" ".join([x[1] for x in d.values()])) for card in self.cards: qa = formatQA(None, self.modelId, d, card.splitTags(), card.cardModel) card.question = qa['question'] card.answer = qa['answer'] card.setModified()
def onHistory(self): m = QMenu(self) for nid in self.history: if self.mw.col.findNotes("nid:%s" % nid): fields = self.mw.col.getNote(nid).fields txt = stripHTMLMedia(",".join(fields))[:30] a = m.addAction(_("Edit %s") % txt) a.triggered.connect(lambda b, nid=nid: self.editHistory(nid)) else: a = m.addAction(_("(Note deleted)")) a.setEnabled(False) runHook("AddCards.onHistory", self, m) m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
def exportDeck(): deckId = chooseDeck(prompt="Choose deck to export scheduling from") if deckId == 0: return cids = mw.col.decks.cids(deckId, children=False) cards = {} for cid in cids: card = mw.col.getCard(cid) note = mw.col.getNote(card.nid) sfld = stripHTMLMedia(note.fields[note.col.models.sortIdx( note._model)]) # Skip new cards if card.queue == 0: continue if sfld in cards: showText("Card with duplicated field found; aborting.") return revlogKeys = ["id", "ease", "ivl", "lastIvl", "factor", "time", "type"] revlogsArray = mw.col.db.all( "select " + ", ".join(revlogKeys) + " from revlog " + "where cid = ?", cid) revlogsDict = list( map( lambda row: {revlogKeys[i]: row[i] for i in range(0, len(revlogKeys))}, revlogsArray)) cards[sfld] = dict(due=card.due, queue=card.queue, ivl=card.ivl, factor=card.factor, left=card.left, type=card.type, lapses=card.lapses, reps=card.reps, flags=card.flags, revlogs=revlogsDict) file = getSaveFile( mw, "Export scheduling info to file", "anki-times", "JSON", ".json", re.sub('[\\\\/?<>:*|"^]', '_', mw.col.decks.name(deckId)) + "-scheduling") if not file: return output = {"meta": {"crt": mw.col.crt}, "cards": cards} with open(file, "w") as f: json.dump(output, f, ensure_ascii=False)
def updateFieldCache(self, nids): "Update field checksums and sort cache, after find&replace, etc." snids = ids2str(nids) r = [] for (nid, mid, flds) in self._fieldData(snids): fields = splitFields(flds) model = self.models.get(mid) if not model: # note points to invalid model continue r.append((stripHTMLMedia(fields[self.models.sortIdx(model)]), fieldChecksum(fields[0]), nid)) # apply, relying on calling code to bump usn+mod self.db.executemany("update notes set sfld=?, csum=? where id=?", r)
def _findDupes(self, val): # caller must call stripHTMLMedia on passed val try: mid, val = val.split(",", 1) except OSError: return csum = fieldChecksum(val) nids = [] for nid, flds in self.col.db.execute( "select id, flds from notes where mid=? and csum=?", mid, csum): if stripHTMLMedia(splitFields(flds)[0]) == val: nids.append(nid) return "n.id in %s" % ids2str(nids)
def dupeOrEmptyWithOrds(self): """ Returns a tuple. The contents of each element are as follows: 1) 1 if first is empty; 2 if first is a duplicate, False otherwise. 2) For a duplicate (2), this returns the list of ordinals that make up the key. Otherwise this is None. """ val = self.fields[0] if not val.strip(): return 1, None csum = fieldChecksum(val) # find any matching csums and compare for flds in self.col.db.list( "select flds from notes where csum = ? and id != ? and mid = ?", csum, self.id or 0, self.mid): model = self.model() field_ords = [0] for fld in model["flds"]: if fld["ord"] == 0: continue elif fld["name"].endswith(KEY_SUFFIX): field_ords.append(fld["ord"]) all_fields_equal = True fields_split = splitFields(flds) for field_ord in field_ords: if stripHTMLMedia(fields_split[field_ord]) != stripHTMLMedia( self.fields[field_ord]): all_fields_equal = False if all_fields_equal: return 2, field_ords return False, None
def showDupes(self): """ Shows the duplicates for the current note in the editor by conducting a search in the browser. This basically performs the normal dupes search that Anki does but appends additional search terms for other keys that have the _pk suffix. """ contents = stripHTMLMedia(self.note.fields[0]) browser = aqt.dialogs.open("Browser", self.mw) model = self.note.model() # Find other notes with the same content for the first field. search_cmds = ['"dupe:%s,%s"' % (model['id'], contents)] # If any other field names end in the special suffix, then they are considered part of the "key" # that uniquely identifies a note. Search for notes that have the same content for these fields, # in addition to having the first field match. for fld in model["flds"]: # First field is already filtered on by the dupe check. if fld["ord"] == 0: continue elif fld["name"].endswith(KEY_SUFFIX): term = stripHTMLMedia(self.note.fields[fld["ord"]]) cmd_args = (fld["name"], term) if '"' in term and "'" in term: # ignore, unfortunately we can't search for it pass elif '"' in term: search_cmds.append("%s:'%s'" % cmd_args) else: search_cmds.append("%s:\"%s\"" % cmd_args) browser.form.searchEdit.lineEdit().setText(" ".join(search_cmds)) browser.onSearchActivated()
def flush(self, mod=None): assert self.scm == self.col.scm self._preFlush() self.mod = mod if mod else intTime() self.usn = self.col.usn() sfld = stripHTMLMedia(self.fields[self.col.models.sortIdx(self._model)]) tags = self.stringTags() csum = fieldChecksum(self.fields[0]) res = self.col.db.execute(""" insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)""", self.id, self.guid, self.mid, self.mod, self.usn, tags, self.joinedFields(), sfld, csum, self.flags, self.data) self.col.tags.register(self.tags) self._postFlush()
def setModified(self, textChanged=False, deck=None, media=True): "Mark modified and update cards." self.modified = time.time() if textChanged: if not deck: # FIXME: compat code import ankiqt if not getattr(ankiqt, 'setModWarningShown', None): import sys; sys.stderr.write( "plugin needs to pass deck to fact.setModified()") ankiqt.setModWarningShown = True deck = ankiqt.mw.deck assert deck self.spaceUntil = stripHTMLMedia(u" ".join( self.values())) for card in self.cards: card.rebuildQA(deck)
def swap_meaning_and_extra_info(): note = current_note_in_review() if not note or MEANING not in note or EXTRA_INFO not in note: return meaning = note[MEANING] extra_info = note[EXTRA_INFO] if levenshtein(stripHTMLMedia(meaning), PRACTICE_SENTENCE) < 5: # PRACTICE SENTENCE!!!! note[MEANING] = extra_info note[EXTRA_INFO] = "" else: note[MEANING] = extra_info note[EXTRA_INFO] = meaning note.flush() reshow_card()
def allSearch(note): model = note.model() sortIdx = note.col.models.sortIdx(model) fields = note.fields allfields = [stripHTMLMedia(field) for field in fields] sfields = [ allfields[idx] for idx in range(len(fields)) if idx != sortIdx and allfields[idx] != fields[idx] ] if allfields[sortIdx] != fields[sortIdx] or getUserOption( "sort field", True): firstField = [allfields[sortIdx]] else: firstField = [] sfields = firstField + sfields sfield = " ".join(sfields) changeSfield(note, sfield)
def setModified(self, textChanged=False, deck=None, media=True): "Mark modified and update cards." self.modified = time.time() if textChanged: if not deck: # FIXME: compat code import ankiqt if not getattr(ankiqt, 'setModWarningShown', None): import sys sys.stderr.write( "plugin needs to pass deck to fact.setModified()") ankiqt.setModWarningShown = True deck = ankiqt.mw.deck assert deck self.spaceUntil = stripHTMLMedia(u" ".join(self.values())) for card in self.cards: card.rebuildQA(deck)
def extract_field(model_id, field_name) -> Iterable[Tuple[int, str]]: # type works better in future anki model = self.models.get(model_id) assert model # type is optional, but None should never come back note_ids = self.findNotes(" ".join(search_filters + [f'note:{model["name"]}'])) field_ord: int = next(field["ord"] for field in model["flds"] if field["name"] == field_name) assert self.db for note_id, fields in self.db.all( "select id, flds from notes where id in " + ids2str(note_ids)): value = splitFields(fields)[field_ord] yield note_id, stripHTMLMedia(value)
def render_sections(self, template, context): """Expands sections.""" while 1: match = self.section_re.search(template) if match is None: break section, section_name, inner = match.group(0, 1, 2) section_name = section_name.strip() # check for cloze m = re.match("c[qa]:(\d+):(.+)", section_name) if m: # get full field text txt = get_or_attr(context, m.group(2), None) m = re.search(clozeReg % m.group(1), txt) if m: it = m.group(1) else: it = None else: it = get_or_attr(context, section_name, None) replacer = '' # if it and isinstance(it, collections.Callable): # replacer = it(inner) if isinstance(it, basestring): it = stripHTMLMedia(it).strip() if it and not hasattr(it, '__iter__'): if section[2] != '^': replacer = inner elif it and hasattr(it, 'keys') and hasattr(it, '__getitem__'): if section[2] != '^': replacer = self.render(inner, it) elif it: insides = [] for item in it: insides.append(self.render(inner, item)) replacer = ''.join(insides) elif not it and section[2] == '^': replacer = inner template = template.replace(section, replacer) return template
def render_sections(self, template, context): """Expands sections.""" while 1: match = self.section_re.search(template) if match is None: break section, section_name, inner = match.group(0, 1, 2) section_name = section_name.strip() # check for cloze m = re.match("c[qa]:(\d+):(.+)", section_name) if m: # get full field text txt = get_or_attr(context, m.group(2), None) m = re.search(clozeReg%m.group(1), txt) if m: it = m.group(1) else: it = None else: it = get_or_attr(context, section_name, None) replacer = '' # if it and isinstance(it, collections.Callable): # replacer = it(inner) if isinstance(it, basestring): it = stripHTMLMedia(it).strip() if it and not hasattr(it, '__iter__'): if section[2] != '^': replacer = inner elif it and hasattr(it, 'keys') and hasattr(it, '__getitem__'): if section[2] != '^': replacer = self.render(inner, it) elif it: insides = [] for item in it: insides.append(self.render(inner, item)) replacer = ''.join(insides) elif not it and section[2] == '^': replacer = inner template = template.replace(section, replacer) return template
def createNote(deck, model, fields): m = _find_model(model) m['did'] = _find_deck(deck) note = Note(mw.col, m) _set_fields(note, fields) mw.col.addNote(note) duplicateOrEmpty = note.dupeOrEmpty() if duplicateOrEmpty == 1: raise Exception('cannot create note because it is empty') elif duplicateOrEmpty == 2: key = m['flds'][0]['name'] value = stripHTMLMedia(fields[key]) if key in fields else '' raise DuplicateException('"{0}" note already exists for "{1}"'.format(model, value)) elif duplicateOrEmpty == False: return else: raise Exception('cannot create note for unknown reason')
def get_data(editor): "" "" # get word from current field word = stripHTMLMedia(editor.note.fields[editor.currentField]) clean_word = _normalize_word(word) audio_file = save_audio(clean_word) if (audio_file == None): showWarning( 'Vocabolaudio: no information found for the word: {}'.format(word)) return editor.addMedia(audio_file) for field in editor.note.keys(): if field == 'Audio' and audio_file != None: editor.note[field] = str('[sound:{}.{}]'.format(clean_word, 'mp3')) editor.note.flush() mw.reset()
def render_sections(self, template, context): """replace {{#foo}}bar{{/foo}} and {{^foo}}bar{{/foo}} by their normal value.""" while 1: match = self.section_re.search(template) if match is None: break section, section_name, inner = match.group(0, 1, 2) section_name = section_name.strip() # val will contain the content of the field considered # right now val = None m = re.match(r"c[qa]:(\d+):(.+)", section_name) if m: # get full field text txt = get_or_attr(context, m.group(2), None) m = re.search(clozeReg % m.group(1), txt) if m: val = m.group(1) else: val = get_or_attr(context, section_name, None) replacer = '' # Whether it's {{^ inverted = section[2] == "^" # Ensuring we don't consider whitespace in wval if val: val = stripHTMLMedia(val).strip() if (val and not inverted) or (not val and inverted): replacer = inner template = template.replace(section, replacer) return template
except sre_constants.error: return if not nids: return "0" return "n.id in %s" % ids2str(nids) def _findDupes(self, (val, args)): # caller must call stripHTMLMedia on passed val try: mid, val = val.split(",", 1) except OSError: return csum = fieldChecksum(val) nids = [] for nid, flds in self.col.db.execute("select id, flds from notes where mid=? and csum=?", mid, csum): if stripHTMLMedia(splitFields(flds)[0]) == val: nids.append(nid) return "n.id in %s" % ids2str(nids) # Find and replace ########################################################################## def findReplace(col, nids, src, dst, regex=False, field=None, fold=True): "Find and replace fields in a note." mmap = {} if field: for m in col.models.all(): for f in m["flds"]: if f["name"] == field:
def addHistory(self, note): txt = stripHTMLMedia(",".join(note.fields))[:30] self.history.append((note.id, txt)) self.history = self.history[-15:] self.historyButton.setEnabled(True)
def _copyScheduling(deckFrom, deckTo): now = intTime() logs = [] cids = mw.col.decks.cids(deckTo["id"], children=False) copiedN = 0 updates = [] for cid in cids: card = mw.col.getCard(cid) note = mw.col.getNote(card.nid) sfld = stripHTMLMedia(note.fields[note.col.models.sortIdx(note._model)]) sourceCids = mw.col.db.list( "select distinct(c.id) from cards c, notes n where c.nid=n.id and c.did=? and n.sfld=?", deckFrom["id"], sfld ) # If there are no source cards, skip if not sourceCids: continue if len(sourceCids) > 1: logs.append("Multiple source cards not supported. Matched field={0}".format(sfld)) continue sourceCid = sourceCids[0] sourceCard = mw.col.getCard(sourceCid) # Skip new cards if sourceCard.queue == 0: continue logs.append("Matched card {0}".format(sfld)) updates.append(dict( cid=cid, due=sourceCard.due, queue=sourceCard.queue, ivl=sourceCard.ivl, factor=sourceCard.factor, left=sourceCard.left, type=sourceCard.type, now=now, usn=mw.col.usn() )) def copyRevlog(offset): mw.col.db.execute( "insert into revlog " "select r.id + :offset, :newcid, :usn, r.ease, r.ivl, r.lastIvl, r.factor, r.time, r.type " "from revlog as r " "where cid=:oldcid", offset=offset, oldcid=sourceCid, newcid=cid, usn=mw.col.usn() ) for i in range(0, MAX_RETRIES + 1): try: copyRevlog(2 << i) break except: if i == MAX_RETRIES: raise copiedN += 1 #logs.append("updates {0}".format(updates)) mw.col.db.executemany( "update cards set " "due=:due, mod=:now, usn=:usn, queue=:queue, " "ivl=:ivl, factor=:factor, left=:left, type=:type " "where id=:cid", updates ) logs.append("Copied {0} cards".format(copiedN)) showText("\n".join(logs), title="Copy scheduling log") mw.reset()
def addHistory(self, note): txt = stripHTMLMedia(",".join(note.fields))[:30] self.history.insert(0, (note.id, txt)) self.history = self.history[:15] self.historyButton.setEnabled(True)
def deck_notes(self, did): import re, os from anki import utils #afx = AnkiFx(self.col) #cids = afx.did2cids(did=did) cids = self.cids(did, utils) components = self.col.split(os.sep) mediadir = ('/'.join(components[:-1]) + '/collection.media') nids = [] for cid in cids: card = self.card_spec('nid', cid=cid) nids.append(card[0]) notes = [] for nid in nids: ndict = { 'nid': None, 'flds': None, 'tags': None, 'mid': None, 'mname': None, 'img': None } # note info for each note id note = self.note_info(nid=nid) # readable note fields, strip HTML flds = filter(None, note['flds'].split('\x1f')) m = lambda i: [ re.findall('src="([^"]+)"', f, re.DOTALL) for f in i ] img = [x[0] for x in m(flds) if x] f = lambda x: [utils.stripHTMLMedia(i) for i in x] flds = f(flds) #f = lambda x: [utils.stripHTMLMedia(i) for i in x] #flds = f(filter(None, note['flds'].split('\x1f'))) # from note model id, retrieve model fields mid = str(note['mid']) #model = afx.did2cids(mid=mid) model = self.model_info(mid=mid) # get field names for model mflds = [mfld['name'] for mfld in model['flds']] ndict['nid'] = nid #ndict['flds'] = (' '.join(flds)).encode('utf-8') ndict['flds'] = flds ndict['tags'] = note['tags'] ndict['mid'] = mid ndict['mname'] = model['name'] if img: ndict['img'] = '{}/{}'.format(mediadir, img[0]) notes.append(ndict) return notes
def importMedia(self, mime, _old): """import audios and images from goldendict""" # find out where we are if dialogs._dialogs['AddCards'][1]: # we are adding cards window = dialogs._dialogs['AddCards'][1] elif dialogs._dialogs['Browser'][1]: # we are browsing cards window = dialogs._dialogs['Browser'][1] elif dialogs._dialogs['EditCurrent'][1]: # we are editing cards window = dialogs._dialogs['EditCurrent'][1] else: # I don't know where we are, just exit return _old(self, mime) html = mime.html() soup = BeautifulSoup(html) newMime = QMimeData() addressMap = Setup.config['addressMap'] # sound links = [link for link in soup.findAll('a') if 'gdau' in link['href']] # images links += [link for link in soup.findAll('img') if 'bres' in link['src']] for link in links: if link.get('href'): # audio attr = 'href' elif link.get('src'): # image attr = 'src' else: # something else, I don't know, at least not # something we're looking for, skip continue goldenPath = link.get(attr) matchObj = re.search(r'(?<=(gdau|bres)://)[^/\\]*', goldenPath) if not matchObj: continue code = matchObj.group(0) if code not in addressMap: # new media filename = os.path.basename(goldenPath) res = addNewMedia(code, filename) if not res: # media import failed, continue to # process the next link continue # get the full path of the media file prefix = re.search(r'^(gdau|bres)://[^/\\]*', goldenPath).group(0) filePath = link[attr].replace(prefix, addressMap[code]) # import the file to anki ankiMedia = window.editor._addMedia(filePath, canDelete=True) # sound if attr == 'href': span = link.parent # delete the original link, # because we don't need it any more del link # append ankiMedia span.string = ankiMedia # images else: img = BeautifulSoup(ankiMedia) link.replaceWith(img) html = str(soup).decode('utf8') # assign the modified html to new Mime newMime = QMimeData() newMime.setHtml(html) # set text so the addon is able to work even when StripHTML is on newMime.setText(stripHTMLMedia(html)) # default _processHtml method return _old(self, newMime)