#!/usr/bin/env python#-*- coding: utf-8 -*-version="0.1.3"importosimportfileinputimportgetoptimportsysimportreimportwin32clipboard, win32con#===============================================================================classDialect:
shortcuts={}
synonyms={}
required={}
short_tags=()classHtmlDialect(Dialect):
shortcuts={'cc:ie': {'opening_tag':''},'cc:ie6': {'opening_tag':''},'cc:ie7': {'opening_tag':''},'cc:noie': {'opening_tag':''},'html:4t': {'expand': True,'opening_tag':'\n'+'\n'+'
\n'+''+'\n'+''+'\n'+'\n'+'','closing_tag':'\n'+''},'html:4s': {'expand': True,'opening_tag':'\n'+'\n'+'\n'+''+'\n'+''+'\n'+'\n'+'','closing_tag':'\n'+''},'html:xt': {'expand': True,'opening_tag':'\n'+'\n'+'\n'+''+'\n'+''+'\n'+'\n'+'','closing_tag':'\n'+''},'html:xs': {'expand': True,'opening_tag':'\n'+'\n'+'\n'+''+'\n'+''+'\n'+'\n'+'','closing_tag':'\n'+''},'html:xxs': {'expand': True,'opening_tag':'\n'+'\n'+'\n'+''+'\n'+''+'\n'+'\n'+'','closing_tag':'\n'+''},'html:5': {'expand': True,'opening_tag':'\n'+'\n'+'\n'+''+'\n'+''+'\n'+'\n'+'','closing_tag':'\n'+''},'input:button': {'name':'input','attributes': {'class':'button','type':'button','name':'','value':''}},'input:password': {'name':'input','attributes': {'class':'text password','type':'password','name':'','value':''}
},'input:radio': {'name':'input','attributes': {'class':'radio','type':'radio','name':'','value':''}
},'input:checkbox': {'name':'input','attributes': {'class':'checkbox','type':'checkbox','name':'','value':''}
},'input:file': {'name':'input','attributes': {'class':'file','type':'file','name':'','value':''}
},'input:text': {'name':'input','attributes': {'class':'text','type':'text','name':'','value':''}
},'input:submit': {'name':'input','attributes': {'class':'submit','type':'submit','value':''}
},'input:hidden': {'name':'input','attributes': {'type':'hidden','name':'','value':''}
},'script:src': {'name':'script','attributes': {'src':''}
},'script:jquery': {'name':'script','attributes': {'src':'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'}
},'script:jsapi': {'name':'script','attributes': {'src':'http://www.google.com/jsapi'}
},'script:jsapix': {'name':'script','text':'\n google.load("jquery", "1.3.2");\n google.setOnLoadCallback(function() {\n \n });\n'},'link:css': {'name':'link','attributes': {'rel':'stylesheet','type':'text/css','href':'','media':'all'},
},'link:print': {'name':'link','attributes': {'rel':'stylesheet','type':'text/css','href':'','media':'print'},
},'link:favicon': {'name':'link','attributes': {'rel':'shortcut icon','type':'image/x-icon','href':''},
},'link:touch': {'name':'link','attributes': {'rel':'apple-touch-icon','href':''},
},'link:rss': {'name':'link','attributes': {'rel':'alternate','type':'application/rss+xml','title':'RSS','href':''},
},'link:atom': {'name':'link','attributes': {'rel':'alternate','type':'application/atom+xml','title':'Atom','href':''},
},'meta:ie7': {'name':'meta','attributes': {'http-equiv':'X-UA-Compatible','content':'IE=7'},
},'meta:ie8': {'name':'meta','attributes': {'http-equiv':'X-UA-Compatible','content':'IE=8'},
},'form:get': {'name':'form','attributes': {'method':'get'},
},'form:g': {'name':'form','attributes': {'method':'get'},
},'form:post': {'name':'form','attributes': {'method':'post'},
},'form:p': {'name':'form','attributes': {'method':'post'},
},
}
synonyms={'checkbox':'input:checkbox','check':'input:checkbox','input:c':'input:checkbox','button':'input:button','input:b':'input:button','input:h':'input:hidden','hidden':'input:hidden','submit':'input:submit','input:s':'input:submit','radio':'input:radio','input:r':'input:radio','text':'input:text','passwd':'input:password','password':'input:password','pw':'input:password','input:t':'input:text','linkcss':'link:css','scriptsrc':'script:src','jquery':'script:jquery','jsapi':'script:jsapi','html5':'html:5','html4':'html:4s','html4s':'html:4s','html4t':'html:4t','xhtml':'html:xxs','xhtmlt':'html:xt','xhtmls':'html:xs','xhtml11':'html:xxs','opt':'option','st':'strong','css':'style','csss':'link:css','css:src':'link:css','csssrc':'link:css','js':'script','jss':'script:src','js:src':'script:src','jssrc':'script:src',
}
short_tags=('area','base','basefont','br','embed','hr', \'input','img','link','param','meta')
required={'a': {'href':''},'base': {'href':''},'abbr': {'title':''},'acronym':{'title':''},'bdo': {'dir':''},'link': {'rel':'stylesheet','href':''},'style': {'type':'text/css'},'script': {'type':'text/javascript'},'img': {'src':'','alt':''},'iframe': {'src':'','frameborder':'0'},'embed': {'src':'','type':''},'object': {'data':'','type':''},'param': {'name':'','value':''},'form': {'action':'','method':'post'},'table': {'cellspacing':'0'},'input': {'type':'','name':'','value':''},'base': {'href':''},'area': {'shape':'','coords':'','href':'','alt':''},'select': {'name':''},'option': {'value':''},'textarea':{'name':''},'meta': {'content':''},
}classParser:"""The parser."""#Constructor#---------------------------------------------------------------------------def__init__(self, options=None, str='', dialect=HtmlDialect()):"""Constructor."""self.tokens=[]
self.str=str
self.options=options
self.dialect=dialect
self.root=Element(parser=self)
self.caret=[]
self.caret.append(self.root)
self._last=[]#Methods#---------------------------------------------------------------------------defload_string(self, str):"""Loads a string to parse."""self.str=str
self._tokenize()
self._parse()defrender(self):"""Renders.
Called by [[Router]]."""#Get the initial render of the root nodeoutput=self.root.render()#Indent by whatever the input is indented withindent=re.findall("^[\r\n]*(\s*)", self.str)[0]
output=indent+output.replace("\n","\n"+indent)#Strip newline if not neededifself.options.has("no-last-newline") \orself.prefixorself.suffix:
output=re.sub(r'\n\s*$','', output)#TextMate modeifself.options.has("textmate"):
output=self._textmatify(output)returnoutput#Protected methods#---------------------------------------------------------------------------def_textmatify(self, output):"""Returns a version of the output with TextMate placeholders in it."""matches=re.findall(r'(>)|("")|(\n\s+)\n|(.|\s)', output)
output=''n=1foriinmatches:ifi[0]:
output+='>$%i'%n
n+=1elifi[1]:
output+='"$%i"'%n
n+=1elifi[2]:
output+=i[2]+'$%i\n'%n
n+=1elifi[3]:
output+=i[3]
output+="$0"returnoutputdef_tokenize(self):"""Tokenizes.
Initializes [[self.tokens]]."""str=self.str.strip()#Find prefix/suffixwhileTrue:
match=re.match(r"^(\s*]+>\s*)", str)ifmatchisNone:breakifself.prefixisNone: self.prefix=''self.prefix+=match.group(0)
str=str[len(match.group(0)):]whileTrue:
match=re.findall(r"(\s*]+>[\s\n\r]*)$", str)ifnotmatch:breakifself.suffixisNone: self.suffix=''self.suffix=match[0]+self.suffix
str=str[:-len(match[0])]#Split by the element separatorsfortokeninre.split('(|\+(?!\\s*\+|$))', str):iftoken.strip()!='':
self.tokens.append(Token(token, parser=self))def_parse(self):"""Takes the tokens and does its thing.
Populates [[self.root]]."""#Carry it over to the root node.ifself.prefixorself.suffix:
self.root.prefix=self.prefix
self.root.suffix=self.suffix
self.root.depth+=1fortokeninself.tokens:iftoken.type==Token.ELEMENT:#Reset the "last elements added" list. We will#repopulate this with the new elements added now.self._last[:]=[]#Create [[Element]]s from a [[Token]].#They will be created as many as the multiplier specifies,#multiplied by how many carets we havecount=0forcaretinself.caret:
local_count=0foriinrange(token.multiplier):
count+=1local_count+=1new=Element(token, caret,
count=count,
local_count=local_count,
parser=self)
self._last.append(new)
caret.append(new)#For >eliftoken.type==Token.CHILD:#The last children added.self.caret[:]=self._last#For
self.caret[:]=[parent]return#Properties#---------------------------------------------------------------------------#Property: dialect#The dialect of XMLdialect=None#Property: str#The stringstr=''#Property: tokens#The list of tokenstokens=[]#Property: options#Reference to the [[Options]] instanceoptions=None#Property: root#The root [[Element]] node.root=None#Property: caret#The current insertion point.caret=None#Property: _last#List of the last appended stuff_last=None#Property: indent#Yeahindent=''#Property: prefix#(String) The trailing tag in the beginning.##Description:#For instance, in `
parser=None, opening_tag=None, closing_tag=None, \
attributes=None, name=None, text=None):"""Constructor.
This is called by ???.
Description:
All parameters are optional.
token - (Token) The token (required)
parent - (Element) Parent element; `None` if root
count - (Int) The number to substitute for `&` (e.g., in `li.item-$`)
local_count - (Int) The number to substitute for `$` (e.g., in `li.item-&`)
parser - (Parser) The parser
attributes - ...
name - ...
text - ..."""self.children=[]
self.attributes={}
self.parser=parseriftokenisnotNone:#Assumption is that token is of type [[Token]] and is#a [[Token.ELEMENT]].self.name=token.name
self.attributes=token.attributes.copy()
self.text=token.text
self.populate=token.populate
self.expand=token.expand
self.opening_tag=token.opening_tag
self.closing_tag=token.closing_tag#`count` can be given. This will substitude & in classname and IDifcountisnotNone:forkeyinself.attributes:
attrib=self.attributes[key]
attrib=attrib.replace('&', ("%i"%count))iflocal_countisnotNone:
attrib=attrib.replace('$', ("%i"%local_count))
self.attributes[key]=attrib#Copy over from parametersifattributes: self.attributes=attribuesifname: self.name=nameiftext: self.text=text
self._fill_attributes()
self.parent=parentifparentisnotNone:
self.depth=parent.depth+1ifself.populate: self._populate()defrender(self):"""Renders the element, along with it's subelements, into HTML code.
[Grouped under "Rendering methods"]"""output=""try: spaces_count=int(self.parser.options.options['indent-spaces'])except: spaces_count=4spaces=''*spaces_count
indent=self.depth*spaces
prefix, suffix=('','')ifself.prefix: prefix=self.prefix+"\n"ifself.suffix: suffix=self.suffix#Make the guide from the ID (/#header), or the class if there's no ID (/.item)#This is for the start-guide, end-guide and post-tag-guidesguide_str=''if'id'inself.attributes:
guide_str+="#%s"%self.attributes['id']elif'class'inself.attributes:
guide_str+=".%s"%self.attributes['class'].replace('','.')#Build the post-tag guide (e.g.,
(('id'inself.attributes)or('class'inself.attributes))):if(self.parser.options.has('post-tag-guides')):
guide=""%guide_strif(self.parser.options.has('start-guide-format')):
format=self.parser.options.get('start-guide-format')try: start_guide=format%guide_strexcept: start_guide=(format+""+guide_str).strip()
start_guide="%s\n"%(indent, start_guide)if(self.parser.options.has('end-guide-format')):
format=self.parser.options.get('end-guide-format')try: end_guide=format%guide_strexcept: end_guide=(format+""+guide_str).strip()
end_guide="\n%s"%(indent, end_guide)#Short, self-closing tags (
)short_tags=self.parser.dialect.short_tags#When it should be expanded..#(That is,
output+=child.render()#For expand divs: if there are no children (that is, `output`#is still blank despite above), fill it with a blank line.if(output==''): output=indent+spaces+"\n"#If we're a root node and we have a prefix or suffix...#(Only the root node can have a prefix or suffix.)ifprefixorsuffix:
output="%s%s%s%s%s\n"%\
(indent, prefix, output, suffix, guide)#Uh..elifself.name!=''or\
self.opening_tagisnotNoneor\
self.closing_tagisnotNone:
output=start_guide+\
indent+self.get_opening_tag()+"\n"+\
output+\
indent+self.get_closing_tag()+\
guide+end_guide+"\n"#Short, self-closing tags (
)elifself.nameinshort_tags:
output="%s\n"%(indent, self.get_default_tag())#Tags with text, possiblyelifself.name!=''or\
self.opening_tagisnotNoneor\
self.closing_tagisnotNone:
output="%s%s%s%s%s%s%s%s"%\
(start_guide, indent, self.get_opening_tag(), \
self.text, \
self.get_closing_tag(), \
guide, end_guide,"\n")#Else, it's an empty-named element (like the root). Pass.else:passreturnoutputdefget_default_tag(self):"""Returns the opening tag (without brackets).
Usage:
element.get_default_tag()
[Grouped under "Rendering methods"]"""output='%s'%(self.name)forkey, valueinself.attributes.iteritems():
output+='%s="%s"'%(key, value)returnoutputdefget_opening_tag(self):ifself.opening_tagisNone:return""%self.get_default_tag()else:returnself.opening_tagdefget_closing_tag(self):ifself.closing_tagisNone:return"%s>"%self.nameelse:returnself.closing_tagdefappend(self, object):"""Registers an element as a child of this element.
Usage:
element.append(child)
Description:
Adds a given element `child` to the children list of this element. It
will be rendered when [[render()]] is called on the element.
See also:
- [[get_last_child()]]
[Grouped under "Traversion methods"]"""self.children.append(object)defget_last_child(self):"""Returns the last child element which was [[append()]]ed to this element.
Usage:
element.get_last_child()
Description:
This is the same as using `element.children[-1]`.
[Grouped under "Traversion methods"]"""returnself.children[-1]def_populate(self):"""Expands with default items.
This is called when the [[populate]] flag is turned on."""ifself.name=='ul':
elements=[Element(name='li', parent=self, parser=self.parser)]elifself.name=='dl':
elements=[
Element(name='dt', parent=self, parser=self.parser),
Element(name='dd', parent=self, parser=self.parser)]elifself.name=='table':
tr=Element(name='tr', parent=self, parser=self.parser)
td=Element(name='td', parent=tr, parser=self.parser)
tr.children.append(td)
elements=[tr]else:
elements=[]forelinelements:
self.children.append(el)def_fill_attributes(self):"""Fills default attributes for certain elements.
Description:
This is called by the constructor.
[Protected, grouped under "Protected methods"]"""#Make sure 's have a href, 's have an src, etc.required=self.parser.dialect.requiredforelement, attribsinrequired.iteritems():ifself.name==element:forattribinattribs:ifattribnotinself.attributes:
self.attributes[attrib]=attribs[attrib]#---------------------------------------------------------------------------#Property: last_child#[Read-only]last_child=property(get_last_child)#---------------------------------------------------------------------------#Property: parent#(Element) The parent element.parent=None#Property: name#(String) The name of the element (e.g., `div`)name=''#Property: attributes#(Dict) The dictionary of attributes (e.g., `{'src': 'image.jpg'}`)attributes=None#Property: children#(List of Elements) The childrenchildren=None#Property: opening_tag#(String or None) The opening tag. Optional; will use `name` and#`attributes` if this is not given.opening_tag=None#Property: closing_tag#(String or None) The closing tagclosing_tag=None
text=''depth=-1expand=False
populate=False
parser=None#Property: prefix#Only the root note can have this.prefix=None
suffix=None#===============================================================================classToken:def__init__(self, str, parser=None):"""Token.
Description:
str - The string to parse
In the string `div > ul`, there are 3 tokens. (`div`, `>`, and `ul`)
For `>`, it will be a `Token` with `type` set to `Token.CHILD`"""self.str=str.strip()
self.attributes={}
self.parser=parser#Set the type.ifself.str=='
self.type=Token.PARENTelifself.str=='>':
self.type=Token.CHILDelifself.str=='+':
self.type=Token.SIBLINGelse:
self.type=Token.ELEMENT
self._init_element()def_init_element(self):"""Initializes. Only called if the token is an element token.
[Private]"""#Get the tag name. Default to DIV if none given.name=re.findall('^([\w\-:]*)', self.str)[0]
name=name.lower().replace('-',':')#Find synonyms through this thesaurussynonyms=self.parser.dialect.synonymsifnameinsynonyms.keys():
name=synonyms[name]if':'inname:try: spaces_count=int(self.parser.options.get('indent-spaces'))except: spaces_count=4indent=''*spaces_count
shortcuts=self.parser.dialect.shortcutsifnameinshortcuts.keys():forkey, valueinshortcuts[name].iteritems():
setattr(self, key, value)if'html'inname:returnelse:
self.name=nameelif(name==''): self.name='div'else: self.name=name#Look for attributesattribs=[]forattribinre.findall('\[([^\]]*)\]', self.str):
attribs.append(attrib)
self.str=self.str.replace("["+attrib+"]","")iflen(attribs)>0:forattribinattribs:try: key, value=attrib.split('=',1)except: key, value=attrib,''self.attributes[key]=value#Try looking for texttext=Nonefortextinre.findall('\{([^\}]*)\}', self.str):
self.str=self.str.replace("{"+text+"}","")iftextisnotNone:
self.text=text#Get the class namesclasses=[]forclassnameinre.findall('\.([\$a-zA-Z0-9_\-\&]+)', self.str):
classes.append(classname)iflen(classes)>0:try: self.attributes['class']except: self.attributes['class']=''self.attributes['class']+=''+''.join(classes)
self.attributes['class']=self.attributes['class'].strip()#Get the IDid=Noneforidinre.findall('#([\$a-zA-Z0-9_\-\&]+)', self.str):passifidisnotNone:
self.attributes['id']=id#See if there's a multiplier (e.g., "li*3")multiplier=Noneformultiplierinre.findall('\*\s*([0-9]+)', self.str):passifmultiplierisnotNone:
self.multiplier=int(multiplier)#Populate flag (e.g., ul+)flags=Noneforflagsinre.findall('[\+\!]+$', self.str):passifflagsisnotNone:if'+'inflags: self.populate=Trueif'!'inflags: self.expand=Truedef__str__(self):returnself.str
str=''parser=None#For elements#See the properties of `Element` for description on these.name=''attributes=None
multiplier=1expand=False
populate=False
text=''opening_tag=None
closing_tag=None#Typetype=0
ELEMENT=2CHILD=4PARENT=8SIBLING=16#===============================================================================classRouter:"""The router."""#Constructor#---------------------------------------------------------------------------def__init__(self):pass#Methods#---------------------------------------------------------------------------defstart(self, options=None, str=None, ret=None):if(options):
self.options=Options(router=self, options=options, argv=None)else:
self.options=Options(router=self, argv=sys.argv[1:], options=None)if(self.options.has('help')):returnself.help()elif(self.options.has('version')):returnself.version()else:returnself.parse(str=str, ret=ret)defhelp(self):print"Usage: %s [OPTIONS]"%sys.argv[0]print"Expands input into HTML."print""forshort, long, infoinself.options.cmdline_keys:if"Deprecated"ininfo:continueifnotshort=='': short='-%s,'%shortifnotlong=='': long='--%s'%long.replace("=","=XXX")print"%6s %-25s %s"%(short, long, info)print""print"\n".join(self.help_content)defversion(self):print"Uhm, yeah."defparse(self, str=None, ret=None):
self.parser=Parser(self.options)try:#Read the files#for line in fileinput.input(): lines.append(line.rstrip(os.linesep))ifstrisnotNone:
lines=strelse:
lines=sys.stdin.readline()#lines = [sys.stdin.read()]#lines = " ".join(lines)exceptKeyboardInterrupt:passexcept:
sys.stderr.write("Reading failed.\n")returntry:
self.parser.load_string(lines)
output=self.parser.render()ifret:returnoutput
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText(output)
win32clipboard.CloseClipboard()#sys.stdout.write(output)except:
sys.stderr.write("Parse error. Check your input.\n")printsys.exc_info()[0]printsys.exc_info()[1]defexit(self):
sys.exit()
help_content=["Please refer to the manual for more information.",
]#===============================================================================classOptions:def__init__(self, router, argv, options=None):#Init selfself.router=router#`options` can be given as a dict of stuff to preloadifoptions:fork, vinoptions.iteritems():
self.options[k]=vreturn#Prepare for getopt()short_keys, long_keys="", []forshort, long, infoinself.cmdline_keys:#'v', 'version'short_keys+=short
long_keys.append(long)try:
getoptions, arguments=getopt.getopt(argv, short_keys, long_keys)exceptgetopt.GetoptError:
err=sys.exc_info()[1]
sys.stderr.write("Options error: %s\n"%err)
sys.stderr.write("Try --help for a list of arguments.\n")returnrouter.exit()#Sort them out into optionsoptions={}
i=0foroptioningetoptions:
key, value=option#'--version', ''if(value==''): value=True#If the key is long, write itifkey[0:2]=='--':
clean_key=key[2:]
options[clean_key]=value#If the key is short, look for the long version of itelifkey[0:1]=='-':forshort, long, infoinself.cmdline_keys:ifshort==key[1:]:printlong
options[long]=True#Donefork, vinoptions.iteritems():
self.options[k]=vdef__getattr__(self, attr):returnself.get(attr)defget(self, attr):try:returnself.options[attr]except:returnNonedefhas(self, attr):try:returnself.options.has_key(attr)except:returnFalse
options={'indent-spaces':4}
cmdline_keys=[
('h','help','Shows help'),
('v','version','Shows the version'),
('','no-guides','Deprecated'),
('','post-tag-guides','Adds comments at the end of DIV tags'),
('','textmate','Adds snippet info (textmate mode)'),
('','indent-spaces=','Indent spaces'),
('','expand-divs','Automatically expand divs'),
('','no-last-newline','Skip the trailing newline'),
('','start-guide-format=','To be documented'),
('','end-guide-format=','To be documented'),
]#Property: router#Routerrouter=1#===============================================================================if__name__=="__main__":iflen(sys.argv)>1:
s=sys.argv[1]else:
s=None
z=Router()
z.start(str=s)