def find_by_keyword(self, keywords="", strict_match=False, book=-1):
        self.fyi("find_by_keyword, ... book=%s" % book)
        '''Search notes for a given keyword'''
        self.fyi("nota.find_by_keyword() with keywords %s; book=%s" % (keywords, book))
        keywordsKnown = []
        if not strict_match:
            keywords[0] = keywords[0].lower()
        for k in self.cur.execute("SELECT keyword FROM keyword;").fetchall():
            if strict_match:
                keywordsKnown.extend(k)
            else:
                keywordsKnown.extend(k)
                keywordsKnown.extend([str(k[0]).lower()])
        self.fyi("keywordsKnown: %s" % keywordsKnown)
        # FIXME: only using first keyword here!
        if not strict_match:
            keywords_partial = []
            kl = len(keywords[0])
            if kl > 3:
                for K in keywordsKnown:
                    if K[0:kl] == keywords[0]:
                        if K not in keywords_partial:
                            keywords_partial.append(K)
            # Try fuzzy search only if no direct matches
            keywords_fuzzy = []
            if not len(keywords_partial):
                keywords_fuzzy = difflib.get_close_matches(keywords[0], keywordsKnown, n=1, cutoff=0.6)
            self.fyi("  keywords_partial %s" % keywords_partial)
            self.fyi("  keywords_fuzzy %s" % keywords_fuzzy)
            keywords = list(set(keywords_partial + keywords_fuzzy))
        self.fyi("nota.find_by_keyword() later, keywords: %s" % keywords)
        noteIds = []
        for keyword in keywords:
            self.fyi("keyword: %s" % keyword)
            if strict_match:
                self.fyi("strict match on keyword '%s'" % keyword)
                try:
                    keywordId = self.con.execute("SELECT keywordId FROM keyword WHERE keyword=?;",
                            [keyword]).fetchone()
                except:
                    self.error("cannot look up keyword '%s'" % [keyword])
            else:
                self.fyi("non-strict match on keyword '%s'" % keyword)
                try:
                    keywordId = []
                    for k in self.cur.execute("SELECT keywordId FROM keyword WHERE keyword=? COLLATE NOCASE;", [keyword]).fetchall():
                        keywordId.extend(k)
                except:
                    self.error("cannot look up keyword '%s'" % [keyword])
            try:
                if len(keywordId):
                    self.fyi("looking for noteID matches to keywordId %s" % keywordId)
                    for k in keywordId:
                        self.fyi("k: %s" % k)
                        try:
                            if strict_match:
                                noteIdtest = self.cur.execute("SELECT noteId FROM notekeyword WHERE keywordId=?;", [k])
                            else:
                                noteIdtest = self.cur.execute("SELECT noteId FROM notekeyword WHERE keywordId=? COLLATE NOCASE;", [k])
                        except:
                            self.error("cannot query database to find noteId corresponding to keywordId value %s" % [k])
                        for noteId in noteIdtest:
                            self.fyi("got match to noteID %s" % noteId)
                            if noteId not in noteIds:
                                noteIds.append(noteId)
                                self.fyi("adding to list")
                            else:
                                self.fyi("already in list")
                        self.fyi("done with keyword %s" % k)
                else:
                    pass
            except:
                #20150314 self.error("problem finding keyword or note in database")
                #20150314 shouldn't be an error to not find a note!
                pass
        ## convert from hash to ids. Note that one hash may create several ids.
        self.fyi("noteIds: %s" % noteIds)
        ## Find IDs of just the notes with the proper book value
        noteIds2 = []
        self.fyi("ORIGINAL noteIds: %s" % noteIds)
        for n in noteIds:
            self.fyi("n=%s" % n)
            try:
                row = self.cur.execute("SELECT noteId, book FROM note WHERE noteID=?;", n).fetchone()
            except:
                self.error("cannot look up noteId %s" % n)
            self.fyi("row %s; book=%s" % (row, book))
            if book < 0 or row[1] == book:
                self.fyi("appending id %s" % row[0])
                noteIds2.append((row[0],))
            else:
                self.fyi("skipping id %s because book is wrong" % row[0])
        noteIds = noteIds2
        #self.fyi("  LATER    noteIds2: %s" % noteIds2)
        #self.fyi("  LATER    noteIds: %s" % noteIds)
        rval = []
        for n in noteIds:
            #self.fyi(" processing id=%s" % n)
            try:
                note = self.cur.execute("SELECT noteId, authorId, date, title, content, due, privacy, modified, hash, book FROM note WHERE noteId=?;", n).fetchone()
            except:
                self.warning("Problem extracting note %s from database" % n)
                next
            if note:
                date = note[2]
                due = note[5]
                privacy = note[6]
                keywordIds = []
                keywordIds.extend(self.con.execute("SELECT keywordid FROM notekeyword WHERE notekeyword.noteid=?;", n))
                keywords = []
                for k in keywordIds:
                    keywords.append(self.cur.execute("SELECT keyword FROM keyword WHERE keywordId=?;", k).fetchone()[0])
                rval.append({"noteId":note[0], "title":note[3], "keywords":keywords,
                    "content":note[4], "due":note[5], "privacy":note[6],
                    "date":note[2], "modified":note[7], "hash":note[8], "book":note[9]})
        return rval