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
|
Binary file
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
SPARKUP_PY=sparkup
|
|
2
|
+
VERSION=`date '+%Y%m%d'`
|
|
3
|
+
README=README.md
|
|
4
|
+
|
|
5
|
+
.PHONY: all textmate vim textmate-dist vim-dist plugins plugins-pre generic all-dist
|
|
6
|
+
all: plugins
|
|
7
|
+
|
|
8
|
+
plugins-pre:
|
|
9
|
+
mkdir -p distribution
|
|
10
|
+
|
|
11
|
+
plugins: plugins-pre all-dist
|
|
12
|
+
|
|
13
|
+
textmate-dist: textmate
|
|
14
|
+
cd TextMate && zip -9r ../distribution/sparkup-textmate-${VERSION}.zip . && cd ..
|
|
15
|
+
|
|
16
|
+
vim-dist: vim
|
|
17
|
+
cd vim && zip -9r ../distribution/sparkup-vim-${VERSION}.zip . && cd ..
|
|
18
|
+
|
|
19
|
+
generic-dist: generic
|
|
20
|
+
cd generic && zip -9r ../distribution/sparkup-generic-${VERSION}.zip . && cd ..
|
|
21
|
+
|
|
22
|
+
all-dist:
|
|
23
|
+
zip -9r distribution/sparkup-${VERSION}.zip generic vim textmate README.md -x */sparkup-readme.txt
|
|
24
|
+
cp distribution/sparkup-${VERSION}.zip distribution/sparkup-latest.zip
|
|
25
|
+
|
|
26
|
+
generic:
|
|
27
|
+
cat sparkup.py > generic/sparkup
|
|
28
|
+
chmod +x generic/sparkup
|
|
29
|
+
#cp ${README} generic/sparkup-readme.txt
|
|
30
|
+
|
|
31
|
+
textmate:
|
|
32
|
+
mkdir -p TextMate/Sparkup.tmbundle/Support
|
|
33
|
+
cp ${SPARKUP_PY} TextMate/Sparkup.tmbundle/Support/sparkup.py
|
|
34
|
+
#cp ${README} TextMate/sparkup-readme.txt
|
|
35
|
+
|
|
36
|
+
vim:
|
|
37
|
+
mkdir -p vim/ftplugin/html vim/doc
|
|
38
|
+
cp ${SPARKUP_PY} vim/ftplugin/html/sparkup.py
|
|
39
|
+
# Add asteriks to title, so it gets matched by `:helptags`
|
|
40
|
+
sed '1s/.*/*\0*/' ${README} > vim/doc/sparkup.txt
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Sparkup
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
**Sparkup lets you write HTML code faster.** Don't believe us?
|
|
5
|
+
[See it in action!](http://www.youtube.com/watch?v=Jw3jipcenKc)
|
|
6
|
+
|
|
7
|
+
You can write HTML in a CSS-like syntax, and have Sparkup handle the expansion to full HTML
|
|
8
|
+
code. It is meant to help you write long HTML blocks in your text editor by letting you
|
|
9
|
+
type less characters than needed.
|
|
10
|
+
|
|
11
|
+
Sparkup is written in Python, and requires Python 2.5 or newer (2.5 is preinstalled in
|
|
12
|
+
Mac OS X Leopard). Sparkup also offers intregration into common text editors. Support for VIM
|
|
13
|
+
and TextMate are currently included.
|
|
14
|
+
|
|
15
|
+
A short screencast is available here:
|
|
16
|
+
[http://www.youtube.com/watch?v=Jw3jipcenKc](http://www.youtube.com/watch?v=Jw3jipcenKc)
|
|
17
|
+
|
|
18
|
+
Usage and installation
|
|
19
|
+
----------------------
|
|
20
|
+
You may download Sparkup from Github. [Download the latest version here](http://github.com/rstacruz/sparkup/downloads).
|
|
21
|
+
|
|
22
|
+
- **TextMate**: Simply double-click on the `Sparkup.tmbundle` package in Finder. This
|
|
23
|
+
will install it automatically. In TextMate, open an HTML file (orset the document type to
|
|
24
|
+
HTML) type in something (e.g., `#header > h1`), then press `Ctrl` + `E`. Pressing `Tab`
|
|
25
|
+
will cycle through empty elements.
|
|
26
|
+
|
|
27
|
+
- **VIM**: See the `vim/README.txt` file for details.
|
|
28
|
+
|
|
29
|
+
- **Others/command line use**: You may put `sparkup` in your `$PATH` somewhere. You may then
|
|
30
|
+
invoke it by typing `echo "(input here)" | sparkup`, or `sparkup --help` for a list of commands.
|
|
31
|
+
|
|
32
|
+
Credits
|
|
33
|
+
-------
|
|
34
|
+
|
|
35
|
+
Sparkup is written by Rico Sta. Cruz and is released under the MIT license.
|
|
36
|
+
|
|
37
|
+
This project is inspired by [Zen Coding](http://code.google.com/p/zen-coding/) of
|
|
38
|
+
[Vadim Makeev](http://pepelsbey.net). The Zen HTML syntax is forward-compatible with Sparkup
|
|
39
|
+
(anything that Zen HTML can parse, Sparkup can too).
|
|
40
|
+
|
|
41
|
+
The following people have contributed code to the project:
|
|
42
|
+
|
|
43
|
+
- Guillermo O. Freschi (Tordek @ github)
|
|
44
|
+
Bugfixes to the parsing system
|
|
45
|
+
|
|
46
|
+
- Eric Van Dewoestine (ervandew @ github)
|
|
47
|
+
Improvements to the VIM plugin
|
|
48
|
+
|
|
49
|
+
Examples
|
|
50
|
+
--------
|
|
51
|
+
|
|
52
|
+
**`div`** expands to:
|
|
53
|
+
<div></div>
|
|
54
|
+
|
|
55
|
+
**`div#header`** expands to:
|
|
56
|
+
<div id="header"></div>
|
|
57
|
+
|
|
58
|
+
**`div.align-left#header`** expands to:
|
|
59
|
+
<div id="header" class="align-left"></div>
|
|
60
|
+
|
|
61
|
+
**`div#header + div#footer`** expands to:
|
|
62
|
+
<div id="header"></div>
|
|
63
|
+
<div id="footer"></div>
|
|
64
|
+
|
|
65
|
+
**`#menu > ul`** expands to:
|
|
66
|
+
<div id="menu">
|
|
67
|
+
<ul></ul>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
**`#menu > h3 + ul`** expands to:
|
|
71
|
+
<div id="menu">
|
|
72
|
+
<h3></h3>
|
|
73
|
+
<ul></ul>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
**`#header > h1{Welcome to our site}`** expands to:
|
|
77
|
+
<div id="header">
|
|
78
|
+
<h1>Welcome to our site</h1>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
**`a[href=index.html]{Home}`** expands to:
|
|
82
|
+
<a href="index.html">Home</a>
|
|
83
|
+
|
|
84
|
+
**`ul > li*3`** expands to:
|
|
85
|
+
<ul>
|
|
86
|
+
<li></li>
|
|
87
|
+
<li></li>
|
|
88
|
+
<li></li>
|
|
89
|
+
</ul>
|
|
90
|
+
|
|
91
|
+
**`ul > li.item-$*3`** expands to:
|
|
92
|
+
<ul>
|
|
93
|
+
<li class="item-1"></li>
|
|
94
|
+
<li class="item-2"></li>
|
|
95
|
+
<li class="item-3"></li>
|
|
96
|
+
</ul>
|
|
97
|
+
|
|
98
|
+
**`ul > li.item-$*3 > strong`** expands to:
|
|
99
|
+
<ul>
|
|
100
|
+
<li class="item-1"><strong></strong></li>
|
|
101
|
+
<li class="item-2"><strong></strong></li>
|
|
102
|
+
<li class="item-3"><strong></strong></li>
|
|
103
|
+
</ul>
|
|
104
|
+
|
|
105
|
+
**`table > tr*2 > td.name + td*3`** expands to:
|
|
106
|
+
<table>
|
|
107
|
+
<tr>
|
|
108
|
+
<td class="name"></td>
|
|
109
|
+
<td></td>
|
|
110
|
+
<td></td>
|
|
111
|
+
<td></td>
|
|
112
|
+
</tr>
|
|
113
|
+
<tr>
|
|
114
|
+
<td class="name"></td>
|
|
115
|
+
<td></td>
|
|
116
|
+
<td></td>
|
|
117
|
+
<td></td>
|
|
118
|
+
</tr>
|
|
119
|
+
</table>
|
|
120
|
+
|
|
121
|
+
**`#header > ul > li < p{Footer}`** expands to:
|
|
122
|
+
<!-- The < symbol goes back up the parent; i.e., the opposite of >. -->
|
|
123
|
+
<div id="header">
|
|
124
|
+
<ul>
|
|
125
|
+
<li></li>
|
|
126
|
+
</ul>
|
|
127
|
+
<p>Footer</p>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>beforeRunningCommand</key>
|
|
6
|
+
<string>nop</string>
|
|
7
|
+
<key>command</key>
|
|
8
|
+
<string>#!/usr/bin/env python
|
|
9
|
+
import sys; import os; sys.path.append(os.getenv('TM_BUNDLE_SUPPORT')); import sparkup
|
|
10
|
+
|
|
11
|
+
# You may change these options to your liking.
|
|
12
|
+
# Those starting with # are comments (disabled).
|
|
13
|
+
options = {
|
|
14
|
+
'textmate': True,
|
|
15
|
+
'no-last-newline': True,
|
|
16
|
+
#'start-guide-format': 'Begin %s',
|
|
17
|
+
#'end-guide-format': 'End %s',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
sparkup.Router().start(options=options)</string>
|
|
21
|
+
<key>fallbackInput</key>
|
|
22
|
+
<string>line</string>
|
|
23
|
+
<key>input</key>
|
|
24
|
+
<string>selection</string>
|
|
25
|
+
<key>keyEquivalent</key>
|
|
26
|
+
<string>^e</string>
|
|
27
|
+
<key>name</key>
|
|
28
|
+
<string>Sparkup expand</string>
|
|
29
|
+
<key>output</key>
|
|
30
|
+
<string>insertAsSnippet</string>
|
|
31
|
+
<key>uuid</key>
|
|
32
|
+
<string>73A48D2B-D843-42A1-A288-0D1A6380043B</string>
|
|
33
|
+
</dict>
|
|
34
|
+
</plist>
|
|
@@ -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()
|