def __init__(self): self._directory_path: str = '' self._article_documents: Dict[str, WhitebearDocumentArticle] = {} self._menu_documents: Dict[str, WhitebearDocumentMenu] = {} self._index_document = None self._css_document = None # Prepare xml schemas try: self._xmlschema_index = etree.XMLSchema(etree.parse(Fetch.get_resource_path('schema_index.xsd'))) self._xmlschema_article = etree.XMLSchema(etree.parse(Fetch.get_resource_path('schema_article.xsd'))) self._xmlschema_menu = etree.XMLSchema(etree.parse(Fetch.get_resource_path('schema_menu.xsd'))) except XMLSchemaParseError as ex: raise UnrecognizedFileException(Strings.exception_schema_syntax_error + ':\n' + str(ex))
def test_self(self) -> bool: """ SEO test self for caption, alt and link title. If the image and thumbnail is not accessible on disk, set a special warning image. :return: True if test is ok, False otherwise """ # Clear all error before each retest self._caption_error_message: str = '' # First test the base class seo attributes result = super(AsideImage, self).test_self() # Check caption length must be at least 3 and must not be default if len(self._caption) < Numbers.article_image_caption_min or len( self._caption) > Numbers.article_image_caption_max: self._caption_error_message = Strings.seo_error_image_caption_length result = False if self._caption == Strings.label_article_image_caption: self._caption_error_message = Strings.seo_error_default_value result = False # Check thumbnail image disk path if not self._thumbnail_path or not os.path.exists(self._thumbnail_path): # The image has the same dimensions as the main image self._image = wx.Image(Fetch.get_resource_path('main_image_thumbnail_missing.png'), wx.BITMAP_TYPE_PNG) self._thumbnail_size = (0, 0) result = False else: image = wx.Image(Fetch.get_resource_path(self._thumbnail_path), wx.BITMAP_TYPE_ANY) self._thumbnail_size = image.GetSize() if self._thumbnail_size == (Numbers.main_image_width, Numbers.main_image_height): self._image = image else: self._image = wx.Image(Fetch.get_resource_path('main_image_thumbnail_wrong.png'), wx.BITMAP_TYPE_PNG) self._thumbnail_size = image.GetSize() result = False # Check full image disk path, size can be whatever the user likes if not self._original_image_path or not os.path.exists(self._original_image_path): self._image = wx.Image(Fetch.get_resource_path('main_image_missing.png'), wx.BITMAP_TYPE_PNG) result = False # Spell check if not self._spell_check(self._caption): self._caption_error_message = Strings.spelling_error result = False if not result: self._status_color = wx.RED return result
def validate(html_string: str, schema: str) -> (bool, List[str]): """ Validate a document against a xml schema. :param html_string: Html document as string. :param schema: The name of the schema to use. :return: Tuple of boolean validation result and optional list of error messages. :raises UnrecognizedFileException if html parse fails. :raises UnrecognizedFileException if xml schema is incorrect. """ errors = [] try: xmlschema = etree.XMLSchema( etree.parse(Fetch.get_resource_path(schema))) xml_doc = html.fromstring(html_string) is_valid = xmlschema.validate(xml_doc) except XMLSchemaParseError as e: raise UnrecognizedFileException( Strings.exception_schema_syntax_error + ':\n' + str(e)) except XMLSyntaxError as e: raise UnrecognizedFileException( Strings.exception_html_syntax_error + ':\n' + str(e)) except ParserError as e: raise UnrecognizedFileException( Strings.exception_html_syntax_error + ':\n' + str(e)) for error in xmlschema.error_log: errors.append(error.message) return is_valid, errors
def test_self(self) -> bool: """ SEO test self for alt and link title. If the image and thumbnail is not accessible on disk, set a special warning image. :return: True if test is ok, False otherwise """ # First test the base class seo attributes result = super(ImageInText, self).test_self() # Check thumbnail image disk path if not self._thumbnail_path or not os.path.exists( self._thumbnail_path): # The image has generic text and can be reused. self._image = wx.Image( Fetch.get_resource_path('main_image_thumbnail_missing.png'), wx.BITMAP_TYPE_PNG) self._thumbnail_size = (0, 0) result = False else: # Image thumbnails in text must not be larger than 534 px. image = wx.Image(Fetch.get_resource_path(self._thumbnail_path), wx.BITMAP_TYPE_ANY) self._thumbnail_size = image.GetSize() if self._thumbnail_size[0] <= Numbers.text_image_max_size \ and self._thumbnail_size[1] <= Numbers.text_image_max_size: self._image = image else: self._image = wx.Image( Fetch.get_resource_path('main_image_thumbnail_wrong.png'), wx.BITMAP_TYPE_PNG) self._thumbnail_size = self._image.GetSize() result = False # Check full image disk path, size can be whatever the user likes if not self._original_image_path or not os.path.exists( self._original_image_path): self._image = wx.Image( Fetch.get_resource_path('main_image_missing.png'), wx.BITMAP_TYPE_PNG) result = False if not result: self._status_color = wx.RED return result
def _button_to_cancel(self) -> None: """ Change the upload button to the cancel upload state. :return: None """ self._upload_button.SetBitmap( wx.Bitmap(Fetch.get_resource_path('upload_cancel.png'), wx.BITMAP_TYPE_PNG)) self._upload_button.SetBitmapPosition(wx.TOP) self._upload_button.SetLabelText(Strings.button_cancel)
def get_original_size(self) -> (int, int): """ Return a tuple of this image's original size (width, height). :return: Return a tuple of this image's original size (width, height). """ if not self._original_image_path: return None if not os.path.exists(self._original_image_path): return None image = wx.Image(Fetch.get_resource_path(self._original_image_path), wx.BITMAP_TYPE_ANY) self._original_size = image.GetSize() return self._original_size
def test_self(self, online: bool) -> bool: """ SEO check self for correct title, url and dimensions. :param online: Do online url test. :return: True if no error is found. """ # Disk paths have to be checked by the subclasses. # Clear all error before each retest self._link_title_error_message = '' self._url_error_message = '' self._size_error_message = '' self._status_color = wx.NullColour result = True self._image = wx.Image(Fetch.get_resource_path('video_placeholder.png'), wx.BITMAP_TYPE_PNG) # Check video link title if len(self._link_title) < Numbers.article_image_title_min or len( self._link_title) > Numbers.article_image_title_max: self._link_title_error_message = Strings.seo_error_link_title_length self._image = wx.Image(Fetch.get_resource_path('video_seo_error.png'), wx.BITMAP_TYPE_PNG) result = False # Check dimensions if self._width != Numbers.video_width or self._height != Numbers.video_height: self._size_error_message = Strings.seo_error_video_size_wrong self._image = wx.Image(Fetch.get_resource_path('video_size_incorrect.png'), wx.BITMAP_TYPE_PNG) result = False # Check url, if online test is not run on document switching this causes wrong results. if online: h = httplib2.Http(timeout=Numbers.online_test_timeout) try: resp = h.request(self._url, 'HEAD') if int(resp[0]['status']) >= 400: self._url_error_message = Strings.seo_error_url_nonexistent self._image = wx.Image(Fetch.get_resource_path('video_seo_error.png'), wx.BITMAP_TYPE_PNG) result = False except KeyError as _: self._url_error_message = Strings.seo_error_url_malformed result = False except (httplib2.ServerNotFoundError, httplib2.RelativeURIError, SSLCertVerificationError) as _: self._url_error_message = Strings.seo_error_url_nonexistent self._image = wx.Image(Fetch.get_resource_path('video_seo_error.png'), wx.BITMAP_TYPE_PNG) result = False except ConnectionResetError as _: pass finally: h.close() # Spell check if not self._spell_check(self._link_title): self._link_title_error_message = Strings.spelling_error self._image = wx.Image(Fetch.get_resource_path('video_seo_error.png'), wx.BITMAP_TYPE_PNG) result = False if not result: self._status_color = wx.RED return result
def __init__(self, parent, work_dir: str): """ This dialog helps with adding a new menu logo into the folder structure of the website. Display a dialog with information about the image where the user can edit it. :param parent: Parent frame. :param work_dir: The working directory of the editor. """ wx.Dialog.__init__(self, parent, title=Strings.label_dialog_add_logo, size=(Numbers.add_logo_dialog_width, Numbers.add_logo_dialog_height), style=wx.DEFAULT_DIALOG_STYLE) self._main_vertical_sizer = wx.BoxSizer(wx.VERTICAL) self._horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL) self._vertical_sizer = wx.BoxSizer(wx.VERTICAL) self._information_sizer = wx.BoxSizer(wx.VERTICAL) self._image_path = None self._image_name = None self._menu_image = None self._logos_path = None self._working_directory = work_dir self._file_path = None self._config_manager = ConfigManager.get_instance() # Disk location self._original_disk_location_sub_sizer = wx.BoxSizer(wx.HORIZONTAL) self._label_image_original_path = wx.StaticText(self, -1, Strings.label_image + ': ') self._content_image_original_path = wx.StaticText(self, -1, Strings.label_none, style=wx.ST_ELLIPSIZE_MIDDLE | wx.ST_NO_AUTORESIZE) self._original_disk_location_sub_sizer.Add(self._label_image_original_path, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL) self._original_disk_location_sub_sizer.Add(self._content_image_original_path, 1, flag=wx.EXPAND) self._information_sizer.Add(self._original_disk_location_sub_sizer, flag=wx.EXPAND | wx.TOP, border=Numbers.widget_border_size) # Original size self._image_original_size_sub_sizer = wx.BoxSizer(wx.HORIZONTAL) self._label_image_size = wx.StaticText(self, -1, Strings.label_size + ': ') self._content_image_size = wx.StaticText(self, -1, Strings.label_none, style=wx.ST_ELLIPSIZE_MIDDLE | wx.ST_NO_AUTORESIZE) self._image_original_size_sub_sizer.Add(self._label_image_size, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL) self._image_original_size_sub_sizer.Add((14, -1)) self._image_original_size_sub_sizer.Add(self._content_image_size, 1, flag=wx.EXPAND) self._information_sizer.Add(self._image_original_size_sub_sizer, flag=wx.EXPAND | wx.TOP, border=Numbers.widget_border_size) # Image name sub sizer self._name_sub_sizer = wx.BoxSizer(wx.HORIZONTAL) self._label_image_name = wx.StaticText(self, -1, Strings.label_name + ': ') self._field_image_name = wx.TextCtrl(self, -1) self._field_image_name.Disable() self._name_sub_sizer.Add(self._label_image_name, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL) self._name_sub_sizer.Add((5, -1)) self._name_sub_sizer.Add(self._field_image_name, proportion=1) self._information_sizer.Add(self._name_sub_sizer, flag=wx.EXPAND | wx.TOP, border=Numbers.widget_border_size) self._field_image_name_tip = Tools.get_warning_tip(self._field_image_name, Strings.label_image_name) self._field_image_name_tip.SetMessage('') # Image preview self._image_sizer = wx.BoxSizer(wx.VERTICAL) self._bitmap = wx.StaticBitmap(self, -1, wx.Bitmap(wx.Image(Fetch.get_resource_path('menu_image_missing.png'), wx.BITMAP_TYPE_PNG))) self._image_sizer.Add(self._bitmap, flag=wx.ALL, border=1) # Buttons self._button_sizer = wx.BoxSizer(wx.VERTICAL) grouping_sizer = wx.BoxSizer(wx.HORIZONTAL) self._cancel_button = wx.Button(self, wx.ID_CANCEL, Strings.button_cancel) self._save_button = wx.Button(self, wx.ID_OK, Strings.button_save) self._browse_button = wx.Button(self, wx.ID_OPEN, Strings.button_browse) self._save_button.Disable() self._browse_button.SetDefault() grouping_sizer.Add(self._save_button) grouping_sizer.Add((Numbers.widget_border_size, Numbers.widget_border_size)) grouping_sizer.Add(self._cancel_button) grouping_sizer.Add((Numbers.widget_border_size, Numbers.widget_border_size)) grouping_sizer.Add(self._browse_button) self._button_sizer.Add(grouping_sizer, flag=wx.ALIGN_CENTER_HORIZONTAL) # Putting the sizers together self._vertical_sizer.Add(self._information_sizer, 0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=Numbers.widget_border_size) self._horizontal_sizer.Add(self._vertical_sizer, 1) self._horizontal_sizer.Add(self._image_sizer, flag=wx.TOP | wx.RIGHT, border=Numbers.widget_border_size) self._main_vertical_sizer.Add(self._horizontal_sizer, 1, flag=wx.EXPAND) self._main_vertical_sizer.Add(self._button_sizer, 0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=Numbers.widget_border_size) self.SetSizer(self._main_vertical_sizer) # Bind handlers self.Bind(wx.EVT_BUTTON, self._handle_buttons, self._save_button) self.Bind(wx.EVT_BUTTON, self._handle_buttons, self._cancel_button) self.Bind(wx.EVT_BUTTON, self._handle_buttons, self._browse_button) self.Bind(wx.EVT_TEXT, self._switch_default_button, self._field_image_name)
def __init__(self, parent, menus: Dict[str, WhitebearDocumentMenu], articles: Dict[str, WhitebearDocumentArticle], css: WhitebearDocumentCSS, index: WhitebearDocumentIndex): """ Display a dialog that allows editing additional data used in html generation. Default main title, author, contact, keywords, main page meta description. script, main page red/black text :param parent: The parent frame. :param menus: Currently loaded dictionary of menus. :param articles: Currently loaded dictionary of articles. :param css: Currently loaded css document. :param index: Currently loaded index document. """ wx.Dialog.__init__(self, parent, title=Strings.label_dialog_new_document, size=(Numbers.new_file_dialog_width, Numbers.new_file_dialog_height), style=wx.DEFAULT_DIALOG_STYLE) self._config_manager: ConfigManager = ConfigManager.get_instance() self._doc = None self._menus = menus self._articles = articles self._css_document = css self._index = index self._article_image = None self._menu_item = None self._document_path = None self._main_vertical_sizer = wx.BoxSizer(wx.VERTICAL) self._horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL) self._vertical_sizer = wx.BoxSizer(wx.VERTICAL) self._information_sizer = wx.BoxSizer(wx.VERTICAL) # Name sub sizer self._name_sub_sizer = wx.BoxSizer(wx.HORIZONTAL) self._label_file_name = wx.StaticText(self, -1, Strings.label_file_name + ': ') self._field_name = wx.TextCtrl(self, -1) self._name_sub_sizer.Add(self._label_file_name, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL) self._name_sub_sizer.Add(self._field_name, proportion=1) self._information_sizer.Add(self._name_sub_sizer, flag=wx.EXPAND | wx.TOP, border=Numbers.widget_border_size) self._field_name_tip = Tools.get_warning_tip(self._field_name, Strings.label_file_name) self._field_name.SetBackgroundColour(Numbers.RED_COLOR) self._field_name_tip.SetMessage(Strings.warning_empty) choices: List[str] = [ menu.get_page_name()[0] for menu in self._menus.values() ] choices.append('-') # Category sub sizer self._category_sub_sizer = wx.BoxSizer(wx.HORIZONTAL) self._label_category = wx.StaticText( self, -1, Strings.label_target_section + ': ') self._box_menu = wx.ComboBox(self, -1, choices=choices, style=wx.CB_DROPDOWN | wx.CB_SORT | wx.CB_READONLY) self._box_menu.SetSelection(0) self._box_menu.SetBackgroundColour(Numbers.RED_COLOR) self._category_sub_sizer.Add(self._label_category, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL) self._category_sub_sizer.Add(16, -1) self._category_sub_sizer.Add(self._box_menu, proportion=1) self._information_sizer.Add(self._category_sub_sizer, flag=wx.EXPAND | wx.TOP, border=Numbers.widget_border_size) # Image buttons self._image_buttons_sub_sizer = wx.BoxSizer(wx.HORIZONTAL) self._menu_logo_button = wx.Button( self, wx.ID_FILE1, style=wx.BU_EXACTFIT | wx.BORDER_NONE, size=wx.Size(Numbers.menu_logo_image_size, Numbers.menu_logo_image_size)) self._menu_logo_button.Disable() self._menu_logo_button.SetBitmap( wx.Bitmap( wx.Image(Fetch.get_resource_path('menu_image.png'), wx.BITMAP_TYPE_PNG))) self._main_image_button = wx.Button(self, wx.ID_FILE2, style=wx.BU_EXACTFIT | wx.BORDER_NONE) self._main_image_button.Disable() self._main_image_button.SetBitmap( wx.Bitmap( wx.Image(Fetch.get_resource_path('article_image.png'), wx.BITMAP_TYPE_PNG))) self._image_buttons_sub_sizer.Add(self._main_image_button, 1, flag=wx.EXPAND) self._image_buttons_sub_sizer.Add(Numbers.widget_border_size, Numbers.widget_border_size) self._image_buttons_sub_sizer.Add(self._menu_logo_button, 1, flag=wx.EXPAND) self._information_sizer.Add(self._image_buttons_sub_sizer, flag=wx.EXPAND | wx.TOP, border=Numbers.widget_border_size) # Buttons self._button_sizer = wx.BoxSizer(wx.VERTICAL) grouping_sizer = wx.BoxSizer(wx.HORIZONTAL) self._cancel_button = wx.Button(self, wx.ID_CANCEL, Strings.button_cancel) self._ok_button = wx.Button(self, wx.ID_OK, Strings.button_ok) self._ok_button.SetDefault() self._ok_button.Disable() grouping_sizer.Add(self._ok_button) grouping_sizer.Add( (Numbers.widget_border_size, Numbers.widget_border_size)) grouping_sizer.Add(self._cancel_button) grouping_sizer.Add( (Numbers.widget_border_size, Numbers.widget_border_size)) self._button_sizer.Add(grouping_sizer, flag=wx.ALIGN_CENTER_HORIZONTAL) # Putting the sizers together self._vertical_sizer.Add(self._information_sizer, 0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=Numbers.widget_border_size) self._horizontal_sizer.Add(self._vertical_sizer, 1) self._main_vertical_sizer.Add(self._horizontal_sizer, 1, flag=wx.EXPAND) self._main_vertical_sizer.Add(self._button_sizer, 0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=Numbers.widget_border_size) self.SetSizer(self._main_vertical_sizer) # Bind handlers self.Bind(wx.EVT_BUTTON, self._handle_buttons, self._ok_button) self.Bind(wx.EVT_BUTTON, self._handle_buttons, self._cancel_button) self.Bind(wx.EVT_TEXT, self._handle_fields, self._field_name) self.Bind(wx.EVT_BUTTON, self._handle_image_buttons, self._main_image_button) self.Bind(wx.EVT_BUTTON, self._handle_image_buttons, self._menu_logo_button) self.Bind(wx.EVT_COMBOBOX, self._handle_fields, self._box_menu) # If there are no menus, disable the dialog. if not choices: self._field_name.Disable() self._box_menu.Disable()
def convert_to_html(self) -> None: """ Converts this document into a html white bear article page. :return: None :raise UnrecognizedFileException if template file can not be validated. :raise UnrecognizedFileException if html parse fails. :raise UnrecognizedFileException if generated html fails validation. :raises UnrecognizedFileException if xml schema is incorrect. """ with open(Fetch.get_resource_path('article_template.html'), 'r') as template: template_string = template.read() is_valid, errors = Tools.validate(template_string, 'schema_article_template.xsd') if not is_valid: raise UnrecognizedFileException( Strings.exception_html_syntax_error + '\n' + 'article_template.html ' + str(errors)) parsed_template = BeautifulSoup(template_string, 'html5lib') # Fill title. title: Tag = parsed_template.find(name='title') title.string = self._page_name + ' - ' + self._menu_section.get_section_name( ) + ' | ' + Strings.page_name # Fill description. description = parsed_template.find_all(name='meta', attrs={ 'name': 'description', 'content': True }) if len(description) == 1: description[0]['content'] = self._meta_description else: raise UnrecognizedFileException( Strings.exception_parse_multiple_descriptions) # Fill keywords. keywords = parsed_template.find_all(name='meta', attrs={ 'name': 'keywords', 'content': True }) if len(keywords) == 1: keywords[0]['content'] = ', '.join(self._meta_keywords) else: raise UnrecognizedFileException( Strings.exception_parse_multiple_keywords) # Fill author. author = parsed_template.find_all(name='meta', attrs={ 'name': 'author', 'content': True }) if len(author) == 1: author[0]['content'] = self._config_manager.get_author() else: raise UnrecognizedFileException( Strings.exception_parse_multiple_authors) # Fill script. script = parsed_template.find(name='script') script.string = self._config_manager.get_script() # Fill global title. figure = parsed_template.find(name='header').figure figure.figcaption.string = self._config_manager.get_global_title() heading = parsed_template.find(name='h1', attrs={'id': 'heading'}) heading.string = self._config_manager.get_global_title() # Activate correct menu, generate menu items according to menus. menu_container = parsed_template.find(name='nav') for instance in sorted(self._menus.values(), key=lambda x: x.get_section_name(), reverse=True): new_item = parsed_template.new_tag('a', attrs={ 'class': 'menu', 'href': instance.get_filename(), 'title': instance.get_page_name()[0] }) new_item.string = instance.get_page_name()[0] if instance.get_filename() == self._menu_section.get_filename(): new_item['id'] = 'active' menu_container.append(new_item) # Fill main page title. article = parsed_template.find(name='article', attrs={'class': 'textPage'}) article.h2.string = self._page_name # Fill date. parsed_template.find(name='p', attrs={ 'id': 'date' }).string = self._date # Fill main image. main_image_figure = parsed_template.find(name='figure', attrs={'id': 'articleImg'}) main_image_figure.a['href'] = self.get_article_image( ).get_full_filename() main_image_figure.a['title'] = self.get_article_image().get_link_title( )[0] main_image_figure.img['src'] = self.get_article_image( ).get_thumbnail_filename() main_image_figure.img['alt'] = self.get_article_image().get_image_alt( )[0] main_image_figure.figcaption.string = self.get_article_image( ).get_caption()[0] # Fill main text. text_section = parsed_template.find(name='section', attrs='mainText') if not self._enabled: # Save the disabled state into the html, once the article is enabled, this special class will be removed. text_section['class'] = 'mainText disabled' for element in self.get_main_text_elements(): if isinstance(element, Heading): size = 'h3' if element.get_size() == Heading.SIZE_H3 else 'h4' text = element.get_text().get_text() color = element.get_text().get_color() new_h = parsed_template.new_tag(size) new_h.string = text if color != Strings.color_black: # Black text is default, so ignore it. new_h['class'] = color text_section.append(new_h) elif isinstance(element, ImageInText): new_div = parsed_template.new_tag('div', attrs={'class': 'center'}) href = element.get_full_filename() title = element.get_link_title()[0] src = element.get_thumbnail_filename() alt = element.get_image_alt()[0] width = element.get_thumbnail_size()[0] height = element.get_thumbnail_size()[1] new_a = parsed_template.new_tag('a', attrs={ 'href': href, 'target': Strings.blank, 'title': title }) new_img = parsed_template.new_tag('img', attrs={ 'src': src, 'alt': alt, 'width': width, 'height': height }) new_a.append(new_img) new_div.append(new_a) text_section.append(new_div) elif isinstance(element, Video): new_div = parsed_template.new_tag('div', attrs={'class': 'center'}) title = element.get_title()[0] width = element.get_size()[0] height = element.get_size()[1] src = element.get_url()[0] new_iframe = parsed_template.new_tag('iframe', attrs={ 'title': title, 'height': height, 'width': width, 'src': src, 'allowfullscreen': None }) new_div.append(new_iframe) text_section.append(new_div) elif isinstance(element, Paragraph): new_p = parsed_template.new_tag('p') new_p = self._convert_text_contents(new_p, element, parsed_template) text_section.append(new_p) elif isinstance(element, UnorderedList): new_ul = parsed_template.new_tag('ul') for par in element.get_paragraphs(): new_li = parsed_template.new_tag('li') self._convert_text_contents(new_li, par, parsed_template) new_ul.append(new_li) text_section.append(new_ul) # Fill aside images. aside = parsed_template.find(name='aside') for img in self._aside_images: new_figure = parsed_template.new_tag('figure') new_figcaption = parsed_template.new_tag( 'figcaption', attrs={'class': 'photoCaption'}) href = img.get_full_filename() title = img.get_link_title()[0] src = img.get_thumbnail_filename() alt = img.get_image_alt()[0] text = img.get_caption()[0] new_figcaption.string = text new_a = parsed_template.new_tag('a', attrs={ 'href': href, 'target': Strings.blank, 'title': title }) # Width and height are different from the thumbnail here because the image expands in the page. new_img = parsed_template.new_tag( 'img', attrs={ 'src': src, 'alt': alt, 'width': Numbers.aside_thumbnail_width, 'height': Numbers.aside_thumbnail_height, 'class': 'imgAside' }) new_a.append(new_img) new_figure.append(new_a) new_figure.append(new_figcaption) aside.append(new_figure) output = str(parsed_template) is_valid, errors = Tools.validate(output, 'schema_article.xsd') if not is_valid: raise UnrecognizedFileException(Strings.exception_bug + '\n' + self.get_filename() + ' \n' + str(errors)) self._html = output # Save the fact that this file is changed into the list of file that we need to upload. This survives editor # exit and can be restored on start. This list is cleared when a file is uploaded. self._config_manager.store_not_uploaded(self.get_filename())
def convert_to_html(self) -> None: """ Converts this document into a html white bear menu page. :return: None :raise UnrecognizedFileException if template file can not be validated. :raise UnrecognizedFileException if html parse fails. :raise UnrecognizedFileException if generated html fails validation. :raises UnrecognizedFileException if xml schema is incorrect. """ with open(Fetch.get_resource_path('menu_template.html'), 'r') as template: template_string = template.read() is_valid, errors = Tools.validate(template_string, 'schema_menu_template.xsd') if not is_valid: raise UnrecognizedFileException(Strings.exception_html_syntax_error + '\n' + 'menu_template.html ' + str(errors)) parsed_template = BeautifulSoup(template_string, 'html5lib') # Fill title. title: Tag = parsed_template.find(name='title') title.string = Strings.menu_title_stump + ' ' + self._page_name + ' | ' + Strings.page_name # Fill description. description = parsed_template.find_all(name='meta', attrs={'name': 'description', 'content': True}) if len(description) == 1: description[0]['content'] = self._meta_description else: raise UnrecognizedFileException(Strings.exception_parse_multiple_descriptions) # Fill keywords. keywords = parsed_template.find_all(name='meta', attrs={'name': 'keywords', 'content': True}) if len(keywords) == 1: keywords[0]['content'] = ', '.join(self._meta_keywords) else: raise UnrecognizedFileException(Strings.exception_parse_multiple_keywords) # Fill author. author = parsed_template.find_all(name='meta', attrs={'name': 'author', 'content': True}) if len(author) == 1: author[0]['content'] = self._config_manager.get_author() else: raise UnrecognizedFileException(Strings.exception_parse_multiple_authors) # Fill script. script = parsed_template.find(name='script') script.string = self._config_manager.get_script() # Fill global title. figure = parsed_template.find(name='header').figure figure.figcaption.string = self._config_manager.get_global_title() heading = parsed_template.find(name='h1', attrs={'id': 'heading'}) heading.string = self._config_manager.get_global_title() # Activate correct menu, generate menu items according to menus. menu_container = parsed_template.find(name='nav') for instance in sorted(self._menus.values(), key=lambda x: x.get_section_name(), reverse=True): new_item = parsed_template.new_tag('a', attrs={'class': 'menu', 'href': instance.get_filename(), 'title': instance.get_page_name()[0]}) new_item.string = instance.get_page_name()[0] if instance.get_filename() == self._file_name: new_item['id'] = 'active' menu_container.append(new_item) # Fill main page title. article = parsed_template.find(name='article', attrs={'class': 'menuPage'}) article.h2.string = self._page_name # Fill menu items. menu_container = parsed_template.find(name='nav', attrs={'class': 'sixItems'}) for item in self._menu_items: item: MenuItem attrs = {'class': 'link'} if not item.get_article(): # Skip deleted articles. continue if not item.get_article().test_self(self._config_manager.get_online_test()): # Hide this menu item because this article is not yet finished or is deleted. The item will be available # for future parsing though so the editor will load the menu item correctly for the unfinished article. attrs['class'] = 'link hidden' new_div = parsed_template.new_tag('div', attrs=attrs) href = item.get_link_href() title = item.get_link_title()[0] width = item.get_image_size()[0] height = item.get_image_size()[1] src = item.get_filename() alt = item.get_image_alt()[0] text = item.get_article_name()[0] new_a = parsed_template.new_tag('a', attrs={'href': href, 'title': title}) new_img = parsed_template.new_tag('img', attrs={'width': width, 'height': height, 'src': src, 'alt': alt}) new_p = parsed_template.new_tag('p') new_p.string = text new_a.append(new_img) new_div.append(new_a) new_div.append(new_p) menu_container.append(new_div) output = str(parsed_template) is_valid, errors = Tools.validate(output, 'schema_menu.xsd') if not is_valid: raise UnrecognizedFileException(Strings.exception_bug + '\n' + self.get_filename() + ' \n' + str(errors)) self._html = output
def convert_to_html(self) -> None: """ Converts this document into a html white bear article page. :return: None :raise UnrecognizedFileException if template file can not be validated. :raise UnrecognizedFileException if html parse fails. :raise UnrecognizedFileException if generated html fails validation. :raises UnrecognizedFileException if xml schema is incorrect. """ self.update_content() with open(Fetch.get_resource_path('index_template.html'), 'r') as template: template_string = template.read() is_valid, errors = Tools.validate(template_string, 'schema_index_template.xsd') if not is_valid: raise UnrecognizedFileException( Strings.exception_html_syntax_error + '\n' + 'index_template.html ' + str(errors)) parsed_template = BeautifulSoup(template_string, 'html5lib') # Fill title. title: Tag = parsed_template.find(name='title') title.string = self._page_name + ' | ' + self._global_title # Fill description. description = parsed_template.find_all(name='meta', attrs={ 'name': 'description', 'content': True }) if len(description) == 1: description[0]['content'] = self._meta_description else: raise UnrecognizedFileException( Strings.exception_parse_multiple_descriptions) # Fill keywords. keywords = parsed_template.find_all(name='meta', attrs={ 'name': 'keywords', 'content': True }) if len(keywords) == 1: keywords[0]['content'] = self._meta_keywords else: raise UnrecognizedFileException( Strings.exception_parse_multiple_authors) # Fill author. author = parsed_template.find_all(name='meta', attrs={ 'name': 'author', 'content': True }) if len(author) == 1: author[0]['content'] = self._author else: raise UnrecognizedFileException( Strings.exception_parse_multiple_descriptions) # Fill script. script = parsed_template.find(name='script') script.string = self._script # Fill global title. figure = parsed_template.find(name='header').figure figure.figcaption.string = self._global_title heading = parsed_template.find(name='h1', attrs={'id': 'heading'}) heading.string = self._global_title # Activate correct menu, generate menu items according to menus. menu_container = parsed_template.find(name='nav') for instance in sorted(self._menus.values(), key=lambda x: x.get_section_name(), reverse=True): new_item = parsed_template.new_tag('a', attrs={ 'class': 'menu', 'href': instance.get_filename(), 'title': instance.get_page_name()[0] }) new_item.string = instance.get_page_name()[0] if instance.get_filename() == self.get_filename(): new_item['id'] = 'active' menu_container.append(new_item) # Fill main page title. article = parsed_template.find(name='article', attrs={'class': 'indexPage'}) article.h2.string = self._page_name # Fill text. new_black_p = parsed_template.new_tag('p') new_black_p.string = self._black_text new_red_p = parsed_template.new_tag('p') new_strong = parsed_template.new_tag('strong', attrs={'class': 'red'}) new_strong.string = self._red_text new_red_p.append(new_strong) article.h2.insert_after(new_red_p) article.h2.insert_after(new_black_p) # Fill news. news = parsed_template.find(name='h3', attrs={'id': 'news'}) # Sort all articles by date. sorted_articles = sorted(self._articles.values(), key=lambda x: x.get_computable_date(), reverse=True) new_ul = parsed_template.new_tag('ul') limit = self._number_of_news for index, item in enumerate(sorted_articles): if index >= limit: break if item.test_self(self._config_manager.get_online_test()): new_li = parsed_template.new_tag('li') href = item.get_filename() title = item.get_page_name()[0] date = item.get_date()[0] + ' ' new_a = parsed_template.new_tag('a', attrs={ 'href': href, 'title': title }) new_a.string = title new_li.string = date new_li.append(new_a) new_ul.append(new_li) else: # Add the next article to news. limit = limit + 1 news.insert_after(new_ul) # Fill contact. contact = parsed_template.find(name='h3', attrs={'id': 'contact'}) # Insert the author's contact as an image. image: wx.Bitmap = Tools.create_image(self._contact) image_path = os.path.join(self._working_directory, Strings.folder_images, Strings.contact_file) image.SaveFile(image_path, wx.BITMAP_TYPE_PNG) src = os.path.join(Strings.folder_images, Strings.contact_file) new_img = parsed_template.new_tag('img', attrs={ 'width': image.GetWidth(), 'height': image.GetHeight(), 'src': src, 'alt': Strings.contact_default_alt }) contact.insert_after(new_img) # Fill design. new_p = parsed_template.new_tag('p') new_p.string = 'Web design: ' + self._author new_img.insert_after(new_p) # Fill aside images from the newest articles. latest_images = [] for article in sorted_articles: if article.is_enabled(): images = article.get_aside_images() if images: latest_images.append(images[0]) if len(latest_images) >= Numbers.max_index_images: break aside = parsed_template.find(name='aside') for img in latest_images: new_figure = parsed_template.new_tag('figure') new_figcaption = parsed_template.new_tag( 'figcaption', attrs={'class': 'photoCaption'}) href = img.get_full_filename() title = img.get_link_title()[0] src = img.get_thumbnail_filename() alt = img.get_image_alt()[0] text = img.get_caption()[0] new_figcaption.string = text new_a = parsed_template.new_tag('a', attrs={ 'href': href, 'target': Strings.blank, 'title': title }) # Width and height are different from the thumbnail here because the image expands in the page. new_img = parsed_template.new_tag( 'img', attrs={ 'src': src, 'alt': alt, 'width': Numbers.aside_thumbnail_width, 'height': Numbers.aside_thumbnail_height, 'class': 'imgAside' }) new_a.append(new_img) new_figure.append(new_a) new_figure.append(new_figcaption) aside.append(new_figure) output = str(parsed_template) is_valid, errors = Tools.validate(output, 'schema_index.xsd') if not is_valid: raise UnrecognizedFileException(Strings.exception_bug + '\n' + self.get_filename() + ' \n' + str(errors)) self._html = output
def test_self(self) -> bool: """ SEO test self for page name, alt and link title. If the menu image is not accessible on disk, set a special warning image. :return: True if test is ok, False otherwise """ # Clear all error before each retest self._article_name_error_message: str = '' self._link_title_error_message: str = '' self._image_alt_error_message: str = '' self._status_color = wx.NullColour result = True # Check page name length must be at least 3 and must not be default if len(self._article_name) < Numbers.menu_name_min_length or len( self._article_name) > Numbers.menu_name_max_length: self._article_name_error_message = Strings.seo_error_menu_name_length result = False if self._article_name == Strings.label_article_menu_logo_name_placeholder: self._article_name_error_message = Strings.seo_error_default_value result = False # Check menu image disk path if not self._menu_image_path: self._menu_image = wx.Image(Fetch.get_resource_path('menu_image_missing.png'), wx.BITMAP_TYPE_PNG) result = False else: try: image = wx.Image(Fetch.get_resource_path(self._menu_image_path), wx.BITMAP_TYPE_ANY) if image.GetSize() == (Numbers.menu_logo_image_size, Numbers.menu_logo_image_size): self._menu_image = image else: self._menu_image = wx.Image(Fetch.get_resource_path('menu_image_wrong.png'), wx.BITMAP_TYPE_PNG) result = False except FileNotFoundError as _: self._menu_image = wx.Image(Fetch.get_resource_path('menu_image_missing.png'), wx.BITMAP_TYPE_PNG) result = False # Check article image link title if len(self._link_title) < Numbers.article_image_title_min or len( self._link_title) > Numbers.article_image_title_max: self._link_title_error_message = Strings.seo_error_link_title_length result = False if self._link_title == Strings.label_menu_logo_link_title_placeholder: self._link_title_error_message = Strings.seo_error_default_value result = False # Check article image alt if len(self._image_alt) < Numbers.article_image_alt_min or len( self._image_alt) > Numbers.article_image_alt_max: self._image_alt_error_message = Strings.seo_error_image_alt_length result = False if self._image_alt == Strings.label_article_image_alt: self._image_alt_error_message = Strings.seo_error_default_value result = False # Spellchecks if not self._spell_check(self._article_name): self._article_name_error_message = Strings.spelling_error result = False if not self._spell_check(self._link_title): self._link_title_error_message = Strings.spelling_error result = False if not self._spell_check(self._image_alt): self._image_alt_error_message = Strings.spelling_error result = False if not result: self._status_color = wx.RED return result