James Gardner: Home > Work > Code > SiteTool > 0.3.0 > Dreamweaver Support

SiteTool v0.3.0 documentation

Dreamweaver Support

The dreamweavertemplate module provides support for working with templates and library items in Dreamweaver format.

Caution

Since I don’t actually have a copy of Dreamweaver I haven’t been able to test that what I’ve implemented here is actually compatible with Dreamweaver. That actually doesn’t matter to me too much because its the functionality I’m after, not the compatibility. That said, I would like to know if it is compatible and would be happy to accept patches with tests.

Caution

The software relies on the posixpath module which isn’t available for Windows.

Introduction

Simple Example

As an example consider this template (based on a real example used to generate the pythonweb.org website back in 2002):

>>> main_template = """\
... <?xml version="1.0" encoding="iso-8859-1"?>
... <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
...   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
... <html xmlns="http://www.w3.org/1999/xhtml">
... <head>
... <!-- TemplateBeginEditable name="doctitle" -->
... <title>Python Web Project</title>
... <!-- TemplateEndEditable -->
... <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
... <link href="../css/style.css" rel="stylesheet" type="text/css" />
... <!--     TemplateBeginEditable name="head" --><!--  TemplateEndEditable -->
... </head>
... <body>
... <!--
... Copyright 2002-2004 James Gardner All Rights Reserved.
... -->
... <div id="body1">
...      <div id="nav1">
... <!-- #BeginLibraryItem  "../Library/main_links.lbi" -->
... <ul>
...     <li id="active"><a href="../index.html" id="current">Home</a></li>
...     <li><a href="../projects/index.html">Projects</a></li>
...     <li><a href="../contribute/index.html">Contribute</a></li>
...     <li><a href="../technology/index.html">Technology</a></li>
...     <li><a href="../about/index.html">About</a></li>
...     <li><a href="../contact/index.html">Contact </a></li>
... </ul>
... <!--  #EndLibraryItem   -->
...     </div>
...     <div id="content1">
...         <!-- Begin Content -->
...         <h2 class="heading">
...             <!-- TemplateBeginEditable name="tagLine" -->PythonWeb<!--
...             TemplateEndEditable -->
...         </h2>
...         <p class="indent1">
...             <!-- TemplateBeginEditable name="subParagraph" -->
...                 <span class="small">This involves desinging and
...                     writing the necessary modules, packaging
...                     distributions and documenting the modules with
...                     examples and tutorials as well as spreading the
...                     word about Python.
...                     <a href="./">Read more &raquo;</a>
...                 </span>
...             <!-- TemplateEndEditable -->
...         </p>
...         <!-- End Content -->
...     </div>
...     <div id="footer">
...         <!-- TemplateBeginEditable name="Footer" -->
...         <!-- #BeginLibraryItem "../Library/footer.lbi" -->
...         <p class="footer">Copyright &copy; 2002-2005 James Gardner All
...         Rights Reserved.<br />
...         <i>62,561 page views since launch on 04th October 2004 until
...         the end of 2004</i></p>
...         <!-- #EndLibraryItem -->
...         <!-- TemplateEndEditable -->
...     </div>
... </div>
... </body>
... </html>"""
>>>

Notice that there is extra whitespace in some of the comments. This module can deal with extra whitespace.

Let’s look at the other components of this template:

>>> main_links_library_item = """\
... <ul>
...     <li id="active"><a href="../index.html" id="current">Home</a></li>
...     <li><a href="../projects/index.html">Projects</a></li>
...     <li><a href="../contribute/index.html">Contribute</a></li>
...     <li><a href="../technology/index.html">Technology</a></li>
...     <li><a href="../about/index.html">About</a></li>
...     <li><a href="../contact/index.html">Contact </a></li>
... </ul>"""
>>>
>>> footer_library_item = """\
...         <p class="footer">Copyright &copy; 2002-2005 James Gardner All
...         Rights Reserved.<br />
...         <i>62,561 page views since launch on 04th October 2004 until
...         the end of 2004</i></p>"""
>>>

Let’s create a directory structure containing these templates and library items:

>>> import os
>>> test_dir = 'example_dir'
>>> if os.path.exists(test_dir):
...     raise Exception("The %s directory already exists"%test_dir)
... else:
...     os.mkdir(test_dir)
...     os.mkdir(os.path.join(test_dir, 'downloads'))
...     os.mkdir(os.path.join(test_dir, 'Templates'))
...     fp = open(os.path.join(test_dir, 'Templates', 'main.dwt'), 'w')
...     fp.write(main_template)
...     fp.close()
...     os.mkdir(os.path.join(test_dir, 'Library'))
...     fp = open(os.path.join(test_dir, 'Library', 'main_links.lbi'), 'w')
...     fp.write(main_links_library_item)
...     fp.close()
...     fp = open(os.path.join(test_dir, 'Library', 'footer.lbi'), 'w')
...     fp.write(footer_library_item)
...     fp.close()
>>>

Working with Regions

Now let’s create a new file from the main.dwt template:

>>> from dreamweavertemplate import DreamweaverTemplateInstance
>>> new_page = DreamweaverTemplateInstance(
...     filename=os.path.join(test_dir, 'Templates', 'main.dwt')
... )

The new_page instance behaves a bit like a dictionary. You can access its regions like this (ignoring blank lines):

>>> print new_page['doctitle'].strip('\n')
<title>Python Web Project</title>

and set new content for a region like this:

>>> new_page['Footer'] = 'This is the new footer'
>>> print new_page['Footer']
This is the new footer

You can append content to the end of a region using the += operator:

>>> new_page['Footer'] += ' with more content'
>>> print new_page['Footer']
This is the new footer with more content

You can’t create new regions:

>>> new_page['new_region'] = 'This is a new region'
Traceback (most recent call last):
  File ...
TemplateError: Error, 'new_region' is not an editable region of the template.

Saving Templates

Finally print the whole document complete with the altered editable regions. I’ve missed out most of the content and just shown the footer so you can see that it has changed:

>>> print new_page.save_as_template()
<?xml version="1.0" encoding="iso-8859-1"?>
...
    <div id="footer">
        <!-- TemplateBeginEditable name="Footer" -->This is the new footer with more content<!-- TemplateEndEditable -->
    </div>
...
</html>

If you don’t give a name the current template filename is used. If you want to save the template to disk as another template you only need to specify the path. You should only save files in the Templates directory:

>>> new_page.save_as_template(os.path.join(test_dir, 'new.dwt'))

Pages relying on the template are not updated automatically when you save a template, you need to perform updates manually. See later.

Tidying Source Code

The save_as_template() method can use uTidyLib to tidy up your HTML. The library can be installed like this:

$ easy_install DreamweaverTemplate[test]

It can be used standalone like this:

>>> import tidylib
>>> options = dict(output_xhtml=1,
...                add_xml_decl=1,
...                indent=1,
...                tidy_mark=0)
>>> output, error = tidylib.tidy_document('<Html>Hello Tidy!', options)
>>> print output
<?xml version="1.0" encoding="us-ascii"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title></title>
  </head>
  <body>
    Hello Tidy!
  </body>
</html>
<BLANKLINE>

It is used with save_as_template() like this:

>>> print new_page.save_as_template(tidy=True)
<?xml version="1.0" encoding="iso-8859-1"?>
...
      <div id="footer">
        <!-- TemplateBeginEditable name="Footer" -->This is the new footer
        with more content<!-- TemplateEndEditable -->
      </div>
...
</html>
<BLANKLINE>

Notice that the template has been tidied up and formatted to a 78 character line width.

Warning

Tidying up HTML might sound like a good idea but it has two problems:

  • It may make the file size larger due to extra indentation
  • If you aren’t used to writing very good quality HTML and CSS then re-formatting your HTML might affect the way it behaves
  • If you have written invalid HTML, tidying it up might result in extra tags being added which again could change the way you indended it to appear.

On the other hand, if you tidy up your HMTL and it still renderd correctly you can be fairly confident it is robust HTML.

Saving Pages

The same template instance can also be saved as a page with the save_as_page() method. This takes a number of options:

filename
An optional filename to save the page to. If not specified, the page is just returned as a string.

old_path

new_path
If the filename is not specified, the path to assume the file would be saved needs to be specified so that links in the template or library items can be correctly updated.
tidy

Specifies whether the regions should be tidied up

Warning

This tidies up the entire page not just the regions so could result in a page which is no longer exactly the same as the template on which it is based.

update_library_items
Defaults to True causing any library items contained within the regions to be updated.

XXX Should the regions be updated or not, at the moment they are.

When the page is saved, all the library items in the regions are updated. It is assumed any library items in the template are already up to date. It is also assumed the content you are adding to the page will need no adjustment but if it does, you can use the update_links() function which looks like this:

>>> from dreamweavertemplate import update_links
>>> content = """\
...     <p><a href="../files/file1.zip">Download file 1</a><p>
...     <p><a href="../files/">Files</a><p>
...     <p><a href=".">Download List</a><p>"""
>>> print update_links(
...     site_root = test_dir,
...     old_path = os.path.join(test_dir, 'download', 'files.html'),
...     new_path = os.path.join(test_dir, 'files', 'index.html'),
...     content = content,
... )
    <p><a href="file1.zip">Download file 1</a><p>
    <p><a href="./">Files</a><p>
    <p><a href="../download/">Download List</a><p>

Here site_root is the full path to the directory which the Templates and Library folders are contained in, the old_path is the full path to where the page containing the links used to be and new_path is the full path to where the page will be after the move.

Notice that all the links have been updated and directory names still correctly end with a / character.

>>> print new_page.save_as_page(new_path=os.path.join(test_dir, 'page.html'))
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><!-- InstanceBegin template="Templates/main.dwt" codeOutsideHTMLIsLocked="false" -->
<head>
<!-- InstanceBeginEditable name="doctitle" -->
<title>Python Web Project</title>
<!-- InstanceEndEditable -->
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<link href="css/style.css" rel="stylesheet" type="text/css" />
<!--     InstanceBeginEditable name="head" --><!--  InstanceEndEditable -->
</head>
<body>
<!--
Copyright 2002-2004 James Gardner All Rights Reserved.
-->
<div id="body1">
     <div id="nav1">
<!-- #BeginLibraryItem  "Library/main_links.lbi" -->
<ul>
    <li id="active"><a href="index.html" id="current">Home</a></li>
    <li><a href="projects/index.html">Projects</a></li>
    <li><a href="contribute/index.html">Contribute</a></li>
    <li><a href="technology/index.html">Technology</a></li>
    <li><a href="about/index.html">About</a></li>
    <li><a href="contact/index.html">Contact </a></li>
</ul>
<!--  #EndLibraryItem   -->
    </div>
    <div id="content1">
        <!-- Begin Content -->
        <h2 class="heading">
            <!-- InstanceBeginEditable name="tagLine" -->PythonWeb<!--
            InstanceEndEditable -->
        </h2>
        <p class="indent1">
            <!-- InstanceBeginEditable name="subParagraph" -->
                <span class="small">This involves desinging and
                    writing the necessary modules, packaging
                    distributions and documenting the modules with
                    examples and tutorials as well as spreading the
                    word about Python.
                    <a href="Templates/">Read more &raquo;</a>
                </span>
            <!-- InstanceEndEditable -->
        </p>
        <!-- End Content -->
    </div>
    <div id="footer">
        <!-- InstanceBeginEditable name="Footer" -->This is the new footer with more content<!-- InstanceEndEditable -->
    </div>
</div>
</body>
<!-- InstanceEnd --></html>

We have to specify the new_path argument so that the save_as_page() method can re-calculate links, even if you aren’t specifying a filename to save to.

>>> new_page.save_as_page(filename=os.path.join(test_dir, 'page.html'))

Notice how all the links in the both the page template and the library items have been corrected so that they work using relative paths at the new location.

Extra DreamweaverTemplateInstance Functionality

Here’s a simpler example with some other useful methods:

>>> simple_template = """\
... <?xml version="1.0" encoding="iso-8859-1"?>
... <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
...   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
... <html xmlns="http://www.w3.org/1999/xhtml">
... <head>
... <title>Simple Page</title>
... </head>
... <body>
... <!-- TemplateBeginEditable name="a" -->A<!-- TemplateEndEditable -->
... <!-- TemplateBeginEditable name="b" -->B<!-- TemplateEndEditable -->
... <!-- TemplateBeginEditable name="c" -->C<!-- TemplateEndEditable -->
... </body>
... </html>
... """
>>>
>>> from dreamweavertemplate import DreamweaverTemplateInstance
>>> simple_page = DreamweaverTemplateInstance(template=simple_template)
>>> print simple_page.keys()
['a', 'c', 'b']
>>> print simple_page.has_key('Content')
False
>>> print simple_page.items()
[('a', 'A'), ('c', 'C'), ('b', 'B')]

Dealing with Pages

Now you know how to work with tempates and how to create pages from them, let’s look at working with pages.

>>> page = DreamweaverTemplateInstance(filename=os.path.join(test_dir, 'page.html'))
>>> print page
<DreamweaverTemplateInstance (instance), example_dir/page.html>
>>> page.page_regions.keys()
[u'doctitle', u'tagLine', u'head', u'subParagraph', u'Footer']

You can also see which template it uses:

>>> page.template_path == os.path.join(os.getcwd(), test_dir, 'Templates', 'main.dwt')
True

Dealing with Template Updates

When you make a change to a template you will want the changes to be reflected in all the pages which use the template.

Sometimes you just want to regenerate a single page to ensure it is using the latest version of a template.

First let’s change the page so it needs updating:

>>> page_path = os.path.join(test_dir, 'page.html')
>>> fp = open(page_path, 'r')
>>> data = fp.read()
>>> print data.find('Copyright')
649
>>> data = data.replace('Copyright', 'Copywrong')
>>> fp.close()
>>> fp = open(page_path, 'w')
>>> fp.write(data)
>>> fp.close()

Now let’s reapply the template and see if the word Copywrong is present afterwards:

>>> from sitetool.template.dreamweaver import reapply_template_to_pages
>>> reapply_template_to_pages(
...     os.path.join(test_dir, 'Templates/main.dwt'),
...     [page_path],
...     site_root = test_dir,
... )
>>> fp = open(page_path, 'rw')
>>> data = fp.read()
>>> fp.close()
>>> data.find('Copywrong')
-1

As you can see the template has been re-applied, so there are no longer any instances of the word Copywrong.

Moving a Page

You can move a page from one place to another like this:

>>> os.listdir(os.path.join(test_dir, 'downloads'))
[]
>>> from sitetool.template.dreamweaver import move_file
>>> src = os.path.join(test_dir, 'page.html')
>>> dst = os.path.join(test_dir, 'downloads', 'moved.html')
>>> move_file(src, dst, site_root=test_dir) == [dst]
True
>>> os.listdir(os.path.join(test_dir, 'downloads'))
['moved.html']

Now let’s move them back:

>>> move_file(dst, src, site_root=test_dir) == [src]
True
>>> os.listdir(os.path.join(test_dir, 'downloads'))
[]

Warning

This doesn’t update all the other pages which link to this page.

Dealing with Library Item Updates

Sometimes you just want to regenerate a single page to ensure it is using the latest version of a library item.

Let’s change the main links:

>>> fp = open(os.path.join(test_dir, 'Library', 'main_links.lbi'), 'w')
>>> fp.write('<p>No more links</p>')
>>> fp.close()

Now let’s reapply the library items:

>>> from sitetool.template.dreamweaver import reapply_library_items_to_page
>>> reapply_library_items_to_page(os.path.join(test_dir, 'page.html'), None, True, True, True)

Here’s the result:

>>> fp = open(os.path.join(test_dir, 'page.html'), 'r')
>>> print fp.read()
<?xml version="1.0" encoding="iso-8859-1"?>
...
<!-- #BeginLibraryItem  "Library/main_links.lbi" --><p>No more links</p><!--  #EndLibraryItem   -->
...
>>> fp.close()

Notice that the library item has been updated.

The reapply_library_items_to_page() function operates on the whole page, both template regions and page regions. It does this on the basis that the template regions should aready be up to date anyway so updating them again can’t hurt.

Dreamweaver Extensions

All the functionality you’ve seen so far is available in Dreamweaver but in addition, this implementation has some extra features including:

Dynamic Items
These are like Library Items but their contents is replaced when the page is viewed using Server Side Includes if the server you are using supports them. Otherwise a static fallback version of the content is shown.
Context Items
These are also like Library Items but their content is different in every page in which they appear in. The content is not dynamic though, it is the same every time a page is viewed.

Dynamic Items

Dynamic Items are stored in the Dynamic directory and have the extensions .dyi. They can appear in templates or in page regions just like Library Items but cannot appear in other Library Items, Dynamic Items or Context Items.

Dynamic Items rely on Server Side Includes so usually look something like this:

<!--# block name="one" -->
This is the content which will be displayed if the page is not being viewed
in a dynamic environment or if the inclusion of the dynamic content fails for
any reason.
<!--# endblock -->
<!--# include virtual="/some.cgi" stub="one" -->

You add a Dynamic Item to a template or page region exaclty as you would a Library Item but use the text DynamicItem rather than LibraryItem. Here’s an example:

<!-- #BeginDynamicItem "Dynamic/example.dyi" -->
<!-- #EndDynamicItem   -->

When this page is rendered on an equipped server, it will result in /some.cgi being executed and the HTML it returns being renderd in its place. On non SSI-equipped servers it will just render the fallback content and the SSI include will be treated as a comment.

If you include the same dynamic item in a page more than once two fallback blocks will end up being produced and this may cause problems with some servers.

Dynamic item markup is updated in pages and templates in the same way Library Items are.

Caution

On non-SSI-enabled servers all the text within the #BeginDynamicItem comments will be in the HTML source of the page so it is important it doesn’t contain any information you would not want made public.

Context Items

Context Items are primarily used for navigation components. The contents of the navigation components depend on the location of the files and hence are context-sensitive. Context Items are stored in the Context directory with a .cti extension.

Here’s an example:

<!-- #BeginContextItem "Context/section_navigation.cti" --><!-- #EndContextItem   -->

There is usually no content in-between the #BeginContextItem comments since it would be replaced when the Context Item was updated anyway.

Creating your own context items is not easy, they are programtic controls built into this system. Let’s look at the ones available.

The Section Navigation Context Item

Imagine the section_navigation.cti Context Item is used in a page called home.html and there are two other pages blog.html and about.html in the same directory. The section_navigation.cti context item would look at the titles of each of the pages and generate links to all three from their titles in the place the Context Navigation is entered.

First let’s set up some items we’ll need:

>>> fp = open(os.path.join(test_dir, 'Templates', 'context.dwt'), 'w')
>>> fp.write(main_template)
>>> fp.close()
>>> os.mkdir(os.path.join(test_dir, 'Context'))
>>> fp = open(os.path.join(test_dir, 'Context', 'section_navigation.cti'), 'w')
>>> fp.write('')
>>> fp.close()
>>> os.mkdir(os.path.join(test_dir, 'sections'))

Now the example:

>>> template = DreamweaverTemplateInstance(
...     filename=os.path.join(test_dir, 'Templates', 'context.dwt')
... )
>>> template['Footer'] = """
... <!-- #BeginContextItem "../Context/section_navigation.cti" -->
... <!-- #EndContextItem -->
... """
>>> template['doctitle'] = "<title>Sections</title>"
>>> template.save_as_page(
...     filename=os.path.join(test_dir, 'sections', 'index.html')
... )
>>> template['doctitle'] = "<title>Blog</title>"
>>> template.save_as_page(
...     filename=os.path.join(test_dir, 'sections', 'blog.html')
... )
>>> template['doctitle'] = "<title>About</title>"
>>> template.save_as_page(
...     filename=os.path.join(test_dir, 'sections', 'about.html')
... )
>>> reapply_library_items_to_page(os.path.join(test_dir, 'sections', 'about.html'), None, True, True, True)
>>> fp = open(os.path.join(test_dir, 'sections', 'about.html'), 'r')
>>> data = fp.read()
>>> fp.close()
>>> print data
<?xml version="1.0" encoding="iso-8859-1"?>
...
<!-- #BeginContextItem "../Context/section_navigation.cti" -->
<a href="blog.html">Blog</a>
<a href="index.html">Sections</a>
<a href="about.html">About</a>
<!-- #EndContextItem -->
<!-- InstanceEndEditable -->
...
<!-- InstanceEnd --></html>

There’s also a context item for breadcrumbs:

>>> fp = open(os.path.join(test_dir, 'Context', 'breadcrumbs.cti'), 'w')
>>> fp.write('')
>>> fp.close()

Let’s also create a site index page with no title to see how the breadcrumbs context item works:

>>> fp = open(os.path.join(test_dir, 'index.html'), 'w')
>>> fp.write('<html><body>This is the site index.</body></html>')
>>> fp.close()

Here’s the example:

>>> template = DreamweaverTemplateInstance(
...     filename=os.path.join(test_dir, 'Templates', 'context.dwt')
... )
>>> template['Footer'] = """
... <!-- #BeginContextItem "../Context/breadcrumbs.cti" -->
... <!-- #EndContextItem -->
... """
>>> template['doctitle'] = "<title>Blog</title>"
>>> template.save_as_page(
...     filename=os.path.join(test_dir, 'sections', 'blog.html')
... )
>>> reapply_library_items_to_page(os.path.join(test_dir, 'sections', 'blog.html'), None, True, True, True)
>>> fp = open(os.path.join(test_dir, 'sections', 'blog.html'), 'r')
>>> data = fp.read()
>>> fp.close()
>>> print data
<?xml version="1.0" encoding="iso-8859-1"?>
...
<!-- #BeginContextItem "../Context/breadcrumbs.cti" -->
<a href="index.html">Sections</a> &gt;
Blog
<!-- #EndContextItem -->
<!-- InstanceEndEditable -->
...
<!-- InstanceEnd --></html>

Future

  • Maintain a sitemap.xml file http://www.sitemaps.org/protocol.php
  • Generate an A-Z index and main sections from the sitemap
  • Have a config file for options like tidy
  • Allow context specific items to be handled in templates
  • Match URLs
James Gardner: Home > Work > Code > SiteTool > 0.3.0 > Dreamweaver Support