Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
3288 lines (2925 sloc) 92.5 KB
/*
println=function(){
var arg = Array.prototype.slice.call(arguments).join(' ');
Thread.create(function()Main.stdout.writeLine(arg));
};
*/
console = { 'log' : println, 'debug' : println, };
//終了操作のためのタスクトレイアイコン
Main.createNotifyIcon();
//安定性向上のため推奨
Main.process.priority=Process.priority.high;
var status = {};
function exec_wrapWithAbbreviation () {
return status.zen_coding.wrapWithAbbreviation(status.selector, status.target_text, status.syntax, status.profile_name);
};
function exec_expandAbbreviation () {
return status.zen_coding.expandAbbreviation(status.selector, status.syntax, status.profile_name);
};
require('Hotstrokes').Hotstrokes.map({
'Shift+Space' : function(){
status = {
'syntax' : 'html',
'profile_name' : 'html',
'exec_zen' : function () {},
'target_text' : '',
'selector' : '',
};
this.cancel();
var self = this;
var Clipboard = require('Clipboard').Clipboard;
var backup_text = Clipboard.text;
Clipboard.text = '';
self.send('Ctrl+[c]');
Thread.create(function () {
sleep(100);
status.target_text = Clipboard.text;
Clipboard.text = backup_text;
status.exec_zen = status.target_text
? exec_wrapWithAbbreviation
: exec_expandAbbreviation
;
Thread.create(function () {
status.zen_coding = load_zen();
with(require('Window')){
Window.create({
title:'Zen Coding',
width : 500,
height : 50,
commands:{
'exec' : function () {
status.selector = this.editor.text;
this.close(true);
var zen = status.exec_zen().replace(/\{%::[-\w]+::%\}/g, '') + '\n';
var Clipboard = require('Clipboard').Clipboard;
try {
var clip = Clipboard.open();
var backup_text = clip.text;
clip.text = zen;
} catch (e) {
console.debug(e);
} finally {
clip.close();
};
self.send('Ctrl+[v]');
Thread.create(function () {
sleep(100);
Clipboard.text = backup_text;
});
},
'cancel' : function () (this.close(true)),
},
accelerators : {
'Enter' : 'exec',
'Tab' : 'exec',
'Esc' : 'cancel',
},
children:{
editor:{
type:Edit,
top:2,
left:2,
right:2,
bottom:2
},
},
}).show(true).activate(true).focus('editor');
};
});
});
},
}).register();
function load_zen () {
/**
* Zen Coding settings
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*/
var zen_settings = {
/**
* Variables that can be placed inside snippets or abbreviations as ${variable}
* ${child} variable is reserved, don't use it
*/
'variables': {
'lang': 'en',
'locale': 'en-US',
'charset': 'UTF-8',
'profile': 'xhtml',
/** Inner element indentation */
'indentation': '\t' // TODO take from Aptana settings
},
'css': {
'snippets': {
"@i": "@import url(|);",
"@m": "@media print {\n\t|\n}",
"@f": "@font-face {\n\tfont-family:|;\n\tsrc:url(|);\n}",
"!": "!important",
"pos": "position:|;",
"pos:s": "position:static;",
"pos:a": "position:absolute;",
"pos:r": "position:relative;",
"pos:f": "position:fixed;",
"t": "top:|;",
"t:a": "top:auto;",
"r": "right:|;",
"r:a": "right:auto;",
"b": "bottom:|;",
"b:a": "bottom:auto;",
"l": "left:|;",
"l:a": "left:auto;",
"z": "z-index:|;",
"z:a": "z-index:auto;",
"fl": "float:|;",
"fl:n": "float:none;",
"fl:l": "float:left;",
"fl:r": "float:right;",
"cl": "clear:|;",
"cl:n": "clear:none;",
"cl:l": "clear:left;",
"cl:r": "clear:right;",
"cl:b": "clear:both;",
"d": "display:|;",
"d:n": "display:none;",
"d:b": "display:block;",
"d:ib": "display:inline;",
"d:li": "display:list-item;",
"d:ri": "display:run-in;",
"d:cp": "display:compact;",
"d:tb": "display:table;",
"d:itb": "display:inline-table;",
"d:tbcp": "display:table-caption;",
"d:tbcl": "display:table-column;",
"d:tbclg": "display:table-column-group;",
"d:tbhg": "display:table-header-group;",
"d:tbfg": "display:table-footer-group;",
"d:tbr": "display:table-row;",
"d:tbrg": "display:table-row-group;",
"d:tbc": "display:table-cell;",
"d:rb": "display:ruby;",
"d:rbb": "display:ruby-base;",
"d:rbbg": "display:ruby-base-group;",
"d:rbt": "display:ruby-text;",
"d:rbtg": "display:ruby-text-group;",
"v": "visibility:|;",
"v:v": "visibility:visible;",
"v:h": "visibility:hidden;",
"v:c": "visibility:collapse;",
"ov": "overflow:|;",
"ov:v": "overflow:visible;",
"ov:h": "overflow:hidden;",
"ov:s": "overflow:scroll;",
"ov:a": "overflow:auto;",
"ovx": "overflow-x:|;",
"ovx:v": "overflow-x:visible;",
"ovx:h": "overflow-x:hidden;",
"ovx:s": "overflow-x:scroll;",
"ovx:a": "overflow-x:auto;",
"ovy": "overflow-y:|;",
"ovy:v": "overflow-y:visible;",
"ovy:h": "overflow-y:hidden;",
"ovy:s": "overflow-y:scroll;",
"ovy:a": "overflow-y:auto;",
"ovs": "overflow-style:|;",
"ovs:a": "overflow-style:auto;",
"ovs:s": "overflow-style:scrollbar;",
"ovs:p": "overflow-style:panner;",
"ovs:m": "overflow-style:move;",
"ovs:mq": "overflow-style:marquee;",
"zoo": "zoom:1;",
"cp": "clip:|;",
"cp:a": "clip:auto;",
"cp:r": "clip:rect(|);",
"bxz": "box-sizing:|;",
"bxz:cb": "box-sizing:content-box;",
"bxz:bb": "box-sizing:border-box;",
"bxsh": "box-shadow:|;",
"bxsh:n": "box-shadow:none;",
"bxsh:w": "-webkit-box-shadow:0 0 0 #000;",
"bxsh:m": "-moz-box-shadow:0 0 0 0 #000;",
"m": "margin:|;",
"m:a": "margin:auto;",
"m:0": "margin:0;",
"m:2": "margin:0 0;",
"m:3": "margin:0 0 0;",
"m:4": "margin:0 0 0 0;",
"mt": "margin-top:|;",
"mt:a": "margin-top:auto;",
"mr": "margin-right:|;",
"mr:a": "margin-right:auto;",
"mb": "margin-bottom:|;",
"mb:a": "margin-bottom:auto;",
"ml": "margin-left:|;",
"ml:a": "margin-left:auto;",
"p": "padding:|;",
"p:0": "padding:0;",
"p:2": "padding:0 0;",
"p:3": "padding:0 0 0;",
"p:4": "padding:0 0 0 0;",
"pt": "padding-top:|;",
"pr": "padding-right:|;",
"pb": "padding-bottom:|;",
"pl": "padding-left:|;",
"w": "width:|;",
"w:a": "width:auto;",
"h": "height:|;",
"h:a": "height:auto;",
"maw": "max-width:|;",
"maw:n": "max-width:none;",
"mah": "max-height:|;",
"mah:n": "max-height:none;",
"miw": "min-width:|;",
"mih": "min-height:|;",
"o": "outline:|;",
"o:n": "outline:none;",
"oo": "outline-offset:|;",
"ow": "outline-width:|;",
"os": "outline-style:|;",
"oc": "outline-color:#000;",
"oc:i": "outline-color:invert;",
"bd": "border:|;",
"bd+": "border:1px solid #000;",
"bd:n": "border:none;",
"bdbk": "border-break:|;",
"bdbk:c": "border-break:close;",
"bdcl": "border-collapse:|;",
"bdcl:c": "border-collapse:collapse;",
"bdcl:s": "border-collapse:separate;",
"bdc": "border-color:#000;",
"bdi": "border-image:url(|);",
"bdi:n": "border-image:none;",
"bdi:w": "-webkit-border-image:url(|) 0 0 0 0 stretch stretch;",
"bdi:m": "-moz-border-image:url(|) 0 0 0 0 stretch stretch;",
"bdti": "border-top-image:url(|);",
"bdti:n": "border-top-image:none;",
"bdri": "border-right-image:url(|);",
"bdri:n": "border-right-image:none;",
"bdbi": "border-bottom-image:url(|);",
"bdbi:n": "border-bottom-image:none;",
"bdli": "border-left-image:url(|);",
"bdli:n": "border-left-image:none;",
"bdci": "border-corner-image:url(|);",
"bdci:n": "border-corner-image:none;",
"bdci:c": "border-corner-image:continue;",
"bdtli": "border-top-left-image:url(|);",
"bdtli:n": "border-top-left-image:none;",
"bdtli:c": "border-top-left-image:continue;",
"bdtri": "border-top-right-image:url(|);",
"bdtri:n": "border-top-right-image:none;",
"bdtri:c": "border-top-right-image:continue;",
"bdbri": "border-bottom-right-image:url(|);",
"bdbri:n": "border-bottom-right-image:none;",
"bdbri:c": "border-bottom-right-image:continue;",
"bdbli": "border-bottom-left-image:url(|);",
"bdbli:n": "border-bottom-left-image:none;",
"bdbli:c": "border-bottom-left-image:continue;",
"bdf": "border-fit:|;",
"bdf:c": "border-fit:clip;",
"bdf:r": "border-fit:repeat;",
"bdf:sc": "border-fit:scale;",
"bdf:st": "border-fit:stretch;",
"bdf:ow": "border-fit:overwrite;",
"bdf:of": "border-fit:overflow;",
"bdf:sp": "border-fit:space;",
"bdl": "border-length:|;",
"bdl:a": "border-length:auto;",
"bdsp": "border-spacing:|;",
"bds": "border-style:|;",
"bds:n": "border-style:none;",
"bds:h": "border-style:hidden;",
"bds:dt": "border-style:dotted;",
"bds:ds": "border-style:dashed;",
"bds:s": "border-style:solid;",
"bds:db": "border-style:double;",
"bds:dtds": "border-style:dot-dash;",
"bds:dtdtds": "border-style:dot-dot-dash;",
"bds:w": "border-style:wave;",
"bds:g": "border-style:groove;",
"bds:r": "border-style:ridge;",
"bds:i": "border-style:inset;",
"bds:o": "border-style:outset;",
"bdw": "border-width:|;",
"bdt": "border-top:|;",
"bdt+": "border-top:1px solid #000;",
"bdt:n": "border-top:none;",
"bdtw": "border-top-width:|;",
"bdts": "border-top-style:|;",
"bdts:n": "border-top-style:none;",
"bdtc": "border-top-color:#000;",
"bdr": "border-right:|;",
"bdr+": "border-right:1px solid #000;",
"bdr:n": "border-right:none;",
"bdrw": "border-right-width:|;",
"bdrs": "border-right-style:|;",
"bdrs:n": "border-right-style:none;",
"bdrc": "border-right-color:#000;",
"bdb": "border-bottom:|;",
"bdb+": "border-bottom:1px solid #000;",
"bdb:n": "border-bottom:none;",
"bdbw": "border-bottom-width:|;",
"bdbs": "border-bottom-style:|;",
"bdbs:n": "border-bottom-style:none;",
"bdbc": "border-bottom-color:#000;",
"bdl": "border-left:|;",
"bdl+": "border-left:1px solid #000;",
"bdl:n": "border-left:none;",
"bdlw": "border-left-width:|;",
"bdls": "border-left-style:|;",
"bdls:n": "border-left-style:none;",
"bdlc": "border-left-color:#000;",
"bdrs": "border-radius:|;",
"bdtrrs": "border-top-right-radius:|;",
"bdtlrs": "border-top-left-radius:|;",
"bdbrrs": "border-bottom-right-radius:|;",
"bdblrs": "border-bottom-left-radius:|;",
"bg": "background:|;",
"bg+": "background:#FFF url(|) 0 0 no-repeat;",
"bg:n": "background:none;",
"bg:ie": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='|x.png');",
"bgc": "background-color:#FFF;",
"bgi": "background-image:url(|);",
"bgi:n": "background-image:none;",
"bgr": "background-repeat:|;",
"bgr:n": "background-repeat:no-repeat;",
"bgr:x": "background-repeat:repeat-x;",
"bgr:y": "background-repeat:repeat-y;",
"bga": "background-attachment:|;",
"bga:f": "background-attachment:fixed;",
"bga:s": "background-attachment:scroll;",
"bgp": "background-position:0 0;",
"bgpx": "background-position-x:|;",
"bgpy": "background-position-y:|;",
"bgbk": "background-break:|;",
"bgbk:bb": "background-break:bounding-box;",
"bgbk:eb": "background-break:each-box;",
"bgbk:c": "background-break:continuous;",
"bgcp": "background-clip:|;",
"bgcp:bb": "background-clip:border-box;",
"bgcp:pb": "background-clip:padding-box;",
"bgcp:cb": "background-clip:content-box;",
"bgcp:nc": "background-clip:no-clip;",
"bgo": "background-origin:|;",
"bgo:pb": "background-origin:padding-box;",
"bgo:bb": "background-origin:border-box;",
"bgo:cb": "background-origin:content-box;",
"bgz": "background-size:|;",
"bgz:a": "background-size:auto;",
"bgz:ct": "background-size:contain;",
"bgz:cv": "background-size:cover;",
"c": "color:#000;",
"tbl": "table-layout:|;",
"tbl:a": "table-layout:auto;",
"tbl:f": "table-layout:fixed;",
"cps": "caption-side:|;",
"cps:t": "caption-side:top;",
"cps:b": "caption-side:bottom;",
"ec": "empty-cells:|;",
"ec:s": "empty-cells:show;",
"ec:h": "empty-cells:hide;",
"lis": "list-style:|;",
"lis:n": "list-style:none;",
"lisp": "list-style-position:|;",
"lisp:i": "list-style-position:inside;",
"lisp:o": "list-style-position:outside;",
"list": "list-style-type:|;",
"list:n": "list-style-type:none;",
"list:d": "list-style-type:disc;",
"list:c": "list-style-type:circle;",
"list:s": "list-style-type:square;",
"list:dc": "list-style-type:decimal;",
"list:dclz": "list-style-type:decimal-leading-zero;",
"list:lr": "list-style-type:lower-roman;",
"list:ur": "list-style-type:upper-roman;",
"lisi": "list-style-image:|;",
"lisi:n": "list-style-image:none;",
"q": "quotes:|;",
"q:n": "quotes:none;",
"q:ru": "quotes:'\00AB' '\00BB' '\201E' '\201C';",
"q:en": "quotes:'\201C' '\201D' '\2018' '\2019';",
"ct": "content:|;",
"ct:n": "content:normal;",
"ct:oq": "content:open-quote;",
"ct:noq": "content:no-open-quote;",
"ct:cq": "content:close-quote;",
"ct:ncq": "content:no-close-quote;",
"ct:a": "content:attr(|);",
"ct:c": "content:counter(|);",
"ct:cs": "content:counters(|);",
"coi": "counter-increment:|;",
"cor": "counter-reset:|;",
"va": "vertical-align:|;",
"va:sup": "vertical-align:super;",
"va:t": "vertical-align:top;",
"va:tt": "vertical-align:text-top;",
"va:m": "vertical-align:middle;",
"va:bl": "vertical-align:baseline;",
"va:b": "vertical-align:bottom;",
"va:tb": "vertical-align:text-bottom;",
"va:sub": "vertical-align:sub;",
"ta": "text-align:|;",
"ta:l": "text-align:left;",
"ta:c": "text-align:center;",
"ta:r": "text-align:right;",
"tal": "text-align-last:|;",
"tal:a": "text-align-last:auto;",
"tal:l": "text-align-last:left;",
"tal:c": "text-align-last:center;",
"tal:r": "text-align-last:right;",
"td": "text-decoration:|;",
"td:n": "text-decoration:none;",
"td:u": "text-decoration:underline;",
"td:o": "text-decoration:overline;",
"td:l": "text-decoration:line-through;",
"te": "text-emphasis:|;",
"te:n": "text-emphasis:none;",
"te:ac": "text-emphasis:accent;",
"te:dt": "text-emphasis:dot;",
"te:c": "text-emphasis:circle;",
"te:ds": "text-emphasis:disc;",
"te:b": "text-emphasis:before;",
"te:a": "text-emphasis:after;",
"th": "text-height:|;",
"th:a": "text-height:auto;",
"th:f": "text-height:font-size;",
"th:t": "text-height:text-size;",
"th:m": "text-height:max-size;",
"ti": "text-indent:|;",
"ti:-": "text-indent:-9999px;",
"tj": "text-justify:|;",
"tj:a": "text-justify:auto;",
"tj:iw": "text-justify:inter-word;",
"tj:ii": "text-justify:inter-ideograph;",
"tj:ic": "text-justify:inter-cluster;",
"tj:d": "text-justify:distribute;",
"tj:k": "text-justify:kashida;",
"tj:t": "text-justify:tibetan;",
"to": "text-outline:|;",
"to+": "text-outline:0 0 #000;",
"to:n": "text-outline:none;",
"tr": "text-replace:|;",
"tr:n": "text-replace:none;",
"tt": "text-transform:|;",
"tt:n": "text-transform:none;",
"tt:c": "text-transform:capitalize;",
"tt:u": "text-transform:uppercase;",
"tt:l": "text-transform:lowercase;",
"tw": "text-wrap:|;",
"tw:n": "text-wrap:normal;",
"tw:no": "text-wrap:none;",
"tw:u": "text-wrap:unrestricted;",
"tw:s": "text-wrap:suppress;",
"tsh": "text-shadow:|;",
"tsh+": "text-shadow:0 0 0 #000;",
"tsh:n": "text-shadow:none;",
"lh": "line-height:|;",
"whs": "white-space:|;",
"whs:n": "white-space:normal;",
"whs:p": "white-space:pre;",
"whs:nw": "white-space:nowrap;",
"whs:pw": "white-space:pre-wrap;",
"whs:pl": "white-space:pre-line;",
"whsc": "white-space-collapse:|;",
"whsc:n": "white-space-collapse:normal;",
"whsc:k": "white-space-collapse:keep-all;",
"whsc:l": "white-space-collapse:loose;",
"whsc:bs": "white-space-collapse:break-strict;",
"whsc:ba": "white-space-collapse:break-all;",
"wob": "word-break:|;",
"wob:n": "word-break:normal;",
"wob:k": "word-break:keep-all;",
"wob:l": "word-break:loose;",
"wob:bs": "word-break:break-strict;",
"wob:ba": "word-break:break-all;",
"wos": "word-spacing:|;",
"wow": "word-wrap:|;",
"wow:nm": "word-wrap:normal;",
"wow:n": "word-wrap:none;",
"wow:u": "word-wrap:unrestricted;",
"wow:s": "word-wrap:suppress;",
"lts": "letter-spacing:|;",
"f": "font:|;",
"f+": "font:1em Arial,sans-serif;",
"fw": "font-weight:|;",
"fw:n": "font-weight:normal;",
"fw:b": "font-weight:bold;",
"fw:br": "font-weight:bolder;",
"fw:lr": "font-weight:lighter;",
"fs": "font-style:|;",
"fs:n": "font-style:normal;",
"fs:i": "font-style:italic;",
"fs:o": "font-style:oblique;",
"fv": "font-variant:|;",
"fv:n": "font-variant:normal;",
"fv:sc": "font-variant:small-caps;",
"fz": "font-size:|;",
"fza": "font-size-adjust:|;",
"fza:n": "font-size-adjust:none;",
"ff": "font-family:|;",
"ff:s": "font-family:serif;",
"ff:ss": "font-family:sans-serif;",
"ff:c": "font-family:cursive;",
"ff:f": "font-family:fantasy;",
"ff:m": "font-family:monospace;",
"fef": "font-effect:|;",
"fef:n": "font-effect:none;",
"fef:eg": "font-effect:engrave;",
"fef:eb": "font-effect:emboss;",
"fef:o": "font-effect:outline;",
"fem": "font-emphasize:|;",
"femp": "font-emphasize-position:|;",
"femp:b": "font-emphasize-position:before;",
"femp:a": "font-emphasize-position:after;",
"fems": "font-emphasize-style:|;",
"fems:n": "font-emphasize-style:none;",
"fems:ac": "font-emphasize-style:accent;",
"fems:dt": "font-emphasize-style:dot;",
"fems:c": "font-emphasize-style:circle;",
"fems:ds": "font-emphasize-style:disc;",
"fsm": "font-smooth:|;",
"fsm:a": "font-smooth:auto;",
"fsm:n": "font-smooth:never;",
"fsm:aw": "font-smooth:always;",
"fst": "font-stretch:|;",
"fst:n": "font-stretch:normal;",
"fst:uc": "font-stretch:ultra-condensed;",
"fst:ec": "font-stretch:extra-condensed;",
"fst:c": "font-stretch:condensed;",
"fst:sc": "font-stretch:semi-condensed;",
"fst:se": "font-stretch:semi-expanded;",
"fst:e": "font-stretch:expanded;",
"fst:ee": "font-stretch:extra-expanded;",
"fst:ue": "font-stretch:ultra-expanded;",
"op": "opacity:|;",
"op:ie": "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);",
"op:ms": "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';",
"rz": "resize:|;",
"rz:n": "resize:none;",
"rz:b": "resize:both;",
"rz:h": "resize:horizontal;",
"rz:v": "resize:vertical;",
"cur": "cursor:|;",
"cur:a": "cursor:auto;",
"cur:d": "cursor:default;",
"cur:c": "cursor:crosshair;",
"cur:ha": "cursor:hand;",
"cur:he": "cursor:help;",
"cur:m": "cursor:move;",
"cur:p": "cursor:pointer;",
"cur:t": "cursor:text;",
"pgbb": "page-break-before:|;",
"pgbb:au": "page-break-before:auto;",
"pgbb:al": "page-break-before:always;",
"pgbb:l": "page-break-before:left;",
"pgbb:r": "page-break-before:right;",
"pgbi": "page-break-inside:|;",
"pgbi:au": "page-break-inside:auto;",
"pgbi:av": "page-break-inside:avoid;",
"pgba": "page-break-after:|;",
"pgba:au": "page-break-after:auto;",
"pgba:al": "page-break-after:always;",
"pgba:l": "page-break-after:left;",
"pgba:r": "page-break-after:right;",
"orp": "orphans:|;",
"wid": "widows:|;"
}
},
'html': {
'filters': 'html',
'snippets': {
'cc:ie6': '<!--[if lte IE 6]>\n\t${child}|\n<![endif]-->',
'cc:ie': '<!--[if IE]>\n\t${child}|\n<![endif]-->',
'cc:noie': '<!--[if !IE]><!-->\n\t${child}|\n<!--<![endif]-->',
'html:4t': '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' +
'<html lang="${lang}">\n' +
'<head>\n' +
' <meta http-equiv="Content-Type" content="text/html;charset=${charset}">\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n\t${child}|\n</body>\n' +
'</html>',
'html:4s': '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' +
'<html lang="${lang}">\n' +
'<head>\n' +
' <meta http-equiv="Content-Type" content="text/html;charset=${charset}">\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n\t${child}|\n</body>\n' +
'</html>',
'html:xt': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' +
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${lang}">\n' +
'<head>\n' +
' <meta http-equiv="Content-Type" content="text/html;charset=${charset}" />\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n\t${child}|\n</body>\n' +
'</html>',
'html:xs': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${lang}">\n' +
'<head>\n' +
' <meta http-equiv="Content-Type" content="text/html;charset=${charset}" />\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n\t${child}|\n</body>\n' +
'</html>',
'html:xxs': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' +
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${lang}">\n' +
'<head>\n' +
' <meta http-equiv="Content-Type" content="text/html;charset=${charset}" />\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n\t${child}|\n</body>\n' +
'</html>',
'html:5': '<!DOCTYPE HTML>\n' +
'<html lang="${locale}">\n' +
'<head>\n' +
' <meta charset="${charset}">\n' +
' <title></title>\n' +
'</head>\n' +
'<body>\n\t${child}|\n</body>\n' +
'</html>'
},
'abbreviations': {
'a': '<a href=""></a>',
'a:link': '<a href="http://|"></a>',
'a:mail': '<a href="mailto:|"></a>',
'abbr': '<abbr title=""></abbr>',
'acronym': '<acronym title=""></acronym>',
'base': '<base href="" />',
'bdo': '<bdo dir=""></bdo>',
'bdo:r': '<bdo dir="rtl"></bdo>',
'bdo:l': '<bdo dir="ltr"></bdo>',
'link:css': '<link rel="stylesheet" type="text/css" href="|style.css" media="all" />',
'link:print': '<link rel="stylesheet" type="text/css" href="|print.css" media="print" />',
'link:favicon': '<link rel="shortcut icon" type="image/x-icon" href="|favicon.ico" />',
'link:touch': '<link rel="apple-touch-icon" href="|favicon.png" />',
'link:rss': '<link rel="alternate" type="application/rss+xml" title="RSS" href="|rss.xml" />',
'link:atom': '<link rel="alternate" type="application/atom+xml" title="Atom" href="atom.xml" />',
'meta:utf': '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />',
'meta:win': '<meta http-equiv="Content-Type" content="text/html;charset=windows-1251" />',
'meta:compat': '<meta http-equiv="X-UA-Compatible" content="IE=7" />',
'style': '<style type="text/css"></style>',
'script': '<script type="text/javascript"></script>',
'script:src': '<script type="text/javascript" src=""></script>',
'img': '<img src="" alt="" />',
'iframe': '<iframe src="" frameborder="0"></iframe>',
'embed': '<embed src="" type="" />',
'object': '<object data="" type=""></object>',
'param': '<param name="" value="" />',
'map': '<map name=""></map>',
'area': '<area shape="" coords="" href="" alt="" />',
'area:d': '<area shape="default" href="" alt="" />',
'area:c': '<area shape="circle" coords="" href="" alt="" />',
'area:r': '<area shape="rect" coords="" href="" alt="" />',
'area:p': '<area shape="poly" coords="" href="" alt="" />',
'link': '<link rel="stylesheet" href="" />',
'form': '<form action=""></form>',
'form:get': '<form action="" method="get"></form>',
'form:post': '<form action="" method="post"></form>',
'label': '<label for=""></label>',
'input': '<input type="" />',
'input:hidden': '<input type="hidden" name="" />',
'input:h': '<input type="hidden" name="" />',
'input:text': '<input type="text" name="" id="" />',
'input:t': '<input type="text" name="" id="" />',
'input:search': '<input type="search" name="" id="" />',
'input:email': '<input type="email" name="" id="" />',
'input:url': '<input type="url" name="" id="" />',
'input:password': '<input type="password" name="" id="" />',
'input:p': '<input type="password" name="" id="" />',
'input:datetime': '<input type="datetime" name="" id="" />',
'input:date': '<input type="date" name="" id="" />',
'input:datetime-local': '<input type="datetime-local" name="" id="" />',
'input:month': '<input type="month" name="" id="" />',
'input:week': '<input type="week" name="" id="" />',
'input:time': '<input type="time" name="" id="" />',
'input:number': '<input type="number" name="" id="" />',
'input:color': '<input type="color" name="" id="" />',
'input:checkbox': '<input type="checkbox" name="" id="" />',
'input:c': '<input type="checkbox" name="" id="" />',
'input:radio': '<input type="radio" name="" id="" />',
'input:r': '<input type="radio" name="" id="" />',
'input:range': '<input type="range" name="" id="" />',
'input:file': '<input type="file" name="" id="" />',
'input:f': '<input type="file" name="" id="" />',
'input:submit': '<input type="submit" value="" />',
'input:s': '<input type="submit" value="" />',
'input:image': '<input type="image" src="" alt="" />',
'input:i': '<input type="image" src="" alt="" />',
'input:reset': '<input type="reset" value="" />',
'input:button': '<input type="button" value="" />',
'input:b': '<input type="button" value="" />',
'select': '<select name="" id=""></select>',
'option': '<option value=""></option>',
'textarea': '<textarea name="" id="" cols="30" rows="10"></textarea>',
'menu:context': '<menu type="context"></menu>',
'menu:c': '<menu type="context"></menu>',
'menu:toolbar': '<menu type="toolbar"></menu>',
'menu:t': '<menu type="toolbar"></menu>',
'video': '<video src=""></video>',
'audio': '<audio src=""></audio>',
'html:xml': '<html xmlns="http://www.w3.org/1999/xhtml"></html>',
'bq': '<blockquote></blockquote>',
'acr': '<acronym></acronym>',
'fig': '<figure></figure>',
'ifr': '<iframe></iframe>',
'emb': '<embed></embed>',
'obj': '<object></object>',
'src': '<source></source>',
'cap': '<caption></caption>',
'colg': '<colgroup></colgroup>',
'fst': '<fieldset></fieldset>',
'btn': '<button></button>',
'optg': '<optgroup></optgroup>',
'opt': '<option></option>',
'tarea': '<textarea></textarea>',
'leg': '<legend></legend>',
'sect': '<section></section>',
'art': '<article></article>',
'hdr': '<header></header>',
'ftr': '<footer></footer>',
'adr': '<address></address>',
'dlg': '<dialog></dialog>',
'str': '<strong></strong>',
'prog': '<progress></progress>',
'fset': '<fieldset></fieldset>',
'datag': '<datagrid></datagrid>',
'datal': '<datalist></datalist>',
'kg': '<keygen></keygen>',
'out': '<output></output>',
'det': '<details></details>',
'cmd': '<command></command>',
// expandos
'ol+': 'ol>li',
'ul+': 'ul>li',
'dl+': 'dl>dt+dd',
'map+': 'map>area',
'table+': 'table>tr>td',
'colgroup+': 'colgroup>col',
'colg+': 'colgroup>col',
'tr+': 'tr>td',
'select+': 'select>option',
'optgroup+': 'optgroup>option',
'optg+': 'optgroup>option'
},
'element_types': {
'empty': 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,keygen,command',
'block_level': 'address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,link,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul,h1,h2,h3,h4,h5,h6',
'inline_level': 'a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'
}
},
'xsl': {
'extends': 'html',
'filters': 'html, xsl',
'abbreviations': {
'tm': '<xsl:template match="" mode=""></xsl:template>',
'tmatch': 'tm',
'tn': '<xsl:template name=""></xsl:template>',
'tname': 'tn',
'xsl:when': '<xsl:when test=""></xsl:when>',
'wh': 'xsl:when',
'var': '<xsl:variable name="">|</xsl:variable>',
'vare': '<xsl:variable name="" select=""/>',
'if': '<xsl:if test=""></xsl:if>',
'call': '<xsl:call-template name=""/>',
'attr': '<xsl:attribute name=""></xsl:attribute>',
'wp': '<xsl:with-param name="" select=""/>',
'par': '<xsl:param name="" select=""/>',
'val': '<xsl:value-of select=""/>',
'co': '<xsl:copy-of select=""/>',
'each': '<xsl:for-each select=""></xsl:for-each>',
'for': 'each',
'ap': '<xsl:apply-templates select="" mode=""/>',
//expandos
'choose+': 'xsl:choose>xsl:when+xsl:otherwise'
}
},
'haml': {
'filters': 'haml',
'extends': 'html'
}
};/**
* Core library that do all Zen Coding magic
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
* @include "settings.js"
* @include "/EclipseMonkey/scripts/monkey-doc.js"
*/
var zen_coding = (function(){
var re_tag = /<\/?[\w:\-]+(?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*\s*(\/?)>$/;
var TYPE_ABBREVIATION = 'zen-tag',
TYPE_EXPANDO = 'zen-expando',
/** Reference to another abbreviation or tag */
TYPE_REFERENCE = 'zen-reference',
caret_placeholder = '{%::zen-caret::%}',
newline = '\n',
default_tag = 'div';
var default_profile = {
tag_case: 'lower',
attr_case: 'lower',
attr_quotes: 'double',
// each tag on new line
tag_nl: 'decide',
place_cursor: true,
// indent tags
indent: true,
// how many inline elements should be to force line break
// (set to 0 to disable)
inline_break: 3,
// use self-closing style for writing empty elements, e.g. <br /> or <br>
self_closing_tag: 'xhtml'
};
var profiles = {};
/** List of registered filters */
var filters = {},
/** Filters that will be applied for unknown syntax */
basic_filters = 'html';
/**
* Проверяет, является ли символ допустимым в аббревиатуре
* @param {String} ch
* @return {Boolean}
*/
function isAllowedChar(ch) {
var char_code = ch.charCodeAt(0),
special_chars = '#.>+*:$-_!@[]()|';
return (char_code > 64 && char_code < 91) // uppercase letter
|| (char_code > 96 && char_code < 123) // lowercase letter
|| (char_code > 47 && char_code < 58) // number
|| special_chars.indexOf(ch) != -1; // special character
}
/**
* Возвращает символ перевода строки, используемый в редакторе
* @return {String}
*/
function getNewline() {
return zen_coding.getNewline();
}
/**
* Split text into lines. Set <code>remove_empty</code> to true to filter
* empty lines
* @param {String} text
* @param {Boolean} [remove_empty]
* @return {Array}
*/
function splitByLines(text, remove_empty) {
// IE fails to split string by regexp,
// need to normalize newlines first
// Also, Mozilla's Rhiho JS engine has a wierd newline bug
var nl = getNewline();
var lines = (text || '')
.replace(/\r\n/g, '\n')
.replace(/\n\r/g, '\n')
.replace(/\n/g, nl)
.split(nl);
if (remove_empty) {
for (var i = lines.length; i >= 0; i--) {
if (!trim(lines[i]))
lines.splice(i, 1);
}
}
return lines;
}
/**
* Trim whitespace from string
* @param {String} text
* @return {String}
*/
function trim(text) {
return (text || "").replace( /^\s+|\s+$/g, "" );
}
function createProfile(options) {
var result = {};
for (var p in default_profile)
result[p] = (p in options) ? options[p] : default_profile[p];
return result;
}
function setupProfile(name, options) {
profiles[name.toLowerCase()] = createProfile(options || {});
}
/**
* Helper function that transforms string into hash
* @return {Object}
*/
function stringToHash(str){
var obj = {}, items = str.split(",");
for ( var i = 0; i < items.length; i++ )
obj[ items[i] ] = true;
return obj;
}
/**
* Repeats string <code>how_many</code> times
* @param {String} str
* @param {Number} how_many
* @return {String}
*/
function repeatString(str, how_many) {
var result = '';
for (var i = 0; i < how_many; i++)
result += str;
return result;
}
/**
* Indents text with padding
* @param {String} text Text to indent
* @param {String|Number} pad Padding size (number) or padding itself (string)
* @return {String}
*/
function padString(text, pad) {
var pad_str = (typeof(pad) == 'number')
? repeatString(getIndentation(), pad)
: pad,
result = '';
var lines = splitByLines(text),
nl = getNewline();
result += lines[0];
for (var j = 1; j < lines.length; j++)
result += nl + pad_str + lines[j];
return result;
}
/**
* Class inheritance method
* @param {Function} derived Derived class
* @param {Function} from Base class
*/
function inherit(derived, from) {
var Inheritance = function(){};
Inheritance.prototype = from.prototype;
derived.prototype = new Inheritance();
derived.prototype.constructor = derived;
derived.baseConstructor = from;
derived.superClass = from.prototype;
};
/**
* Check if passed abbreviation is snippet
* @param {String} abbr
* @param {String} type
* @return {Boolean}
*/
function isShippet(abbr, type) {
return getSnippet(type, abbr) ? true : false;
}
/**
* Test if passed string ends with XHTML tag. This method is used for testing
* '>' character: it belongs to tag or it's a part of abbreviation?
* @param {String} str
* @return {Boolean}
*/
function isEndsWithTag(str) {
return re_tag.test(str);
}
/**
* Returns specified elements collection (like 'empty', 'block_level') from
* <code>resource</code>. If collections wasn't found, returns empty object
* @param {Object} resource
* @param {String} type
* @return {Object}
*/
function getElementsCollection(resource, type) {
if (resource && resource.element_types)
return resource.element_types[type] || {}
else
return {};
}
/**
* Replace variables like ${var} in string
* @param {String} str
* @param {Object} [vars] Variable set (default is <code>zen_settings.variables</code>)
* @return {String}
*/
function replaceVariables(str, vars) {
vars = vars || zen_settings.variables;
return str.replace(/\$\{([\w\-]+)\}/g, function(str, p1){
return (p1 in vars) ? vars[p1] : str;
});
}
/**
* Tag
* @class
* @param {String} name tag name
* @param {Number} count Output multiplier (default: 1)
* @param {String} type Tag type (html, xml)
*/
function Tag(name, count, type) {
name = name.toLowerCase();
type = type || 'html';
var abbr = getAbbreviation(type, name);
if (abbr && abbr.type == TYPE_REFERENCE)
abbr = getAbbreviation(type, abbr.value);
this.name = (abbr) ? abbr.value.name : name.replace('+', '');
this.count = count || 1;
this.children = [];
this.attributes = [];
this._attr_hash = {};
this._abbr = abbr;
this._res = zen_settings[type];
this._content = '';
this.repeat_by_lines = false;
this.parent = null;
// add default attributes
if (this._abbr && this._abbr.value.attributes) {
var def_attrs = this._abbr.value.attributes;
if (def_attrs) {
for (var i = 0; i < def_attrs.length; i++) {
var attr = def_attrs[i];
this.addAttribute(attr.name, attr.value);
}
}
}
}
Tag.prototype = {
/**
* Adds new child tag to current one
* @param {Tag} tag
*/
addChild: function(tag) {
tag.parent = this;
this.children.push(tag);
},
/**
* Adds new attribute
* @param {String} name Attribute's name
* @param {String} value Attribute's value
*/
addAttribute: function(name, value) {
// the only place in Tag where pipe (caret) character may exist
// is the attribute: escape it with internal placeholder
value = replaceUnescapedSymbol(value, '|', caret_placeholder);
var a;
if (name in this._attr_hash) {
// attribute already exists, decide what to do
a = this._attr_hash[name];
if (name == 'class') {
// 'class' is a magic attribute
a.value += ((a.value) ? ' ' : '') + value;
} else {
a.value = value;
}
} else {
a = {name: name, value: value};
this._attr_hash[name] = a
this.attributes.push(a);
}
},
/**
* This function tests if current tags' content contains xHTML tags.
* This function is mostly used for output formatting
*/
hasTagsInContent: function() {
return this.getContent() && re_tag.test(this.getContent());
},
/**
* Set textual content for tag
* @param {String} str Tag's content
*/
setContent: function(str) {
this._content = str;
},
/**
* Returns tag's textual content
* @return {String}
*/
getContent: function() {
return this._content;
},
/**
* Search for deepest and latest child of current element
* @return {Tag|null} Returns null if there's no children
*/
findDeepestChild: function() {
if (!this.children.length)
return null;
var deepest_child = this;
while (true) {
deepest_child = deepest_child.children[ deepest_child.children.length - 1 ];
if (!deepest_child.children.length)
break;
}
return deepest_child;
}
};
function Snippet(name, count, type) {
/** @type {String} */
this.name = name;
this.count = count || 1;
this.children = [];
this._content = '';
this.repeat_by_lines = false;
this.attributes = {'id': caret_placeholder, 'class': caret_placeholder};
this.value = replaceUnescapedSymbol(getSnippet(type, name), '|', caret_placeholder);
this.parent = null;
}
inherit(Snippet, Tag);
/**
* Returns abbreviation value from data set
* @param {String} type Resource type (html, css, ...)
* @param {String} abbr Abbreviation name
* @return {Object|null}
*/
function getAbbreviation(type, abbr) {
return getSettingsResource(type, abbr, 'abbreviations');
}
/**
* Returns snippet value from data set
* @param {String} type Resource type (html, css, ...)
* @param {String} snippet_name Snippet name
* @return {Object|null}
*/
function getSnippet(type, snippet_name) {
return getSettingsResource(type, snippet_name, 'snippets');
}
/**
* Returns variable value
* @return {String}
*/
function getVariable(name) {
return zen_settings.variables[name];
}
/**
* Returns indentation string
* @return {String}
*/
function getIndentation() {
return getVariable('indentation');
}
/**
* Creates resource inheritance chain for lookups
* @param {String} syntax Syntax name
* @param {String} name Resource name
* @return {Array}
*/
function createResourceChain(syntax, name) {
var resource = zen_settings[syntax],
result = [];
if (resource) {
if (name in resource)
result.push(resource[name]);
if ('extends' in resource) {
// find resource in ancestors
for (var i = 0; i < resource['extends'].length; i++) {
var type = resource['extends'][i];
if (zen_settings[type] && zen_settings[type][name])
result.push(zen_settings[type][name]);
}
}
}
return result;
}
/**
* Get resource collection from settings file for specified syntax.
* It follows inheritance chain if resource wasn't directly found in
* syntax settings
* @param {String} syntax Syntax name
* @param {String} name Resource name
*/
function getResource(syntax, name) {
var chain = createResourceChain(syntax, name);
return chain[0];
}
/**
* Returns resurce value from data set with respect of inheritance
* @param {String} syntax Resource syntax (html, css, ...)
* @param {String} abbr Abbreviation name
* @param {String} name Resource name ('snippets' or 'abbreviation')
* @return {Object|null}
*/
function getSettingsResource(syntax, abbr, name) {
var chain = createResourceChain(syntax, name);
for (var i = 0, il = chain.length; i < il; i++) {
if (abbr in chain[i])
return chain[i][abbr];
}
return null;
}
/**
* Get word, starting at <code>ix</code> character of <code>str</code>
*/
function getWord(ix, str) {
var m = str.substring(ix).match(/^[\w\-:\$]+/);
return m ? m[0] : '';
}
/**
* Extract attributes and their values from attribute set
* @param {String} attr_set
*/
function extractAttributes(attr_set) {
attr_set = trim(attr_set);
var loop_count = 100, // endless loop protection
re_string = /^(["'])((?:(?!\1)[^\\]|\\.)*)\1/,
result = [],
attr;
while (attr_set && loop_count--) {
var attr_name = getWord(0, attr_set);
attr = null;
if (attr_name) {
attr = {name: attr_name, value: ''};
// result[attr_name] = '';
// let's see if attribute has value
var ch = attr_set.charAt(attr_name.length);
switch (ch) {
case '=':
var ch2 = attr_set.charAt(attr_name.length + 1);
if (ch2 == '"' || ch2 == "'") {
// we have a quoted string
var m = attr_set.substring(attr_name.length + 1).match(re_string);
if (m) {
attr.value = m[2];
attr_set = trim(attr_set.substring(attr_name.length + m[0].length + 1));
} else {
// something wrong, break loop
attr_set = '';
}
} else {
// unquoted string
var m = attr_set.substring(attr_name.length + 1).match(/(.+?)(\s|$)/);
if (m) {
attr.value = m[1];
attr_set = trim(attr_set.substring(attr_name.length + m[1].length + 1));
} else {
// something wrong, break loop
attr_set = '';
}
}
break;
default:
attr_set = trim(attr_set.substring(attr_name.length));
break;
}
} else {
// something wrong, can't extract attribute name
break;
}
if (attr) result.push(attr);
}
return result;
}
/**
* Parses tag attributes extracted from abbreviation
* @param {String} str
*/
function parseAttributes(str) {
/*
* Example of incoming data:
* #header
* .some.data
* .some.data#header
* [attr]
* #item[attr=Hello other="World"].class
*/
var result = [],
class_name,
char_map = {'#': 'id', '.': 'class'};
// walk char-by-char
var i = 0,
il = str.length,
val;
while (i < il) {
var ch = str.charAt(i);
switch (ch) {
case '#': // id
val = getWord(i, str.substring(1));
result.push({name: char_map[ch], value: val});
i += val.length + 1;
break;
case '.': // class
val = getWord(i, str.substring(1));
if (!class_name) {
// remember object pointer for value modification
class_name = {name: char_map[ch], value: ''};
result.push(class_name);
}
class_name.value += ((class_name.value) ? ' ' : '') + val;
i += val.length + 1;
break;
case '[': //begin attribute set
// search for end of set
var end_ix = str.indexOf(']', i);
if (end_ix == -1) {
// invalid attribute set, stop searching
i = str.length;
} else {
var attrs = extractAttributes(str.substring(i + 1, end_ix));
for (var j = 0, jl = attrs.length; j < jl; j++) {
result.push(attrs[j]);
}
i = end_ix;
}
break;
default:
i++;
}
}
return result;
}
/**
* Creates group element
* @param {String} expr Part of abbreviation that belongs to group item
* @param {abbrGroup} [parent] Parent group item element
*/
function abbrGroup(parent) {
return {
expr: '',
parent: parent || null,
children: [],
addChild: function() {
var child = abbrGroup(this);
this.children.push(child);
return child;
},
cleanUp: function() {
for (var i = this.children.length - 1; i >= 0; i--) {
var expr = this.children[i].expr;
if (!expr)
this.children.splice(i, 1);
else {
// remove operators at the and of expression
// this.children[i].expr = expr.replace(/[\+>]+$/, '');
this.children[i].cleanUp();
}
}
}
}
}
/**
* Split abbreviation by groups
* @param {String} abbr
* @return {abbrGroup()}
*/
function splitByGroups(abbr) {
var root = abbrGroup(),
last_parent = root,
cur_item = root.addChild(),
stack = [],
i = 0,
il = abbr.length;
while (i < il) {
var ch = abbr.charAt(i);
switch(ch) {
case '(':
// found new group
var operator = i ? abbr.charAt(i - 1) : '';
if (operator == '>') {
stack.push(cur_item);
last_parent = cur_item;
} else {
stack.push(last_parent);
}
cur_item = null;
break;
case ')':
last_parent = stack.pop();
cur_item = null;
var next_char = (i < il - 1) ? abbr.charAt(i + 1) : '';
if (next_char == '+' || next_char == '>')
// next char is group operator, skip it
i++;
break;
default:
if (ch == '+' || ch == '>') {
// skip operator if it's followed by parenthesis
var next_char = (i + 1 < il) ? abbr.charAt(i + 1) : '';
if (next_char == '(') break;
}
if (!cur_item)
cur_item = last_parent.addChild();
cur_item.expr += ch;
}
i++;
}
root.cleanUp();
return root;
}
/**
* @class
* Creates simplified tag from Zen Coding tag
* @param {Tag} tag
*/
function ZenNode(tag) {
this.type = (tag instanceof Snippet) ? 'snippet' : 'tag';
this.name = tag.name;
this.attributes = tag.attributes;
this.children = [];
/** @type {Tag} Source element from which current tag was created */
this.source = tag;
// relations
/** @type {ZenNode} */
this.parent = null;
/** @type {ZenNode} */
this.nextSibling = null;
/** @type {ZenNode} */
this.previousSibling = null;
// output params
this.start = '';
this.end = '';
this.content = '';
this.padding = '';
}
ZenNode.prototype = {
/**
* @type {ZenNode} tag
*/
addChild: function(tag) {
tag.parent = this;
var last_child = this.children[this.children.length - 1];
if (last_child) {
tag.previousSibling = last_child;
last_child.nextSibling = tag;
}
this.children.push(tag);
},
/**
* Get attribute's value.
* @param {String} name
* @return {String|null} Returns <code>null</code> if attribute wasn't found
*/
getAttribute: function(name) {
name = name.toLowerCase();
for (var i = 0, il = this.attributes.length; i < il; i++) {
if (this.attributes[i].name.toLowerCase() == name)
return this.attributes[i].value;
}
return null;
},
/**
* Test if current tag is unary (no closing tag)
* @return {Boolean}
*/
isUnary: function() {
if (this.type == 'snippet')
return false;
return (this.source._abbr && this.source._abbr.value.is_empty) || (this.name in getElementsCollection(this.source._res, 'empty'));
},
/**
* Test if current tag is inline-level (like &lt;strong&gt;, &lt;img&gt;)
* @return {Boolean}
*/
isInline: function() {
return (this.name in getElementsCollection(this.source._res, 'inline_level'));
},
/**
* Test if current element is block-level
* @return {Boolean}
*/
isBlock: function() {
return this.type == 'snippet' || !this.isInline();
},
/**
* This function tests if current tags' content contains xHTML tags.
* This function is mostly used for output formatting
*/
hasTagsInContent: function() {
return this.content && re_tag.test(this.content);
},
/**
* Check if tag has child elements
* @return {Boolean}
*/
hasChildren: function() {
return !!this.children.length;
},
/**
* Test if current tag contains block-level children
* @return {Boolean}
*/
hasBlockChildren: function() {
if (this.hasTagsInContent() && this.isBlock()) {
return true;
}
for (var i = 0; i < this.children.length; i++) {
if (this.children[i].isBlock())
return true;
}
return false;
},
/**
* Search for deepest and latest child of current element
* @return {ZenNode|null} Returns <code>null</code> if there's no children
*/
findDeepestChild: function() {
if (!this.children.length)
return null;
var deepest_child = this;
while (true) {
deepest_child = deepest_child.children[ deepest_child.children.length - 1 ];
if (!deepest_child.children.length)
break;
}
return deepest_child;
},
/**
* @return {String}
*/
toString: function() {
var content = '';
for (var i = 0, il = this.children.length; i < il; i++) {
content += this.children[i].toString();
}
return this.start + this.content + content + this.end;
}
}
/**
* Roll outs basic Zen Coding tree into simplified, DOM-like tree.
* The simplified tree, for example, represents each multiplied element
* as a separate element sets with its own content, if exists.
*
* The simplified tree element contains some meta info (tag name, attributes,
* etc.) as well as output strings, which are exactly what will be outputted
* after expanding abbreviation. This tree is used for <i>filtering</i>:
* you can apply filters that will alter output strings to get desired look
* of expanded abbreviation.
*
* @param {Tag} tree
* @param {ZenNode} [parent]
*/
function rolloutTree(tree, parent) {
parent = parent || new ZenNode(tree);
var how_many = 1,
tag_content = '';
for (var i = 0, il = tree.children.length; i < il; i++) {
/** @type {Tag} */
var child = tree.children[i];
how_many = child.count;
if (child.repeat_by_lines) {
// it's a repeating element
tag_content = splitByLines(child.getContent(), true);
how_many = Math.max(tag_content.length, 1);
} else {
tag_content = child.getContent();
}
for (var j = 0; j < how_many; j++) {
var tag = new ZenNode(child);
parent.addChild(tag);
if (child.children.length)
rolloutTree(child, tag);
var add_point = tag.findDeepestChild() || tag;
if (tag_content) {
add_point.content = (typeof(tag_content) == 'string')
? tag_content
: (tag_content[j] || '');
}
}
}
return parent;
}
/**
* Runs filters on tree
* @param {ZenNode} tree
* @param {String|Object} profile
* @param {String[]|String} filter_list
* @return {ZenNode}
*/
function runFilters(tree, profile, filter_list) {
if (typeof(profile) == 'string' && profile in profiles)
profile = profiles[profile];
if (!profile)
profile = profiles['plain'];
if (typeof(filter_list) == 'string')
filter_list = filter_list.split(/[\|,]/g);
for (var i = 0, il = filter_list.length; i < il; i++) {
var name = trim(filter_list[i].toLowerCase());
if (name && name in filters) {
tree = filters[name](tree, profile);
}
}
return tree;
}
/**
* Transforms abbreviation into a primary internal tree. This tree should'n
* be used ouside of this scope
* @param {String} abbr Abbreviation
* @param {String} [type] Document type (xsl, html, etc.)
* @return {Tag}
*/
function abbrToPrimaryTree(abbr, type) {
type = type || 'html';
var root = new Tag('', 1, type),
parent = root,
last = null,
multiply_elem = null,
res = zen_settings[type],
re = /([\+>])?([a-z@\!\#\.][a-z0-9:\-]*)((?:(?:[#\.][\w\-\$]+)|(?:\[[^\]]+\]))+)?(\*(\d*))?(\+$)?/ig;
// re = /([\+>])?([a-z@\!][a-z0-9:\-]*)(#[\w\-\$]+)?((?:\.[\w\-\$]+)*)(\*(\d*))?(\+$)?/ig;
if (!abbr)
return null;
// replace expandos
abbr = abbr.replace(/([a-z][\w\:\-]*)\+$/i, function(str){
var a = getAbbreviation(type, str);
return a ? a.value : str;
});
abbr = abbr.replace(re, function(str, operator, tag_name, attrs, has_multiplier, multiplier, has_expando){
var multiply_by_lines = (has_multiplier && !multiplier);
multiplier = multiplier ? parseInt(multiplier) : 1;
var tag_ch = tag_name.charAt(0);
if (tag_ch == '#' || tag_ch == '.') {
attrs = tag_name + (attrs || '');
tag_name = default_tag;
}
if (has_expando)
tag_name += '+';
var current = isShippet(tag_name, type) ? new Snippet(tag_name, multiplier, type) : new Tag(tag_name, multiplier, type);
if (attrs) {
attrs = parseAttributes(attrs);
for (var i = 0, il = attrs.length; i < il; i++) {
current.addAttribute(attrs[i].name, attrs[i].value);
}
}
// dive into tree
if (operator == '>' && last)
parent = last;
parent.addChild(current);
last = current;
if (multiply_by_lines)
multiply_elem = current;
return '';
});
root.last = last;
root.multiply_elem = multiply_elem;
// empty 'abbr' string means that abbreviation was successfully expanded,
// if not — abbreviation wasn't valid
return (!abbr) ? root : null;
}
/**
* Expand single group item
* @param {abbrGroup} group
* @param {String} type
* @param {Tag} parent
*/
function expandGroup(group, type, parent) {
var tree = abbrToPrimaryTree(group.expr, type),
/** @type {Tag} */
last_item = null;
if (tree) {
for (var i = 0, il = tree.children.length; i < il; i++) {
last_item = tree.children[i];
parent.addChild(last_item);
}
} else {
throw new Error('InvalidGroup');
}
// set repeating element to the topmost node
var root = parent;
while (root.parent)
root = root.parent;
root.last = tree.last;
if (tree.multiply_elem)
root.multiply_elem = tree.multiply_elem;
// process child groups
if (group.children.length) {
var add_point = last_item.findDeepestChild() || last_item;
for (var j = 0, jl = group.children.length; j < jl; j++) {
expandGroup(group.children[j], type, add_point);
}
}
}
/**
* Pad string with zeroes
* @param {String} str
* @param {Number} pad
*/
function zeroPadString(str, pad) {
var padding = '',
il = str.length;
while (pad > il++) padding += '0';
return padding + str;
}
/**
* Replaces unescaped symbols in <code>str</code>. For example, the '$' symbol
* will be replaced in 'item$count', but not in 'item\$count'.
* @param {String} str Original string
* @param {String} symbol Symbol to replace
* @param {String|Function} replace Symbol replacement
* @return {String}
*/
function replaceUnescapedSymbol(str, symbol, replace) {
var i = 0,
il = str.length,
sl = symbol.length,
match_count = 0;
while (i < il) {
if (str.charAt(i) == '\\') {
// escaped symbol, skip next character
i += sl + 1;
} else if (str.substr(i, sl) == symbol) {
// have match
var cur_sl = sl;
match_count++;
var new_value = replace;
if (typeof(replace) !== 'string') {
var replace_data = replace(str, symbol, i, match_count);
if (replace_data) {
cur_sl = replace_data[0].length;
new_value = replace_data[1];
} else {
new_value = false;
}
}
if (new_value === false) { // skip replacement
i++;
continue;
}
str = str.substring(0, i) + new_value + str.substring(i + cur_sl);
// adjust indexes
il = str.length;
i += new_value.length;
} else {
i++;
}
}
return str;
}
// create default profiles
setupProfile('xhtml');
setupProfile('html', {self_closing_tag: false});
setupProfile('xml', {self_closing_tag: true, tag_nl: true});
setupProfile('plain', {tag_nl: false, indent: false, place_cursor: false});
return {
/** Hash of all available actions */
actions: {},
/**
* Adds new Zen Coding action. This action will be available in
* <code>zen_settings.actions</code> object.
* @param {String} name Action's name
* @param {Function} fn Action itself. The first argument should be
* <code>zen_editor</code> instance.
*/
registerAction: function(name, fn) {
this.actions[name] = fn;
},
/**
* Runs Zen Coding action. For list of available actions and their
* arguments see <code>zen_actions.js</code> file.
* @param {String} name Action name
* @param {Array} args Additional arguments. It may be array of arguments
* or inline arguments. The first argument should be <code>zen_editor</code> instance
* @example
* zen_coding.runActions('expand_abbreviation', zen_editor);
* zen_coding.runActions('wrap_with_abbreviation', [zen_editor, 'div']);
*/
runAction: function(name, args) {
if (!(args instanceof Array))
args = Array.prototype.slice.call(arguments, 1);
try {
if (name in this.actions)
return this.actions[name].apply(this, args);
} catch(e){
return false;
}
},
expandAbbreviation: function(abbr, type, profile) {
type = type || 'html';
var tree_root = this.parseIntoTree(abbr, type);
if (tree_root) {
var tree = rolloutTree(tree_root);
this.applyFilters(tree, type, profile, tree_root.filters);
return replaceVariables(tree.toString());
}
return '';
},
/**
* Extracts abbreviations from text stream, starting from the end
* @param {String} str
* @return {String} Abbreviation or empty string
*/
extractAbbreviation: function(str) {
var cur_offset = str.length,
start_index = -1,
brace_count = 0;
while (true) {
cur_offset--;
if (cur_offset < 0) {
// moved to the beginning of the line
start_index = 0;
break;
}
var ch = str.charAt(cur_offset);
if (ch == ']')
brace_count++;
else if (ch == '[')
brace_count--;
else {
if (brace_count)
// respect all characters inside attribute sets
continue;
else if (!isAllowedChar(ch) || (ch == '>' && isEndsWithTag(str.substring(0, cur_offset + 1)))) {
// found stop symbol
start_index = cur_offset + 1;
break;
}
}
}
if (start_index != -1)
// found somethind, return abbreviation
return str.substring(start_index);
else
return '';
},
/**
* Parses abbreviation into a node set
* @param {String} abbr Abbreviation
* @param {String} type Document type (xsl, html, etc.)
* @return {Tag}
*/
parseIntoTree: function(abbr, type) {
type = type || 'html';
// remove filters from abbreviation
var filter_list = '';
abbr = abbr.replace(/\|([\w\|\-]+)$/, function(str, p1){
filter_list = p1;
return '';
});
// split abbreviation by groups
var group_root = splitByGroups(abbr),
tree_root = new Tag('', 1, type);
// then recursively expand each group item
try {
for (var i = 0, il = group_root.children.length; i < il; i++) {
expandGroup(group_root.children[i], type, tree_root);
}
} catch(e) {
// there's invalid group, stop parsing
return null;
}
tree_root.filters = filter_list;
return tree_root;
},
/**
* Indents text with padding
* @param {String} text Text to indent
* @param {String|Number} pad Padding size (number) or padding itself (string)
* @return {String}
*/
padString: padString,
setupProfile: setupProfile,
getNewline: function(){
return newline;
},
setNewline: function(str) {
newline = str;
},
/**
* Wraps passed text with abbreviation. Text will be placed inside last
* expanded element
* @param {String} abbr Abbreviation
* @param {String} text Text to wrap
* @param {String} [type] Document type (html, xml, etc.). Default is 'html'
* @param {String} [profile] Output profile's name. Default is 'plain'
* @return {String}
*/
wrapWithAbbreviation: function(abbr, text, type, profile) {
type = type || 'html';
var tree_root = this.parseIntoTree(abbr, type);
if (tree_root) {
var repeat_elem = tree_root.multiply_elem || tree_root.last;
repeat_elem.setContent(text);
repeat_elem.repeat_by_lines = !!tree_root.multiply_elem;
var tree = rolloutTree(tree_root);
this.applyFilters(tree, type, profile, tree_root.filters);
return replaceVariables(tree.toString());
}
return null;
},
splitByLines: splitByLines,
/**
* Check if cursor is placed inside xHTML tag
* @param {String} html Contents of the document
* @param {Number} cursor_pos Current caret position inside tag
* @return {Boolean}
*/
isInsideTag: function(html, cursor_pos) {
var re_tag = /^<\/?\w[\w\:\-]*.*?>/;
// search left to find opening brace
var pos = cursor_pos;
while (pos > -1) {
if (html.charAt(pos) == '<')
break;
pos--;
}
if (pos != -1) {
var m = re_tag.exec(html.substring(pos));
if (m && cursor_pos > pos && cursor_pos < pos + m[0].length)
return true;
}
return false;
},
/**
* Returns caret placeholder
* @return {String}
*/
getCaretPlaceholder: function() {
return (typeof(caret_placeholder) != 'string')
? caret_placeholder()
: caret_placeholder
},
/**
* Set caret placeholder: a string (like '|') or function.
* You may use a function as a placeholder generator. For example,
* TextMate uses ${0}, ${1}, ..., ${n} natively for quick Tab-switching
* between them.
* @param {String|Function} value
*/
setCaretPlaceholder: function(value) {
caret_placeholder = value;
},
rolloutTree: rolloutTree,
/**
* Register new filter
* @param {String} name Filter name
* @param {Function} fn Filter function
*/
registerFilter: function(name, fn) {
filters[name] = fn;
},
/**
* Factory method that produces <code>ZenNode</code> instance
* @param {String} name Node name
* @param {Array} [attrs] Array of attributes as key/value objects
* @return {ZenNode}
*/
nodeFactory: function(name, attrs) {
return new ZenNode({name: name, attributes: attrs || []});
},
/**
* Applies filters to tree according to syntax
* @param {ZenNode} tree Tag tree to apply filters to
* @param {String} syntax Syntax name ('html', 'css', etc.)
* @param {String|Object} profile Profile or profile's name
* @param {String|Array} [additional_filters] List or pipe-separated
* string of additional filters to apply
*
* @return {ZenNode}
*/
applyFilters: function(tree, syntax, profile, additional_filters){
var _filters = getResource(syntax, 'filters') || basic_filters;
if (additional_filters)
_filters += '|' + ((typeof(additional_filters) == 'string')
? additional_filters
: additional_filters.join('|'));
if (!_filters)
// looks like unknown syntax, apply basic filters
_filters = basic_filters;
return runFilters(tree, profile, _filters);
},
runFilters: runFilters,
repeatString: repeatString,
getVariable: getVariable,
setVariable: function(name, value) {
zen_settings.variables[name] = value;
},
replaceVariables: replaceVariables,
/**
* Escapes special characters used in Zen Coding, like '$', '|', etc.
* Use this method before passing to actions like "Wrap with Abbreviation"
* to make sure that existing spacial characters won't be altered
* @param {String} text
* @return {String}
*/
escapeText: function(text) {
return text.replace(/([\$\|\\])/g, '\\$1');
},
/**
* Unescapes special characters used in Zen Coding, like '$', '|', etc.
* @param {String} text
* @return {String}
*/
unescapeText: function(text) {
return text.replace(/\\(.)/g, '$1');
},
/**
* Replaces '$' character in string assuming it might be escaped with '\'
* @param {String} str
* @param {String|Number} value
* @return {String}
*/
replaceCounter: function(str, value) {
var symbol = '$';
value = String(value);
return replaceUnescapedSymbol(str, symbol, function(str, symbol, pos, match_num){
if (str.charAt(pos + 1) == '{') {
// it's a variable, skip it
return false;
}
// replace sequense of $ symbols with padded number
var j = pos + 1;
while(str.charAt(j) == '$' && str.charAt(j + 1) != '{') j++;
return [str.substring(pos, j), zeroPadString(value, j - pos)];
});
},
/**
* Get profile by it's name. If profile wasn't found, returns 'plain'
* profile
*/
getProfile: function(name) {
return (name in profiles) ? profiles[name] : profiles['plain'];
},
settings_parser: (function(){
/**
* Unified object for parsed data
*/
function entry(type, key, value) {
return {
type: type,
key: key,
value: value
};
}
/** Regular expression for XML tag matching */
var re_tag = /^<(\w+\:?[\w\-]*)((?:\s+[\w\:\-]+\s*=\s*(['"]).*?\3)*)\s*(\/?)>/,
re_attrs = /([\w\-]+)\s*=\s*(['"])(.*?)\2/g;
/**
* Make expando from string
* @param {String} key
* @param {String} value
* @return {Object}
*/
function makeExpando(key, value) {
return entry(TYPE_EXPANDO, key, value);
}
/**
* Make abbreviation from string
* @param {String} key Abbreviation key
* @param {String} tag_name Expanded element's tag name
* @param {String} attrs Expanded element's attributes
* @param {Boolean} is_empty Is expanded element empty or not
* @return {Object}
*/
function makeAbbreviation(key, tag_name, attrs, is_empty) {
var result = {
name: tag_name,
is_empty: Boolean(is_empty)
};
if (attrs) {
var m;
result.attributes = [];
while (m = re_attrs.exec(attrs)) {
result.attributes.push({
name: m[1],
value: m[3]
});
}
}
return entry(TYPE_ABBREVIATION, key, result);
}
/**
* Parses all abbreviations inside object
* @param {Object} obj
*/
function parseAbbreviations(obj) {
for (var key in obj) {
var value = obj[key], m;
key = trim(key);
if (key.substr(-1) == '+') {
// this is expando, leave 'value' as is
obj[key] = makeExpando(key, value);
} else if (m = re_tag.exec(value)) {
obj[key] = makeAbbreviation(key, m[1], m[2], m[4] == '/');
} else {
// assume it's reference to another abbreviation
obj[key] = entry(TYPE_REFERENCE, key, value);
}
}
}
return {
/**
* Parse user's settings
* @param {Object} settings
*/
parse: function(settings) {
for (var p in settings) {
if (p == 'abbreviations')
parseAbbreviations(settings[p]);
else if (p == 'extends') {
var ar = settings[p].split(',');
for (var i = 0; i < ar.length; i++)
ar[i] = trim(ar[i]);
settings[p] = ar;
}
else if (typeof(settings[p]) == 'object')
arguments.callee(settings[p]);
}
},
extend: function(parent, child) {
for (var p in child) {
if (typeof(child[p]) == 'object' && parent.hasOwnProperty(p))
arguments.callee(parent[p], child[p]);
else
parent[p] = child[p];
}
},
/**
* Create hash maps on certain string properties
* @param {Object} obj
*/
createMaps: function(obj) {
for (var p in obj) {
if (p == 'element_types') {
for (var k in obj[p])
obj[p][k] = stringToHash(obj[p][k]);
} else if (typeof(obj[p]) == 'object') {
arguments.callee(obj[p]);
}
}
},
TYPE_ABBREVIATION: TYPE_ABBREVIATION,
TYPE_EXPANDO: TYPE_EXPANDO,
/** Reference to another abbreviation or tag */
TYPE_REFERENCE: TYPE_REFERENCE
}
})()
}
})();
if ('zen_settings' in this || zen_settings) {
// first we need to expand some strings into hashes
zen_coding.settings_parser.createMaps(zen_settings);
if ('my_zen_settings' in this) {
// we need to extend default settings with user's
zen_coding.settings_parser.createMaps(my_zen_settings);
zen_coding.settings_parser.extend(zen_settings, my_zen_settings);
}
// now we need to parse final set of settings
zen_coding.settings_parser.parse(zen_settings);
}
/**
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*/
(function(){
// Regular Expressions for parsing tags and attributes
var start_tag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
end_tag = /^<\/([\w\:\-]+)[^>]*>/,
attr = /([\w\-:]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
// Empty Elements - HTML 4.01
var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
// Block Elements - HTML 4.01
var block = makeMap("address,applet,blockquote,button,center,dd,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
// Inline Elements - HTML 4.01
var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
// Elements that you can, intentionally, leave open
// (and which close themselves)
var close_self = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
/** Current matching mode */
var cur_mode = 'xhtml';
/** Last matched HTML pair */
var last_match = {
opening_tag: null, // tag() or comment() object
closing_tag: null, // tag() or comment() object
start_ix: -1,
end_ix: -1
};
function setMode(new_mode) {
if (!new_mode || new_mode != 'html')
new_mode = 'xhtml';
cur_mode = new_mode;
}
function tag(match, ix) {
var name = match[1].toLowerCase();
return {
name: name,
full_tag: match[0],
start: ix,
end: ix + match[0].length,
unary: Boolean(match[3]) || (name in empty && cur_mode == 'html'),
has_close: Boolean(match[3]),
type: 'tag',
close_self: (name in close_self && cur_mode == 'html')
};
}
function comment(start, end) {
return {
start: start,
end: end,
type: 'comment'
};
}
function makeMap(str){
var obj = {}, items = str.split(",");
for ( var i = 0; i < items.length; i++ )
obj[ items[i] ] = true;
return obj;
}
/**
* Makes selection ranges for matched tag pair
* @param {tag} opening_tag
* @param {tag} closing_tag
* @param {Number} ix
*/
function makeRange(opening_tag, closing_tag, ix) {
ix = ix || 0;
var start_ix = -1,
end_ix = -1;
if (opening_tag && !closing_tag) { // unary element
start_ix = opening_tag.start;
end_ix = opening_tag.end;
} else if (opening_tag && closing_tag) { // complete element
if (
(opening_tag.start < ix && opening_tag.end > ix) ||
(closing_tag.start <= ix && closing_tag.end > ix)
) {
start_ix = opening_tag.start;
end_ix = closing_tag.end;
} else {
start_ix = opening_tag.end;
end_ix = closing_tag.start;
}
}
return [start_ix, end_ix];
}
/**
* Save matched tag for later use and return found indexes
* @param {tag} opening_tag
* @param {tag} closing_tag
* @param {Number} ix
* @return {Array}
*/
function saveMatch(opening_tag, closing_tag, ix) {
ix = ix || 0;
last_match.opening_tag = opening_tag;
last_match.closing_tag = closing_tag;
var range = makeRange(opening_tag, closing_tag, ix);
last_match.start_ix = range[0];
last_match.end_ix = range[1];
return last_match.start_ix != -1 ? [last_match.start_ix, last_match.end_ix] : null;
}
/**
* Handle unary tag: find closing tag if needed
* @param {String} text
* @param {Number} ix
* @param {tag} open_tag
* @return {tag|null} Closing tag (or null if not found)
*/
function handleUnaryTag(text, ix, open_tag) {
if (open_tag.has_close)
return null;
else {
// TODO finish this method
}
}
/**
* Search for matching tags in <code>html</code>, starting from
* <code>start_ix</code> position
* @param {String} html Code to search
* @param {Number} start_ix Character index where to start searching pair
* (commonly, current caret position)
* @param {Function} action Function that creates selection range
* @return {Array|null}
*/
function findPair(html, start_ix, mode, action) {
action = action || makeRange;
setMode(mode);
var forward_stack = [],
backward_stack = [],
/** @type {tag()} */
opening_tag = null,
/** @type {tag()} */
closing_tag = null,
range = null,
html_len = html.length,
m,
ix,
tmp_tag;
forward_stack.last = backward_stack.last = function() {
return this[this.length - 1];
}
function hasMatch(str, start) {
if (arguments.length == 1)
start = ix;
return html.substr(start, str.length) == str;
}
function searchCommentStart(from) {
while (from--) {
if (html.charAt(from) == '<' && hasMatch('<!--', from))
break;
}
return from;
}
// find opening tag
ix = start_ix;
while (ix-- && ix >= 0) {
var ch = html.charAt(ix);
if (ch == '<') {
var check_str = html.substring(ix, html_len);
if ( (m = check_str.match(end_tag)) ) { // found closing tag
tmp_tag = tag(m, ix);
if (tmp_tag.start < start_ix && tmp_tag.end > start_ix) // direct hit on searched closing tag
closing_tag = tmp_tag;
else
backward_stack.push(tmp_tag);
} else if ( (m = check_str.match(start_tag)) ) { // found opening tag
tmp_tag = tag(m, ix);
if (tmp_tag.unary) {
if (tmp_tag.start < start_ix && tmp_tag.end > start_ix) // exact match
// TODO handle unary tag
return action(tmp_tag, null, start_ix);
} else if (backward_stack.last() && backward_stack.last().name == tmp_tag.name) {
backward_stack.pop();
} else { // found nearest unclosed tag
opening_tag = tmp_tag;
break;
}
} else if (check_str.indexOf('<!--') == 0) { // found comment start
var end_ix = check_str.search('-->') + ix + 3;
if (ix < start_ix && end_ix >= start_ix)
return action( comment(ix, end_ix) );
}
} else if (ch == '-' && hasMatch('-->')) { // found comment end
// search left until comment start is reached
ix = searchCommentStart(ix);
}
}
if (!opening_tag)
return action(null);
// find closing tag
if (!closing_tag) {
for (ix = start_ix; ix < html_len; ix++) {
var ch = html.charAt(ix);
if (ch == '<') {
var check_str = html.substring(ix, html_len);
if ( (m = check_str.match(start_tag)) ) { // found opening tag
tmp_tag = tag(m, ix);
if (!tmp_tag.unary)
forward_stack.push( tmp_tag );
} else if ( (m = check_str.match(end_tag)) ) { // found closing tag
var tmp_tag = tag(m, ix);
if (forward_stack.last() && forward_stack.last().name == tmp_tag.name)
forward_stack.pop();
else { // found matched closing tag
closing_tag = tmp_tag;
break;
}
} else if (hasMatch('<!--')) { // found comment
ix += check_str.search('-->') + 3;
}
} else if (ch == '-' && hasMatch('-->')) {
// looks like cursor was inside comment with invalid HTML
if (!forward_stack.last() || forward_stack.last().type != 'comment') {
var end_ix = ix + 3;
return action(comment( searchCommentStart(ix), end_ix ));
}
}
}
}
return action(opening_tag, closing_tag, start_ix);
}
/**
* Search for matching tags in <code>html</code>, starting
* from <code>start_ix</code> position. The result is automatically saved in
* <code>last_match</code> property
*
* @return {Array|null}
*/
var HTMLPairMatcher = this.HTMLPairMatcher = function(/* String */ html, /* Number */ start_ix, /* */ mode){
return findPair(html, start_ix, mode, saveMatch);
}
HTMLPairMatcher.start_tag = start_tag;
HTMLPairMatcher.end_tag = end_tag;
/**
* Search for matching tags in <code>html</code>, starting from
* <code>start_ix</code> position. The difference between
* <code>HTMLPairMatcher</code> function itself is that <code>find</code>
* method doesn't save matched result in <code>last_match</code> property.
* This method is generally used for lookups
*/
HTMLPairMatcher.find = function(html, start_ix, mode) {
return findPair(html, start_ix, mode);
};
/**
* Search for matching tags in <code>html</code>, starting from
* <code>start_ix</code> position. The difference between
* <code>HTMLPairMatcher</code> function itself is that <code>getTags</code>
* method doesn't save matched result in <code>last_match</code> property
* and returns array of opening and closing tags
* This method is generally used for lookups
*/
HTMLPairMatcher.getTags = function(html, start_ix, mode) {
return findPair(html, start_ix, mode, function(opening_tag, closing_tag){
return [opening_tag, closing_tag];
});
};
HTMLPairMatcher.last_match = last_match;
try {
zen_coding.html_matcher = HTMLPairMatcher;
} catch(e){}
})();/**
* Comment important tags (with 'id' and 'class' attributes)
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*/
(function(){
/**
* Add comments to tag
* @param {ZenNode} node
*/
function addComments(node, i) {
var id_attr = node.getAttribute('id'),
class_attr = node.getAttribute('class'),
nl = zen_coding.getNewline();
if (id_attr || class_attr) {
var comment_str = '',
padding = (node.parent) ? node.parent.padding : '';
if (id_attr) comment_str += '#' + id_attr;
if (class_attr) comment_str += '.' + class_attr;
node.start = node.start.replace(/</, '<!-- ' + comment_str + ' -->' + nl + padding + '<');
node.end = node.end.replace(/>/, '>' + nl + padding + '<!-- /' + comment_str + ' -->');
// replace counters
node.start = zen_coding.replaceCounter(node.start, i + 1);
node.end = zen_coding.replaceCounter(node.end, i + 1);
}
}
function process(tree, profile) {
if (profile.tag_nl === false)
return tree;
for (var i = 0, il = tree.children.length; i < il; i++) {
/** @type {ZenNode} */
var item = tree.children[i];
if (item.isBlock())
addComments(item, i);
process(item, profile);
}
return tree;
}
zen_coding.registerFilter('c', process);
})();/**
* Filter for escaping unsafe XML characters: <, >, &
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*/
(function(){
var char_map = {
'<': '&lt;',
'>': '&gt;',
'&': '&amp;'
}
function escapeChars(str) {
return str.replace(/([<>&])/g, function(str, p1){
return char_map[p1];
});
}
function process(tree, profile, level) {
for (var i = 0, il = tree.children.length; i < il; i++) {
/** @type {ZenNode} */
var item = tree.children[i];
item.start = escapeChars(item.start);
item.end = escapeChars(item.end);
process(item);
}
return tree;
}
zen_coding.registerFilter('e', process);
})();/**
* Format CSS properties: add space after property name:
* padding:0; → padding: 0;
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*/
(function(){
function process(tree, profile) {
for (var i = 0, il = tree.children.length; i < il; i++) {
/** @type {ZenNode} */
var item = tree.children[i];
// CSS properties are always snippets
if (item.type == 'snippet') {
item.start = item.start.replace(/([\w\-]+\s*:)\s*/, '$1 ');
}
process(item, profile);
}
return tree;
}
zen_coding.registerFilter('fc', process);
})();/**
* Generic formatting filter: creates proper indentation for each tree node,
* placing "%s" placeholder where the actual output should be. You can use
* this filter to preformat tree and then replace %s placeholder to whatever you
* need. This filter should't be called directly from editor as a part
* of abbreviation.
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*
* @include "../zen_coding.js"
*/
(function(){
var child_token = '${child}',
placeholder = '%s';
function getNewline() {
return zen_coding.getNewline();
}
function getIndentation() {
return zen_coding.getVariable('indentation');
}
/**
* Test if passed node has block-level sibling element
* @param {ZenNode} item
* @return {Boolean}
*/
function hasBlockSibling(item) {
return (item.parent && item.parent.hasBlockChildren());
}
/**
* Test if passed itrem is very first child of the whole tree
* @param {ZenNode} tree
*/
function isVeryFirstChild(item) {
return item.parent && !item.parent.parent && !item.previousSibling;
}
/**
* Need to add line break before element
* @param {ZenNode} node
* @param {Object} profile
* @return {Boolean}
*/
function shouldBreakLine(node, profile) {
if (!profile.inline_break)
return false;
// find toppest non-inline sibling
while (node.previousSibling && node.previousSibling.isInline())
node = node.previousSibling;
if (!node.isInline())
return false;
// calculate how many inline siblings we have
var node_count = 1;
while (node = node.nextSibling) {
if (node.isInline())
node_count++;
else
break;
}
return node_count >= profile.inline_break;
}
/**
* Need to add newline because <code>item</code> has too many inline children
* @param {ZenNode} node
* @param {Object} profile
*/
function shouldBreakChild(node, profile) {
// we need to test only one child element, because
// hasBlockChildren() method will do the rest
return (node.children.length && shouldBreakLine(node.children[0], profile));
}
/**
* Processes element with <code>snippet</code> type
* @param {ZenNode} item
* @param {Object} profile
* @param {Number} [level] Depth level
*/
function processSnippet(item, profile, level) {
var data = item.source.value;
if (!data)
// snippet wasn't found, process it as tag
return processTag(item, profile, level);
item.start = item.end = placeholder;
var padding = (item.parent)
? item.parent.padding
: zen_coding.repeatString(getIndentation(), level);
if (!isVeryFirstChild(item)) {
item.start = getNewline() + padding + item.start;
}
// adjust item formatting according to last line of <code>start</code> property
var parts = data.split(child_token),
lines = zen_coding.splitByLines(parts[0] || ''),
padding_delta = getIndentation();
if (lines.length > 1) {
var m = lines[lines.length - 1].match(/^(\s+)/);
if (m)
padding_delta = m[1];
}
item.padding = padding + padding_delta;
return item;
}
/**
* Processes element with <code>tag</code> type
* @param {ZenNode} item
* @param {Object} profile
* @param {Number} [level] Depth level
*/
function processTag(item, profile, level) {
if (!item.name)
// looks like it's a root element
return item;
item.start = item.end = placeholder;
var is_unary = (item.isUnary() && !item.children.length);
// formatting output
if (profile.tag_nl !== false) {
var padding = (item.parent)
? item.parent.padding
: zen_coding.repeatString(getIndentation(), level),
force_nl = (profile.tag_nl === true),
should_break = shouldBreakLine(item, profile);
// formatting block-level elements
if (( (item.isBlock() || should_break) && item.parent) || force_nl) {
// snippet children should take different formatting
if (!item.parent || (item.parent.type != 'snippet' && !isVeryFirstChild(item)))
item.start = getNewline() + padding + item.start;
if (item.hasBlockChildren() || shouldBreakChild(item, profile) || (force_nl && !is_unary))
item.end = getNewline() + padding + item.end;
if (item.hasTagsInContent() || (force_nl && !item.hasChildren() && !is_unary))
item.start += getNewline() + padding + getIndentation();
} else if (item.isInline() && hasBlockSibling(item) && !isVeryFirstChild(item)) {
item.start = getNewline() + padding + item.start;
}
item.padding = padding + getIndentation();
}
return item;
}
/**
* Processes simplified tree, making it suitable for output as HTML structure
* @param {ZenNode} tree
* @param {Object} profile
* @param {Number} [level] Depth level
*/
function process(tree, profile, level) {
level = level || 0;
for (var i = 0, il = tree.children.length; i < il; i++) {
/** @type {ZenNode} */
var item = tree.children[i];
item = (item.type == 'tag')
? processTag(item, profile, level)
: processSnippet(item, profile, level);
if (item.content)
item.content = zen_coding.padString(item.content, item.padding);
process(item, profile, level + 1);
}
return tree;
}
zen_coding.registerFilter('_format', process);
})();/**
* Filter that produces HAML tree
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*
* @include "../zen_coding.js"
*/
(function(){
var child_token = '${child}';
/**
* Creates HTML attributes string from tag according to profile settings
* @param {ZenNode} tag
* @param {default_profile} profile
*/
function makeAttributesString(tag, profile) {
// make attribute string
var attrs = '',
attr_quote = profile.attr_quotes == 'single' ? "'" : '"',
cursor = profile.place_cursor ? zen_coding.getCaretPlaceholder() : '',
attr_name,
i,
a;
// use short notation for ID and CLASS attributes
for (i = 0; i < tag.attributes.length; i++) {
a = tag.attributes[i];
switch (a.name.toLowerCase()) {
case 'id':
attrs += '#' + (a.value || cursor);
break;
case 'class':
attrs += '.' + (a.value || cursor);
break;
}
}
var other_attrs = [];
// process other attributes
for (i = 0; i < tag.attributes.length; i++) {
a = tag.attributes[i];
var attr_name_lower = a.name.toLowerCase();
if (attr_name_lower != 'id' && attr_name_lower != 'class') {
attr_name = (profile.attr_case == 'upper') ? a.name.toUpperCase() : attr_name_lower;
other_attrs.push(':' +attr_name + ' => ' + attr_quote + (a.value || cursor) + attr_quote);
}
}
if (other_attrs.length)
attrs += '{' + other_attrs.join(', ') + '}';
return attrs;
}
/**
* Processes element with <code>snippet</code> type
* @param {ZenNode} item
* @param {Object} profile
* @param {Number} [level] Depth level
*/
function processSnippet(item, profile, level) {
var data = item.source.value;
if (!data)
// snippet wasn't found, process it as tag
return processTag(item, profile, level);
var parts = data.split(child_token),
start = parts[0] || '',
end = parts[1] || '',
padding = item.parent ? item.parent.padding : '';
item.start = item.start.replace('%s', zen_coding.padString(start, padding));
item.end = item.end.replace('%s', zen_coding.padString(end, padding));
return item;
}
/**
* Test if passed node has block-level sibling element
* @param {ZenNode} item
* @return {Boolean}
*/
function hasBlockSibling(item) {
return (item.parent && item.parent.hasBlockChildren());
}
/**
* Processes element with <code>tag</code> type
* @param {ZenNode} item
* @param {Object} profile
* @param {Number} [level] Depth level
*/
function processTag(item, profile, level) {
if (!item.name)
// looks like it's root element
return item;
var attrs = makeAttributesString(item, profile),
content = '',
cursor = profile.place_cursor ? zen_coding.getCaretPlaceholder() : '',
self_closing = '',
is_unary = (item.isUnary() && !item.children.length),
start= '',
end = '';
if (profile.self_closing_tag && is_unary)
self_closing = '/';
// define tag name
var tag_name = '%' + ((profile.tag_case == 'upper') ? item.name.toUpperCase() : item.name.toLowerCase());
if (tag_name.toLowerCase() == '%div' && attrs && attrs.indexOf('{') == -1)
// omit div tag
tag_name = '';
item.end = '';
start = tag_name + attrs + self_closing;
var placeholder = '%s';
// We can't just replace placeholder with new value because
// JavaScript will treat double $ character as a single one, assuming
// we're using RegExp literal.
var pos = item.start.indexOf(placeholder);
item.start = item.start.substring(0, pos) + start + item.start.substring(pos + placeholder.length);
if (!item.children.length && !is_unary)
item.start += cursor;
return item;
}
/**
* Processes simplified tree, making it suitable for output as HTML structure
* @param {ZenNode} tree
* @param {Object} profile
* @param {Number} [level] Depth level
*/
function process(tree, profile, level) {
level = level || 0;
if (level == 0)
// preformat tree
tree = zen_coding.runFilters(tree, profile, '_format');
for (var i = 0, il = tree.children.length; i < il; i++) {
/** @type {ZenNode} */
var item = tree.children[i];
item = (item.type == 'tag')
? processTag(item, profile, level)
: processSnippet(item, profile, level);
// replace counters
item.start = zen_coding.replaceCounter(item.start, i + 1);
item.end = zen_coding.replaceCounter(item.end, i + 1);
process(item, profile, level + 1);
}
return tree;
}
zen_coding.registerFilter('haml', process);
})();/**
* Filter that produces HTML tree
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*
* @include "../zen_coding.js"
*/
(function(){
var child_token = '${child}';
/**
* Creates HTML attributes string from tag according to profile settings
* @param {ZenNode} tag
* @param {default_profile} profile
*/
function makeAttributesString(tag, profile) {
// make attribute string
var attrs = '',
attr_quote = profile.attr_quotes == 'single' ? "'" : '"',
cursor = profile.place_cursor ? zen_coding.getCaretPlaceholder() : '',
attr_name;
for (var i = 0; i < tag.attributes.length; i++) {
var a = tag.attributes[i];
attr_name = (profile.attr_case == 'upper') ? a.name.toUpperCase() : a.name.toLowerCase();
attrs += ' ' + attr_name + '=' + attr_quote + (a.value || cursor) + attr_quote;
}
return attrs;
}
/**
* Processes element with <code>snippet</code> type
* @param {ZenNode} item
* @param {Object} profile
* @param {Number} [level] Depth level
*/
function processSnippet(item, profile, level) {
var data = item.source.value;
if (!data)
// snippet wasn't found, process it as tag
return processTag(item, profile, level);
var parts = data.split(child_token),
start = parts[0] || '',
end = parts[1] || '',
padding = item.parent ? item.parent.padding : '';
item.start = item.start.replace('%s', zen_coding.padString(start, padding));
item.end = item.end.replace('%s', zen_coding.padString(end, padding));
return item;
}
/**
* Test if passed node has block-level sibling element
* @param {ZenNode} item
* @return {Boolean}
*/
function hasBlockSibling(item) {
return (item.parent && item.parent.hasBlockChildren());
}
/**
* Processes element with <code>tag</code> type
* @param {ZenNode} item
* @param {Object} profile
* @param {Number} [level] Depth level
*/
function processTag(item, profile, level) {
if (!item.name)
// looks like it's root element
return item;
var attrs = makeAttributesString(item, profile),
content = '',
cursor = profile.place_cursor ? zen_coding.getCaretPlaceholder() : '',
self_closing = '',
is_unary = (item.isUnary() && !item.children.length),
start= '',
end = '';
if (profile.self_closing_tag == 'xhtml')
self_closing = ' /';
else if (profile.self_closing_tag === true)
self_closing = '/';
// define opening and closing tags
var tag_name = (profile.tag_case == 'upper') ? item.name.toUpperCase() : item.name.toLowerCase();
if (is_unary) {
start = '<' + tag_name + attrs + self_closing + '>';
item.end = '';
} else {
start = '<' + tag_name + attrs + '>';
end = '</' + tag_name + '>';
}
var placeholder = '%s';
// We can't just replace placeholder with new value because
// JavaScript will treat double $ character as a single one, assuming
// we're using RegExp literal.
var pos = item.start.indexOf(placeholder);
item.start = item.start.substring(0, pos) + start + item.start.substring(pos + placeholder.length);
pos = item.end.indexOf(placeholder);
item.end = item.end.substring(0, pos) + end + item.end.substring(pos + placeholder.length);
if (!item.children.length && !is_unary)
item.start += cursor;
return item;
}
/**
* Processes simplified tree, making it suitable for output as HTML structure
* @param {ZenNode} tree
* @param {Object} profile
* @param {Number} [level] Depth level
*/
function process(tree, profile, level) {
level = level || 0;
if (level == 0)
tree = zen_coding.runFilters(tree, profile, '_format');
for (var i = 0, il = tree.children.length; i < il; i++) {
/** @type {ZenNode} */
var item = tree.children[i];
item = (item.type == 'tag')
? processTag(item, profile, level)
: processSnippet(item, profile, level);
// replace counters
item.start = zen_coding.replaceCounter(item.start, i + 1);
item.end = zen_coding.replaceCounter(item.end, i + 1);
process(item, profile, level + 1);
}
return tree;
}
zen_coding.registerFilter('html', process);
})();/**
* Filter for trimming "select" attributes from some tags that contains
* child elements
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*/
(function(){
var tags = {
'xsl:variable': 1,
'xsl:with-param': 1
};
/**
* Removes "select" attribute from node
* @param {ZenNode} node
*/
function trimAttribute(node) {
node.start = node.start.replace(/\s+select\s*=\s*(['"]).*?\1/, '');
}
function process(tree) {
for (var i = 0, il = tree.children.length; i < il; i++) {
/** @type {ZenNode} */
var item = tree.children[i];
if (item.type == 'tag' && item.name.toLowerCase() in tags && item.children.length)
trimAttribute(item);
process(item);
}
}
zen_coding.registerFilter('xsl', process);
})();
return zen_coding;
};