#!/usr/bin/env python
"""
site2template
This script will convert an iWeb site into an iWeb template and install it.
The usage is fairly simple. Just create a site in iWeb with the name
that you want the template to have. The site should should have each
of the standard pages (i.e. About Me, Welcome, Photos, etc.).
Once you have a site that looks the way you want, open the Terminal
application and run:
site2template site-name
where site-name is the name of the site you want to convert to a template.
site2template will generate all of the webtemplate files and install
them in the iWeb application. It will also resize the thumnail images
and install them in the proper location.
The location of the thumbnail files can be specified using the --thumbs=DIR
option where DIR is the name of the directory where the images are
located. The images should be in TIFF format and have a name that is the
name of the type of page (i.e. About Me, Welcome, etc.) with the file
extension '.tiff'. So the image names would be 'About Me.tiff',
'Welcome.tiff', etc. You can obtain these images by using the Preview
application and taking screenshots of your site pages. Each thumbnail
will be resized automatically.
This script comes with no warranty or guarantees. Use at your own risk.
"""
TEMPLATE_NAMES = ['About Me', 'Welcome', 'Photos', 'Movie', 'Blog', 'Podcast']
INPUT_FILE = '~/Library/Application Support/iWeb/Domain.sites/index.xml.gz'
IWEB_TEMPLATES = '/Applications/iWeb.app/Contents/Resources/English.lproj/Templates'
import os, sys, gzip, re, shutil
import CoreGraphics as CG
INPUT_FILE = os.path.expanduser(INPUT_FILE)
IWEB_TEMPLATES = os.path.expanduser(IWEB_TEMPLATES)
class DomainSites(object):
def __init__(self, sitename, thumbs=None):
self.name = sitename
self.thumbs = None
if thumbs:
self.thumbs = os.path.expanduser(thumbs)
# Read XML of domain
self.sitesxml = gzip.open(INPUT_FILE, 'r').read()
# Get metadata for domain
self.metadata = re.compile(r'(<\w+:metadata>.+?\w+:metadata>)',
re.S).search(self.sitesxml).group(1)
# Store dictionary of all unfiltered images
self.unfiltered = {}
for name, data in re.compile(r'<\w+:unfiltered\s+sfa:ID="([^"]+)"[^>]*>(.+?)\w+:unfiltered>', re.S).findall(self.sitesxml):
self.unfiltered[name] = data
def getSites(self):
""" Get XML for all sites """
return re.compile(r'(<\w+:site [^>]*?>.+?\w+:site>)',
re.S).findall(self.sitesxml)
def getSite(self, name):
"""
Get requested site XML
Required Arguments:
name -- name of the site to retrieve
Returns:
XML of the requested site
"""
for site in self.getSites():
n = re.search(r'\w+:name="([^"]+)"', site).group(1).strip()
if n == name:
return site
raise ValueError, 'There is no site with the name "%s"' % name
def getSiteNodes(self, name):
"""
Get XML nodes for requested site
Required Arguments:
name -- name of the site to retrieve the nodes of
Returns:
dictionary containing site nodes
"""
xml = self.getSite(name)
# Get XML for all site nodes
nodesxml = re.compile(r'<\w+:site-nodes[^>]*?>(.+?)\w+:site-nodes>',
re.S).search(xml).group(1).strip()
# Put site nodes into an array
sitenodes = [x[0] for x in re.compile(r'(<\w+:site-(page|blog)[^>]*?>.+?\w+:site-\2>)\s*', re.S).findall(nodesxml)]
# Put site pages into a dictionary
templates = {}
for node in sitenodes:
name = re.search(r'\w+:name="([^"]+)"', node).group(1).strip()
templates[name] = node
return templates
def generateWebTemplates(self, name):
"""
Generate '.webtemplate' files for site `name`
Required Arguments:
name -- name of site to convert to a set of templates
"""
# Get document wrapper
root = re.compile(r'(<\w+:document\b[^>]*?>)',
re.S).search(self.sitesxml).group(1)
root = root.replace(':document',':template')
# Print out the templates to iWeb resources
for type, template in self.getSiteNodes(name).items():
if TEMPLATE_NAMES and type not in TEMPLATE_NAMES:
continue
os.chdir(IWEB_TEMPLATES)
try: os.mkdir(type)
except: pass
os.chdir(type)
filename = '%s %s.webtemplate' % (name, type)
try: os.mkdir(filename)
except: pass
os.chdir(filename)
self.unfilterednum = 0
def dounfiltered(x):
self.unfilterednum += 1
return '<%s:unfiltered %s:ID="%s-%s">%s%s:unfiltered>' % \
(x.group(1), x.group(2), x.group(3), self.unfilterednum,
self.unfiltered[x.group(3)], x.group(1))
# Replace image references with actual image data
template = re.sub(r'<(\w+):unfiltered-ref\s+(\w+):IDREF="([^"]+)"\s*/>', dounfiltered, template)
fp = gzip.open('index.xml.gz', 'w')
fp.write(root)
fp.write('')
fp.write(self.metadata)
fp.write('')
fp.write(template)
fp.write('')
fp.close()
# Copy image and other files into web template directory
for src in re.findall(r'\w+:path="([^"]+)"', template):
dst = src
src = os.path.join(os.path.dirname(INPUT_FILE), src)
if os.path.isfile(src):
shutil.copyfile(src, dst)
# If there is a thumbnail image, copy it into place
if self.thumbs:
if not os.path.isdir('thumbs'):
os.mkdir('thumbs')
thumbnail = os.path.join(self.thumbs, type + '.tiff')
if os.path.isfile(thumbnail):
self.createThumbnail(thumbnail,
os.path.join('thumbs','page_thumb_1-1.tiff'))
os.chdir('..')
def createThumbnail(self, inputfile, outputfile, width=98, height=128):
""" Resize the image """
# Load image
img = CG.CGImageImport(CG.CGDataProviderCreateWithFilename(inputfile))
# Get new dimensions
aspect = img.getWidth() / float(img.getHeight())
h = height
w = h * aspect
if w > width:
w = width
h = w / aspect
w = int(w)
h = int(h)
# Create new imoge
c = CG.CGBitmapContextCreateWithColor(w, h,
CG.CGColorSpaceCreateDeviceRGB(),
(0, 0, 0, 0))
c.setRGBFillColor(1.0, 1.0, 1.0, 1.0)
pageRect = CG.CGRectMake(0, 0, w, h)
c.drawImage(pageRect.inset(0, 0), img)
# Write image
c.writeToFile(outputfile, CG.kCGImageFormatTIFF)
def uninstallWebTemplates(self, name):
"""
Remove .webtemplate references from plist file for given site
Required Arguments:
name -- name of site to uninstall
"""
tfile = os.path.join(IWEB_TEMPLATES, 'TemplatesInfo.plist')
# Read template info plist
templatesinfo = open(tfile, 'r').read()
# Rip out previous templates with this name
templatesinfo = re.compile(r'%s\s*.+?\s*' %
name, re.S).sub(r'', templatesinfo)
# Pull site name out of sorted templates
templatesinfo = re.sub(r'\s*displayName\s*%s\s*keyName\s*%s\s*\s*' % (name, name), r'', templatesinfo)
# Write out the plist file
open(tfile, 'w').write(templatesinfo)
def installWebTemplates(self, name):
"""
Add .webtemplates to the plist file for the given site
Required Arguments:
name -- name of site to install
"""
# Uninstall previous template
self.uninstallWebTemplates(name)
tfile = os.path.join(IWEB_TEMPLATES, 'TemplatesInfo.plist')
# Read template info plist
templatesinfo = open(tfile, 'r').read()
templateentry = '%(name)s\n\n\tdisplayName\n\t%(type)s\n\tfileName\n\t%(filename)s\n'
# Add each template to the plist file
for type, template in self.getSiteNodes(name).items():
if TEMPLATE_NAMES and type not in TEMPLATE_NAMES:
continue
filename = '%s %s.webtemplate' % (name, type)
# Update plist info
templatesinfo = re.sub(r'(%s\s*BLEntries\s*\s*)' % type, r'\1%s' % (templateentry % {'name':name, 'type':type, 'filename':filename}), templatesinfo)
# Add theme to sorted themes list
sortedtheme = '\n\tdisplayName\n\t%s\n\tkeyName\n\t%s\n' % (name, name)
templatesinfo = re.sub(r'(sortedThemes\s*)',
r'\1%s' % sortedtheme, templatesinfo)
# Write out the plist file
open(tfile, 'w').write(templatesinfo)
def main():
from optparse import OptionParser
parser = OptionParser('usage: site2template [ options ] site-name')
parser.add_option("-t", "--thumbs", dest="thumbs", metavar="DIR",
default='',
help="directory where thumbnail images reside")
parser.add_option("-u", "--uninstall", dest="uninstall",
action="store_true",
help="uninstall template")
(options, args) = parser.parse_args()
if not args:
parser.print_help()
sys.exit(1)
ds = DomainSites(sys.argv[1], os.path.abspath(options.thumbs))
if options.uninstall:
ds.uninstallWebTemplates(args[0])
else:
ds.generateWebTemplates(args[0])
ds.installWebTemplates(args[0])
if __name__ == '__main__':
main()