redcar-sparkup 1.0 → 1.01
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/assets/jython/jython.jar +0 -0
- data/assets/sparkup/Makefile +40 -0
- data/assets/sparkup/README.md +130 -0
- data/assets/sparkup/TextMate/Sparkup.tmbundle/Commands/Sparkup expand.tmCommand +34 -0
- data/assets/sparkup/TextMate/Sparkup.tmbundle/Support/sparkup.py +1087 -0
- data/assets/sparkup/TextMate/Sparkup.tmbundle/info.plist +14 -0
- data/assets/sparkup/mit-license.txt +22 -0
- data/assets/sparkup/sparkup +1087 -0
- data/assets/sparkup/sparkup-unittest.py +138 -0
- data/assets/sparkup/sparkup.py +1087 -0
- data/assets/sparkup/vim/README.txt +23 -0
- data/assets/sparkup/vim/ftplugin/html/sparkup.py +1087 -0
- data/assets/sparkup/vim/ftplugin/html/sparkup.vim +79 -0
- data/lib/sparkup.rb +115 -0
- metadata +16 -2
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import sys
|
|
4
|
+
import sparkup
|
|
5
|
+
|
|
6
|
+
class SparkupTest:
|
|
7
|
+
options = {
|
|
8
|
+
'textmate': True,
|
|
9
|
+
'no-last-newline': True,
|
|
10
|
+
'post-tag-guides': True,
|
|
11
|
+
}
|
|
12
|
+
options = {
|
|
13
|
+
'default': {'textmate': True, 'no-last-newline': True, 'post-tag-guides': True},
|
|
14
|
+
'guides': {'textmate': True, 'no-last-newline': True, 'post-tag-guides': True, 'start-guide-format': 'Begin %s'}
|
|
15
|
+
}
|
|
16
|
+
cases = {
|
|
17
|
+
'Simple test': {
|
|
18
|
+
'options': 'default',
|
|
19
|
+
'input': 'div',
|
|
20
|
+
'output': '<div>$1</div>$0'
|
|
21
|
+
},
|
|
22
|
+
'Class test': {
|
|
23
|
+
'input': 'div.lol',
|
|
24
|
+
'output': '<div class="lol">$1</div><!-- /.lol -->$0'
|
|
25
|
+
},
|
|
26
|
+
'ID and class test': {
|
|
27
|
+
'input': 'div.class#id',
|
|
28
|
+
'output': '<div class="class" id="id">$1</div><!-- /#id -->$0'
|
|
29
|
+
},
|
|
30
|
+
'ID and class test 2': {
|
|
31
|
+
'input': 'div#id.class',
|
|
32
|
+
'output': '<div class="class" id="id">$1</div><!-- /#id -->$0'
|
|
33
|
+
},
|
|
34
|
+
'Attributes test': {
|
|
35
|
+
'input': 'div#id.class[style=color:blue]',
|
|
36
|
+
'output': '<div style="color:blue" class="class" id="id">$1</div><!-- /#id -->$0'
|
|
37
|
+
},
|
|
38
|
+
'Multiple attributes test': {
|
|
39
|
+
'input': 'div[align=center][style=color:blue][rel=none]',
|
|
40
|
+
'output': '<div align="center" style="color:blue" rel="none">$1</div>$0'
|
|
41
|
+
},
|
|
42
|
+
'Multiple class test': {
|
|
43
|
+
'input': 'div.c1.c2.c3',
|
|
44
|
+
'output': '<div class="c1 c2 c3">$1</div><!-- /.c1.c2.c3 -->$0'
|
|
45
|
+
},
|
|
46
|
+
'Shortcut test': {
|
|
47
|
+
'input': 'input:button',
|
|
48
|
+
'output': '<input type="button" class="button" value="$1" name="$2" />$0'
|
|
49
|
+
},
|
|
50
|
+
'Shortcut synonym test': {
|
|
51
|
+
'input': 'button',
|
|
52
|
+
'output': '<input type="button" class="button" value="$1" name="$2" />$0'
|
|
53
|
+
},
|
|
54
|
+
'Child test': {
|
|
55
|
+
'input': 'div>ul>li',
|
|
56
|
+
'output': "<div>\n <ul>\n <li>$1</li>\n </ul>\n</div>$0"
|
|
57
|
+
},
|
|
58
|
+
'Sibling test': {
|
|
59
|
+
'input': 'div#x + ul+ h3.class',
|
|
60
|
+
'output': '<div id="x">$1</div><!-- /#x -->\n<ul>$2</ul>\n<h3 class="class">$3</h3>$0'
|
|
61
|
+
},
|
|
62
|
+
'Child + sibling test': {
|
|
63
|
+
'input': 'div > ul > li + span',
|
|
64
|
+
'output': '<div>\n <ul>\n <li>$1</li>\n <span>$2</span>\n </ul>\n</div>$0'
|
|
65
|
+
},
|
|
66
|
+
'Multiplier test 1': {
|
|
67
|
+
'input': 'ul > li*3',
|
|
68
|
+
'output': '<ul>\n <li>$1</li>\n <li>$2</li>\n <li>$3</li>\n</ul>$0'
|
|
69
|
+
},
|
|
70
|
+
'Multiplier test 2': {
|
|
71
|
+
'input': 'ul > li.item-$*3',
|
|
72
|
+
'output': '<ul>\n <li class="item-1">$1</li>\n <li class="item-2">$2</li>\n <li class="item-3">$3</li>\n</ul>$0'
|
|
73
|
+
},
|
|
74
|
+
'Multiplier test 3': {
|
|
75
|
+
'input': 'ul > li.item-$*3 > a',
|
|
76
|
+
'output': '<ul>\n <li class="item-1">\n <a href="$1">$2</a>\n </li>\n <li class="item-2">\n <a href="$3">$4</a>\n </li>\n <li class="item-3">\n <a href="$5">$6</a>\n </li>\n</ul>$0'
|
|
77
|
+
},
|
|
78
|
+
'Ampersand test': {
|
|
79
|
+
'input': 'td > tr.row-$*3 > td.cell-&*2',
|
|
80
|
+
'output': '<td>\n <tr class="row-1">\n <td class="cell-1">$1</td>\n <td class="cell-2">$2</td>\n </tr>\n <tr class="row-2">\n <td class="cell-3">$3</td>\n <td class="cell-4">$4</td>\n </tr>\n <tr class="row-3">\n <td class="cell-5">$5</td>\n <td class="cell-6">$6</td>\n </tr>\n</td>$0'
|
|
81
|
+
},
|
|
82
|
+
'Menu test': {
|
|
83
|
+
'input': 'ul#menu > li*3 > a > span',
|
|
84
|
+
'output': '<ul id="menu">\n <li>\n <a href="$1">\n <span>$2</span>\n </a>\n </li>\n <li>\n <a href="$3">\n <span>$4</span>\n </a>\n </li>\n <li>\n <a href="$5">\n <span>$6</span>\n </a>\n </li>\n</ul>$0'
|
|
85
|
+
},
|
|
86
|
+
'Back test': {
|
|
87
|
+
'input': 'ul#menu > li*3 > a < < div',
|
|
88
|
+
'output': '<ul id="menu">\n <li>\n <a href="$1">$2</a>\n </li>\n <li>\n <a href="$3">$4</a>\n </li>\n <li>\n <a href="$5">$6</a>\n </li>\n</ul>\n<div>$7</div>$0'
|
|
89
|
+
},
|
|
90
|
+
'Expand test': {
|
|
91
|
+
'input': 'p#menu > table+ + ul',
|
|
92
|
+
'output': '<p id="menu">\n <table cellspacing="0">\n <tr>\n <td>$1</td>\n </tr>\n </table>\n <ul>$2</ul>\n</p>$0'
|
|
93
|
+
},
|
|
94
|
+
'Text with dot test': {
|
|
95
|
+
'input': 'p { text.com }',
|
|
96
|
+
'output': '<p> text.com </p>$0'
|
|
97
|
+
},
|
|
98
|
+
'Attribute with dot test': {
|
|
99
|
+
'input': 'p [attrib=text.com]',
|
|
100
|
+
'output': '<p attrib="text.com">$1</p>$0'
|
|
101
|
+
},
|
|
102
|
+
# Add: text test, broken test, multi-attribute tests, indentation test, start and end comments test
|
|
103
|
+
}
|
|
104
|
+
def run(self):
|
|
105
|
+
"""Run Forrest run!"""
|
|
106
|
+
|
|
107
|
+
print "Test results:"
|
|
108
|
+
for name, case in self.cases.iteritems():
|
|
109
|
+
try: options_key = case['options']
|
|
110
|
+
except: options_key = 'default'
|
|
111
|
+
|
|
112
|
+
try: options = self.options[options_key]
|
|
113
|
+
except: options = self.options['default']
|
|
114
|
+
|
|
115
|
+
# Output buffer
|
|
116
|
+
r = sparkup.Router()
|
|
117
|
+
input = case['input']
|
|
118
|
+
output = r.start(options=options, str=input, ret=True)
|
|
119
|
+
del r
|
|
120
|
+
|
|
121
|
+
# Did it work?
|
|
122
|
+
result = output == case['output']
|
|
123
|
+
if result: result_str = " OK "
|
|
124
|
+
else: result_str = "FAIL"
|
|
125
|
+
|
|
126
|
+
print " - %-30s [%s]" % (name, result_str)
|
|
127
|
+
if not result:
|
|
128
|
+
print "= %s" % input.replace("\n", "\n= ")
|
|
129
|
+
print "Actual output (condensed):"
|
|
130
|
+
print " | '%s'" % output.replace("\n", r"\n").replace('"', '\"')
|
|
131
|
+
print "Actual output:"
|
|
132
|
+
print " | %s" % output.replace("\n", "\n | ")
|
|
133
|
+
print "Expected:"
|
|
134
|
+
print " | %s" % case['output'].replace("\n", "\ n| ")
|
|
135
|
+
|
|
136
|
+
if __name__ == '__main__':
|
|
137
|
+
s = SparkupTest()
|
|
138
|
+
s.run()
|
|
@@ -0,0 +1,1087 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
version = "0.1.3"
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import fileinput
|
|
7
|
+
import getopt
|
|
8
|
+
import sys
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
# ===============================================================================
|
|
12
|
+
|
|
13
|
+
class Dialect:
|
|
14
|
+
shortcuts = {}
|
|
15
|
+
synonyms = {}
|
|
16
|
+
required = {}
|
|
17
|
+
short_tags = ()
|
|
18
|
+
|
|
19
|
+
class HtmlDialect(Dialect):
|
|
20
|
+
shortcuts = {
|
|
21
|
+
'cc:ie': {
|
|
22
|
+
'opening_tag': '<!--[if IE]>',
|
|
23
|
+
'closing_tag': '<![endif]-->'},
|
|
24
|
+
'cc:ie6': {
|
|
25
|
+
'opening_tag': '<!--[if lte IE 6]>',
|
|
26
|
+
'closing_tag': '<![endif]-->'},
|
|
27
|
+
'cc:ie7': {
|
|
28
|
+
'opening_tag': '<!--[if lte IE 7]>',
|
|
29
|
+
'closing_tag': '<![endif]-->'},
|
|
30
|
+
'cc:noie': {
|
|
31
|
+
'opening_tag': '<!--[if !IE]><!-->',
|
|
32
|
+
'closing_tag': '<!--<![endif]-->'},
|
|
33
|
+
'html:4t': {
|
|
34
|
+
'expand': True,
|
|
35
|
+
'opening_tag':
|
|
36
|
+
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' +
|
|
37
|
+
'<html lang="en">\n' +
|
|
38
|
+
'<head>\n' +
|
|
39
|
+
' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
|
|
40
|
+
' ' + '<title></title>\n' +
|
|
41
|
+
'</head>\n' +
|
|
42
|
+
'<body>',
|
|
43
|
+
'closing_tag':
|
|
44
|
+
'</body>\n' +
|
|
45
|
+
'</html>'},
|
|
46
|
+
'html:4s': {
|
|
47
|
+
'expand': True,
|
|
48
|
+
'opening_tag':
|
|
49
|
+
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' +
|
|
50
|
+
'<html lang="en">\n' +
|
|
51
|
+
'<head>\n' +
|
|
52
|
+
' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
|
|
53
|
+
' ' + '<title></title>\n' +
|
|
54
|
+
'</head>\n' +
|
|
55
|
+
'<body>',
|
|
56
|
+
'closing_tag':
|
|
57
|
+
'</body>\n' +
|
|
58
|
+
'</html>'},
|
|
59
|
+
'html:xt': {
|
|
60
|
+
'expand': True,
|
|
61
|
+
'opening_tag':
|
|
62
|
+
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' +
|
|
63
|
+
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
|
|
64
|
+
'<head>\n' +
|
|
65
|
+
' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
|
|
66
|
+
' ' + '<title></title>\n' +
|
|
67
|
+
'</head>\n' +
|
|
68
|
+
'<body>',
|
|
69
|
+
'closing_tag':
|
|
70
|
+
'</body>\n' +
|
|
71
|
+
'</html>'},
|
|
72
|
+
'html:xs': {
|
|
73
|
+
'expand': True,
|
|
74
|
+
'opening_tag':
|
|
75
|
+
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +
|
|
76
|
+
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
|
|
77
|
+
'<head>\n' +
|
|
78
|
+
' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
|
|
79
|
+
' ' + '<title></title>\n' +
|
|
80
|
+
'</head>\n' +
|
|
81
|
+
'<body>',
|
|
82
|
+
'closing_tag':
|
|
83
|
+
'</body>\n' +
|
|
84
|
+
'</html>'},
|
|
85
|
+
'html:xxs': {
|
|
86
|
+
'expand': True,
|
|
87
|
+
'opening_tag':
|
|
88
|
+
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' +
|
|
89
|
+
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
|
|
90
|
+
'<head>\n' +
|
|
91
|
+
' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
|
|
92
|
+
' ' + '<title></title>\n' +
|
|
93
|
+
'</head>\n' +
|
|
94
|
+
'<body>',
|
|
95
|
+
'closing_tag':
|
|
96
|
+
'</body>\n' +
|
|
97
|
+
'</html>'},
|
|
98
|
+
'html:5': {
|
|
99
|
+
'expand': True,
|
|
100
|
+
'opening_tag':
|
|
101
|
+
'<!DOCTYPE html>\n' +
|
|
102
|
+
'<html lang="en">\n' +
|
|
103
|
+
'<head>\n' +
|
|
104
|
+
' ' + '<meta charset="UTF-8" />\n' +
|
|
105
|
+
' ' + '<title></title>\n' +
|
|
106
|
+
'</head>\n' +
|
|
107
|
+
'<body>',
|
|
108
|
+
'closing_tag':
|
|
109
|
+
'</body>\n' +
|
|
110
|
+
'</html>'},
|
|
111
|
+
'input:button': {
|
|
112
|
+
'name': 'input',
|
|
113
|
+
'attributes': { 'class': 'button', 'type': 'button', 'name': '', 'value': '' }
|
|
114
|
+
},
|
|
115
|
+
'input:password': {
|
|
116
|
+
'name': 'input',
|
|
117
|
+
'attributes': { 'class': 'text password', 'type': 'password', 'name': '', 'value': '' }
|
|
118
|
+
},
|
|
119
|
+
'input:radio': {
|
|
120
|
+
'name': 'input',
|
|
121
|
+
'attributes': { 'class': 'radio', 'type': 'radio', 'name': '', 'value': '' }
|
|
122
|
+
},
|
|
123
|
+
'input:checkbox': {
|
|
124
|
+
'name': 'input',
|
|
125
|
+
'attributes': { 'class': 'checkbox', 'type': 'checkbox', 'name': '', 'value': '' }
|
|
126
|
+
},
|
|
127
|
+
'input:file': {
|
|
128
|
+
'name': 'input',
|
|
129
|
+
'attributes': { 'class': 'file', 'type': 'file', 'name': '', 'value': '' }
|
|
130
|
+
},
|
|
131
|
+
'input:text': {
|
|
132
|
+
'name': 'input',
|
|
133
|
+
'attributes': { 'class': 'text', 'type': 'text', 'name': '', 'value': '' }
|
|
134
|
+
},
|
|
135
|
+
'input:submit': {
|
|
136
|
+
'name': 'input',
|
|
137
|
+
'attributes': { 'class': 'submit', 'type': 'submit', 'value': '' }
|
|
138
|
+
},
|
|
139
|
+
'input:hidden': {
|
|
140
|
+
'name': 'input',
|
|
141
|
+
'attributes': { 'type': 'hidden', 'name': '', 'value': '' }
|
|
142
|
+
},
|
|
143
|
+
'script:src': {
|
|
144
|
+
'name': 'script',
|
|
145
|
+
'attributes': { 'src': '' }
|
|
146
|
+
},
|
|
147
|
+
'script:jquery': {
|
|
148
|
+
'name': 'script',
|
|
149
|
+
'attributes': { 'src': 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js' }
|
|
150
|
+
},
|
|
151
|
+
'script:jsapi': {
|
|
152
|
+
'name': 'script',
|
|
153
|
+
'attributes': { 'src': 'http://www.google.com/jsapi' }
|
|
154
|
+
},
|
|
155
|
+
'script:jsapix': {
|
|
156
|
+
'name': 'script',
|
|
157
|
+
'text': '\n google.load("jquery", "1.3.2");\n google.setOnLoadCallback(function() {\n \n });\n'
|
|
158
|
+
},
|
|
159
|
+
'link:css': {
|
|
160
|
+
'name': 'link',
|
|
161
|
+
'attributes': { 'rel': 'stylesheet', 'type': 'text/css', 'href': '', 'media': 'all' },
|
|
162
|
+
},
|
|
163
|
+
'link:print': {
|
|
164
|
+
'name': 'link',
|
|
165
|
+
'attributes': { 'rel': 'stylesheet', 'type': 'text/css', 'href': '', 'media': 'print' },
|
|
166
|
+
},
|
|
167
|
+
'link:favicon': {
|
|
168
|
+
'name': 'link',
|
|
169
|
+
'attributes': { 'rel': 'shortcut icon', 'type': 'image/x-icon', 'href': '' },
|
|
170
|
+
},
|
|
171
|
+
'link:touch': {
|
|
172
|
+
'name': 'link',
|
|
173
|
+
'attributes': { 'rel': 'apple-touch-icon', 'href': '' },
|
|
174
|
+
},
|
|
175
|
+
'link:rss': {
|
|
176
|
+
'name': 'link',
|
|
177
|
+
'attributes': { 'rel': 'alternate', 'type': 'application/rss+xml', 'title': 'RSS', 'href': '' },
|
|
178
|
+
},
|
|
179
|
+
'link:atom': {
|
|
180
|
+
'name': 'link',
|
|
181
|
+
'attributes': { 'rel': 'alternate', 'type': 'application/atom+xml', 'title': 'Atom', 'href': '' },
|
|
182
|
+
},
|
|
183
|
+
'meta:ie7': {
|
|
184
|
+
'name': 'meta',
|
|
185
|
+
'attributes': { 'http-equiv': 'X-UA-Compatible', 'content': 'IE=7' },
|
|
186
|
+
},
|
|
187
|
+
'meta:ie8': {
|
|
188
|
+
'name': 'meta',
|
|
189
|
+
'attributes': { 'http-equiv': 'X-UA-Compatible', 'content': 'IE=8' },
|
|
190
|
+
},
|
|
191
|
+
'form:get': {
|
|
192
|
+
'name': 'form',
|
|
193
|
+
'attributes': { 'method': 'get' },
|
|
194
|
+
},
|
|
195
|
+
'form:g': {
|
|
196
|
+
'name': 'form',
|
|
197
|
+
'attributes': { 'method': 'get' },
|
|
198
|
+
},
|
|
199
|
+
'form:post': {
|
|
200
|
+
'name': 'form',
|
|
201
|
+
'attributes': { 'method': 'post' },
|
|
202
|
+
},
|
|
203
|
+
'form:p': {
|
|
204
|
+
'name': 'form',
|
|
205
|
+
'attributes': { 'method': 'post' },
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
synonyms = {
|
|
209
|
+
'checkbox': 'input:checkbox',
|
|
210
|
+
'check': 'input:checkbox',
|
|
211
|
+
'input:c': 'input:checkbox',
|
|
212
|
+
'button': 'input:button',
|
|
213
|
+
'input:b': 'input:button',
|
|
214
|
+
'input:h': 'input:hidden',
|
|
215
|
+
'hidden': 'input:hidden',
|
|
216
|
+
'submit': 'input:submit',
|
|
217
|
+
'input:s': 'input:submit',
|
|
218
|
+
'radio': 'input:radio',
|
|
219
|
+
'input:r': 'input:radio',
|
|
220
|
+
'text': 'input:text',
|
|
221
|
+
'passwd': 'input:password',
|
|
222
|
+
'password': 'input:password',
|
|
223
|
+
'pw': 'input:password',
|
|
224
|
+
'input:t': 'input:text',
|
|
225
|
+
'linkcss': 'link:css',
|
|
226
|
+
'scriptsrc': 'script:src',
|
|
227
|
+
'jquery': 'script:jquery',
|
|
228
|
+
'jsapi': 'script:jsapi',
|
|
229
|
+
'html5': 'html:5',
|
|
230
|
+
'html4': 'html:4s',
|
|
231
|
+
'html4s': 'html:4s',
|
|
232
|
+
'html4t': 'html:4t',
|
|
233
|
+
'xhtml': 'html:xxs',
|
|
234
|
+
'xhtmlt': 'html:xt',
|
|
235
|
+
'xhtmls': 'html:xs',
|
|
236
|
+
'xhtml11': 'html:xxs',
|
|
237
|
+
'opt': 'option',
|
|
238
|
+
'st': 'strong',
|
|
239
|
+
'css': 'style',
|
|
240
|
+
'csss': 'link:css',
|
|
241
|
+
'css:src': 'link:css',
|
|
242
|
+
'csssrc': 'link:css',
|
|
243
|
+
'js': 'script',
|
|
244
|
+
'jss': 'script:src',
|
|
245
|
+
'js:src': 'script:src',
|
|
246
|
+
'jssrc': 'script:src',
|
|
247
|
+
}
|
|
248
|
+
short_tags = (
|
|
249
|
+
'area', 'base', 'basefont', 'br', 'embed', 'hr', \
|
|
250
|
+
'input', 'img', 'link', 'param', 'meta')
|
|
251
|
+
required = {
|
|
252
|
+
'a': {'href':''},
|
|
253
|
+
'base': {'href':''},
|
|
254
|
+
'abbr': {'title': ''},
|
|
255
|
+
'acronym':{'title': ''},
|
|
256
|
+
'bdo': {'dir': ''},
|
|
257
|
+
'link': {'rel': 'stylesheet', 'href': ''},
|
|
258
|
+
'style': {'type': 'text/css'},
|
|
259
|
+
'script': {'type': 'text/javascript'},
|
|
260
|
+
'img': {'src':'', 'alt':''},
|
|
261
|
+
'iframe': {'src': '', 'frameborder': '0'},
|
|
262
|
+
'embed': {'src': '', 'type': ''},
|
|
263
|
+
'object': {'data': '', 'type': ''},
|
|
264
|
+
'param': {'name': '', 'value': ''},
|
|
265
|
+
'form': {'action': '', 'method': 'post'},
|
|
266
|
+
'table': {'cellspacing': '0'},
|
|
267
|
+
'input': {'type': '', 'name': '', 'value': ''},
|
|
268
|
+
'base': {'href': ''},
|
|
269
|
+
'area': {'shape': '', 'coords': '', 'href': '', 'alt': ''},
|
|
270
|
+
'select': {'name': ''},
|
|
271
|
+
'option': {'value': ''},
|
|
272
|
+
'textarea':{'name': ''},
|
|
273
|
+
'meta': {'content': ''},
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
class Parser:
|
|
277
|
+
"""The parser.
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
# Constructor
|
|
281
|
+
# ---------------------------------------------------------------------------
|
|
282
|
+
|
|
283
|
+
def __init__(self, options=None, str='', dialect=HtmlDialect()):
|
|
284
|
+
"""Constructor.
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
self.tokens = []
|
|
288
|
+
self.str = str
|
|
289
|
+
self.options = options
|
|
290
|
+
self.dialect = dialect
|
|
291
|
+
self.root = Element(parser=self)
|
|
292
|
+
self.caret = []
|
|
293
|
+
self.caret.append(self.root)
|
|
294
|
+
self._last = []
|
|
295
|
+
|
|
296
|
+
# Methods
|
|
297
|
+
# ---------------------------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
def load_string(self, str):
|
|
300
|
+
"""Loads a string to parse.
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
self.str = str
|
|
304
|
+
self._tokenize()
|
|
305
|
+
self._parse()
|
|
306
|
+
|
|
307
|
+
def render(self):
|
|
308
|
+
"""Renders.
|
|
309
|
+
Called by [[Router]].
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
# Get the initial render of the root node
|
|
313
|
+
output = self.root.render()
|
|
314
|
+
|
|
315
|
+
# Indent by whatever the input is indented with
|
|
316
|
+
indent = re.findall("^[\r\n]*(\s*)", self.str)[0]
|
|
317
|
+
output = indent + output.replace("\n", "\n" + indent)
|
|
318
|
+
|
|
319
|
+
# Strip newline if not needed
|
|
320
|
+
if self.options.has("no-last-newline") \
|
|
321
|
+
or self.prefix or self.suffix:
|
|
322
|
+
output = re.sub(r'\n\s*$', '', output)
|
|
323
|
+
|
|
324
|
+
# TextMate mode
|
|
325
|
+
if self.options.has("textmate"):
|
|
326
|
+
output = self._textmatify(output)
|
|
327
|
+
|
|
328
|
+
return output
|
|
329
|
+
|
|
330
|
+
# Protected methods
|
|
331
|
+
# ---------------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
def _textmatify(self, output):
|
|
334
|
+
"""Returns a version of the output with TextMate placeholders in it.
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
matches = re.findall(r'(></)|("")|(\n\s+)\n|(.|\s)', output)
|
|
338
|
+
output = ''
|
|
339
|
+
n = 1
|
|
340
|
+
for i in matches:
|
|
341
|
+
if i[0]:
|
|
342
|
+
output += '>$%i</' % n
|
|
343
|
+
n += 1
|
|
344
|
+
elif i[1]:
|
|
345
|
+
output += '"$%i"' % n
|
|
346
|
+
n += 1
|
|
347
|
+
elif i[2]:
|
|
348
|
+
output += i[2] + '$%i\n' % n
|
|
349
|
+
n += 1
|
|
350
|
+
elif i[3]:
|
|
351
|
+
output += i[3]
|
|
352
|
+
output += "$0"
|
|
353
|
+
return output
|
|
354
|
+
|
|
355
|
+
def _tokenize(self):
|
|
356
|
+
"""Tokenizes.
|
|
357
|
+
Initializes [[self.tokens]].
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
str = self.str.strip()
|
|
361
|
+
|
|
362
|
+
# Find prefix/suffix
|
|
363
|
+
while True:
|
|
364
|
+
match = re.match(r"^(\s*<[^>]+>\s*)", str)
|
|
365
|
+
if match is None: break
|
|
366
|
+
if self.prefix is None: self.prefix = ''
|
|
367
|
+
self.prefix += match.group(0)
|
|
368
|
+
str = str[len(match.group(0)):]
|
|
369
|
+
|
|
370
|
+
while True:
|
|
371
|
+
match = re.findall(r"(\s*<[^>]+>[\s\n\r]*)$", str)
|
|
372
|
+
if not match: break
|
|
373
|
+
if self.suffix is None: self.suffix = ''
|
|
374
|
+
self.suffix = match[0] + self.suffix
|
|
375
|
+
str = str[:-len(match[0])]
|
|
376
|
+
|
|
377
|
+
# Split by the element separators
|
|
378
|
+
for token in re.split('(<|>|\+(?!\\s*\+|$))', str):
|
|
379
|
+
if token.strip() != '':
|
|
380
|
+
self.tokens.append(Token(token, parser=self))
|
|
381
|
+
|
|
382
|
+
def _parse(self):
|
|
383
|
+
"""Takes the tokens and does its thing.
|
|
384
|
+
Populates [[self.root]].
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
# Carry it over to the root node.
|
|
388
|
+
if self.prefix or self.suffix:
|
|
389
|
+
self.root.prefix = self.prefix
|
|
390
|
+
self.root.suffix = self.suffix
|
|
391
|
+
self.root.depth += 1
|
|
392
|
+
|
|
393
|
+
for token in self.tokens:
|
|
394
|
+
if token.type == Token.ELEMENT:
|
|
395
|
+
# Reset the "last elements added" list. We will
|
|
396
|
+
# repopulate this with the new elements added now.
|
|
397
|
+
self._last[:] = []
|
|
398
|
+
|
|
399
|
+
# Create [[Element]]s from a [[Token]].
|
|
400
|
+
# They will be created as many as the multiplier specifies,
|
|
401
|
+
# multiplied by how many carets we have
|
|
402
|
+
count = 0
|
|
403
|
+
for caret in self.caret:
|
|
404
|
+
local_count = 0
|
|
405
|
+
for i in range(token.multiplier):
|
|
406
|
+
count += 1
|
|
407
|
+
local_count += 1
|
|
408
|
+
new = Element(token, caret,
|
|
409
|
+
count = count,
|
|
410
|
+
local_count = local_count,
|
|
411
|
+
parser = self)
|
|
412
|
+
self._last.append(new)
|
|
413
|
+
caret.append(new)
|
|
414
|
+
|
|
415
|
+
# For >
|
|
416
|
+
elif token.type == Token.CHILD:
|
|
417
|
+
# The last children added.
|
|
418
|
+
self.caret[:] = self._last
|
|
419
|
+
|
|
420
|
+
# For <
|
|
421
|
+
elif token.type == Token.PARENT:
|
|
422
|
+
# If we're the root node, don't do anything
|
|
423
|
+
parent = self.caret[0].parent
|
|
424
|
+
if parent is not None:
|
|
425
|
+
self.caret[:] = [parent]
|
|
426
|
+
return
|
|
427
|
+
|
|
428
|
+
# Properties
|
|
429
|
+
# ---------------------------------------------------------------------------
|
|
430
|
+
|
|
431
|
+
# Property: dialect
|
|
432
|
+
# The dialect of XML
|
|
433
|
+
dialect = None
|
|
434
|
+
|
|
435
|
+
# Property: str
|
|
436
|
+
# The string
|
|
437
|
+
str = ''
|
|
438
|
+
|
|
439
|
+
# Property: tokens
|
|
440
|
+
# The list of tokens
|
|
441
|
+
tokens = []
|
|
442
|
+
|
|
443
|
+
# Property: options
|
|
444
|
+
# Reference to the [[Options]] instance
|
|
445
|
+
options = None
|
|
446
|
+
|
|
447
|
+
# Property: root
|
|
448
|
+
# The root [[Element]] node.
|
|
449
|
+
root = None
|
|
450
|
+
|
|
451
|
+
# Property: caret
|
|
452
|
+
# The current insertion point.
|
|
453
|
+
caret = None
|
|
454
|
+
|
|
455
|
+
# Property: _last
|
|
456
|
+
# List of the last appended stuff
|
|
457
|
+
_last = None
|
|
458
|
+
|
|
459
|
+
# Property: indent
|
|
460
|
+
# Yeah
|
|
461
|
+
indent = ''
|
|
462
|
+
|
|
463
|
+
# Property: prefix
|
|
464
|
+
# (String) The trailing tag in the beginning.
|
|
465
|
+
#
|
|
466
|
+
# Description:
|
|
467
|
+
# For instance, in `<div>ul>li</div>`, the `prefix` is `<div>`.
|
|
468
|
+
prefix = ''
|
|
469
|
+
|
|
470
|
+
# Property: suffix
|
|
471
|
+
# (string) The trailing tag at the end.
|
|
472
|
+
suffix = ''
|
|
473
|
+
pass
|
|
474
|
+
|
|
475
|
+
# ===============================================================================
|
|
476
|
+
|
|
477
|
+
class Element:
|
|
478
|
+
"""An element.
|
|
479
|
+
"""
|
|
480
|
+
|
|
481
|
+
def __init__(self, token=None, parent=None, count=None, local_count=None, \
|
|
482
|
+
parser=None, opening_tag=None, closing_tag=None, \
|
|
483
|
+
attributes=None, name=None, text=None):
|
|
484
|
+
"""Constructor.
|
|
485
|
+
|
|
486
|
+
This is called by ???.
|
|
487
|
+
|
|
488
|
+
Description:
|
|
489
|
+
All parameters are optional.
|
|
490
|
+
|
|
491
|
+
token - (Token) The token (required)
|
|
492
|
+
parent - (Element) Parent element; `None` if root
|
|
493
|
+
count - (Int) The number to substitute for `&` (e.g., in `li.item-$`)
|
|
494
|
+
local_count - (Int) The number to substitute for `$` (e.g., in `li.item-&`)
|
|
495
|
+
parser - (Parser) The parser
|
|
496
|
+
|
|
497
|
+
attributes - ...
|
|
498
|
+
name - ...
|
|
499
|
+
text - ...
|
|
500
|
+
"""
|
|
501
|
+
|
|
502
|
+
self.children = []
|
|
503
|
+
self.attributes = {}
|
|
504
|
+
self.parser = parser
|
|
505
|
+
|
|
506
|
+
if token is not None:
|
|
507
|
+
# Assumption is that token is of type [[Token]] and is
|
|
508
|
+
# a [[Token.ELEMENT]].
|
|
509
|
+
self.name = token.name
|
|
510
|
+
self.attributes = token.attributes.copy()
|
|
511
|
+
self.text = token.text
|
|
512
|
+
self.populate = token.populate
|
|
513
|
+
self.expand = token.expand
|
|
514
|
+
self.opening_tag = token.opening_tag
|
|
515
|
+
self.closing_tag = token.closing_tag
|
|
516
|
+
|
|
517
|
+
# `count` can be given. This will substitude & in classname and ID
|
|
518
|
+
if count is not None:
|
|
519
|
+
for key in self.attributes:
|
|
520
|
+
attrib = self.attributes[key]
|
|
521
|
+
attrib = attrib.replace('&', ("%i" % count))
|
|
522
|
+
if local_count is not None:
|
|
523
|
+
attrib = attrib.replace('$', ("%i" % local_count))
|
|
524
|
+
self.attributes[key] = attrib
|
|
525
|
+
|
|
526
|
+
# Copy over from parameters
|
|
527
|
+
if attributes: self.attributes = attribues
|
|
528
|
+
if name: self.name = name
|
|
529
|
+
if text: self.text = text
|
|
530
|
+
|
|
531
|
+
self._fill_attributes()
|
|
532
|
+
|
|
533
|
+
self.parent = parent
|
|
534
|
+
if parent is not None:
|
|
535
|
+
self.depth = parent.depth + 1
|
|
536
|
+
|
|
537
|
+
if self.populate: self._populate()
|
|
538
|
+
|
|
539
|
+
def render(self):
|
|
540
|
+
"""Renders the element, along with it's subelements, into HTML code.
|
|
541
|
+
|
|
542
|
+
[Grouped under "Rendering methods"]
|
|
543
|
+
"""
|
|
544
|
+
|
|
545
|
+
output = ""
|
|
546
|
+
try: spaces_count = int(self.parser.options.options['indent-spaces'])
|
|
547
|
+
except: spaces_count = 4
|
|
548
|
+
spaces = ' ' * spaces_count
|
|
549
|
+
indent = self.depth * spaces
|
|
550
|
+
|
|
551
|
+
prefix, suffix = ('', '')
|
|
552
|
+
if self.prefix: prefix = self.prefix + "\n"
|
|
553
|
+
if self.suffix: suffix = self.suffix
|
|
554
|
+
|
|
555
|
+
# Make the guide from the ID (/#header), or the class if there's no ID (/.item)
|
|
556
|
+
# This is for the start-guide, end-guide and post-tag-guides
|
|
557
|
+
guide_str = ''
|
|
558
|
+
if 'id' in self.attributes:
|
|
559
|
+
guide_str += "#%s" % self.attributes['id']
|
|
560
|
+
elif 'class' in self.attributes:
|
|
561
|
+
guide_str += ".%s" % self.attributes['class'].replace(' ', '.')
|
|
562
|
+
|
|
563
|
+
# Build the post-tag guide (e.g., </div><!-- /#header -->),
|
|
564
|
+
# the start guide, and the end guide.
|
|
565
|
+
guide = ''
|
|
566
|
+
start_guide = ''
|
|
567
|
+
end_guide = ''
|
|
568
|
+
if ((self.name == 'div') and \
|
|
569
|
+
(('id' in self.attributes) or ('class' in self.attributes))):
|
|
570
|
+
|
|
571
|
+
if (self.parser.options.has('post-tag-guides')):
|
|
572
|
+
guide = "<!-- /%s -->" % guide_str
|
|
573
|
+
|
|
574
|
+
if (self.parser.options.has('start-guide-format')):
|
|
575
|
+
format = self.parser.options.get('start-guide-format')
|
|
576
|
+
try: start_guide = format % guide_str
|
|
577
|
+
except: start_guide = (format + " " + guide_str).strip()
|
|
578
|
+
start_guide = "%s<!-- %s -->\n" % (indent, start_guide)
|
|
579
|
+
|
|
580
|
+
if (self.parser.options.has('end-guide-format')):
|
|
581
|
+
format = self.parser.options.get('end-guide-format')
|
|
582
|
+
try: end_guide = format % guide_str
|
|
583
|
+
except: end_guide = (format + " " + guide_str).strip()
|
|
584
|
+
end_guide = "\n%s<!-- %s -->" % (indent, end_guide)
|
|
585
|
+
|
|
586
|
+
# Short, self-closing tags (<br />)
|
|
587
|
+
short_tags = self.parser.dialect.short_tags
|
|
588
|
+
|
|
589
|
+
# When it should be expanded..
|
|
590
|
+
# (That is, <div>\n...\n</div> or similar -- wherein something must go
|
|
591
|
+
# inside the opening/closing tags)
|
|
592
|
+
if len(self.children) > 0 \
|
|
593
|
+
or self.expand \
|
|
594
|
+
or prefix or suffix \
|
|
595
|
+
or (self.parser.options.has('expand-divs') and self.name == 'div'):
|
|
596
|
+
|
|
597
|
+
for child in self.children:
|
|
598
|
+
output += child.render()
|
|
599
|
+
|
|
600
|
+
# For expand divs: if there are no children (that is, `output`
|
|
601
|
+
# is still blank despite above), fill it with a blank line.
|
|
602
|
+
if (output == ''): output = indent + spaces + "\n"
|
|
603
|
+
|
|
604
|
+
# If we're a root node and we have a prefix or suffix...
|
|
605
|
+
# (Only the root node can have a prefix or suffix.)
|
|
606
|
+
if prefix or suffix:
|
|
607
|
+
output = "%s%s%s%s%s\n" % \
|
|
608
|
+
(indent, prefix, output, suffix, guide)
|
|
609
|
+
|
|
610
|
+
# Uh..
|
|
611
|
+
elif self.name != '' or \
|
|
612
|
+
self.opening_tag is not None or \
|
|
613
|
+
self.closing_tag is not None:
|
|
614
|
+
output = start_guide + \
|
|
615
|
+
indent + self.get_opening_tag() + "\n" + \
|
|
616
|
+
output + \
|
|
617
|
+
indent + self.get_closing_tag() + \
|
|
618
|
+
guide + end_guide + "\n"
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
# Short, self-closing tags (<br />)
|
|
622
|
+
elif self.name in short_tags:
|
|
623
|
+
output = "%s<%s />\n" % (indent, self.get_default_tag())
|
|
624
|
+
|
|
625
|
+
# Tags with text, possibly
|
|
626
|
+
elif self.name != '' or \
|
|
627
|
+
self.opening_tag is not None or \
|
|
628
|
+
self.closing_tag is not None:
|
|
629
|
+
output = "%s%s%s%s%s%s%s%s" % \
|
|
630
|
+
(start_guide, indent, self.get_opening_tag(), \
|
|
631
|
+
self.text, \
|
|
632
|
+
self.get_closing_tag(), \
|
|
633
|
+
guide, end_guide, "\n")
|
|
634
|
+
|
|
635
|
+
# Else, it's an empty-named element (like the root). Pass.
|
|
636
|
+
else: pass
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
return output
|
|
640
|
+
|
|
641
|
+
def get_default_tag(self):
|
|
642
|
+
"""Returns the opening tag (without brackets).
|
|
643
|
+
|
|
644
|
+
Usage:
|
|
645
|
+
element.get_default_tag()
|
|
646
|
+
|
|
647
|
+
[Grouped under "Rendering methods"]
|
|
648
|
+
"""
|
|
649
|
+
|
|
650
|
+
output = '%s' % (self.name)
|
|
651
|
+
for key, value in self.attributes.iteritems():
|
|
652
|
+
output += ' %s="%s"' % (key, value)
|
|
653
|
+
return output
|
|
654
|
+
|
|
655
|
+
def get_opening_tag(self):
|
|
656
|
+
if self.opening_tag is None:
|
|
657
|
+
return "<%s>" % self.get_default_tag()
|
|
658
|
+
else:
|
|
659
|
+
return self.opening_tag
|
|
660
|
+
|
|
661
|
+
def get_closing_tag(self):
|
|
662
|
+
if self.closing_tag is None:
|
|
663
|
+
return "</%s>" % self.name
|
|
664
|
+
else:
|
|
665
|
+
return self.closing_tag
|
|
666
|
+
|
|
667
|
+
def append(self, object):
|
|
668
|
+
"""Registers an element as a child of this element.
|
|
669
|
+
|
|
670
|
+
Usage:
|
|
671
|
+
element.append(child)
|
|
672
|
+
|
|
673
|
+
Description:
|
|
674
|
+
Adds a given element `child` to the children list of this element. It
|
|
675
|
+
will be rendered when [[render()]] is called on the element.
|
|
676
|
+
|
|
677
|
+
See also:
|
|
678
|
+
- [[get_last_child()]]
|
|
679
|
+
|
|
680
|
+
[Grouped under "Traversion methods"]
|
|
681
|
+
"""
|
|
682
|
+
|
|
683
|
+
self.children.append(object)
|
|
684
|
+
|
|
685
|
+
def get_last_child(self):
|
|
686
|
+
"""Returns the last child element which was [[append()]]ed to this element.
|
|
687
|
+
|
|
688
|
+
Usage:
|
|
689
|
+
element.get_last_child()
|
|
690
|
+
|
|
691
|
+
Description:
|
|
692
|
+
This is the same as using `element.children[-1]`.
|
|
693
|
+
|
|
694
|
+
[Grouped under "Traversion methods"]
|
|
695
|
+
"""
|
|
696
|
+
|
|
697
|
+
return self.children[-1]
|
|
698
|
+
|
|
699
|
+
def _populate(self):
|
|
700
|
+
"""Expands with default items.
|
|
701
|
+
|
|
702
|
+
This is called when the [[populate]] flag is turned on.
|
|
703
|
+
"""
|
|
704
|
+
|
|
705
|
+
if self.name == 'ul':
|
|
706
|
+
elements = [Element(name='li', parent=self, parser=self.parser)]
|
|
707
|
+
|
|
708
|
+
elif self.name == 'dl':
|
|
709
|
+
elements = [
|
|
710
|
+
Element(name='dt', parent=self, parser=self.parser),
|
|
711
|
+
Element(name='dd', parent=self, parser=self.parser)]
|
|
712
|
+
|
|
713
|
+
elif self.name == 'table':
|
|
714
|
+
tr = Element(name='tr', parent=self, parser=self.parser)
|
|
715
|
+
td = Element(name='td', parent=tr, parser=self.parser)
|
|
716
|
+
tr.children.append(td)
|
|
717
|
+
elements = [tr]
|
|
718
|
+
|
|
719
|
+
else:
|
|
720
|
+
elements = []
|
|
721
|
+
|
|
722
|
+
for el in elements:
|
|
723
|
+
self.children.append(el)
|
|
724
|
+
|
|
725
|
+
def _fill_attributes(self):
|
|
726
|
+
"""Fills default attributes for certain elements.
|
|
727
|
+
|
|
728
|
+
Description:
|
|
729
|
+
This is called by the constructor.
|
|
730
|
+
|
|
731
|
+
[Protected, grouped under "Protected methods"]
|
|
732
|
+
"""
|
|
733
|
+
|
|
734
|
+
# Make sure <a>'s have a href, <img>'s have an src, etc.
|
|
735
|
+
required = self.parser.dialect.required
|
|
736
|
+
|
|
737
|
+
for element, attribs in required.iteritems():
|
|
738
|
+
if self.name == element:
|
|
739
|
+
for attrib in attribs:
|
|
740
|
+
if attrib not in self.attributes:
|
|
741
|
+
self.attributes[attrib] = attribs[attrib]
|
|
742
|
+
|
|
743
|
+
# ---------------------------------------------------------------------------
|
|
744
|
+
|
|
745
|
+
# Property: last_child
|
|
746
|
+
# [Read-only]
|
|
747
|
+
last_child = property(get_last_child)
|
|
748
|
+
|
|
749
|
+
# ---------------------------------------------------------------------------
|
|
750
|
+
|
|
751
|
+
# Property: parent
|
|
752
|
+
# (Element) The parent element.
|
|
753
|
+
parent = None
|
|
754
|
+
|
|
755
|
+
# Property: name
|
|
756
|
+
# (String) The name of the element (e.g., `div`)
|
|
757
|
+
name = ''
|
|
758
|
+
|
|
759
|
+
# Property: attributes
|
|
760
|
+
# (Dict) The dictionary of attributes (e.g., `{'src': 'image.jpg'}`)
|
|
761
|
+
attributes = None
|
|
762
|
+
|
|
763
|
+
# Property: children
|
|
764
|
+
# (List of Elements) The children
|
|
765
|
+
children = None
|
|
766
|
+
|
|
767
|
+
# Property: opening_tag
|
|
768
|
+
# (String or None) The opening tag. Optional; will use `name` and
|
|
769
|
+
# `attributes` if this is not given.
|
|
770
|
+
opening_tag = None
|
|
771
|
+
|
|
772
|
+
# Property: closing_tag
|
|
773
|
+
# (String or None) The closing tag
|
|
774
|
+
closing_tag = None
|
|
775
|
+
|
|
776
|
+
text = ''
|
|
777
|
+
depth = -1
|
|
778
|
+
expand = False
|
|
779
|
+
populate = False
|
|
780
|
+
parser = None
|
|
781
|
+
|
|
782
|
+
# Property: prefix
|
|
783
|
+
# Only the root note can have this.
|
|
784
|
+
prefix = None
|
|
785
|
+
suffix = None
|
|
786
|
+
|
|
787
|
+
# ===============================================================================
|
|
788
|
+
|
|
789
|
+
class Token:
|
|
790
|
+
def __init__(self, str, parser=None):
|
|
791
|
+
"""Token.
|
|
792
|
+
|
|
793
|
+
Description:
|
|
794
|
+
str - The string to parse
|
|
795
|
+
|
|
796
|
+
In the string `div > ul`, there are 3 tokens. (`div`, `>`, and `ul`)
|
|
797
|
+
|
|
798
|
+
For `>`, it will be a `Token` with `type` set to `Token.CHILD`
|
|
799
|
+
"""
|
|
800
|
+
|
|
801
|
+
self.str = str.strip()
|
|
802
|
+
self.attributes = {}
|
|
803
|
+
self.parser = parser
|
|
804
|
+
|
|
805
|
+
# Set the type.
|
|
806
|
+
if self.str == '<':
|
|
807
|
+
self.type = Token.PARENT
|
|
808
|
+
elif self.str == '>':
|
|
809
|
+
self.type = Token.CHILD
|
|
810
|
+
elif self.str == '+':
|
|
811
|
+
self.type = Token.SIBLING
|
|
812
|
+
else:
|
|
813
|
+
self.type = Token.ELEMENT
|
|
814
|
+
self._init_element()
|
|
815
|
+
|
|
816
|
+
def _init_element(self):
|
|
817
|
+
"""Initializes. Only called if the token is an element token.
|
|
818
|
+
[Private]
|
|
819
|
+
"""
|
|
820
|
+
|
|
821
|
+
# Get the tag name. Default to DIV if none given.
|
|
822
|
+
name = re.findall('^([\w\-:]*)', self.str)[0]
|
|
823
|
+
name = name.lower().replace('-', ':')
|
|
824
|
+
|
|
825
|
+
# Find synonyms through this thesaurus
|
|
826
|
+
synonyms = self.parser.dialect.synonyms
|
|
827
|
+
if name in synonyms.keys():
|
|
828
|
+
name = synonyms[name]
|
|
829
|
+
|
|
830
|
+
if ':' in name:
|
|
831
|
+
try: spaces_count = int(self.parser.options.get('indent-spaces'))
|
|
832
|
+
except: spaces_count = 4
|
|
833
|
+
indent = ' ' * spaces_count
|
|
834
|
+
|
|
835
|
+
shortcuts = self.parser.dialect.shortcuts
|
|
836
|
+
if name in shortcuts.keys():
|
|
837
|
+
for key, value in shortcuts[name].iteritems():
|
|
838
|
+
setattr(self, key, value)
|
|
839
|
+
if 'html' in name:
|
|
840
|
+
return
|
|
841
|
+
else:
|
|
842
|
+
self.name = name
|
|
843
|
+
|
|
844
|
+
elif (name == ''): self.name = 'div'
|
|
845
|
+
else: self.name = name
|
|
846
|
+
|
|
847
|
+
# Look for attributes
|
|
848
|
+
attribs = []
|
|
849
|
+
for attrib in re.findall('\[([^\]]*)\]', self.str):
|
|
850
|
+
attribs.append(attrib)
|
|
851
|
+
self.str = self.str.replace("[" + attrib + "]", "")
|
|
852
|
+
if len(attribs) > 0:
|
|
853
|
+
for attrib in attribs:
|
|
854
|
+
try: key, value = attrib.split('=', 1)
|
|
855
|
+
except: key, value = attrib, ''
|
|
856
|
+
self.attributes[key] = value
|
|
857
|
+
|
|
858
|
+
# Try looking for text
|
|
859
|
+
text = None
|
|
860
|
+
for text in re.findall('\{([^\}]*)\}', self.str):
|
|
861
|
+
self.str = self.str.replace("{" + text + "}", "")
|
|
862
|
+
if text is not None:
|
|
863
|
+
self.text = text
|
|
864
|
+
|
|
865
|
+
# Get the class names
|
|
866
|
+
classes = []
|
|
867
|
+
for classname in re.findall('\.([\$a-zA-Z0-9_\-\&]+)', self.str):
|
|
868
|
+
classes.append(classname)
|
|
869
|
+
if len(classes) > 0:
|
|
870
|
+
try: self.attributes['class']
|
|
871
|
+
except: self.attributes['class'] = ''
|
|
872
|
+
self.attributes['class'] += ' ' + ' '.join(classes)
|
|
873
|
+
self.attributes['class'] = self.attributes['class'].strip()
|
|
874
|
+
|
|
875
|
+
# Get the ID
|
|
876
|
+
id = None
|
|
877
|
+
for id in re.findall('#([\$a-zA-Z0-9_\-\&]+)', self.str): pass
|
|
878
|
+
if id is not None:
|
|
879
|
+
self.attributes['id'] = id
|
|
880
|
+
|
|
881
|
+
# See if there's a multiplier (e.g., "li*3")
|
|
882
|
+
multiplier = None
|
|
883
|
+
for multiplier in re.findall('\*\s*([0-9]+)', self.str): pass
|
|
884
|
+
if multiplier is not None:
|
|
885
|
+
self.multiplier = int(multiplier)
|
|
886
|
+
|
|
887
|
+
# Populate flag (e.g., ul+)
|
|
888
|
+
flags = None
|
|
889
|
+
for flags in re.findall('[\+\!]+$', self.str): pass
|
|
890
|
+
if flags is not None:
|
|
891
|
+
if '+' in flags: self.populate = True
|
|
892
|
+
if '!' in flags: self.expand = True
|
|
893
|
+
|
|
894
|
+
def __str__(self):
|
|
895
|
+
return self.str
|
|
896
|
+
|
|
897
|
+
str = ''
|
|
898
|
+
parser = None
|
|
899
|
+
|
|
900
|
+
# For elements
|
|
901
|
+
# See the properties of `Element` for description on these.
|
|
902
|
+
name = ''
|
|
903
|
+
attributes = None
|
|
904
|
+
multiplier = 1
|
|
905
|
+
expand = False
|
|
906
|
+
populate = False
|
|
907
|
+
text = ''
|
|
908
|
+
opening_tag = None
|
|
909
|
+
closing_tag = None
|
|
910
|
+
|
|
911
|
+
# Type
|
|
912
|
+
type = 0
|
|
913
|
+
ELEMENT = 2
|
|
914
|
+
CHILD = 4
|
|
915
|
+
PARENT = 8
|
|
916
|
+
SIBLING = 16
|
|
917
|
+
|
|
918
|
+
# ===============================================================================
|
|
919
|
+
|
|
920
|
+
class Router:
|
|
921
|
+
"""The router.
|
|
922
|
+
"""
|
|
923
|
+
|
|
924
|
+
# Constructor
|
|
925
|
+
# ---------------------------------------------------------------------------
|
|
926
|
+
|
|
927
|
+
def __init__(self):
|
|
928
|
+
pass
|
|
929
|
+
|
|
930
|
+
# Methods
|
|
931
|
+
# ---------------------------------------------------------------------------
|
|
932
|
+
|
|
933
|
+
def start(self, options=None, str=None, ret=None):
|
|
934
|
+
if (options):
|
|
935
|
+
self.options = Options(router=self, options=options, argv=None)
|
|
936
|
+
else:
|
|
937
|
+
self.options = Options(router=self, argv=sys.argv[1:], options=None)
|
|
938
|
+
|
|
939
|
+
if (self.options.has('help')):
|
|
940
|
+
return self.help()
|
|
941
|
+
|
|
942
|
+
elif (self.options.has('version')):
|
|
943
|
+
return self.version()
|
|
944
|
+
|
|
945
|
+
else:
|
|
946
|
+
return self.parse(str=str, ret=ret)
|
|
947
|
+
|
|
948
|
+
def help(self):
|
|
949
|
+
print "Usage: %s [OPTIONS]" % sys.argv[0]
|
|
950
|
+
print "Expands input into HTML."
|
|
951
|
+
print ""
|
|
952
|
+
for short, long, info in self.options.cmdline_keys:
|
|
953
|
+
if "Deprecated" in info: continue
|
|
954
|
+
if not short == '': short = '-%s,' % short
|
|
955
|
+
if not long == '': long = '--%s' % long.replace("=", "=XXX")
|
|
956
|
+
|
|
957
|
+
print "%6s %-25s %s" % (short, long, info)
|
|
958
|
+
print ""
|
|
959
|
+
print "\n".join(self.help_content)
|
|
960
|
+
|
|
961
|
+
def version(self):
|
|
962
|
+
print "Uhm, yeah."
|
|
963
|
+
|
|
964
|
+
def parse(self, str=None, ret=None):
|
|
965
|
+
self.parser = Parser(self.options)
|
|
966
|
+
|
|
967
|
+
try:
|
|
968
|
+
# Read the files
|
|
969
|
+
# for line in fileinput.input(): lines.append(line.rstrip(os.linesep))
|
|
970
|
+
if str is not None:
|
|
971
|
+
lines = str
|
|
972
|
+
else:
|
|
973
|
+
lines = [sys.stdin.read()]
|
|
974
|
+
lines = " ".join(lines)
|
|
975
|
+
|
|
976
|
+
except KeyboardInterrupt:
|
|
977
|
+
pass
|
|
978
|
+
|
|
979
|
+
except:
|
|
980
|
+
sys.stderr.write("Reading failed.\n")
|
|
981
|
+
return
|
|
982
|
+
|
|
983
|
+
try:
|
|
984
|
+
self.parser.load_string(lines)
|
|
985
|
+
output = self.parser.render()
|
|
986
|
+
if ret: return output
|
|
987
|
+
sys.stdout.write(output)
|
|
988
|
+
|
|
989
|
+
except:
|
|
990
|
+
sys.stderr.write("Parse error. Check your input.\n")
|
|
991
|
+
print sys.exc_info()[0]
|
|
992
|
+
print sys.exc_info()[1]
|
|
993
|
+
|
|
994
|
+
def exit(self):
|
|
995
|
+
sys.exit()
|
|
996
|
+
|
|
997
|
+
help_content = [
|
|
998
|
+
"Please refer to the manual for more information.",
|
|
999
|
+
]
|
|
1000
|
+
|
|
1001
|
+
# ===============================================================================
|
|
1002
|
+
|
|
1003
|
+
class Options:
|
|
1004
|
+
def __init__(self, router, argv, options=None):
|
|
1005
|
+
# Init self
|
|
1006
|
+
self.router = router
|
|
1007
|
+
|
|
1008
|
+
# `options` can be given as a dict of stuff to preload
|
|
1009
|
+
if options:
|
|
1010
|
+
for k, v in options.iteritems():
|
|
1011
|
+
self.options[k] = v
|
|
1012
|
+
return
|
|
1013
|
+
|
|
1014
|
+
# Prepare for getopt()
|
|
1015
|
+
short_keys, long_keys = "", []
|
|
1016
|
+
for short, long, info in self.cmdline_keys: # 'v', 'version'
|
|
1017
|
+
short_keys += short
|
|
1018
|
+
long_keys.append(long)
|
|
1019
|
+
|
|
1020
|
+
try:
|
|
1021
|
+
getoptions, arguments = getopt.getopt(argv, short_keys, long_keys)
|
|
1022
|
+
|
|
1023
|
+
except getopt.GetoptError:
|
|
1024
|
+
err = sys.exc_info()[1]
|
|
1025
|
+
sys.stderr.write("Options error: %s\n" % err)
|
|
1026
|
+
sys.stderr.write("Try --help for a list of arguments.\n")
|
|
1027
|
+
return router.exit()
|
|
1028
|
+
|
|
1029
|
+
# Sort them out into options
|
|
1030
|
+
options = {}
|
|
1031
|
+
i = 0
|
|
1032
|
+
for option in getoptions:
|
|
1033
|
+
key, value = option # '--version', ''
|
|
1034
|
+
if (value == ''): value = True
|
|
1035
|
+
|
|
1036
|
+
# If the key is long, write it
|
|
1037
|
+
if key[0:2] == '--':
|
|
1038
|
+
clean_key = key[2:]
|
|
1039
|
+
options[clean_key] = value
|
|
1040
|
+
|
|
1041
|
+
# If the key is short, look for the long version of it
|
|
1042
|
+
elif key[0:1] == '-':
|
|
1043
|
+
for short, long, info in self.cmdline_keys:
|
|
1044
|
+
if short == key[1:]:
|
|
1045
|
+
print long
|
|
1046
|
+
options[long] = True
|
|
1047
|
+
|
|
1048
|
+
# Done
|
|
1049
|
+
for k, v in options.iteritems():
|
|
1050
|
+
self.options[k] = v
|
|
1051
|
+
|
|
1052
|
+
def __getattr__(self, attr):
|
|
1053
|
+
return self.get(attr)
|
|
1054
|
+
|
|
1055
|
+
def get(self, attr):
|
|
1056
|
+
try: return self.options[attr]
|
|
1057
|
+
except: return None
|
|
1058
|
+
|
|
1059
|
+
def has(self, attr):
|
|
1060
|
+
try: return self.options.has_key(attr)
|
|
1061
|
+
except: return False
|
|
1062
|
+
|
|
1063
|
+
options = {
|
|
1064
|
+
'indent-spaces': 4
|
|
1065
|
+
}
|
|
1066
|
+
cmdline_keys = [
|
|
1067
|
+
('h', 'help', 'Shows help'),
|
|
1068
|
+
('v', 'version', 'Shows the version'),
|
|
1069
|
+
('', 'no-guides', 'Deprecated'),
|
|
1070
|
+
('', 'post-tag-guides', 'Adds comments at the end of DIV tags'),
|
|
1071
|
+
('', 'textmate', 'Adds snippet info (textmate mode)'),
|
|
1072
|
+
('', 'indent-spaces=', 'Indent spaces'),
|
|
1073
|
+
('', 'expand-divs', 'Automatically expand divs'),
|
|
1074
|
+
('', 'no-last-newline', 'Skip the trailing newline'),
|
|
1075
|
+
('', 'start-guide-format=', 'To be documented'),
|
|
1076
|
+
('', 'end-guide-format=', 'To be documented'),
|
|
1077
|
+
]
|
|
1078
|
+
|
|
1079
|
+
# Property: router
|
|
1080
|
+
# Router
|
|
1081
|
+
router = 1
|
|
1082
|
+
|
|
1083
|
+
# ===============================================================================
|
|
1084
|
+
|
|
1085
|
+
if __name__ == "__main__":
|
|
1086
|
+
z = Router()
|
|
1087
|
+
z.start()
|