# $SnapHashLicense:
#
# SnapLogic - Open source data services
#
# Copyright (C) 2008 - 2009, SnapLogic, Inc. All rights reserved.
#
# See http://www.snaplogic.org for more information about
# the SnapLogic project.
#
# This program is free software, distributed under the terms of
# the GNU General Public License Version 2. See the LEGAL file
# at the top of the source tree.
#
# "SnapLogic" is a trademark of SnapLogic, Inc.
#
#
# $
# $Id: XmlWrite.py 10330 2009-12-24 22:13:38Z grisha $
"""
SnapLogic XML Writer component
This component provides XML generation for input records. The main features are:
* Support for file output, or XML generation within a pipeline.
* Easy XML generation for some common formats using predefined templates.
* Ability to use custom templates for complex XML generation requirements.
The predefined output modes are:
* xmlflat: generates a simple xml format, with element names matching field names.
* xmlnested : generated a simple xml format with element names matching field
names, treating multiple input views views as hierarchical.
* xhtml: generates a basic html enclosing each record in <ul> tags, and
each field in <li> tags.
* xhtmltable: generates a basic html table, with records in rows, and fields
in columns.
* atom: Generates Atom/RFC2487 output, with an <entry> for each record,
and the fields in the <snap:> namespace, with the field name included in
the element.
The custom template mode allows the specification of a user defined template to
control the XML generation. The template engine is Genshi
(http://genshi.edgewall.org), and the full Genshi XML templating language can
be used.
In custom template mode, the data from the input views of the XMLWriter is made
available to the template as part of the Genshi data context. A py:for loop
can be used to iterate through records in a view, and the individual data
fields can be referenced in the loop.
For example, to write the field 'link' from the view 'bookmarks' using a custom
template, the template would be::
<data xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
lang="en">
<ul>
<li py:for="item in bookmarks">${item.link}</li>
</ul>
</data>
"""
__docformat__ = "epytext en"
import StringIO
import datetime
import re
import os
from snaplogic.common import snap_log
from snaplogic.common.snap_exceptions import *
from snaplogic.common import version_info
from snaplogic.cc import component_api
from snaplogic.cc.component_api import ComponentAPI
from snaplogic.common.data_types import SnapString
import snaplogic.components.FileUtils as FileUtils
import snaplogic.cc.prop as prop
from snaplogic.cc import component_api
from snaplogic.snapi_base import keys
from genshi.template import TemplateLoader,Context
from genshi.template import MarkupTemplate
class XmlWrite(ComponentAPI):
"""Implementation of an XML Writer using genshi as a templating engine """
api_version = '1.0'
component_version = '1.1'
capabilities = {
ComponentAPI.CAPABILITY_INPUT_VIEW_LOWER_LIMIT : 1,
ComponentAPI.CAPABILITY_INPUT_VIEW_UPPER_LIMIT : ComponentAPI.UNLIMITED_VIEWS,
ComponentAPI.CAPABILITY_OUTPUT_VIEW_LOWER_LIMIT : 0,
ComponentAPI.CAPABILITY_OUTPUT_VIEW_UPPER_LIMIT : 1,
ComponentAPI.CAPABILITY_ALLOW_PASS_THROUGH : False,
ComponentAPI.CAPABILITY_OUTPUT_VIEW_ALLOW_BINARY : True,
}
component_description = "Generates XML Output using predefined formats, or a custom template."
component_label = "XML Write"
component_doc_uri = "https://www.snaplogic.org/trac/wiki/Documentation/%s/ComponentRef/XMLWrite" % \
version_info.doc_uri_version
def __init__(self):
"""Initialize the template modes, and set up a dispatch table for the
template generators
"""
super(XmlWrite, self).__init__()
self.modes = { 'custom':self.custom,
'xmlflat':self.xmlflat,
'xmlnested':self.xmlnested,
'xhtml':self.xhtml,
'xhtmltable':self.xhtmltable,
'atom':self.atom }
"""dispatch table mapping modes to template generation functions"""
self.media_types = ( 'application/xml',
'text/xml',
'application/xhtml+xml',
'application/atom+xml')
"""list of possible media types """
self.input_records = {}
"""data structure to store our input records, one entry per view"""
self.view_complete = {}
"""dictionary to store processing status for each view"""
self.view_handlers = {}
"""dictionary to store the handler (callback) for each view
the handler for all views is self._process_record()"""
self.context = Context()
"""our genshi data context"""
self.auto_template = None
"""Stores any generated template data in auto modes"""
def convert_accept_to_types(self,accept_list):
"""convert list of ACCEPT specification to list of content types
strips quality specifications, and accounts for lists of
types with a single quality spec. Typical inputs:
('image/jpeg; q=0.6', 'image/png; q=0.6', 'image/gif; q=0.5')
('text/xml,application/xml,application/xhtml+xml,text/html;q=0.9','text/plain;q=0.8','image/png,*/*;q=0.5')
"""
result = list()
for i in accept_list:
typelist = i.partition(';')
for type in typelist[0].split(','):
result.append(type.strip())
return result
def validate_config_file(self):
""" Process the component config file.
The root_directory specifies the prefix for all file writes.
The template_directory specifies the location for template files.
"""
root_directory = self.config_dictionary.get('root_directory', '.')
if not os.path.exists(root_directory):
msg = "The path specified for root_directory does not exist (%s)" % \
root_directory
raise SnapComponentConfigError(msg)
for k in self.config_dictionary:
if k == "schemes":
# Make sure "schemes" contains only the schemes we support
FileUtils.validate_schemes(self.config_dictionary.get("schemes"), FileUtils.writer_schemes)
if k not in ["root_directory", "schemes"]:
raise SnapComponentConfigError("Unexpected config file entry encountered (%s)" % k)
def create_resource_template(self):
"""XML Writer resource definition
There are three properties:
templatemode: controls whether we use a custom template, or one of the
predefined templates which can be generated.
templatename: The pathname of a Genshi template file. Must be
specified if we are running in custom mode. Uses file:// syntax
filename: The output filename to write the output to. If not
specified, we will write the output to the first field of the first
(alphabetically sorted) output view. Uses file:// syntax.
"""
# XXX template mode should have an LOV defined, when we support it.
p = prop.SimpleProp("Template mode",
SnapString,
"one of the predefined modes, or custom",
{ "lov" : self.modes.keys() },
True)
self.set_property_def('templatemode', p)
self.set_property_value('templatemode', 'xmlflat')
p = prop.SimpleProp("Template file name",
SnapString,
"Pathname of a custom template file")
self.set_property_def('templatename', p)
self.set_property_value('templatename', '')
p = prop.SimpleProp("Output file name",
SnapString,
"Pathname of the output file")
self.set_property_def('filename', p)
self.set_property_value('filename', '')
def validate(self, err_obj):
"""Validate the XML Writer properties
Checks for a valid template mode
Check for output filename versus output view with a single field.
"""
if len(self.list_output_view_names()) == 0:
# no output view, so a file is required
v = self.get_property_value('filename')
if v is None or len(v) == 0:
msg = 'No output view was found, and the output filename property is not set'
err_obj.get_property_err('filename').set_message(msg)
else:
vname = self.list_output_view_names()[0]
vdef = self.get_output_view_def(vname)
if vdef['is_record'] == False:
# binary output view, check content types
accept_types = vdef['view_content_types']
type_list = self.convert_accept_to_types(accept_types)
if len(type_list) == 0:
msg = 'A content type must be set for the output view'
err_obj.get_output_view_err().set_message(msg)
for type in type_list:
if type not in self.media_types:
msg = 'The content type %s is not supported for this component' % type
err_obj.get_output_view_err().set_message(msg)
else:
# record view
if len(vdef['fields']) != 1:
msg = 'The output view can only have 1 field'
err_obj.get_output_view_err().set_message(msg)
# Do some validation of the mode, but only if it's not paramtererized
mode = self.get_property_value('templatemode')
if not component_api.has_param(mode):
if mode == 'custom':
v = self.get_property_value('templatename')
if v is None or len(v) == 0:
msg = 'The templatename property must be specifed for custom template mode'
err_obj.get_property_err('templatename').set_message(msg)
# check for multiple input views versus mode (xmlflat/xmlnested/custom only)
# note, the xhtml, and xhtmltable modes _can_ work with multiple input
# views, but they have been disabled since the output generated is't really
# intuitive, and likely to surprise the user.
if (mode not in ['xmlflat', 'xmlnested', 'custom']) and len(self.list_input_view_names()) > 1:
msg = 'Multiple input views can only be used when mode is xmlflat, xmlnested or custom, not with %s' % mode
err_obj.get_property_err('templatemode').set_message(msg)
# Validate that the filename complies with the allowed URI schemes,
# unless it's specified via a parameter
FileUtils.validate_filename_property(self.get_property_value("filename"), "filename", err_obj,
self.config_dictionary.get("schemes"), FileUtils.writer_schemes)
def logmsg(self, msg):
"""Convenience method for logging a message."""
self.log(snap_log.LEVEL_INFO, msg)
def debug(self, msg):
"""Convenience method for logging a debug message."""
self.log(snap_log.LEVEL_DEBUG, msg)
def execute(self, input_views, output_views):
"""Runtime processing for XML Write
After initialization, the XML Writer, reads data from it's input views, and
buffers the data in the self.input_records dict.
The XML Writer _init() does all the setup and initialization of runtime
data structures, then we call ComponentApi.process_input_views with a
dictionary of handlers for each view. All views are handled by
_process_record()
"""
try:
input_view = input_views.values()[keys.SINGLE_VIEW]
except IndexError:
raise SnapComponentError("No input views connected.")
self.input_views = input_views
self.output_views = output_views
self._init()
self.process_input_views(self.view_handlers)
def _init(self):
"""One time initialization for the XML Writer at runtime
Creates or loads the Genshi template. Also initializes data
structures to hold records till all data is received.
Ideally, we should be able to process the template directly from
the input data by reading the streams as iterators, but SnapLogic 1.0 doesn't
support that. For now, it's buffered and stored until all data has been passed
to this component.
"""
self.debug("Beginning initialization")
# generate a template, or use a custom defined template
self.templatemode = self.get_property_value('templatemode')
# Check mode at runtime , since validate can't catch errors
# when parameters are used.
if self.templatemode not in self.modes.keys():
msg = 'The mode %s is not a valid template mode\nValid modes are %s' \
% (self.templatemode, self.modes.keys())
raise SnapComponentError(msg)
self.logmsg('Using mode %s' % self.templatemode)
self.modes[self.templatemode]()
# initialize a data structure to store our input records and status
# for each input view:
# self.input_records[viewname] = viewdata
# where viewdata is an iterable of record dictionaries dict[fieldname]
# self.view_complete[viewname] is set to True on the end of each view
# self.view_handlers[viewname] points to self._process_record()
self.input_records = {}
self.view_complete = {}
self.view_handlers = {}
for view_name in self.input_views.keys():
self.input_records[view_name] = []
self.view_complete[view_name] = False
# install a record handler
self.view_handlers[self.input_views[view_name]] = self._process_record
self.debug("Found input view %s " % view_name)
# create a single entry dictionary with the view name, and push it into
# the context.
# Genshi question... I wonder if someday we could just push
# self.input_records[] as a single dictonary, and let Genshi read it?
d = {}
d[view_name] = self.input_records[view_name]
self.context.push(d)
if self.templatemode == 'atom':
# atom specific initialization
self.atom_init()
# check for an output view, and decide whether we are writing to a file
# or the output view. The view takes precedence.
self.write_to_file = False
self.debug("output views are %s" % self.output_views.keys())
if len(self.output_views) == 0:
# no output view, so write to a file
try:
filename = self.get_property_value('filename')
except KeyError:
raise SnapComponentError(
'No output view was found, and the output filename property is not set')
if filename is None or len(filename) == 0:
raise SnapComponentError(
'No output view was found, and the output filename property is not set')
# Make sure the filename is always qualified
filename = FileUtils.get_file_location(filename, self.config_dictionary)
# Validate filename URI scheme
error = FileUtils.validate_file_scheme(filename, self.config_dictionary.get("schemes"), FileUtils.writer_schemes)
if error is not None:
raise SnapComponentError(error)
self.out = open(filename, 'w')
self.write_to_file = True
self.logmsg("Writing to the file %s since no output view was found" % filename )
else:
self.oviewname = self.list_output_view_names()[0]
vdef = self.get_output_view_def(self.oviewname)
self.binary_output = not vdef['is_record']
if not self.binary_output:
# setup to write to the first field of the output view
self.outfield = self.output_views[self.oviewname].field_names[0]
self.outrecord = self.output_views[self.oviewname].create_record()
self.logmsg("Writing record to the output view %s, field %s" % (self.oviewname, self.outfield) )
else:
self.logmsg("Writing binary to the output view %s" % (self.oviewname))
self.write_to_file = False
def atom_init(self):
""" initialization specific to the atom format
Adds Atom feed level data to the Genshi context, so it can be used
by the auto-generated Atom template
"""
# add atom feed level data to the context
# this is dict of feed level info, referenced as ${__atomfeed.xxx}
# in the template
# XXX these values should be derived from some pipeline level data,
# rather than being harcoded.
feed = {}
feed['title'] = 'feed title'
feed['author_name'] = 'SnapLogic'
feed['updated'] = datetime.datetime.now().isoformat()
feed['id'] = 'feed id'
feed['link'] = 'feed URL'
d = {}
d['__atomfeed'] = feed
self.context.push(d)
def _process_record(self, data, input_view, list_of_active_input_views):
""" Collect and buffer the input records for later XML generation
This function is installed as a callback in _init(). Its stores the
current record in input_records, and checks for end of data on the view.
When all view data has been processed, this triggers the final output
generation.
"""
record = data
vname = input_view.name
# in atom mode, generate item entry data as well and
# add it to the record if it's not included as part of the input
if data:
if self.templatemode == 'atom':
seq = len(self.input_records[vname])
record.setdefault('id', '%s' % seq)
record.setdefault('title', 'Record %s' % seq )
record.setdefault('content', 'Content for record %s' % seq)
record.setdefault('summary', 'Summary for record %s' % seq)
# XXX need a better time reference , maybe pipeline execution time ?
record.setdefault('updated', datetime.datetime.now().isoformat())
self.input_records[vname].append(record)
else:
#end of that view, check the rest
self.view_complete[vname] = True
self.debug("End of data for input view %s " % vname)
self.debug('View status flags are %s ' % self.view_complete)
for stat in self.view_complete.itervalues():
if not stat:
return
self.debug("All data has been read.")
# generate final output
self.generate_output()
def generate_output(self):
"""Generate the final output data by running the template engine
The context already has references to the data, so we just run
the template engine.
"""
# XXX Genshi's stream render() returns a whole string....really need to manage the
# memory use here, maybe use stream.serialize() with an iterator...
self.debug("XML Write: running the tempate engine.")
stream = self.tmpl.generate(self.context)
# Genshi's stream render() returns unicode object (which is what we want)
# if encoding parameter is None. Default value for encoding param 'utf-8',
# in which case it would return a string encoded in utf-8.
data = stream.render('xml', encoding=None, strip_whitespace=False)
if self.write_to_file:
self.out.write(data)
self.out.close()
else:
# write to the output view
if self.binary_output:
vdef = self.get_output_view_def(self.oviewname)
accept_types = vdef['view_content_types']
type_list = self.convert_accept_to_types(accept_types)
if len(type_list) == 0:
raise SnapComponentError(
'A content type must be specified for a stream output view')
# we use the fist content type specified
self.output_views[self.oviewname].write_binary(data, type_list[0])
self.output_views[self.oviewname].completed()
else:
self.outrecord[self.outfield] = data
self.output_views[self.oviewname].write_record(self.outrecord)
self.output_views[self.oviewname].completed()
# template loading and generation functions
# self.modes is a dispatch table which references these functions
# most of these are pairs of routines - a top level routine, and
# a recursive routine to handle multiple input views as nested hierarchical data.
# When reading multiple input views, the views are processed in sorted order
# according to view name since there's no concept of view ordering.
def custom(self):
"""Load up the custom Genshi Template"""
loader = TemplateLoader(['.'])
# interpret the templatname as relative
tmpl_file = self.get_property_value('templatename')
tmpl_file = FileUtils.get_file_location(tmpl_file, self.config_dictionary)
self.tmpl = loader.load(tmpl_file)
def xmlflat(self):
"""Generate a flat xml template
Creates a default template based on the current input view definitions.
This creates a template which will produce valid xml output in a
basic form, of <field_name>field_value</field_name>.
It's 'flat' because multiple view will be included sequentially within
<viewname> elements.
xmlviewtemplate() is called to generate the template for the
individual views.
"""
self.auto_template = StringIO.StringIO()
# create template header
self.auto_template.write('<?xml version="1.0" encoding="utf-8"?>\n')
self.auto_template.write('<snaplogic xmlns:py="http://genshi.edgewall.org/">\n')
self.auto_template.write('<!--! xmlflat genshi template autogenerated by SnapLogic -->\n')
# get the sorted view list
view_names = sorted(self.input_views.keys())
for viewname in view_names:
self.xmlviewtemplate(0, 0, viewname, self.input_views)
self.auto_template.write('</snaplogic>\n')
self.debug("generated xmlflat template:")
self.debug(self.auto_template.getvalue())
self.tmpl = MarkupTemplate(self.auto_template.getvalue())
def xmlnested(self):
"""Generate a nested xml template
Creates a default template based on the current input view definitions.
This will create a template which will produce valid xml output in a
basic form, of <field_name>field_value</field_name>.
It's 'nested' because multiple view will be included hierarchically within
<viewname> elements. The nesting is based on the sorted view names, with the
first view as the outermost element, and the last view as the innermost element.
xmlviewtemplate() is called recursively to generate the template for the
individual views.
"""
self.auto_template = StringIO.StringIO()
# create template header
self.auto_template.write('<?xml version="1.0" encoding="utf-8"?>\n')
self.auto_template.write('<snaplogic xmlns:py="http://genshi.edgewall.org/">\n')
self.auto_template.write('<!--! xmlnested genshi template autogenerated by SnapLogic -->\n')
# get the sorted view list
view_names = sorted(self.input_views.keys())
view_count = len(view_names)
self.xmlviewtemplate(view_count, view_count-1, view_names[0], self.input_views)
self.auto_template.write('</snaplogic>\n')
self.debug("generated xmlnested template:")
self.debug(self.auto_template.getvalue())
self.tmpl = MarkupTemplate(self.auto_template.getvalue())
def xmlviewtemplate(self, total_levels, level, viewname, views):
"""Recursive routine to generate template for a single view
@param total_levels: the total number of views to be processed
@type: integer
@param level: The current level in the recursion.
@type: integer
@param viewname: The name of the view we are generating the template
data for.
@type: string
@param views: All the view names, as keys in a dictionary.
@type: dict
"""
t = self.auto_template
spc = ' ' * (total_levels - level-1)
t.write('<%s py:for="record in %s">\n' % (viewname, viewname))
for field in views[viewname].field_names:
t.write(spc)
t.write('<%s>${record.%s}</%s>\n' % (field, field, field))
if level != 0:
viewlist = sorted(views.keys())
name = viewlist[-level]
self.xmlviewtemplate(total_levels, level-1, name, views)
t.write(spc)
t.write('</%s>\n' % viewname )
def xhtml(self):
"""Generate an html template for ul/li output
This will create a template which generates a basic html output,
enclosing each record in <ul> tags, and each field in <li> tags.
Multiple views are included sequentially in the output, and the class of
the <ul> element is set to the view name.
"""
self.auto_template = StringIO.StringIO()
self.auto_template.write('<?xml version="1.0" encoding="utf-8"?>\n')
self.auto_template.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n ')
self.auto_template.write(' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n')
self.auto_template.write(
'<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/" >\n')
self.auto_template.write('<!--! xhtml genshi template autogenerated by SnapLogic -->\n')
self.auto_template.write('<body>\n')
# get the sorted view list
view_names = sorted(self.input_views.keys())
for viewname in view_names:
self.xhtmlviewtemplate(0, 0, viewname, self.input_views)
self.auto_template.write('</body>\n')
self.auto_template.write('</html>\n')
self.debug("generated xhtml template:")
self.debug(self.auto_template.getvalue())
self.tmpl = MarkupTemplate(self.auto_template.getvalue())
def xhtmlviewtemplate(self, total_levels, level, viewname, views):
"""Recursive routine to generate template for a single view
using ul/li
@param total_levels: the total number of views to be processed
@type: integer
@param level: The current level in the recursion.
@type: integer
@param viewname: The name of the view we are generating the template
data for.
@type: string
@param views: All the view names, as keys in a dictionary.
@type: dict
"""
t = self.auto_template
spc = ' ' * (total_levels - level-1)
t.write('<ul class="%s" py:for="%srecord in %s">\n' % (viewname, viewname, viewname))
for field in views[viewname].field_names:
t.write(spc)
t.write('<li class="%s">${%srecord.%s}</li>\n' % (field, viewname, field))
if level != 0:
viewlist = sorted(views.keys())
name = viewlist[-level]
self.xmlviewtemplate(total_levels, level-1, name, views)
t.write(spc)
t.write('</ul>\n')
def xhtmltable(self):
"""Generate an html template for html table output.
Creates a template for a basic html table, with records in rows, and fields
in columns.
Multiple views will be included sequentially based on the sorted view names,
resulting in multiple tables.
The class of the <table> and <tr> elements is set to the viewname, and the
class of the <td> elements is set to the field name. Just in case anyone
ever wants to scrape it.
"""
# It would be trivial to change this to create nested tables, if someone really
# wanted that type of output.
self.auto_template = StringIO.StringIO()
self.auto_template.write('<?xml version="1.0" encoding="utf-8"?>\n')
self.auto_template.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n ')
self.auto_template.write(' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n')
self.auto_template.write(
'<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://genshi.edgewall.org/" >\n')
self.auto_template.write('<!--! xhtmltable genshi template autogenerated by SnapLogic -->\n')
self.auto_template.write('<body>\n')
# get the sorted view list
view_names = sorted(self.input_views.keys())
for viewname in view_names:
self.xhtmltableviewtemplate(0, 0, viewname, self.input_views)
self.auto_template.write('</body>\n')
self.auto_template.write('</html>\n')
self.debug("generated xhtmltable template:")
self.debug(self.auto_template.getvalue())
self.tmpl = MarkupTemplate(self.auto_template.getvalue())
def xhtmltableviewtemplate(self, total_levels, level, viewname, views):
"""Recursive routine to generate template for a single view using table/tr/td
@param total_levels: the total number of views to be processed
@type: integer
@param level: The current level in the recursion.
@type: integer
@param viewname: The name of the view we are generating the template
data for.
@type: string
@param views: All the view names, as keys in a dictionary.
@type: dict
"""
t = self.auto_template
spc = ' ' * (total_levels - level-1)
t.write('<table class="%s">\n' % (viewname))
t.write('<tr class="%s" py:for="%srecord in %s">\n' % (viewname, viewname, viewname))
for field in views[viewname].field_names:
t.write(spc)
t.write('<td class="%s">${%srecord.%s}</td>\n' % (field, viewname, field))
if level != 0:
viewlist = sorted(views.keys())
name = viewlist[-level]
self.xmlviewtemplate(total_levels, level-1, name, views)
t.write(spc)
t.write('</tr>\n')
t.write('</table>\n')
def atom(self):
"""Generate an xml template for an Atom/RFC4287 feed
This will create a template which will produce valid Atom xml output in
a basic form, with the required Atom fields, and the view data data in
<snap:field_name>field_value</snap:field_name> elements.
When generating this template, the special __atomfeed dictionary is used to
reference feed level data, and that data is generated in atom_init(). This simplifies
the generation of valid Atom output from a single view at the expense of flexibility.
Real Atom feeds will probably use custom templates with multiple input views.
"""
self.auto_template = StringIO.StringIO()
# create template header
self.auto_template.write('<?xml version="1.0" encoding="utf-8"?>\n')
self.auto_template.write(
'<feed xmlns="http://www.w3.org/2005/Atom" xmlns:snap="http://www.snaplogic.org/feed" xmlns:py="http://genshi.edgewall.org/">\n')
self.auto_template.write('<!--! Atom genshi template autogenerated by SnapLogic -->\n')
self.auto_template.write('<title>${__atomfeed.title}</title>\n')
self.auto_template.write('<author><name>${__atomfeed.author_name}</name></author>\n')
self.auto_template.write('<updated>${__atomfeed.updated}</updated>\n')
self.auto_template.write('<id>${__atomfeed.id}</id>\n')
self.auto_template.write('<link rel="self" >${__atomfeed.link}</link>\n')
self.auto_template.write('<generator uri="http://www.snaplogic.org">SnapLogic</generator>\n')
# get the sorted view list
view_names = sorted(self.input_views.keys())
for viewname in view_names[0:1]: # we only use the first (alphabetical) view
self.atomviewtemplate(0, 0, viewname, self.input_views)
self.auto_template.write('</feed>\n')
self.debug("generated atom template:")
self.debug(self.auto_template.getvalue())
self.tmpl = MarkupTemplate(self.auto_template.getvalue())
def atomviewtemplate(self, total_levels, level, viewname, views):
"""Recursive routine to generate atom template for a single view
@param total_levels: the total number of views to be processed
@type: integer
@param level: The current level in the recursion.
@type: integer
@param viewname: The name of the view we are generating the template
data for.
@type: string
@param views: All the view names, as keys in a dictionary.
@type: dict
When generating the atom template, the special fields
['id', 'content', 'summary', 'title', 'updated'] have to be in
the Atom namespace (which is the template default), so they are treated
specially.
"""
atom_special = ['id', 'content', 'summary', 'title', 'updated']
t = self.auto_template
spc = ' ' * (total_levels - level-1)
t.write('<entry py:for="record in %s">\n' % (viewname))
fields = views[viewname].field_names
# Add atom required elements, using atom namespace
for field in atom_special:
t.write(spc)
t.write('<%s>${record.%s}</%s>\n' % (field, field, field))
# add view fields, using Snap namespace
for field in fields:
if field in atom_special:
continue
t.write(spc)
t.write('<snap:%s>${record.%s}</snap:%s>\n' % (field, field, field))
if level != 0:
viewlist = sorted(views.keys())
name = viewlist[-level]
self.atomviewtemplate(total_levels, level-1, name, views)
t.write(spc)
t.write('</entry>\n' )
def upgrade_1_0_to_1_1(self):
"""
No-op upgrade only to change component doc URI during the upgrade
which will be by cc_info before calling this method.
"""
pass
|