XmlWrite.py :  » Development » SnapLogic » snaplogic » components » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Development » SnapLogic 
SnapLogic » snaplogic » components » XmlWrite.py
# $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
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.