Changes between Initial Version and Version 1 of contentBBCode_parser


Ignore:
Timestamp:
Aug 21, 2006, 6:29:32 AM (18 years ago)
Author:
Piotr Malinski <riklaunim@…>
Comment:

Page Creation

Legend:

Unmodified
Added
Removed
Modified
  • contentBBCode_parser

    v1 v1  
     1Django has textile, markdown and other markup languages as filters in the templates. But those filters can't do complex operations. For example we want such tags in our page content:
     2{{{
     3[art id="34"]
     4[art id="11"]
     5}}}
     6And we want them to become in the browser:
     7{{{
     8- <a href="/link/to/article34/">article title34</a><br>
     9- <a href="/link/to/article11/">article title11</a><br>
     10}}}
     11Something like BBcode but with the ability to respond do attributes passed in the tags. From some time I was using such tags in my PHP scripts. I called them “ContentBBcode". Now the time have come to djangoify the concept.
     12
     13=== The Code – The Implementation ===
     14I've written the parser that finds and replaces ContentBBcode tags (CBC for short) with proper response, and I won't go to details about it. Put this code in a file and save it as cbcparser.py (I saved it in the application folder in which I want to use it):
     15{{{
     16import re
     17import sys
     18sys.path.append('wiki/cbcplugins/')
     19
     20def parse_cbc_tags(text):
     21        # double: [tag]something here[/tag]
     22        tags = re.findall( r'(?xs)\[\s*rk:([a-z]*)\s*(.*?)\](.*?)\[(?=\s*/rk)\s*/rk:(\1)\s*\]''', text, re.MULTILINE)
     23        parsed_double = {}
     24        for tag in tags:
     25                k = str(tag[0]).strip()
     26                v = tag[1]
     27                v = v.split(' ')
     28                vals = {}
     29                vals['attributes'] = {}
     30                for attr in v:
     31                        attr = attr.split('=')
     32                        val = attr[1]
     33                        attr[1] = val[1:-1]
     34                        vals['attributes'][attr[0]] = attr[1]
     35                vals['code'] = tag[2]
     36                vals['tag'] = '[rk:' + tag[0] + ' ' + tag[1] + ']' + tag[2] + '[/rk:' + tag[0] + ']'
     37                if not parsed_double.has_key(k):
     38                        parsed_double[k] = list()
     39                parsed_double[k].append(vals)
     40       
     41        for plugin in parsed_double:
     42                try:
     43                        exec 'from ' + plugin + ' import *'
     44                except:
     45                        pass
     46                else:
     47                        text = render(parsed_double[plugin], text)
     48                       
     49        # single: [tag]
     50        tags = re.findall('\[rk:([a-z]*) ([a-zA-z0-9 =.,"\']*)\]', text)
     51        parsed = {}
     52        for tag in tags:
     53                k = str(tag[0]).strip()
     54                v = tag[1]
     55                v = v.split(' ')
     56                vals = {}
     57                vals['attributes'] = {}
     58                for attr in v:
     59                        attr = attr.split('=')
     60                        val = attr[1]
     61                        attr[1] = val[1:-1]
     62                        vals['attributes'][attr[0]] = attr[1]
     63                vals['tag'] = '[rk:' + tag[0] + ' ' + tag[1] + ']'
     64                if not parsed.has_key(k):
     65                        parsed[k] = list()
     66                parsed[k].append(vals)
     67       
     68        for plugin in parsed:
     69                try:
     70                        exec 'from ' + plugin + ' import *'
     71                except:
     72                        pass
     73                else:
     74                        text = render(parsed[plugin], text)
     75        return text
     76}}}
     77Note on the line:
     78{{{
     79sys.path.append('wiki/cbcplugins/')
     80}}}
     81each tag for this parser is a python file ([art...] would need art.py). If don't put your plugins in PYTHONPATH then you need to append location of the plugin folder to PYTHONPATH. In my case wiki/ was my application folder and wiki/cbcplugins/ folder with plugins. Change the path to fit your needs.
     82
     83Now we will make a template filter out of it. Create '''templategas''' folder in your application folder and create empty '''__init__.py''' file and '''cbc.py''' file with the code:
     84{{{
     85from project.aplication.cbcparser import *
     86from django import template
     87
     88register = template.Library()
     89
     90def cbc(value): # Only one argument.
     91    return parse_cbc_tags(value)
     92
     93register.filter('cbc', cbc)
     94}}}
     95Where '''from project.aplication.cbcparser import *''' is the  cbcparser.py importing. In my case it is '''from diamanda.wiki.cbcparser import *'''
     96
     97And we are done. In a template in which you want to use it put
     98{{{
     99{% load cbc %}
     100}}}
     101and then to parse a string you just use:
     102{{{
     103{{ mystring|cbc }}
     104}}}
     105
     106
     107Now about the plugins. The parser tries to load them and if it succeeds it will call '''render''' function passing two variables – a dictionary with parsed data from the tag and the string. The tag should look like:
     108{{{
     109[rk:tagname attr1="value1" attr2="value2"]
     110}}}
     111Where '''tagname''' is the name of the tag and name of the plugin filename (tagname.py).
     112
     113A basic plugin code would look like this:
     114{{{
     115def render(dic, text):
     116        for i in dic:
     117                text = text.replace(i['tag'], '<h1>Article ID ' + i['attributes']['id'] + '</h1>')
     118        return text
     119}}}
     120for a CBC:
     121{{{
     122[rk:art id="value"]
     123}}}
     124'''dic''' is a dictionary which has few dictionaries in it. '''attributes''' has all the attributes (as dictionaries), '''tag''' is a string containing the tag code which we replace.
     125 '''attributes[ 'attrname' ]''' gives you value from given attribute from the tag ( '''attributes[ 'attr1' ]''' would give you '''value1'''). In the end the “rk:art" tag would become:
     126{{{
     127<h1>Article ID *value*</h1>
     128}}}
     129The parser supports also double tags:
     130{{{
     131[rk:tagname attr="value"]code here[/rk:tagname]
     132}}}
     133They work the same as single-tags with one difference – the code between tags is also available in the plugin as '''dic[ 'code' ]'''. For example for a tag:
     134{{{
     135[rk:codder lang="python"]a code here[/rk:codder]
     136}}}
     137We would create codder.py plugin:
     138{{{
     139def render(dic, text):
     140        for i in dic:
     141                text = text.replace(i['tag'], '<B>'+ i['attributes']['lang'] +'</B><pre><code>' + i['code'] + '</code></pre>')
     142        return text
     143}}}
     144
     145
     146=== Usage ===
     147- as a wrappers for JavaScript and similar code/widgets
     148
     149- as a markup tags that need to get data from somewhere (database etc.)
     150
     151
     152=== Real Example ===
     153Now we will make a plugin for [http://www.dreamprojections.com/syntaxhighlighter/ dp.syntaxhighlighter] – a javascript based code highlighter. We have our '''codder.py''' plugin but it doesn't do any usefull things.
     154
     155 * Download the script archive and extract it to an empty folder
     156 * Copy '''Styles''' and '''Scripts''' folders to your django media folder  (I've placed them in MEDIA_ROOT/syntax/)
     157 * You can open in a text editor one of examples to see how it works...
     158
     159It  works like this:
     160
     1611. First we have call to the CSS
     162{{{
     163<link type="text/css" rel="stylesheet" href="Styles/SyntaxHighlighter.css"></link>
     164}}}
     1652. Then we place code for highlighting in textareas:
     166{{{
     167<textarea name="code" class="LANGNAME"> code here </textarea>
     168}}}
     169The ''' name="code" class="LANGNAME"''' part make the textarea to work.
     1703. After all textareas we load JS files for languages we use and one main one (shCore.js):
     171{{{
     172<script class="javascript" src="Scripts/shCore.js"></script>
     173<script class="javascript" src="Scripts/shBrushCSharp.js"></script>
     174<script class="javascript" src="Scripts/shBrushPhp.js"></script>
     175<script class="javascript" src="Scripts/shBrushJScript.js"></script>
     176<script class="javascript" src="Scripts/shBrushJava.js"></script>
     177<script class="javascript" src="Scripts/shBrushVb.js"></script>
     178<script class="javascript" src="Scripts/shBrushSql.js"></script>
     179<script class="javascript" src="Scripts/shBrushXml.js"></script>
     180<script class="javascript" src="Scripts/shBrushDelphi.js"></script>
     181<script class="javascript" src="Scripts/shBrushPython.js"></script>
     182<script class="javascript" src="Scripts/shBrushRuby.js"></script>
     183<script class="javascript" src="Scripts/shBrushCss.js"></script>
     184}}}
     1854. At the end we initialize the script:
     186{{{
     187<script class="javascript">
     188dp.SyntaxHighlighter.HighlightAll('code');
     189</script>
     190}}}
     191And thats all. Now how to make a plugin out of it? Like this:
     192{{{
     193def render(dic, text):
     194        # w3c will kill us for this :)
     195        text = '<link type="text/css" rel="stylesheet" href="/site_media/syntax/Styles/SyntaxHighlighter.css"></link>' + text
     196        langs = {}
     197        for i in dic:
     198                text = text.replace(i['tag'], '<textarea name="code" class="'+ i['attributes']['lang'] +'" rows="15" cols="90">' + i['code'] + '</textarea>')
     199                # what langs are used?
     200                langs[i['attributes']['lang']] = True
     201       
     202        # add the core JS
     203        text = text + '<script class="javascript" src="/site_media/syntax/Scripts/shCore.js"></script>'
     204        # add only those lang-JS files that we realy need. For example i limit it to two
     205        if langs.has_key('python'):
     206                text = text + '<script class="javascript" src="/site_media/syntax/Scripts/shBrushPython.js"></script>'
     207        if langs.has_key('xml'):
     208                text = text + '<script class="javascript" src="/site_media/syntax/Scripts/shBrushXml.js"></script>'
     209        # the end, activate the code
     210        text = text + '<script class="javascript">dp.SyntaxHighlighter.HighlightAll(\'code\');</script>'
     211        return text
     212}}}
     213 * We changed paths to static files (JS and CSS). If you intend to use it often you could move CSS “injection" to HEAD in your template.
     214 * Next we replace all “codder" tags with the textarea with language we want to use
     215 * We make a dictionary which gathers all languages we use
     216 * Next we inject the main JS file and then those language specific JS files we really need
     217 * And at the end we add the initialization code.
     218
     219Now CBC like:
     220{{{
     221[rk:codder lang="python"]
     222for foo in bar:
     223   print foo
     224[/rk:codder]
     225}}}
     226Will get highlighted. '''See a screenshot [http://www.fotosik.pl/showFullSize.php?id=4a82e9c901329a06 here]'''
Back to Top