Skip to content

Commit

Permalink
adds frame/CMS Category handling
Browse files Browse the repository at this point in the history
  • Loading branch information
WolfgangFahl committed Nov 5, 2023
1 parent ecc3542 commit bd60541
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 77 deletions.
2 changes: 1 addition & 1 deletion frontend/cmsmain.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class CmsMain(WebserverCmd):
"""
ContentManagement System Main Program
"""

def getArgParser(self,description:str,version_msg)->ArgumentParser:
"""
override the default argparser call
Expand Down
124 changes: 124 additions & 0 deletions frontend/frame.py
Original file line number Diff line number Diff line change
@@ -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 = """
<!-- Hamburger Menu Start -->
<style>
/* Basic styling */
.menu { display: none; }
.hamburger { cursor: pointer; }
.hamburger:hover { opacity: 0.7; }
/* Menu items layout */
.menu ul { list-style-type: none; padding: 0; }
.menu li { padding: 8px; background-color: #f0f0f0; margin-bottom: 5px; }
/* Show the menu when .show class is added via JavaScript */
.show { display: block; }
</style>
<!-- Hamburger Icon -->
<div class="hamburger" onclick="toggleMenu()">☰</div>
<!-- Menu Items -->
<div class="menu" id="mainMenu">
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#services">Services</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</div>
<script>
function toggleMenu() {
var menu = document.getElementById("mainMenu");
if (menu.classList.contains("show")) {
menu.classList.remove("show");
} else {
menu.classList.add("show");
}
}
</script>
<!-- Hamburger Menu End -->
"""
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"""<!doctype html>
<html lang="{self.lang}">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{self.title}</title>
{style_html}
</head>
<body>
"""
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}
</body>
</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()}
<div class="container">
{content}
</div><!-- /.container -->
{self.footer()}"""
return html
4 changes: 2 additions & 2 deletions frontend/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
9 changes: 3 additions & 6 deletions frontend/webserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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}:<br>{error}")
return HTMLResponse(html)
response=frontend.get_path_response(f"/{page_path}")
return response

def enableSites(self, siteNames):
'''
Expand Down
136 changes: 68 additions & 68 deletions frontend/wikicms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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><body>", "")
html = html.replace("</body></html>", "")
# Remove empty paragraphs
html = re.sub(r'<p class="mw-empty-elt">\s*</p>', '', 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:
Expand Down Expand Up @@ -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}:<br>{error}"
framed_html=frame.frame(html)
response=HTMLResponse(framed_html)
return response
Loading

0 comments on commit bd60541

Please sign in to comment.