def render(self, provides_own_theme_data=False): """ The render method is what generates the frames for the screen and obtains the display information from the renderer. At this point all slides are built for the given display size. :param provides_own_theme_data: This switch disables the usage of the item's theme. However, this is disabled by default. If this is used, it has to be taken care, that the renderer knows the correct theme data. However, this is needed for the theme manager. """ log.debug('Render called') self._display_frames = [] self.bg_image_bytes = None if not provides_own_theme_data: self.renderer.set_item_theme(self.theme) self.theme_data, self.main, self.footer = self.renderer.pre_render( ) if self.service_item_type == ServiceItemType.Text: log.debug('Formatting slides: {title}'.format(title=self.title)) # Save rendered pages to this dict. In the case that a slide is used twice we can use the pages saved to # the dict instead of rendering them again. previous_pages = {} for slide in self._raw_frames: verse_tag = slide['verseTag'] if verse_tag in previous_pages and previous_pages[verse_tag][ 0] == slide['raw_slide']: pages = previous_pages[verse_tag][1] else: pages = self.renderer.format_slide(slide['raw_slide'], self) previous_pages[verse_tag] = (slide['raw_slide'], pages) for page in pages: page = page.replace('<br>', '{br}') html_data = expand_tags(html.escape(page.rstrip())) self._display_frames.append({ 'title': clean_tags(page), 'text': clean_tags(page.rstrip()), 'html': html_data.replace('&nbsp;', ' '), 'verseTag': verse_tag }) elif self.service_item_type == ServiceItemType.Image or self.service_item_type == ServiceItemType.Command: pass else: log.error('Invalid value renderer: {item}'.format( item=self.service_item_type)) self.title = clean_tags(self.title) # The footer should never be None, but to be compatible with a few # nightly builds between 1.9.4 and 1.9.5, we have to correct this to # avoid tracebacks. if self.raw_footer is None: self.raw_footer = [] self.foot_text = '<br>'.join([_f for _f in self.raw_footer if _f])
def render(self, provides_own_theme_data=False): """ The render method is what generates the frames for the screen and obtains the display information from the renderer. At this point all slides are built for the given display size. ``provides_own_theme_data`` This switch disables the usage of the item's theme. However, this is disabled by default. If this is used, it has to be taken care, that the renderer knows the correct theme data. However, this is needed for the theme manager. """ log.debug(u'Render called') self._display_frames = [] self.bg_image_bytes = None if not provides_own_theme_data: self.renderer.set_item_theme(self.theme) self.themedata, self.main, self.footer = self.renderer.pre_render() if self.service_item_type == ServiceItemType.Text: log.debug(u'Formatting slides: %s' % self.title) # Save rendered pages to this dict. In the case that a slide is used # twice we can use the pages saved to the dict instead of rendering # them again. previous_pages = {} for slide in self._raw_frames: verse_tag = slide[u'verseTag'] if verse_tag in previous_pages and previous_pages[verse_tag][0] == slide[u'raw_slide']: pages = previous_pages[verse_tag][1] else: pages = self.renderer.format_slide(slide[u'raw_slide'], self) previous_pages[verse_tag] = (slide[u'raw_slide'], pages) for page in pages: page = page.replace(u'<br>', u'{br}') html = expand_tags(cgi.escape(page.rstrip())) self._display_frames.append({ u'title': clean_tags(page), u'text': clean_tags(page.rstrip()), u'html': html.replace(u'&nbsp;', u' '), u'verseTag': verse_tag }) elif self.service_item_type == ServiceItemType.Image or self.service_item_type == ServiceItemType.Command: pass else: log.error(u'Invalid value renderer: %s' % self.service_item_type) self.title = clean_tags(self.title) # The footer should never be None, but to be compatible with a few # nightly builds between 1.9.4 and 1.9.5, we have to correct this to # avoid tracebacks. if self.raw_footer is None: self.raw_footer = [] self.foot_text = u'<br>'.join(filter(None, self.raw_footer))
def test_clean_tags(self): """ Test clean_tags() method. """ with patch('openlp.core.lib.FormattingTags.get_html_tags' ) as mocked_get_tags: # GIVEN: Mocked get_html_tags() method. mocked_get_tags.return_value = [{ 'desc': 'Black', 'start tag': '{b}', 'start html': '<span style="-webkit-text-fill-color:black">', 'end tag': '{/b}', 'end html': '</span>', 'protected': True, 'temporary': False }] string_to_pass = '******' wanted_string = 'ASDF\nfoo\nbar black' # WHEN: Clean the string. result_string = clean_tags(string_to_pass) # THEN: The strings should be identical. self.assertEqual(wanted_string, result_string, 'The strings should be identical')
def clean_song(manager, song): """ Cleans the search title, rebuilds the search lyrics, adds a default author if the song does not have one and other clean ups. This should always called when a new song is added or changed. :param manager: The song database manager object. :param song: The song object. """ from .openlyricsxml import SongXML if song.title: song.title = clean_title(song.title) else: song.title = '' if song.alternate_title: song.alternate_title = clean_title(song.alternate_title) else: song.alternate_title = '' song.search_title = clean_string(song.title) + '@' + clean_string(song.alternate_title) if isinstance(song.lyrics, bytes): song.lyrics = str(song.lyrics, encoding='utf8') verses = SongXML().get_verses(song.lyrics) song.search_lyrics = ' '.join([clean_string(clean_tags(verse[1])) for verse in verses]) # The song does not have any author, add one. if not song.authors_songs: name = SongStrings.AuthorUnknown author = manager.get_object_filtered(Author, Author.display_name == name) if author is None: author = Author.populate(display_name=name, last_name='', first_name='') song.add_author(author) if song.copyright: song.copyright = CONTROL_CHARS.sub('', song.copyright).strip()
def clean_song(manager, song): """ Cleans the search title, rebuilds the search lyrics, adds a default author if the song does not have one and other clean ups. This should always called when a new song is added or changed. :param manager: The song database manager object. :param song: The song object. """ from .openlyricsxml import SongXML if song.title: song.title = clean_title(song.title) else: song.title = '' if song.alternate_title: song.alternate_title = clean_title(song.alternate_title) else: song.alternate_title = '' song.search_title = clean_string(song.title) + '@' + clean_string(song.alternate_title) if isinstance(song.lyrics, bytes): song.lyrics = str(song.lyrics, encoding='utf8') verses = SongXML().get_verses(song.lyrics) song.search_lyrics = ' '.join([clean_string(clean_tags(verse[1], True)) for verse in verses]) # The song does not have any author, add one. if not song.authors_songs: name = SongStrings.AuthorUnknown author = manager.get_object_filtered(Author, Author.display_name == name) if author is None: author = Author.populate(display_name=name, last_name='', first_name='') song.add_author(author) if song.copyright: song.copyright = CONTROL_CHARS.sub('', song.copyright).strip()
def test_clean_tags(self): """ Test clean_tags() method. """ with patch('openlp.core.lib.FormattingTags.get_html_tags') as mocked_get_tags: # GIVEN: Mocked get_html_tags() method. mocked_get_tags.return_value = [{ 'desc': 'Black', 'start tag': '{b}', 'start html': '<span style="-webkit-text-fill-color:black">', 'end tag': '{/b}', 'end html': '</span>', 'protected': True, 'temporary': False }] string_to_pass = '******' wanted_string = 'ASDF\nfoo\nbar black' # WHEN: Clean the string. result_string = clean_tags(string_to_pass) # THEN: The strings should be identical. self.assertEqual(wanted_string, result_string, 'The strings should be identical')
def render(self, provides_own_theme_data=False): """ The render method is what generates the frames for the screen and obtains the display information from the renderer. At this point all slides are built for the given display size. :param provides_own_theme_data: This switch disables the usage of the item's theme. However, this is disabled by default. If this is used, it has to be taken care, that the renderer knows the correct theme data. However, this is needed for the theme manager. """ log.debug('Render called') self._display_frames = [] self.bg_image_bytes = None if not provides_own_theme_data: self.renderer.set_item_theme(self.theme) self.theme_data, self.main, self.footer = self.renderer.pre_render() if self.service_item_type == ServiceItemType.Text: log.debug('Formatting slides: %s' % self.title) # Save rendered pages to this dict. In the case that a slide is used twice we can use the pages saved to # the dict instead of rendering them again. if self.get_plugin_name() == 'songs': if not self.extra_data_dict: self.extra_data_dict = {} if not self.xml_version.startswith('<?xml') and not self.xml_version.startswith('<song'): # This is an old style song, without XML. verses = self.xml_version.split('\n\n') for count, verse in enumerate(verses): self.extra_data_dict['v' + str(count)] = str(verse).split('\n') elif self.xml_version.startswith('<?xml'): # New style song, output as XML. song_xml_split = re.split('(<verse[\w"= ]*>)', self.xml_version) current_section = '' for song_part in song_xml_split: if song_part.startswith("<verse name"): current_section = song_part.replace('<verse name="', '').replace('">', '') elif song_part.startswith("<lines"): # New line needed between <lines> sections song_part = song_part.replace('</lines><line', '</lines><br/><line') # Split out <lines>, </lines>, </verse>, </lyrics> and </song> tags song_part = ''.join(re.split('<lines[\w"= ]*>', song_part)) song_part = song_part.replace('</lines>','').replace('</verse>', '') song_part = song_part.replace('</lyrics>','').replace('</song>', '') # Convert to list self.extra_data_dict[current_section] = song_part.split('<br/>') previous_pages = {} # Get key and transpose amount, if used in this song if "<key>" in self.xml_version: current_key = self.xml_version[(self.xml_version.index('<key>') + 5):(self.xml_version.index('<key>')+7)] if current_key[1] == '<': current_key = current_key[0] else: current_key = '' if "<transposition>" in self.xml_version: current_transpose = self.xml_version[(self.xml_version.index('<transposition>') + 15):(self.xml_version.index('<transposition>')+18)] # Possible options: 1,...,9, 10, 11, -1, ... -9, -10, -11 if current_transpose[1] == '<': current_transpose = int(current_transpose[0]) elif current_transpose[2] == '<': current_transpose = int(current_transpose[0:2]) else: current_transpose = 0 # Calculate resultant key for musician oriented view if current_key != '': resultant_key = Chords.transpose_chord(current_key, current_key, current_transpose) else: resultant_key = '' for slide in self._raw_frames: verse_tag = slide['verseTag'] if verse_tag in previous_pages and previous_pages[verse_tag][0] == slide['raw_slide']: pages = previous_pages[verse_tag][1] else: pages = self.renderer.format_slide(slide['raw_slide'], self) previous_pages[verse_tag] = (slide['raw_slide'], pages) xml_lines = self.extra_data_dict[verse_tag.lower()] xml_line_lower, xml_line_upper = 0, 0 subpage_count = 0 for page in pages: subpage_count += 1 page_lines = page.split('<br>') # Given the current page_lines, calculate the corresponding xml_lines xml_segment = '' xml_line_lower = xml_line_upper for line in page_lines: while (xml_line_upper < len(xml_lines)) and not (''.join(re.split('<chord[\w\+#"=// ]* />', xml_lines[xml_line_upper])).strip() == line.strip()): xml_line_upper += 1 xml_line_upper += 1 page_xml = xml_lines[xml_line_lower: xml_line_upper] # Exclude any [br] or [---] elements from the XML list page_xml_short = [] for item in page_xml: if item != '[br]' and item != '[---]': # split item by chord tags, then transpose each chord tag if current_transpose != 0: item_sections = re.split('(<chord[\w\+#"=// ]* />)', item) transposed_item = '' for item_section in item_sections: if item_section.startswith('<chord name'): transposed_item = transposed_item + Chords.transpose_chord_tag(item_section, current_key, current_transpose) else: transposed_item = transposed_item + item_section page_xml_short.append(transposed_item) else: page_xml_short.append(item) page = page.replace('<br>', '{br}') html_data = expand_tags(html.escape(page.rstrip())) self._display_frames.append({ 'title': clean_tags(page), 'text': clean_tags(page.rstrip()), 'html': html_data.replace('&nbsp;', ' '), 'verseTag': verse_tag, 'verseSubpage': subpage_count, 'extraInfo': '<br>'.join(page_xml_short), 'playedKey': resultant_key }) # Deal with any [br] tag in XML before processing next page # TODO: Check if we need to also do this for [---] tags if xml_line_upper < len(xml_lines) and xml_lines[xml_line_upper].strip() == '[br]': xml_line_upper += 1 else: previous_pages = {} for slide in self._raw_frames: verse_tag = slide['verseTag'] if verse_tag in previous_pages and previous_pages[verse_tag][0] == slide['raw_slide']: pages = previous_pages[verse_tag][1] else: pages = self.renderer.format_slide(slide['raw_slide'], self) previous_pages[verse_tag] = (slide['raw_slide'], pages) for page in pages: page = page.replace('<br>', '{br}') html_data = expand_tags(html.escape(page.rstrip())) self._display_frames.append({ 'title': clean_tags(page), 'text': clean_tags(page.rstrip()), 'html': html_data.replace('&nbsp;', ' '), 'verseTag': verse_tag, }) elif self.service_item_type == ServiceItemType.Image or self.service_item_type == ServiceItemType.Command: pass else: log.error('Invalid value renderer: %s' % self.service_item_type) self.title = clean_tags(self.title) # The footer should never be None, but to be compatible with a few # nightly builds between 1.9.4 and 1.9.5, we have to correct this to # avoid tracebacks. if self.raw_footer is None: self.raw_footer = [] self.foot_text = '<br>'.join([_f for _f in self.raw_footer if _f])