diff --git a/frontend/cmsmain.py b/frontend/cmsmain.py index 820ca80..661c526 100644 --- a/frontend/cmsmain.py +++ b/frontend/cmsmain.py @@ -12,7 +12,7 @@ class CmsMain(WebserverCmd): """ ContentManagement System Main Program """ - + def getArgParser(self,description:str,version_msg)->ArgumentParser: """ override the default argparser call diff --git a/frontend/frame.py b/frontend/frame.py new file mode 100644 index 0000000..e015cfa --- /dev/null +++ b/frontend/frame.py @@ -0,0 +1,124 @@ +class HtmlFrame: + """ + A class to frame html content with a basic HTML document structure. + + Attributes: + lang (str): Language of the HTML document. + title (str): Title of the HTML document. + """ + + def __init__(self, frontend,title: str, lang: str = "en") -> None: + """ + Initialize HtmlFrame with a specified language and title. + + Args: + title (str): Title for the HTML document. + lang (str, optional): Language of the HTML document. Defaults to "en". + """ + self.frontend=frontend + self.lang = lang + self.title = title + + def hamburger_menu(self) -> str: + """ + Generate the HTML, CSS, and JavaScript for a hamburger menu. + + Returns: + str: Hamburger menu HTML, CSS, and JavaScript. + """ + menu_html = """ + + + + +
+ + + + + + +""" + return menu_html + + def header(self) -> str: + """ + Generate the header part of the HTML document. + + Returns: + str: Header part of an HTML document as a string. + """ + style_key=f"CMS/style" + style_html=self.frontend.cms_pages.get(style_key,"") + html = f""" + + + + + {self.title} + {style_html} + + +""" + return html + + def footer(self) -> str: + """ + Generate the footer part of the HTML document. + + Returns: + str: Footer part of an HTML document as a string. + """ + footer_key=f"CMS/footer/{self.lang}" + footer_html=self.frontend.cms_pages.get(footer_key,"") + html = f"""{footer_html} + + +""" + return html + + def frame(self, content: str) -> str: + """ + Frame the given HTML content with the header and footer of the document. + + Args: + content (str): HTML content to be framed within the HTML structure. + + Returns: + str: Complete HTML document as a string with the provided content framed. + """ + html = f"""{self.header()} +{self.hamburger_menu()} +
+{content} +
+{self.footer()}""" + return html diff --git a/frontend/server.py b/frontend/server.py index cec9fbd..b0f160d 100644 --- a/frontend/server.py +++ b/frontend/server.py @@ -193,7 +193,7 @@ def getStorePath(self,prefix:str="serverConfig")->str: iniPath=self.homePath+"/.wikicms" if not os.path.isdir(iniPath): os.makedirs(iniPath) - storePath="%s/%s" % (iniPath,prefix) + storePath=f"{iniPath}/{prefix}" return storePath def store(self): @@ -245,7 +245,7 @@ def checkApacheConfiguration(self,conf,status='enabled')->str: Returns: a state symbol ''' - path="/etc/apache2/sites-%s/%s.conf" % (status,conf) + path=f"/etc/apache2/sites-{status}/{conf}.conf" confExists=os.path.isfile(path) stateSymbol=self.stateSymbol(confExists) return stateSymbol diff --git a/frontend/webserver.py b/frontend/webserver.py index 58fcb69..0f19979 100644 --- a/frontend/webserver.py +++ b/frontend/webserver.py @@ -56,7 +56,7 @@ async def wikis(client:Client): return RedirectResponse('/login') return await self.wikis() - @app.get('/{frontend_name}/{page_path}') + @app.get('/{frontend_name}/{page_path:path}') def render_path(frontend_name: str, page_path: str) -> HTMLResponse: """ Handles a GET request to render the path of the given frontend. @@ -89,11 +89,8 @@ def render_path(self,frontend_name:str,page_path:str): frontend=self.server.frontends.get(frontend_name,None) if frontend is None: raise HTTPException(status_code=404, detail=f"frontend {frontend_name} is not available") - pagetitle,content,error=frontend.getContent(page_path) - html=content - if error: - html=(f"error getting {pagetitle} for {frontend_name}:
{error}") - return HTMLResponse(html) + response=frontend.get_path_response(f"/{page_path}") + return response def enableSites(self, siteNames): ''' diff --git a/frontend/wikicms.py b/frontend/wikicms.py index 592ebfb..d6d8db8 100644 --- a/frontend/wikicms.py +++ b/frontend/wikicms.py @@ -6,30 +6,35 @@ from wikibot3rd.wikiclient import WikiClient from wikibot3rd.smw import SMWClient from frontend.site import Site -from bs4 import BeautifulSoup +from bs4 import BeautifulSoup, Comment +import re import traceback import requests - +from fastapi import Response +from fastapi.responses import HTMLResponse +from frontend.frame import HtmlFrame class Frontend(object): """ Wiki Content Management System Frontend """ - def __init__(self, siteName: str, debug: bool = False, filterKeys=None): + def __init__(self, site_name: str,parser:str="lxml",debug: bool = False, filterKeys=None): """ Constructor Args: - siteName(str): the name of the site this frontend is for + site_name(str): the name of the site this frontend is for + parser(str): the beautiful soup parser to use e.g. html.parser debug: (bool): True if debugging should be on filterKeys: (list): a list of keys for filters to be applied e.g. editsection """ - - self.site = Site(siteName) + self.name=site_name + self.parser=parser + self.site = Site(site_name) self.debug = debug self.wiki = None if filterKeys is None: - self.filterKeys = ["editsection", "parser-output"] + self.filterKeys = ["editsection", "parser-output","parser-output"] else: self.filterKeys = [] @@ -85,6 +90,20 @@ def open(self, ws=None): self.wiki.login() self.smwclient = SMWClient(self.wiki.getSite()) self.site.open(ws) + self.cms_pages=self.get_cms_pages() + + def get_cms_pages(self)->dict: + """ + get the Content Management elements for this site + """ + cms_pages={} + ask_query="[[Category:CMS]]" + page_records=self.smwclient.query(ask_query, "cms pages") + for page_title in list(page_records): + page_title,html,error=self.getContent(page_title) + if not error: + cms_pages[page_title]=html + return cms_pages def errMsg(self, ex): if self.debug: @@ -157,7 +176,10 @@ def proxy(self, path: str) -> str: return response - def filter(self, html): + def filter(self, html:str)->str: + """ + filter the given html + """ return self.doFilter(html, self.filterKeys) def fixNode(self, node, attribute, prefix, delim=None): @@ -204,62 +226,39 @@ def fixHtml(self, soup): self.fixNode(a, "href", "/") return soup - def unwrap(self, soup): + def unwrap(self, soup)->str: + """ + unwrap the soup + """ html = str(soup) html = html.replace("", "") html = html.replace("", "") + # Remove empty paragraphs + html = re.sub(r'

\s*

', '', html) + + # Replace multiple newline characters with a single newline character + html = re.sub(r'\n\s*\n', '\n', html) return html def doFilter(self, html, filterKeys): # https://stackoverflow.com/questions/5598524/can-i-remove-script-tags-with-beautifulsoup - soup = BeautifulSoup(html, "lxml") + soup = BeautifulSoup(html, self.parser) if "parser-output" in filterKeys: parserdiv = soup.find("div", {"class": "mw-parser-output"}) if parserdiv: soup = parserdiv + inner_html = parserdiv.decode_contents() + # Parse the inner HTML string to create a new BeautifulSoup object + soup = BeautifulSoup(inner_html, self.parser) pass # https://stackoverflow.com/questions/5041008/how-to-find-elements-by-class if "editsection" in filterKeys: for s in soup.select("span.mw-editsection"): s.extract() + for comments in soup.findAll(text=lambda text: isinstance(text, Comment)): + comments.extract() return soup - def getFrame(self, pageTitle): - """ - get the frame template to be used for the given pageTitle# - - Args: - pageTitle(str): the pageTitle to get the Property:Frame for - - Returns: - str: the frame or None - """ - askQuery = ( - """{{#ask: [[%s]] -|mainlabel=- -|?Frame=frame -}} -""" - % pageTitle - ) - frame = None - frameResult = {} - try: - frameResult = self.smwclient.query(askQuery) - except Exception as ex: - if "invalid characters" in self.unwrap(ex): - pass - else: - raise ex - if pageTitle in frameResult: - frameRow = frameResult[pageTitle] - frame = frameRow["frame"] - # legacy java handling - if frame is not None: - frame = frame.replace(".rythm", "") - pass - return frame - def getContent(self, pagePath: str): """get the content for the given pagePath Args: @@ -312,33 +311,34 @@ def toReveal(self, html): html = self.unwrap(soup) return html - def render(self, path: str, **kwargs) -> str: + def get_path_response(self, path: str) -> str: """ - render the given path + get the repsonse for the the given path Args: path(str): the path to render the content for - kwargs(): optional keyword arguments - + Returns: - str: the rendered result + Response: a FastAPI response """ if self.needsProxy(path): - result = self.proxy(path) + html_response = self.proxy(path) + # Create a FastAPI response object + response=Response(content=html_response.content, + status_code=html_response.status_code, + headers=dict(html_response.headers)) else: - pageTitle, content, error = self.getContent(path) - frame = self.getFrame(pageTitle) - if frame is not None: - template = "%s.html" % frame - if frame == "reveal" and error is None: - content = self.toReveal(content) - else: - template = self.site.template - if not "title" in kwargs: - kwargs["title"] = pageTitle - if not "content" in kwargs: - kwargs["content"] = content - if not "error" in kwargs: - kwargs["error"] = error - result = self.renderTemplate(template, **kwargs) - return result + page_title, content, error = self.getContent(path) + frame=HtmlFrame(self,title=page_title) + html=content + # frame = self.getFrame(pageTitle) + # if frame is not None: + # template = "%s.html" % frame + if frame == "reveal" and error is None: + content = self.toReveal(content) + html=content + if error: + html=f"error getting {page_title} for {self.name}:
{error}" + framed_html=frame.frame(html) + response=HTMLResponse(framed_html) + return response diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 97decd8..dbd5e48 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -3,6 +3,7 @@ @author: wf """ +import json import unittest from frontend.wikicms import Frontend from tests.test_webserver import TestWebServer @@ -176,6 +177,19 @@ def testFixHtml(self): self.assertFalse("""srcset="/images""" in content) self.assertTrue("""srcset="/www/images""" in content) pass + + def test_cms_pages(self): + """ + test the content management pages + """ + frontend = self.server.enableFrontend("www") + frontend.open() + cms_pages=frontend.get_cms_pages() + debug=self.debug + debug=True + if debug: + print(json.dumps(cms_pages,indent=2)) + self.assertTrue("CMS/footer/de" in cms_pages) if __name__ == "__main__":