text: #!/usr/bin/env python3
  import pathlib
  import os.path
  import json
  import markdown
  import xml.dom.minidom as minidom
  from datetime import datetime, timezone, timedelta
  import rsserver.server
  
  class RSSConstructorException(Exception):
  	"""Exception thrown during RSS object generating.
  	
  	This exception is thrown when, for example, the structure
  	requirements for building an item object or a feed object
  	aren't met.
  	
  	"""
  	pass
  
  class RSSFeed:
  	"""Represents a whole RSS feed.
  
  	Attributes:
  		info (dict): The feed's information, loaded from server_info.json
  		items (list of RSSItem): The feed's items.
  	
  	"""
  	
  	def __init__(self, path):
  		"""Loads all the feed's date from the specified path.
  
  		The feed's info gets loaded from a `server_info.json`, and the items
  		get loaded from the items folder.
  
  		Args:
  			path (str): The path for loading all the information from.
  
  		"""
  		
  		if os.path.isdir(path):
  			path_info = path + "/server_info.json"
  			path_items = path + "/items"
  
  			if os.path.isfile(path_info) and os.path.isdir(path_items):
  				file_info = open(path_info)
  				self.info = json.load(file_info)
  				self.items = []
  
  				plib_items = pathlib.Path(path_items)
  				for file in plib_items.iterdir():
  					if file.is_dir():
  						pathtoitem = full_path(file)
  						self.items.append(RSSItem(pathtoitem))
  
  				self.items.sort()
  			else:
  				raise RSSConstructorException("Required information is missing")
  		else:
  			raise RSSConstructorException("Please specify a directory")
  
  	def generatedoc(self):
  		"""Generates the DOM document
  
  		xml.dom.minidom is used through the whole process, since CDATA nodes are
  		required inside description nodes in items, due to the fact that the
  		content gets translated from Markdown to HTML.
  
  		Returns:
  			xml.dom.minidom.Document: The feed's document
  
  		"""
  		
  		document = minidom.Document()
  		root = document.createElement("rss")
  		root.setAttribute("version", "2.0")
  		document.appendChild(root)
  		cont = document.createElement("channel")
  		root.appendChild(cont)
  
  		title = document.createElement("title")
  		title.appendChild(document.createTextNode(self.info["title"]))
  		cont.appendChild(title)
  		link = document.createElement("link")
  		link.appendChild(document.createTextNode(
  			"http://" +
  			rsserver.server.address + ":" + str(rsserver.server.port)
  			))
  		cont.appendChild(link)
  		description = document.createElement("description")
  		description.appendChild(document.createTextNode(self.info["description"]))
  		cont.appendChild(description)
  
  		for item in self.items:
  			node = item.getnode(document)
  			cont.appendChild(node)
  
  		return document
  
  class RSSItem:
  	"""Represents a single item inside an RSS feed.
  
  	Attributes:
  		name (str): The name of the original folder.
  		info (dict): The item's information, sourced from JSON file.
  		content (str): The item's content, written in Markdown.
  
  	"""
  	def __init__(self, path):
  		"""
  		Loads the required files from the selected folder, then stores
  		their content.
  
  		Note:
  			For initializing, the files `info.json` and `content.md` are
  			required.
  		
  		Args:
  			path (str): The folder's path to be loaded.
  
  		Raises:
  			RSSConstructorException: If the path doesn't exist, is not a folder,
  				or doesn't contain the required files.
  
  		"""
  		path_info = path + "/info.json"
  		path_content = path + "/content.md"
  		parts = path.split("/")
  		self.name = parts[len(parts) - 1]
  
  		if os.path.isfile(path_info) and os.path.isfile(path_content):
  			file_info = open(path_info)
  			file_content = open(path_content)
  
  			self.info = json.load(file_info)
  			self.content = file_content.read()
  
  			file_info.close()
  			file_content.close()
  		else:
  			raise RSSConstructorException("Needed files not found.")
  
  	def __gt__(self, other):
  		"""For ordering items by reverse date.
  
  		The first items to appear in a RSS feed are the most recent ones.
  		Thus, they are ordered from newest to oldest,
  
  		Args:
  			other (RSSItem): The other item to compare
  
  		Returns:
  			True if the other's date is more recent, False otherwise
  
  		"""
  		self_date = dateobj(self.info["date"], self.info["time"],
  		self.info["timezone"])
  		other_date = dateobj(other.info["date"], other.info["time"],
  		other.info["timezone"])
  		return self_date < other_date
  
  	def renderHTML(self):
  		"""Generates a HTML document with the contents of the item.
  
  		The item's "link" could be attempted to open in a browser
  		(which is the original intention of the <link> node), so generating
  		a simple page might fill the gap.
  
  		The HTML code is constructed with strings because Markdown function
  		returns a string, and there was no incentive to make a XHTML document
  		from scratch.
  
  		Returns:
  			A basic HTML document for displaying in a web browser.
  
  		"""
  		title = self.info["title"]
  		body = "<h1>" + title + "</h1>"
  		body += markdown.markdown(self.content)
  		head = "<meta charset='UTF-8'>"
  		
  		#Getting feed's name back from server_info.json
  		base_info_path = rsserver.server.base_directory + "/server_info.json"
  		base_info = open(base_info_path)
  		base_info_dict = json.load(base_info)
  		base_info.close()
  		title += " - " + base_info_dict["title"]
  
  		head += "<title>" + title +"</title>"
  		return "<html><head>" + head + "</head><body>" + body + "</body></html>"
  	
  	def getnode(self, document):
  		"""Generates a node for a document.
  
  		It uses all the values gathered in the info dictionary to build the
  		nodes, so having all keys and values set correctly is necessary for
  		this method to work correctly.
  
  		Args:
  			document (xml.dom.minidom.Document): The document to which the final node
  				will be added
  
  		Returns:
  			xml.dom.minidom.Element: This item's node
  
  		"""
  		
  		link = "http://" + rsserver.server.address + ":"
  		link += str(rsserver.server.port) + "/" + self.name
  		
  		node = document.createElement("item")
  		
  		title = document.createElement("title")
  		title_text = document.createTextNode(self.info["title"])
  		title.appendChild(title_text)
  		node.appendChild(title)
  		#Link nodes, which use the same text node
  		link_text = document.createTextNode(link)
  		link_node = document.createElement("link")
  		link_node.appendChild(link_text.cloneNode(True))
  		guid = document.createElement("guid")
  		guid.setAttribute("isPermaLink", "true")
  		guid.appendChild(link_text)
  		node.appendChild(link_node)
  		node.appendChild(guid)
  
  		description = document.createElement("description")
  		content = document.createCDATASection(markdown.markdown(self.content))
  		description.appendChild(content)
  		node.appendChild(description)
  
  		pubdate = document.createElement("pubDate")
  		pubdate.appendChild(document.createTextNode(genedate(self.info)))
  		node.appendChild(pubdate)
  
  		for c in self.info["categories"]:
  			category = document.createElement("category")
  			category.appendChild(document.createTextNode(c))
  			node.appendChild(category)
  
  		return node
  
  def full_path(path):
  	"""Support function, reassembles a path from a Path object.
  
  	Used for reconstructing a directory Path object's original path.
  
  	Args;
  		path (pathlib.Path): The path object.
  
  	Returns:
  		str: Its original path.
  
  	"""
  	result = ""
  	if isinstance(path, pathlib.Path):
  		parts = path.parts
  		for x in parts:
  			result += x + "/"
  	
  	return result[0:len(result)-1]
  
  def dateobj(stringdate, stringtime, stringtz):
  	"""Support function, creates a datetime object from an item's info attribute.
  
  	Generates a full datetime object with date, time and timezone information
  	from the item's dictionary.
  
  	Args:
  		stringdate (str): The string representing the date in the format YYYY-MM-DD
  			(Y: year, M: month, D: day)
  		stringtime (str): The string representing the time in the format HH:MM:SS
  			(H: hours, M: minutes, S: seconds)
  		stringtz (str): The string representing the timezone in the format xHHMM
  			(x: Plus (+) or minus (-) symbol, H: hours, M: minutes)
  
  	Returns:
  		datetime.datetime: A datetime object with the required data filled
  
  	"""
  	tz_multipliers = {"+": 1, "-": -1}
  	
  	date_num = [int(n) for n in stringdate.split("-")]
  	time_num = [int(n) for n in stringtime.split(":")]
  	tz_hours = int(stringtz[1:3]) * tz_multipliers[stringtz[0]]
  	tz_minutes = int(stringtz[3:5]) * tz_multipliers[stringtz[0]]
  	delta = timedelta(hours=tz_hours, minutes=tz_minutes)
  
  	return datetime(date_num[0], date_num[1], date_num[2],
  	time_num[0], time_num[1], time_num[2], tzinfo=timezone(delta))
  
  def genedate(info):
  	"""Generates RFC 822 compliant date string
  
  	Creates, from an item's info attribute, the string required for
  	the pubDate node, which must be compliant with the standard
  	RFC 822: https://www.w3.org/Protocols/rfc822/#z28
  
  	Args:
  		info (dict): A dictionary sourced from an item object
  
  	Returns:
  		The pubDate string
  
  	"""
  	days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
  	months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
  	"Aug", "Sep", "Oct", "Nov", "Dec"]
  
  	obj = dateobj(info["date"], info["time"], info["timezone"])
  	pubdate = days[obj.weekday()] + ", " + str(obj.day)
  	pubdate += " " + months[obj.month - 1] + " " + str(obj.year)
  	pubdate += " " + info["time"] + " " + info["timezone"]
  
  	return pubdate