def construct(self, bookmark_notes): ''' bookmark_notes: {loc_sort: {color, location, note}…} Optionally include <hr> between booknotes ''' soup = None if bookmark_notes: soup = BeautifulSoup( '''<div class="{0}"></div>'''.format('bookmark_notes')) dtc = 0 for i, location_sort in enumerate(sorted(bookmark_notes.keys())): soup.div.insert( dtc, self.BOOKMARK_TEMPLATE.format( location_sort, bookmark_notes[location_sort]['color'], self._get_style('Location'), bookmark_notes[location_sort]['location'], self._get_style('Note'), bookmark_notes[location_sort]['note'])) dtc += 1 if (i < len(bookmark_notes) - 1 and plugin_prefs.get('appearance_hr_checkbox', False)): soup.div.insert( dtc, plugin_prefs.get('HORIZONTAL_RULE', '<hr width="80%" />')) dtc += 1 return soup
def sort_merged_annotations(merged_soup): ''' Input: a combined group of user annotations Output: sorted by location ''' include_hr = plugin_prefs.get('appearance_hr_checkbox', False) locations = merged_soup.findAll(location_sort=True) locs = [loc['location_sort'] for loc in locations] locs.sort() sorted_soup = BeautifulSoup(ANNOTATIONS_HEADER) dtc = 0 for i, loc in enumerate(locs): next_div = merged_soup.find(attrs={'location_sort': loc}) sorted_soup.div.insert(dtc, next_div) dtc += 1 if include_hr and i < len(locs) - 1: sorted_soup.div.insert(dtc, plugin_prefs.get('HORIZONTAL_RULE', '<hr width="80%" />')) dtc += 1 return sorted_soup
def _log(self, msg=None): ''' Print msg to console ''' from calibre_plugins.marvin_manager.config import plugin_prefs if not plugin_prefs.get('debug_plugin', False): return if msg: debug_print(" %s" % str(msg)) else: debug_print()
def _timestamp_to_datestr(self, timestamp): ''' Convert timestamp to 01 Jan 2011 12:34:56 ''' from calibre_plugins.marvin_manager.appearance import default_timestamp d = datetime.fromtimestamp(float(timestamp)) friendly_timestamp_format = plugin_prefs.get('appearance_timestamp_format', default_timestamp) try: friendly_timestamp = d.strftime(friendly_timestamp_format) except: friendly_timestamp = d.strftime(default_timestamp) return friendly_timestamp
def sort_merged_annotations(merged_soup): ''' Input: a combined group of user annotations Output: sorted by location ''' include_hr = plugin_prefs.get('appearance_hr_checkbox', False) locations = merged_soup.findAll(location_sort=True) locs = [loc['location_sort'] for loc in locations] locs.sort() sorted_soup = BeautifulSoup(ANNOTATIONS_HEADER) dtc = 0 for i, loc in enumerate(locs): next_div = merged_soup.find(attrs={'location_sort': loc}) sorted_soup.div.insert(dtc, next_div) dtc += 1 if include_hr and i < len(locs) - 1: sorted_soup.div.insert( dtc, plugin_prefs.get('HORIZONTAL_RULE', '<hr width="80%" />')) dtc += 1 return sorted_soup
def _log(self, msg=None): """ Print msg to console """ from calibre_plugins.marvin_manager.config import plugin_prefs if not plugin_prefs.get("debug_plugin", False): return if msg: debug_print(" %s" % str(msg)) else: debug_print()
def set_cc_mapping(cc_name, field=None, combobox=None): """ Store element to cc_name in prefs:cc_mappings """ from calibre_plugins.marvin_manager.config import plugin_prefs cc_mappings = plugin_prefs.get("cc_mappings", {}) current_library = current_library_name() if current_library in cc_mappings: cc_mappings[current_library][cc_name] = {"field": field, "combobox": combobox} else: cc_mappings[current_library] = {cc_name: {"field": field, "combobox": combobox}} plugin_prefs.set("cc_mappings", cc_mappings)
def construct(self, bookmark_notes): ''' bookmark_notes: {loc_sort: {color, location, note}…} Optionally include <hr> between booknotes ''' soup = None if bookmark_notes: soup = BeautifulSoup('''<div class="{0}"></div>'''.format('bookmark_notes')) dtc = 0 for i, location_sort in enumerate(sorted(bookmark_notes.keys())): soup.div.insert(dtc, self.BOOKMARK_TEMPLATE.format( location_sort, bookmark_notes[location_sort]['color'], self._get_style('Location'), bookmark_notes[location_sort]['location'], self._get_style('Note'), bookmark_notes[location_sort]['note'])) dtc += 1 if (i < len(bookmark_notes) - 1 and plugin_prefs.get('appearance_hr_checkbox', False)): soup.div.insert(dtc, plugin_prefs.get('HORIZONTAL_RULE', '<hr width="80%" />')) dtc += 1 return soup
def _timestamp_to_datestr(self, timestamp): ''' Convert timestamp to 01 Jan 2011 12:34:56 ''' from calibre_plugins.marvin_manager.appearance import default_timestamp d = datetime.fromtimestamp(float(timestamp)) friendly_timestamp_format = plugin_prefs.get( 'appearance_timestamp_format', default_timestamp) try: friendly_timestamp = d.strftime(friendly_timestamp_format) except: friendly_timestamp = d.strftime(default_timestamp) return friendly_timestamp
def _get_style(self, target): ''' Get the current CSS for target ''' from calibre_plugins.marvin_manager.appearance import default_elements stored_css = plugin_prefs.get('appearance_css', default_elements) style = '' for element in stored_css: if element['name'] == target: style = re.sub('\n', '', element['css']) break else: self._log_location("ERROR: Unable to find '{0}' in stored_css".format(target)) return style
def _get_style(self, target): ''' Get the current CSS for target ''' from calibre_plugins.marvin_manager.appearance import default_elements stored_css = plugin_prefs.get('appearance_css', default_elements) style = '' for element in stored_css: if element['name'] == target: style = re.sub('\n', '', element['css']) break else: self._log_location( "ERROR: Unable to find '{0}' in stored_css".format(target)) return style
def _get_note_style(self): ''' Get the current CSS for book_notes ''' from calibre_plugins.marvin_manager.appearance import default_elements stored_css = plugin_prefs.get('appearance_css', default_elements) note_style = '' for element in stored_css: if element['name'] == 'Note': note_style = re.sub('\n', '', element['css']) break else: _log_location("ERROR: Unable to find 'Note' in stored_css") return note_style
def _log_location(*args): LOCATION_TEMPLATE = "{cls}:{func}({arg1}) {arg2}" from calibre_plugins.marvin_manager.config import plugin_prefs if not plugin_prefs.get("debug_plugin", False): return arg1 = arg2 = "" if len(args) > 0: arg1 = str(args[0]) if len(args) > 1: arg2 = str(args[1]) debug_print( LOCATION_TEMPLATE.format(cls="common_utils", func=sys._getframe(1).f_code.co_name, arg1=arg1, arg2=arg2) )
def _log(self, msg=None): ''' Upon first call, switch to appropriate method ''' from calibre_plugins.marvin_manager.config import plugin_prefs if not plugin_prefs.get('debug_plugin', False): # Neuter the method self._log = self.__null self._log_location = self.__null else: # Log the message, then switch to real method if msg: debug_print(" {0}".format(str(msg))) else: debug_print() self._log = self.__log self._log_location = self.__log_location
def get_cc_mapping(cc_name, element, default=None): ''' Return the element mapped to cc_name in prefs ''' from calibre_plugins.marvin_manager.config import plugin_prefs if element not in ['field', 'combobox']: raise ValueError( "invalid element '{0}' requested for custom column '{1}'".format( element, cc_name)) ans = default cc_mappings = plugin_prefs.get('cc_mappings', {}) current_library = current_library_name() if (current_library in cc_mappings and cc_name in cc_mappings[current_library] and element in cc_mappings[current_library][cc_name]): ans = cc_mappings[current_library][cc_name][element] return ans
def _log_location(*args): LOCATION_TEMPLATE = "{cls}:{func}({arg1}) {arg2}" from calibre_plugins.marvin_manager.config import plugin_prefs if not plugin_prefs.get('debug_plugin', False): return arg1 = arg2 = '' if len(args) > 0: arg1 = str(args[0]) if len(args) > 1: arg2 = str(args[1]) debug_print( LOCATION_TEMPLATE.format(cls='common_utils', func=sys._getframe(1).f_code.co_name, arg1=arg1, arg2=arg2))
def merge_annotations_with_comments(parent, cid, comments_soup, new_soup): ''' comments_soup: comments potentially with user_annotations ''' # Prepare a new COMMENTS_DIVIDER comments_divider = '<div class="comments_divider"><p style="text-align:center;margin:1em 0 1em 0">{0}</p></div>'.format( plugin_prefs.get( 'COMMENTS_DIVIDER', '· · • · ✦ · • · ·' )) # Remove the old comments_divider cds = comments_soup.find('div', 'comments_divider') if cds: cds.extract() # Existing annotations? uas = comments_soup.find('div', 'user_annotations') if uas: # Save the existing annotations to old_soup old_soup = BeautifulSoup(unicode(uas)) # Remove any hrs from old_soup hrs = old_soup.findAll('hr') if hrs: for hr in hrs: hr.extract() # Remove the existing annotations from comments_soup uas.extract() # Merge old_soup with new_soup merged_soup = unicode(comments_soup) + \ unicode(comments_divider) + \ unicode(merge_annotations(parent, cid, old_soup, new_soup)) else: # No existing, just merge comments_soup with already sorted new_soup merged_soup = unicode(comments_soup) + \ unicode(comments_divider) + \ unicode(new_soup) return merged_soup
def get_cc_mapping(cc_name, element, default=None): """ Return the element mapped to cc_name in prefs """ from calibre_plugins.marvin_manager.config import plugin_prefs if element not in ["field", "combobox"]: raise ValueError("invalid element '{0}' requested for custom column '{1}'".format(element, cc_name)) ans = default cc_mappings = plugin_prefs.get("cc_mappings", {}) current_library = current_library_name() if ( current_library in cc_mappings and cc_name in cc_mappings[current_library] and element in cc_mappings[current_library][cc_name] ): ans = cc_mappings[current_library][cc_name][element] return ans
def _log_location(self, *args): ''' Print location, args to console ''' from calibre_plugins.marvin_manager.config import plugin_prefs if not plugin_prefs.get('debug_plugin', False): return arg1 = arg2 = '' if len(args) > 0: arg1 = str(args[0]) if len(args) > 1: arg2 = str(args[1]) debug_print( self.LOCATION_TEMPLATE.format(cls=self.__class__.__name__, func=sys._getframe(1).f_code.co_name, arg1=arg1, arg2=arg2))
def set_cc_mapping(cc_name, field=None, combobox=None): ''' Store element to cc_name in prefs:cc_mappings ''' from calibre_plugins.marvin_manager.config import plugin_prefs cc_mappings = plugin_prefs.get('cc_mappings', {}) current_library = current_library_name() if current_library in cc_mappings: cc_mappings[current_library][cc_name] = { 'field': field, 'combobox': combobox } else: cc_mappings[current_library] = { cc_name: { 'field': field, 'combobox': combobox } } plugin_prefs.set('cc_mappings', cc_mappings)
def _log_location(self, *args): """ Print location, args to console """ from calibre_plugins.marvin_manager.config import plugin_prefs if not plugin_prefs.get("debug_plugin", False): return arg1 = arg2 = "" if len(args) > 0: arg1 = str(args[0]) if len(args) > 1: arg2 = str(args[1]) debug_print( self.LOCATION_TEMPLATE.format( cls=self.__class__.__name__, func=sys._getframe(1).f_code.co_name, arg1=arg1, arg2=arg2 ) )
def merge_annotations_with_comments(parent, cid, comments_soup, new_soup): ''' comments_soup: comments potentially with user_annotations ''' # Prepare a new COMMENTS_DIVIDER comments_divider = '<div class="comments_divider"><p style="text-align:center;margin:1em 0 1em 0">{0}</p></div>'.format( plugin_prefs.get('COMMENTS_DIVIDER', '· · • · ✦ · • · ·')) # Remove the old comments_divider cds = comments_soup.find('div', 'comments_divider') if cds: cds.extract() # Existing annotations? uas = comments_soup.find('div', 'user_annotations') if uas: # Save the existing annotations to old_soup old_soup = BeautifulSoup(unicode(uas)) # Remove any hrs from old_soup hrs = old_soup.findAll('hr') if hrs: for hr in hrs: hr.extract() # Remove the existing annotations from comments_soup uas.extract() # Merge old_soup with new_soup merged_soup = unicode(comments_soup) + \ unicode(comments_divider) + \ unicode(merge_annotations(parent, cid, old_soup, new_soup)) else: # No existing, just merge comments_soup with already sorted new_soup merged_soup = unicode(comments_soup) + \ unicode(comments_divider) + \ unicode(new_soup) return merged_soup
def to_HTML(self, header=''): ''' Generate HTML with user-specified CSS, element order ''' # Retrieve CSS prefs from calibre_plugins.marvin_manager.appearance import default_elements stored_css = plugin_prefs.get('appearance_css', default_elements) elements = [] for element in stored_css: elements.append(element['name']) if element['name'] == 'Note': note_style = re.sub('\n', '', element['css']) elif element['name'] == 'Text': text_style = re.sub('\n', '', element['css']) elif element['name'] == 'Timestamp': ts_style = re.sub('\n', '', element['css']) # Additional CSS for timestamp color and bg to be formatted datetime_style = ("background-color:{0};color:{1};" + ts_style) # Order the elements according to stored preferences comments_body = '' for element in elements: if element == 'Text': comments_body += '{text}' elif element == 'Note': comments_body += '{note}' elif element == 'Timestamp': ts_css = '''<table cellpadding="0" width="100%" style="{ts_style}" color="{color}"> <tr> <td class="location" style="text-align:left">{location}</td> <td class="timestamp" uts="{unix_timestamp}" style="text-align:right">{friendly_timestamp}</td> </tr> </table>''' comments_body += re.sub(r'>\s+<', r'><', ts_css) if self.annotations: soup = BeautifulSoup(ANNOTATIONS_HEADER) dtc = 0 # Add the annotations for i, agroup in enumerate( sorted(self.annotations, key=self._annotation_sorter)): location = agroup.location if location is None: location = '' friendly_timestamp = self._timestamp_to_datestr( agroup.timestamp) text = '' if agroup.text: for agt in agroup.text: text += '<p class="highlight" style="{0}">{1}</p>'.format( text_style, agt) note = '' if agroup.note: for agn in agroup.note: note += '<p class="note" style="{0}">{1}</p>'.format( note_style, agn) try: dt_bgcolor = COLOR_MAP[agroup.highlightcolor]['bg'] dt_fgcolor = COLOR_MAP[agroup.highlightcolor]['fg'] except: if agroup.highlightcolor is None: msg = "No highlight color specified, using Default" else: msg = "Unknown color '%s' specified" % agroup.highlightcolor self._log_location(msg) dt_bgcolor = COLOR_MAP['Default']['bg'] dt_fgcolor = COLOR_MAP['Default']['fg'] if agroup.hash is not None: # Use existing hash when re-rendering hash = agroup.hash else: m = hashlib.md5() m.update(text) m.update(note) hash = m.hexdigest() divTag = Tag(BeautifulSoup(), 'div') content_args = { 'color': agroup.highlightcolor, 'friendly_timestamp': friendly_timestamp, 'location': location, 'note': note, 'text': text, 'ts_style': datetime_style.format(dt_bgcolor, dt_fgcolor), 'unix_timestamp': agroup.timestamp, } divTag.insert(0, comments_body.format(**content_args)) divTag['class'] = "annotation" divTag['genre'] = '' if agroup.genre: divTag['genre'] = escape(agroup.genre) divTag['hash'] = hash divTag['location_sort'] = agroup.location_sort divTag['reader'] = agroup.reader_app divTag['style'] = ANNOTATION_DIV_STYLE soup.div.insert(dtc, divTag) dtc += 1 if i < len(self.annotations) - 1 and \ plugin_prefs.get('appearance_hr_checkbox', False): soup.div.insert( dtc, plugin_prefs.get('HORIZONTAL_RULE', '<hr width="80%" />')) dtc += 1 else: soup = BeautifulSoup(ANNOTATIONS_HEADER) return unicode(soup.renderContents())
def to_HTML(self, header=''): ''' Generate HTML with user-specified CSS, element order ''' # Retrieve CSS prefs from calibre_plugins.marvin_manager.appearance import default_elements stored_css = plugin_prefs.get('appearance_css', default_elements) elements = [] for element in stored_css: elements.append(element['name']) if element['name'] == 'Note': note_style = re.sub('\n', '', element['css']) elif element['name'] == 'Text': text_style = re.sub('\n', '', element['css']) elif element['name'] == 'Timestamp': ts_style = re.sub('\n', '', element['css']) # Additional CSS for timestamp color and bg to be formatted datetime_style = ("background-color:{0};color:{1};" + ts_style) # Order the elements according to stored preferences comments_body = '' for element in elements: if element == 'Text': comments_body += '{text}' elif element == 'Note': comments_body += '{note}' elif element == 'Timestamp': ts_css = '''<table cellpadding="0" width="100%" style="{ts_style}" color="{color}"> <tr> <td class="location" style="text-align:left">{location}</td> <td class="timestamp" uts="{unix_timestamp}" style="text-align:right">{friendly_timestamp}</td> </tr> </table>''' comments_body += re.sub(r'>\s+<', r'><', ts_css) if self.annotations: soup = BeautifulSoup(ANNOTATIONS_HEADER) dtc = 0 # Add the annotations for i, agroup in enumerate(sorted(self.annotations, key=self._annotation_sorter)): location = agroup.location if location is None: location = '' friendly_timestamp = self._timestamp_to_datestr(agroup.timestamp) text = '' if agroup.text: for agt in agroup.text: text += '<p class="highlight" style="{0}">{1}</p>'.format(text_style, agt) note = '' if agroup.note: for agn in agroup.note: note += '<p class="note" style="{0}">{1}</p>'.format(note_style, agn) try: dt_bgcolor = COLOR_MAP[agroup.highlightcolor]['bg'] dt_fgcolor = COLOR_MAP[agroup.highlightcolor]['fg'] except: if agroup.highlightcolor is None: msg = "No highlight color specified, using Default" else: msg = "Unknown color '%s' specified" % agroup.highlightcolor self._log_location(msg) dt_bgcolor = COLOR_MAP['Default']['bg'] dt_fgcolor = COLOR_MAP['Default']['fg'] if agroup.hash is not None: # Use existing hash when re-rendering hash = agroup.hash else: m = hashlib.md5() m.update(text) m.update(note) hash = m.hexdigest() divTag = Tag(BeautifulSoup(), 'div') content_args = { 'color': agroup.highlightcolor, 'friendly_timestamp': friendly_timestamp, 'location': location, 'note': note, 'text': text, 'ts_style': datetime_style.format(dt_bgcolor, dt_fgcolor), 'unix_timestamp': agroup.timestamp, } divTag.insert(0, comments_body.format(**content_args)) divTag['class'] = "annotation" divTag['genre'] = '' if agroup.genre: divTag['genre'] = escape(agroup.genre) divTag['hash'] = hash divTag['location_sort'] = agroup.location_sort divTag['reader'] = agroup.reader_app divTag['style'] = ANNOTATION_DIV_STYLE soup.div.insert(dtc, divTag) dtc += 1 if i < len(self.annotations) - 1 and \ plugin_prefs.get('appearance_hr_checkbox', False): soup.div.insert(dtc, plugin_prefs.get('HORIZONTAL_RULE', '<hr width="80%" />')) dtc += 1 else: soup = BeautifulSoup(ANNOTATIONS_HEADER) return unicode(soup.renderContents())