Seite 1 von 1

Colubra XML Template

Verfasst: Donnerstag 17. November 2005, 21:25
von mitsuhiko
Zu allererst. NICHT VERWENDEN.
Das ist nur ein Testprojekt von mir, da hab ich mich nur mit der XML Syntax und dem Parsern derjenigen auseinandergesetzt.

Aber vielleicht kann ja einer was daraus lernen ^^

colubra.py

Code: Alles auswählen

# -*- coding: utf-8 -*-

from xml.parsers import expat
from xml.sax import saxutils

class PythonWriter:

    one_line_tags = ['img', 'hr', 'br', 'meta', 'link']

    def __init__(self):
        self.doc = []
        self.level = 2
        self.outdent_tags = []
        self.level_indention = '    '

    def header(self):
        header_data = [
            (0, '# -*- coding: utf-8'),
            (0, 'try:'),
            (1, 'from cStringIO import StringIO'),
            (0, 'except:'),
            (1, 'from StringIO import StringIO'),
            (0, 'from xml.sax import saxutils'),
            (0, ''),
            (0, 'class CulebraTemplate:'),
            (1, 'def __init__(self, namespace, output=None):'),
            (2, 'self.__namespace = namespace'),
            (2, 'self.__do_output = False'),
            (2, 'if output is None:'),
            (3, 'self.__result = StringIO()'),
            (3, 'self.__do_output = True'),
            (2, 'else:'),
            (3, 'self.__result = output'),
            (1, ''),
            (1, 'def __call__(self):'),
            (2, 'self.__run__()'),
            (2, 'if self.__do_output:'),
            (3, 'return self.__result.getvalue()'),
            (1, ''),
            (1, 'def __run__(self):'),
            (2, 'for key in self.__namespace:'),
            (3, 'globals()[key] = self.__namespace[key]'),
            (2, 'try: del key'),
            (2, 'except NameError: pass')
        ]
        for line in header_data:
            self.doc.append(line)

    def translate_var(self, var):
        parts = str(var).split('.')
        result = parts[0]
        for part in parts[1:]:
            result += '[\'%s\']' % part.encode('string-escape')
        return result

    def write_line(self, data):
        data = str(data).encode('string-escape')
        self.doc.append((self.level, 'self.__result.write(\'%s\')' % data))

    def write_var(self, data):
        var = self.translate_var(data)
        strvar = '{%s}' % str(data).encode('string-escape')
        self.doc.append((self.level, 'try:'))
        self.doc.append((self.level + 1, 'self.__result.write(%s)' % var))
        self.doc.append((self.level, 'except:'))
        self.doc.append((self.level + 1, 'self.__result.write(\'%s\')' % strvar))
        
    def write_var_quoted(self, data):
        var = self.translate_var(data)
        strvar = saxutils.quoteattr('{%s}' % str(data).encode('string-escape'))
        self.doc.append((self.level, 'try:'))
        self.doc.append((self.level + 1, 'self.__result.write(saxutils.quoteattr(%s))' % var))
        self.doc.append((self.level, 'except:'))
        self.doc.append((self.level + 1, 'self.__result.write(\'%s\')' % strvar))
        
    def write_replace(self, variables, data):
        data = str(data).encode('string-escape')
        self.doc.append((self.level, '__tmp_output = \'%s\'' % data))
        self.doc.append((self.level, '__replacement_table = {'))
        for var in variables.split(','):
            p = var.split(' with ', 1)
            if len(p) == 1:
                _search = str(p[0].strip()).encode('string-escape')
                _replace = self.translate_var(p[0].strip())
            else:
                _search = str(p[0].strip()).encode('string-escape')
                _replace = self.translate_var(p[1].strip())
            
            self.doc.append((self.level + 1, '\'%s\': %s,' % (_search, _replace)))
        self.doc.append((self.level, '}'))
        self.doc.append((self.level, 'for __search, __replace in __replacement_table.items():'))
        self.doc.append((self.level + 1, '__tmp_output = __tmp_output.replace(\'$%s\', __replacement_table[\'%s\'])' % (_search, _search)))
        self.doc.append((self.level, 'self.__result.write(__tmp_output)'))
    
    def write_tag(self, name, attributes):       
        if 'py:if' in attributes:
            self.doc.append((self.level, 'if %s:' % attributes['py:if'].strip()))
            del attributes['py:if']
            self.outdent_tags.append(name)
            self.level += 1
                    
        self.write_line('<' + name)
        
        py_for = False
        if 'py:for' in attributes:
            py_for = attributes['py:for']
        
        if 'py:attrib' in attributes:
            replaces = attributes['py:attrib'].split(',')
            for item in replaces:
                p = item.split(' as ', 1)
                _var = p[0].strip()
                _as = p[1].strip()
                self.write_line(' %s=' % _as)
                self.write_var_quoted(_var)
                if _as in attributes:
                    del attributes[_as]
            del attributes['py:attrib']
        for key in attributes:
            self.write_line(' %s=%s' % (key, saxutils.quoteattr(attributes[key])))
        
        if name in self.one_line_tags:
            self.write_line(' />')
        else:
            self.write_line('>')
           
        if py_for:
            p = py_for.split(' as ')
            _as = p[1].strip()
            _var = p[0].strip()
            self.doc.append((self.level, 'for %s in %s:' % (_as, _var)))
            self.outdent_tags.append(name)
            self.level += 1
    
    def write_end_tag(self, name):
        try:
            last = self.outdent_tags[-1]
            if last == name:
                self.outdent_tags.pop()
                self.level -= 1
        except IndexError:
            pass
        if not name in self.one_line_tags:
            self.write_line('</%s>' % name)

    def get_data(self):
        result = ''
        for indent, line in self.doc:
            result += self.level_indention * indent + line.strip() + '\n'
        return result


class Parser:

    def __init__(self, template):
        self.parser = expat.ParserCreate()
        self.parser.StartElementHandler = self.start_element
        self.parser.EndElementHandler = self.end_element
        self.parser.CharacterDataHandler = self.cdata
        self.compiler = None
        self.template = template
        
        self.cdata_mod = None

    def start_element(self, name, attrs):
        if 'py:insert' in attrs:
            self.cdata_mod = ('insert', attrs['py:insert'])
            del attrs['py:insert']
        elif 'py:replace' in attrs:
            self.cdata_mod = ('replace', attrs['py:replace'])
            del attrs['py:replace']
        
        self.compiler.write_tag(name, attrs)

    def end_element(self, name):
        self.compiler.write_end_tag(name)

    def cdata(self, data):
        if not self.cdata_mod is None:
            if self.cdata_mod[0] == 'insert':
                self.compiler.write_var(self.cdata_mod[1])
            if self.cdata_mod[0] == 'replace':
                self.compiler.write_replace(self.cdata_mod[1], data)
            self.cdata_mod = None
        else:
            self.compiler.write_line(data)

    def parse(self):
        self.compiler = PythonWriter()
        self.compiler.header()
        self.parser.Parse(self.template)
        return self.compiler.get_data()


###############################################################################
# Simple Access

def parse(filename):
    t = file(filename + '.xml').read()
    p = Parser(t)
    return p.parse()

def compile(filename):
    import os
    import py_compile
    
    code = parse(filename)
    filename += '.py'
    file(filename, 'w').write(code)
    py_compile.compile(filename)
    os.unlink(filename)

def execute(filename, namespace):
    try:
        module = __import__(filename, locals(), globals(), ['CulebraTemplate'])
    except ImportError:
        compile(filename)
        module = __import__(filename, locals(), globals(), ['CulebraTemplate'])
    return module.CulebraTemplate(namespace)()
test.py

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import culebra

namespace = {
    'page': {
        'title': 'Demonstation Page'
    },
    'navigation': [
        {'href': '#', 'title': 'current page'},
        {'href': 'index.html', 'title': 'overview'},
        {'href': 'about.html', 'title': 'about'}
    ]
}

print culebra.execute('demo', namespace)
demo.xml

Code: Alles auswählen

<?xml version="1.0" encoding="utf-8"?>
<html>
    <head>
        <title py:replace="title with page.title">CulebraDemoTemplate :: $title</title>
    </head>
    <body>
        <h1 py:insert="page.title">%s</h1>
        <ul py:for="navigation as item">
            <li><a py:attrib="item.href as href, item.title as title" py:insert="item.title">%s</a></li>
        </ul>
        <div id="footer" py:insert="footer">%s</div>
    </body>
</html>
Ausgabe sieht dann etwa so aus:

Code: Alles auswählen

<html>
    <head>
        <title>CulebraDemoTemplate :: Demonstation Page</title>
    </head>
    <body>
        <h1>Demonstation Page</h1>
        <ul>
            <li><a href="#" title="current page">current page</a></li>

            <li><a href="index.html" title="overview">overview</a></li>

            <li><a href="about.html" title="about">about</a></li>
        </ul>
        <div id="footer">{footer}</div>
    </body>
</html>

Verfasst: Donnerstag 17. November 2005, 21:28
von Leonidas
Was soll dass denn werden? Eine Kid-ähnliches Templetingsystem?

Verfasst: Donnerstag 17. November 2005, 21:34
von mitsuhiko
Leonidas hat geschrieben:Was soll dass denn werden? Eine Kid-ähnliches Templetingsystem?
Das soll gar nichts werden. Ich wollte mich mit Expat auseinandersetzen und das ist rausgekommen. Deswegen poste ich es ja in Codesnippets und nicht im Showcase :wink:

Verfasst: Donnerstag 17. November 2005, 22:44
von mitsuhiko
ARGH. das syntax highlighting macht den code kaputt. Wers testen will:
1.) beitrag quoten
2.) eintrag rauskopieren
3.) ein datei einfügen
:wink:

Verfasst: Freitag 18. November 2005, 08:36
von jens
Sieht für mich wie eine Mischung aus simpleTAL mit SpyTee aus ;)

Wenn du dich mit Templating auseinander setzt, solltest du dir auf jeden Fall mal simpleTAL anschauen!

Hier ein paar Links, die du vielleicht schon kennst:
http://www.python-forum.de/viewtopic.php?t=4323
http://www.python-forum.de/viewtopic.php?t=4242
http://www.python-forum.de/viewtopic.php?t=4256
http://www.python-forum.de/viewtopic.php?t=4231

Verfasst: Freitag 18. November 2005, 16:16
von mitsuhiko
jens hat geschrieben:Sieht für mich wie eine Mischung aus simpleTAL mit SpyTee aus ;)

Wenn du dich mit Templating auseinander setzt, solltest du dir auf jeden Fall mal simpleTAL anschauen!
verzichte, dass war nur ein Test ^^
Momentan schreib ich an einem Python 2 Javascript Konverter.

Verfasst: Freitag 18. November 2005, 16:20
von jens
blackbird hat geschrieben:Momentan schreib ich an einem Python 2 Javascript Konverter.
Hm! Da bin ich ja mal gespannt! Ich nehme an für AJAX???

Verfasst: Freitag 18. November 2005, 16:35
von Leonidas
Wie wärs mit CrackAJAX?

Verfasst: Freitag 18. November 2005, 16:37
von mitsuhiko
Leonidas hat geschrieben:Wie wärs mit CrackAJAX?
AHHHHHHHHHHHHHHHHHHHHHHHHHHHHH.
Warum erfahre ich erst jetzt davon? Na egal. Ich bin eh schon weiter :)

Verfasst: Freitag 18. November 2005, 18:07
von Leonidas
blackbird hat geschrieben:Warum erfahre ich erst jetzt davon?
Zu wenig Daily Python URL gelesen? :wink:

Dein Projekt ist schon interessant, so habe ich auch nicht wirklich Lust JavsScript selbst zu schreiben, auch nicht mit MochiKit.

Verfasst: Freitag 18. November 2005, 19:56
von mitsuhiko
Leonidas hat geschrieben:Dein Projekt ist schon interessant, so habe ich auch nicht wirklich Lust JavsScript selbst zu schreiben, auch nicht mit MochiKit.
Deswegen hab ich ja damit angefangen. Aber zuerst wollte ich nur eine javascript bridge zu python funktionen machen. Gestern musste ich dann ein pyc compilieren und habe durch zufall compiler.ast gefunden.
Dann lag das schon fast auf der Hand :-)

Der Parser ist jetzt fertig, ich lege jetzt noch die python objekte als javascript objekte ab. Momentan ist aber nur String zu 70% fertig und einige builtin functionen wie len() und so.