Website Manager

Most of my website pages are static, and don't use frames. Since my web hosting doesn't allow server-side includes, I need a way to reduce the effort of maintaining my navigation bars at the top and left-hand side of every page.

Ideally, these navigation bars should be auto-generated, and inserted into all pages. In addition, I'd like a tool that also maintains the sitemap.xml description of my site, and a way of downloading and uploading the site so that I can make changes at multiple locations.

In all of this I may be re-inventing many wheels, but this is mainly about learning the various technologies involved, and not about finding somebody else's solution.

Website Manager Tasks & Requirements

  1. Site description shall be in XML and shall capture source pages and hierarchy
  2. Site description shall be validated through XML Schema
  3. Website Manager (WSM) shall dynamically adapt to schema updates
  4. WSM shall allow validated editing of site_description.xml
  5. WSM shall allow creation of new pages based on templates
  6. WSM shall auto-generate Horizontal Navigation Bars (HNB) from XML
  7. HNBs shall be unique to each page, and show depth in hierarchy
  8. WSM shall auto-generate Vertical Navigation Menu (VNM) from XML
  9. VNM shall be common across all pages
  10. WSM shall allow user to enter value for maximum VNM depth
  11. WSM shall insert HNB and VNM HTML code into each page of the website
  12. WSM shall auto-generate sitemap.xml from XML
  13. WSM shall query HTML for required local files (PDFs etc) and include these in sitemap.xml
  14. WSM shall provide upload and download facilities

Describing a Website

For my navigation bars to work, I need a way of capturing each page and how it fits into the hierarchy. I've chosen XML as the way to capture this information in a file, and I'll call it site_description.xml. I've also chosen to validate this file, and there are two common choices: DTD or XML Schema.

First, let's look at DTD.

<!ELEMENT website (url, ftp, page)>
<!ELEMENT url (#PCDATA)>
<!ELEMENT ftp (#PCDATA)>
<!ELEMENT page (filename,hnav,hnav_text?,vnav,vnav_text?,page*)>
<!ELEMENT filename (#PCDATA)>
<!ELEMENT hnav (#PCDATA)>
<!ELEMENT hnav_text (#PCDATA)>
<!ELEMENT vnav (#PCDATA)>
<!ELEMENT vnav_text (#PCDATA)>

So, a website has a url , an ftp address to which to upload it, and a page . A page has a corresponding filename, a hnav flag to say whether it appears in the horizontal navigation bar, the optional text for same, a vnav flag to say whether it appears in the vertical navigation bar, the optional text for same, and zero or more child pages.

This nicely captures all the pages in a website, and their hierarchy starting from a single main page.

DTDs don't allow you to validate the type of data you put in your XML file, so now let's look at using an XML Schema to describe the same data.

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://willough.customer.netspace.net.au"
xmlns="http://willough.customer.netspace.net.au"
elementFormDefault="qualified">

<xs:element name="website">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="url" type="xs:string"/>
      <xs:element name="ftp" type="xs:string"/>
      <xs:element name="page" type="pageinfo"/>
    </xs:sequence>
  </xs:complexType>
</xs:element>

<xs:complexType name="pageinfo">
  <xs:sequence>
    <xs:element name="filename" type="xs:string"/>
    <xs:element name="hnav" type="xs:boolean" default="false"/>
    <xs:element name="hnav_text" type="xs:string"/>
    <xs:element name="vnav" type="xs:boolean" default="false"/>
    <xs:element name="vnav_text" type="xs:string" minOccurs="0" maxOccurs="1"/>
    <xs:element name="page" type="pageinfo" minOccurs="0" maxOccurs="unbounded"/>
  </xs:sequence>
</xs:complexType>

</xs:schema>

Replacing Text from a Template

Let's keep it simple, here. We'll mark the start and end of each section we want to replace with special HTML comment markers. This allows us to maintain a simple state machine in the code. We write the input to the output until we find the start marker, and then write nothing else until we find the end marker. At this point, we write the template code to the output, and then resume writing the input to the output. Here's a quick test of this logic in a stand-alone module.

#!/usr/bin/env python

vnav_template = open("vnav_template.html", "rU")
input_html = open("test.html", "rU")
output_html = open("output.html", "wb")

inside_vnav = False

for line in input_html:
  if line[:17] == "<!-- VNAV Start -->":
    # Need to preserve this marker in the output
    output_html.write(line)
    inside_vnav = True
  elif line[:15] == "<!-- VNAV End -->":
    # Substitute the template code
    output_html.write(vnav_template.read())
    inside_vnav = False

  if not inside_vnav:
    output_html.write(line)

vnav_template.close()
input_html.close()
output_html.close()

This is simply extensible, perhaps by adding more flags for more sections. A more sophisticated solution would be to cope with arbitrary numbers and types of start and end tags. I'll leave that as an exercise for the reader. Assume that template start and end markers are not nested, nor interleaved.

OK, I relent. How about using a python dictionary to hold each tag and its corresponding template file? The code is not much more complex, and I think is much more elegant. Here's my solution

Links

HTML5 Powered with CSS3 / Styling, and Semantics