Guest User

Stack Overflow: Zen.Coding ascension operator

a guest
Feb 8th, 2012
487
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. var zen_textarea = (function(){
  2. /**
  3.  * Zen Coding settings
  4.  * @author Sergey Chikuyonok ([email protected])
  5.  * @link http://chikuyonok.ru
  6.  */
  7. var zen_settings = {
  8.     /**
  9.      * Variables that can be placed inside snippets or abbreviations as ${variable}
  10.      * ${child} variable is reserved, don't use it
  11.      */
  12.     'variables': {
  13.         'lang': 'en',
  14.         'locale': 'en-US',
  15.         'charset': 'UTF-8',
  16.        
  17.         /** Inner element indentation */
  18.         'indentation': '\t',
  19.        
  20.         // newline variables, useful for wrapping
  21.         'newline': '\n',
  22.         'nl': '\n'
  23.     },
  24.    
  25.     'css': {
  26.         'filters': 'html,css',
  27.         'snippets': {
  28.             "@i": "@import url(|);",
  29.             "@m": "@media print {\n\t|\n}",
  30.             "@f": "@font-face {\n\tfont-family:|;\n\tsrc:url(|);\n}",
  31.             "!": "!important",
  32.             "pos": "position:|;",
  33.             "pos:s": "position:static;",
  34.             "pos:a": "position:absolute;",
  35.             "pos:r": "position:relative;",
  36.             "pos:f": "position:fixed;",
  37.             "t": "top:|;",
  38.             "t:a": "top:auto;",
  39.             "r": "right:|;",
  40.             "r:a": "right:auto;",
  41.             "b": "bottom:|;",
  42.             "b:a": "bottom:auto;",
  43.             "brad": "-webkit-border-radius: ${1:radius};\n-moz-border-radius: $1;\n-ms-border-radius: $1;\nborder-radius: $1;",
  44.             "bsha": "-webkit-box-shadow: ${1:hoff} ${2:voff} ${3:blur} ${4:rgba(0,0,0,0.5)};\n-moz-box-shadow: $1 $2 $3 $4;\n-ms-box-shadow: $1 $2 $3 $4;\nbox-shadow: $1 $2 $3 $4;",
  45.             "l": "left:|;",
  46.             "l:a": "left:auto;",
  47.             "z": "z-index:|;",
  48.             "z:a": "z-index:auto;",
  49.             "fl": "float:|;",
  50.             "fl:n": "float:none;",
  51.             "fl:l": "float:left;",
  52.             "fl:r": "float:right;",
  53.             "cl": "clear:|;",
  54.             "cl:n": "clear:none;",
  55.             "cl:l": "clear:left;",
  56.             "cl:r": "clear:right;",
  57.             "cl:b": "clear:both;",
  58.             "d": "display:|;",
  59.             "d:n": "display:none;",
  60.             "d:b": "display:block;",
  61.             "d:i": "display:inline;",
  62.             "d:ib": "display:inline-block;",
  63.             "d:li": "display:list-item;",
  64.             "d:ri": "display:run-in;",
  65.             "d:cp": "display:compact;",
  66.             "d:tb": "display:table;",
  67.             "d:itb": "display:inline-table;",
  68.             "d:tbcp": "display:table-caption;",
  69.             "d:tbcl": "display:table-column;",
  70.             "d:tbclg": "display:table-column-group;",
  71.             "d:tbhg": "display:table-header-group;",
  72.             "d:tbfg": "display:table-footer-group;",
  73.             "d:tbr": "display:table-row;",
  74.             "d:tbrg": "display:table-row-group;",
  75.             "d:tbc": "display:table-cell;",
  76.             "d:rb": "display:ruby;",
  77.             "d:rbb": "display:ruby-base;",
  78.             "d:rbbg": "display:ruby-base-group;",
  79.             "d:rbt": "display:ruby-text;",
  80.             "d:rbtg": "display:ruby-text-group;",
  81.             "v": "visibility:|;",
  82.             "v:v": "visibility:visible;",
  83.             "v:h": "visibility:hidden;",
  84.             "v:c": "visibility:collapse;",
  85.             "ov": "overflow:|;",
  86.             "ov:v": "overflow:visible;",
  87.             "ov:h": "overflow:hidden;",
  88.             "ov:s": "overflow:scroll;",
  89.             "ov:a": "overflow:auto;",
  90.             "ovx": "overflow-x:|;",
  91.             "ovx:v": "overflow-x:visible;",
  92.             "ovx:h": "overflow-x:hidden;",
  93.             "ovx:s": "overflow-x:scroll;",
  94.             "ovx:a": "overflow-x:auto;",
  95.             "ovy": "overflow-y:|;",
  96.             "ovy:v": "overflow-y:visible;",
  97.             "ovy:h": "overflow-y:hidden;",
  98.             "ovy:s": "overflow-y:scroll;",
  99.             "ovy:a": "overflow-y:auto;",
  100.             "ovs": "overflow-style:|;",
  101.             "ovs:a": "overflow-style:auto;",
  102.             "ovs:s": "overflow-style:scrollbar;",
  103.             "ovs:p": "overflow-style:panner;",
  104.             "ovs:m": "overflow-style:move;",
  105.             "ovs:mq": "overflow-style:marquee;",
  106.             "zoo": "zoom:1;",
  107.             "cp": "clip:|;",
  108.             "cp:a": "clip:auto;",
  109.             "cp:r": "clip:rect(|);",
  110.             "bxz": "box-sizing:|;",
  111.             "bxz:cb": "box-sizing:content-box;",
  112.             "bxz:bb": "box-sizing:border-box;",
  113.             "bxsh": "box-shadow:|;",
  114.             "bxsh:n": "box-shadow:none;",
  115.             "bxsh:w": "-webkit-box-shadow:0 0 0 #000;",
  116.             "bxsh:m": "-moz-box-shadow:0 0 0 0 #000;",
  117.             "m": "margin:|;",
  118.             "m:a": "margin:auto;",
  119.             "m:0": "margin:0;",
  120.             "m:2": "margin:0 0;",
  121.             "m:3": "margin:0 0 0;",
  122.             "m:4": "margin:0 0 0 0;",
  123.             "mt": "margin-top:|;",
  124.             "mt:a": "margin-top:auto;",
  125.             "mr": "margin-right:|;",
  126.             "mr:a": "margin-right:auto;",
  127.             "mb": "margin-bottom:|;",
  128.             "mb:a": "margin-bottom:auto;",
  129.             "ml": "margin-left:|;",
  130.             "ml:a": "margin-left:auto;",
  131.             "p": "padding:|;",
  132.             "p:0": "padding:0;",
  133.             "p:2": "padding:0 0;",
  134.             "p:3": "padding:0 0 0;",
  135.             "p:4": "padding:0 0 0 0;",
  136.             "pt": "padding-top:|;",
  137.             "pr": "padding-right:|;",
  138.             "pb": "padding-bottom:|;",
  139.             "pl": "padding-left:|;",
  140.             "w": "width:|;",
  141.             "w:a": "width:auto;",
  142.             "h": "height:|;",
  143.             "h:a": "height:auto;",
  144.             "maw": "max-width:|;",
  145.             "maw:n": "max-width:none;",
  146.             "mah": "max-height:|;",
  147.             "mah:n": "max-height:none;",
  148.             "miw": "min-width:|;",
  149.             "mih": "min-height:|;",
  150.             "o": "outline:|;",
  151.             "o:n": "outline:none;",
  152.             "oo": "outline-offset:|;",
  153.             "ow": "outline-width:|;",
  154.             "os": "outline-style:|;",
  155.             "oc": "outline-color:#000;",
  156.             "oc:i": "outline-color:invert;",
  157.             "bd": "border:|;",
  158.             "bd+": "border:1px solid #000;",
  159.             "bd:n": "border:none;",
  160.             "bdbk": "border-break:|;",
  161.             "bdbk:c": "border-break:close;",
  162.             "bdcl": "border-collapse:|;",
  163.             "bdcl:c": "border-collapse:collapse;",
  164.             "bdcl:s": "border-collapse:separate;",
  165.             "bdc": "border-color:#000;",
  166.             "bdi": "border-image:url(|);",
  167.             "bdi:n": "border-image:none;",
  168.             "bdi:w": "-webkit-border-image:url(|) 0 0 0 0 stretch stretch;",
  169.             "bdi:m": "-moz-border-image:url(|) 0 0 0 0 stretch stretch;",
  170.             "bdti": "border-top-image:url(|);",
  171.             "bdti:n": "border-top-image:none;",
  172.             "bdri": "border-right-image:url(|);",
  173.             "bdri:n": "border-right-image:none;",
  174.             "bdbi": "border-bottom-image:url(|);",
  175.             "bdbi:n": "border-bottom-image:none;",
  176.             "bdli": "border-left-image:url(|);",
  177.             "bdli:n": "border-left-image:none;",
  178.             "bdci": "border-corner-image:url(|);",
  179.             "bdci:n": "border-corner-image:none;",
  180.             "bdci:c": "border-corner-image:continue;",
  181.             "bdtli": "border-top-left-image:url(|);",
  182.             "bdtli:n": "border-top-left-image:none;",
  183.             "bdtli:c": "border-top-left-image:continue;",
  184.             "bdtri": "border-top-right-image:url(|);",
  185.             "bdtri:n": "border-top-right-image:none;",
  186.             "bdtri:c": "border-top-right-image:continue;",
  187.             "bdbri": "border-bottom-right-image:url(|);",
  188.             "bdbri:n": "border-bottom-right-image:none;",
  189.             "bdbri:c": "border-bottom-right-image:continue;",
  190.             "bdbli": "border-bottom-left-image:url(|);",
  191.             "bdbli:n": "border-bottom-left-image:none;",
  192.             "bdbli:c": "border-bottom-left-image:continue;",
  193.             "bdf": "border-fit:|;",
  194.             "bdf:c": "border-fit:clip;",
  195.             "bdf:r": "border-fit:repeat;",
  196.             "bdf:sc": "border-fit:scale;",
  197.             "bdf:st": "border-fit:stretch;",
  198.             "bdf:ow": "border-fit:overwrite;",
  199.             "bdf:of": "border-fit:overflow;",
  200.             "bdf:sp": "border-fit:space;",
  201.             "bdl": "border-length:|;",
  202.             "bdl:a": "border-length:auto;",
  203.             "bdsp": "border-spacing:|;",
  204.             "bds": "border-style:|;",
  205.             "bds:n": "border-style:none;",
  206.             "bds:h": "border-style:hidden;",
  207.             "bds:dt": "border-style:dotted;",
  208.             "bds:ds": "border-style:dashed;",
  209.             "bds:s": "border-style:solid;",
  210.             "bds:db": "border-style:double;",
  211.             "bds:dtds": "border-style:dot-dash;",
  212.             "bds:dtdtds": "border-style:dot-dot-dash;",
  213.             "bds:w": "border-style:wave;",
  214.             "bds:g": "border-style:groove;",
  215.             "bds:r": "border-style:ridge;",
  216.             "bds:i": "border-style:inset;",
  217.             "bds:o": "border-style:outset;",
  218.             "bdw": "border-width:|;",
  219.             "bdt": "border-top:|;",
  220.             "bdt+": "border-top:1px solid #000;",
  221.             "bdt:n": "border-top:none;",
  222.             "bdtw": "border-top-width:|;",
  223.             "bdts": "border-top-style:|;",
  224.             "bdts:n": "border-top-style:none;",
  225.             "bdtc": "border-top-color:#000;",
  226.             "bdr": "border-right:|;",
  227.             "bdr+": "border-right:1px solid #000;",
  228.             "bdr:n": "border-right:none;",
  229.             "bdrw": "border-right-width:|;",
  230.             "bdrs": "border-right-style:|;",
  231.             "bdrs:n": "border-right-style:none;",
  232.             "bdrc": "border-right-color:#000;",
  233.             "bdb": "border-bottom:|;",
  234.             "bdb+": "border-bottom:1px solid #000;",
  235.             "bdb:n": "border-bottom:none;",
  236.             "bdbw": "border-bottom-width:|;",
  237.             "bdbs": "border-bottom-style:|;",
  238.             "bdbs:n": "border-bottom-style:none;",
  239.             "bdbc": "border-bottom-color:#000;",
  240.             "bdl": "border-left:|;",
  241.             "bdl+": "border-left:1px solid #000;",
  242.             "bdl:n": "border-left:none;",
  243.             "bdlw": "border-left-width:|;",
  244.             "bdls": "border-left-style:|;",
  245.             "bdls:n": "border-left-style:none;",
  246.             "bdlc": "border-left-color:#000;",
  247.             "bdrs": "border-radius:|;",
  248.             "bdtrrs": "border-top-right-radius:|;",
  249.             "bdtlrs": "border-top-left-radius:|;",
  250.             "bdbrrs": "border-bottom-right-radius:|;",
  251.             "bdblrs": "border-bottom-left-radius:|;",
  252.             "bg": "background:|;",
  253.             "bg+": "background:#FFF url(|) 0 0 no-repeat;",
  254.             "bg:n": "background:none;",
  255.             "bg:ie": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='${1:x}.png',sizingMethod='${2:crop}');",
  256.             "bgc": "background-color:#FFF;",
  257.             "bgi": "background-image:url(|);",
  258.             "bgi:n": "background-image:none;",
  259.             "bgr": "background-repeat:|;",
  260.             "bgr:n": "background-repeat:no-repeat;",
  261.             "bgr:x": "background-repeat:repeat-x;",
  262.             "bgr:y": "background-repeat:repeat-y;",
  263.             "bga": "background-attachment:|;",
  264.             "bga:f": "background-attachment:fixed;",
  265.             "bga:s": "background-attachment:scroll;",
  266.             "bgp": "background-position:0 0;",
  267.             "bgpx": "background-position-x:|;",
  268.             "bgpy": "background-position-y:|;",
  269.             "bgbk": "background-break:|;",
  270.             "bgbk:bb": "background-break:bounding-box;",
  271.             "bgbk:eb": "background-break:each-box;",
  272.             "bgbk:c": "background-break:continuous;",
  273.             "bgcp": "background-clip:|;",
  274.             "bgcp:bb": "background-clip:border-box;",
  275.             "bgcp:pb": "background-clip:padding-box;",
  276.             "bgcp:cb": "background-clip:content-box;",
  277.             "bgcp:nc": "background-clip:no-clip;",
  278.             "bgo": "background-origin:|;",
  279.             "bgo:pb": "background-origin:padding-box;",
  280.             "bgo:bb": "background-origin:border-box;",
  281.             "bgo:cb": "background-origin:content-box;",
  282.             "bgz": "background-size:|;",
  283.             "bgz:a": "background-size:auto;",
  284.             "bgz:ct": "background-size:contain;",
  285.             "bgz:cv": "background-size:cover;",
  286.             "c": "color:#000;",
  287.             "tbl": "table-layout:|;",
  288.             "tbl:a": "table-layout:auto;",
  289.             "tbl:f": "table-layout:fixed;",
  290.             "cps": "caption-side:|;",
  291.             "cps:t": "caption-side:top;",
  292.             "cps:b": "caption-side:bottom;",
  293.             "ec": "empty-cells:|;",
  294.             "ec:s": "empty-cells:show;",
  295.             "ec:h": "empty-cells:hide;",
  296.             "lis": "list-style:|;",
  297.             "lis:n": "list-style:none;",
  298.             "lisp": "list-style-position:|;",
  299.             "lisp:i": "list-style-position:inside;",
  300.             "lisp:o": "list-style-position:outside;",
  301.             "list": "list-style-type:|;",
  302.             "list:n": "list-style-type:none;",
  303.             "list:d": "list-style-type:disc;",
  304.             "list:c": "list-style-type:circle;",
  305.             "list:s": "list-style-type:square;",
  306.             "list:dc": "list-style-type:decimal;",
  307.             "list:dclz": "list-style-type:decimal-leading-zero;",
  308.             "list:lr": "list-style-type:lower-roman;",
  309.             "list:ur": "list-style-type:upper-roman;",
  310.             "lisi": "list-style-image:|;",
  311.             "lisi:n": "list-style-image:none;",
  312.             "q": "quotes:|;",
  313.             "q:n": "quotes:none;",
  314.             "q:ru": "quotes:'\00AB' '\00BB' '\201E' '\201C';",
  315.             "q:en": "quotes:'\201C' '\201D' '\2018' '\2019';",
  316.             "ct": "content:|;",
  317.             "ct:n": "content:normal;",
  318.             "ct:oq": "content:open-quote;",
  319.             "ct:noq": "content:no-open-quote;",
  320.             "ct:cq": "content:close-quote;",
  321.             "ct:ncq": "content:no-close-quote;",
  322.             "ct:a": "content:attr(|);",
  323.             "ct:c": "content:counter(|);",
  324.             "ct:cs": "content:counters(|);",
  325.             "coi": "counter-increment:|;",
  326.             "cor": "counter-reset:|;",
  327.             "va": "vertical-align:|;",
  328.             "va:sup": "vertical-align:super;",
  329.             "va:t": "vertical-align:top;",
  330.             "va:tt": "vertical-align:text-top;",
  331.             "va:m": "vertical-align:middle;",
  332.             "va:bl": "vertical-align:baseline;",
  333.             "va:b": "vertical-align:bottom;",
  334.             "va:tb": "vertical-align:text-bottom;",
  335.             "va:sub": "vertical-align:sub;",
  336.             "ta": "text-align:|;",
  337.             "ta:l": "text-align:left;",
  338.             "ta:c": "text-align:center;",
  339.             "ta:r": "text-align:right;",
  340.             "tal": "text-align-last:|;",
  341.             "tal:a": "text-align-last:auto;",
  342.             "tal:l": "text-align-last:left;",
  343.             "tal:c": "text-align-last:center;",
  344.             "tal:r": "text-align-last:right;",
  345.             "td": "text-decoration:|;",
  346.             "td:n": "text-decoration:none;",
  347.             "td:u": "text-decoration:underline;",
  348.             "td:o": "text-decoration:overline;",
  349.             "td:l": "text-decoration:line-through;",
  350.             "te": "text-emphasis:|;",
  351.             "te:n": "text-emphasis:none;",
  352.             "te:ac": "text-emphasis:accent;",
  353.             "te:dt": "text-emphasis:dot;",
  354.             "te:c": "text-emphasis:circle;",
  355.             "te:ds": "text-emphasis:disc;",
  356.             "te:b": "text-emphasis:before;",
  357.             "te:a": "text-emphasis:after;",
  358.             "th": "text-height:|;",
  359.             "th:a": "text-height:auto;",
  360.             "th:f": "text-height:font-size;",
  361.             "th:t": "text-height:text-size;",
  362.             "th:m": "text-height:max-size;",
  363.             "ti": "text-indent:|;",
  364.             "ti:-": "text-indent:-9999px;",
  365.             "tj": "text-justify:|;",
  366.             "tj:a": "text-justify:auto;",
  367.             "tj:iw": "text-justify:inter-word;",
  368.             "tj:ii": "text-justify:inter-ideograph;",
  369.             "tj:ic": "text-justify:inter-cluster;",
  370.             "tj:d": "text-justify:distribute;",
  371.             "tj:k": "text-justify:kashida;",
  372.             "tj:t": "text-justify:tibetan;",
  373.             "to": "text-outline:|;",
  374.             "to+": "text-outline:0 0 #000;",
  375.             "to:n": "text-outline:none;",
  376.             "tr": "text-replace:|;",
  377.             "tr:n": "text-replace:none;",
  378.             "tt": "text-transform:|;",
  379.             "tt:n": "text-transform:none;",
  380.             "tt:c": "text-transform:capitalize;",
  381.             "tt:u": "text-transform:uppercase;",
  382.             "tt:l": "text-transform:lowercase;",
  383.             "tw": "text-wrap:|;",
  384.             "tw:n": "text-wrap:normal;",
  385.             "tw:no": "text-wrap:none;",
  386.             "tw:u": "text-wrap:unrestricted;",
  387.             "tw:s": "text-wrap:suppress;",
  388.             "tsh": "text-shadow:|;",
  389.             "tsh+": "text-shadow:0 0 0 #000;",
  390.             "tsh:n": "text-shadow:none;",
  391.             "lh": "line-height:|;",
  392.             "whs": "white-space:|;",
  393.             "whs:n": "white-space:normal;",
  394.             "whs:p": "white-space:pre;",
  395.             "whs:nw": "white-space:nowrap;",
  396.             "whs:pw": "white-space:pre-wrap;",
  397.             "whs:pl": "white-space:pre-line;",
  398.             "whsc": "white-space-collapse:|;",
  399.             "whsc:n": "white-space-collapse:normal;",
  400.             "whsc:k": "white-space-collapse:keep-all;",
  401.             "whsc:l": "white-space-collapse:loose;",
  402.             "whsc:bs": "white-space-collapse:break-strict;",
  403.             "whsc:ba": "white-space-collapse:break-all;",
  404.             "wob": "word-break:|;",
  405.             "wob:n": "word-break:normal;",
  406.             "wob:k": "word-break:keep-all;",
  407.             "wob:l": "word-break:loose;",
  408.             "wob:bs": "word-break:break-strict;",
  409.             "wob:ba": "word-break:break-all;",
  410.             "wos": "word-spacing:|;",
  411.             "wow": "word-wrap:|;",
  412.             "wow:nm": "word-wrap:normal;",
  413.             "wow:n": "word-wrap:none;",
  414.             "wow:u": "word-wrap:unrestricted;",
  415.             "wow:s": "word-wrap:suppress;",
  416.             "lts": "letter-spacing:|;",
  417.             "f": "font:|;",
  418.             "f+": "font:1em Arial,sans-serif;",
  419.             "fw": "font-weight:|;",
  420.             "fw:n": "font-weight:normal;",
  421.             "fw:b": "font-weight:bold;",
  422.             "fw:br": "font-weight:bolder;",
  423.             "fw:lr": "font-weight:lighter;",
  424.             "fs": "font-style:|;",
  425.             "fs:n": "font-style:normal;",
  426.             "fs:i": "font-style:italic;",
  427.             "fs:o": "font-style:oblique;",
  428.             "fv": "font-variant:|;",
  429.             "fv:n": "font-variant:normal;",
  430.             "fv:sc": "font-variant:small-caps;",
  431.             "fz": "font-size:|;",
  432.             "fza": "font-size-adjust:|;",
  433.             "fza:n": "font-size-adjust:none;",
  434.             "ff": "font-family:|;",
  435.             "ff:s": "font-family:serif;",
  436.             "ff:ss": "font-family:sans-serif;",
  437.             "ff:c": "font-family:cursive;",
  438.             "ff:f": "font-family:fantasy;",
  439.             "ff:m": "font-family:monospace;",
  440.             "fef": "font-effect:|;",
  441.             "fef:n": "font-effect:none;",
  442.             "fef:eg": "font-effect:engrave;",
  443.             "fef:eb": "font-effect:emboss;",
  444.             "fef:o": "font-effect:outline;",
  445.             "fem": "font-emphasize:|;",
  446.             "femp": "font-emphasize-position:|;",
  447.             "femp:b": "font-emphasize-position:before;",
  448.             "femp:a": "font-emphasize-position:after;",
  449.             "fems": "font-emphasize-style:|;",
  450.             "fems:n": "font-emphasize-style:none;",
  451.             "fems:ac": "font-emphasize-style:accent;",
  452.             "fems:dt": "font-emphasize-style:dot;",
  453.             "fems:c": "font-emphasize-style:circle;",
  454.             "fems:ds": "font-emphasize-style:disc;",
  455.             "fsm": "font-smooth:|;",
  456.             "fsm:a": "font-smooth:auto;",
  457.             "fsm:n": "font-smooth:never;",
  458.             "fsm:aw": "font-smooth:always;",
  459.             "fst": "font-stretch:|;",
  460.             "fst:n": "font-stretch:normal;",
  461.             "fst:uc": "font-stretch:ultra-condensed;",
  462.             "fst:ec": "font-stretch:extra-condensed;",
  463.             "fst:c": "font-stretch:condensed;",
  464.             "fst:sc": "font-stretch:semi-condensed;",
  465.             "fst:se": "font-stretch:semi-expanded;",
  466.             "fst:e": "font-stretch:expanded;",
  467.             "fst:ee": "font-stretch:extra-expanded;",
  468.             "fst:ue": "font-stretch:ultra-expanded;",
  469.             "op": "opacity:|;",
  470.             "op:ie": "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);",
  471.             "op:ms": "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';",
  472.             "rz": "resize:|;",
  473.             "rz:n": "resize:none;",
  474.             "rz:b": "resize:both;",
  475.             "rz:h": "resize:horizontal;",
  476.             "rz:v": "resize:vertical;",
  477.             "cur": "cursor:|;",
  478.             "cur:a": "cursor:auto;",
  479.             "cur:d": "cursor:default;",
  480.             "cur:c": "cursor:crosshair;",
  481.             "cur:ha": "cursor:hand;",
  482.             "cur:he": "cursor:help;",
  483.             "cur:m": "cursor:move;",
  484.             "cur:p": "cursor:pointer;",
  485.             "cur:t": "cursor:text;",
  486.             "pgbb": "page-break-before:|;",
  487.             "pgbb:au": "page-break-before:auto;",
  488.             "pgbb:al": "page-break-before:always;",
  489.             "pgbb:l": "page-break-before:left;",
  490.             "pgbb:r": "page-break-before:right;",
  491.             "pgbi": "page-break-inside:|;",
  492.             "pgbi:au": "page-break-inside:auto;",
  493.             "pgbi:av": "page-break-inside:avoid;",
  494.             "pgba": "page-break-after:|;",
  495.             "pgba:au": "page-break-after:auto;",
  496.             "pgba:al": "page-break-after:always;",
  497.             "pgba:l": "page-break-after:left;",
  498.             "pgba:r": "page-break-after:right;",
  499.             "orp": "orphans:|;",
  500.             "wid": "widows:|;"
  501.         }
  502.     },
  503.    
  504.     'html': {
  505.         'filters': 'html',
  506.         'snippets': {
  507.             'cc:ie6': '<!--[if lte IE 6]>\n\t${child}|\n<![endif]-->',
  508.             'cc:ie': '<!--[if IE]>\n\t${child}|\n<![endif]-->',
  509.             'cc:noie': '<!--[if !IE]><!-->\n\t${child}|\n<!--<![endif]-->',
  510.             'html:4t': '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' +
  511.                     '<html lang="${lang}">\n' +
  512.                     '<head>\n' +
  513.                     '   <meta http-equiv="Content-Type" content="text/html;charset=${charset}">\n' +
  514.                     '   <title></title>\n' +
  515.                     '</head>\n' +
  516.                     '<body>\n\t${child}|\n</body>\n' +
  517.                     '</html>',
  518.            
  519.             'html:4s': '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' +
  520.                     '<html lang="${lang}">\n' +
  521.                     '<head>\n' +
  522.                     '   <meta http-equiv="Content-Type" content="text/html;charset=${charset}">\n' +
  523.                     '   <title></title>\n' +
  524.                     '</head>\n' +
  525.                     '<body>\n\t${child}|\n</body>\n' +
  526.                     '</html>',
  527.            
  528.             'html:xt': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' +
  529.                     '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${lang}">\n' +
  530.                     '<head>\n' +
  531.                     '   <meta http-equiv="Content-Type" content="text/html;charset=${charset}" />\n' +
  532.                     '   <title></title>\n' +
  533.                     '</head>\n' +
  534.                     '<body>\n\t${child}|\n</body>\n' +
  535.                     '</html>',
  536.            
  537.             'html:xs': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +
  538.                     '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${lang}">\n' +
  539.                     '<head>\n' +
  540.                     '   <meta http-equiv="Content-Type" content="text/html;charset=${charset}" />\n' +
  541.                     '   <title></title>\n' +
  542.                     '</head>\n' +
  543.                     '<body>\n\t${child}|\n</body>\n' +
  544.                     '</html>',
  545.            
  546.             'html:xxs': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' +
  547.                     '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${lang}">\n' +
  548.                     '<head>\n' +
  549.                     '   <meta http-equiv="Content-Type" content="text/html;charset=${charset}" />\n' +
  550.                     '   <title></title>\n' +
  551.                     '</head>\n' +
  552.                     '<body>\n\t${child}|\n</body>\n' +
  553.                     '</html>',
  554.            
  555.             'html:5': '<!DOCTYPE HTML>\n' +
  556.                     '<html lang="${locale}">\n' +
  557.                     '<head>\n' +
  558.                     '   <meta charset="${charset}">\n' +
  559.                     '   <title></title>\n' +
  560.                     '</head>\n' +
  561.                     '<body>\n\t${child}|\n</body>\n' +
  562.                     '</html>'
  563.         },
  564.        
  565.         'abbreviations': {
  566.             'a': '<a href="">',
  567.             'a:link': '<a href="http://|">',
  568.             'a:mail': '<a href="mailto:|">',
  569.             'abbr': '<abbr title="">',
  570.             'acronym': '<acronym title="">',
  571.             'base': '<base href="" />',
  572.             'bdo': '<bdo dir="">',
  573.             'bdo:r': '<bdo dir="rtl">',
  574.             'bdo:l': '<bdo dir="ltr">',
  575.             'link:css': '<link rel="stylesheet" type="text/css" href="${1:style}.css" media="all" />',
  576.             'link:print': '<link rel="stylesheet" type="text/css" href="|print.css" media="print" />',
  577.             'link:favicon': '<link rel="shortcut icon" type="image/x-icon" href="|favicon.ico" />',
  578.             'link:touch': '<link rel="apple-touch-icon" href="|favicon.png" />',
  579.             'link:rss': '<link rel="alternate" type="application/rss+xml" title="RSS" href="|rss.xml" />',
  580.             'link:atom': '<link rel="alternate" type="application/atom+xml" title="Atom" href="atom.xml" />',
  581.             'meta:utf': '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />',
  582.             'meta:win': '<meta http-equiv="Content-Type" content="text/html;charset=windows-1251" />',
  583.             'meta:compat': '<meta http-equiv="X-UA-Compatible" content="IE=7" />',
  584.             'style': '<style type="text/css">',
  585.             'script': '<script type="text/javascript">',
  586.             'script:src': '<script type="text/javascript" src="">',
  587.             'img': '<img src="" alt="" />',
  588.             'iframe': '<iframe src="" frameborder="0">',
  589.             'embed': '<embed src="" type="" />',
  590.             'object': '<object data="" type="">',
  591.             'param': '<param name="" value="" />',
  592.             'map': '<map name="">',
  593.             'area': '<area shape="" coords="" href="" alt="" />',
  594.             'area:d': '<area shape="default" href="" alt="" />',
  595.             'area:c': '<area shape="circle" coords="" href="" alt="" />',
  596.             'area:r': '<area shape="rect" coords="" href="" alt="" />',
  597.             'area:p': '<area shape="poly" coords="" href="" alt="" />',
  598.             'link': '<link rel="stylesheet" href="" />',
  599.             'form': '<form action="">',
  600.             'form:get': '<form action="" method="get">',
  601.             'form:post': '<form action="" method="post">',
  602.             'label': '<label for="">',
  603.             'input': '<input type="" />',
  604.             'input:hidden': '<input type="hidden" name="" />',
  605.             'input:h': '<input type="hidden" name="" />',
  606.             'input:text': '<input type="text" name="" id="" />',
  607.             'input:t': '<input type="text" name="" id="" />',
  608.             'input:search': '<input type="search" name="" id="" />',
  609.             'input:email': '<input type="email" name="" id="" />',
  610.             'input:url': '<input type="url" name="" id="" />',
  611.             'input:password': '<input type="password" name="" id="" />',
  612.             'input:p': '<input type="password" name="" id="" />',
  613.             'input:datetime': '<input type="datetime" name="" id="" />',
  614.             'input:date': '<input type="date" name="" id="" />',
  615.             'input:datetime-local': '<input type="datetime-local" name="" id="" />',
  616.             'input:month': '<input type="month" name="" id="" />',
  617.             'input:week': '<input type="week" name="" id="" />',
  618.             'input:time': '<input type="time" name="" id="" />',
  619.             'input:number': '<input type="number" name="" id="" />',
  620.             'input:color': '<input type="color" name="" id="" />',
  621.             'input:checkbox': '<input type="checkbox" name="" id="" />',
  622.             'input:c': '<input type="checkbox" name="" id="" />',
  623.             'input:radio': '<input type="radio" name="" id="" />',
  624.             'input:r': '<input type="radio" name="" id="" />',
  625.             'input:range': '<input type="range" name="" id="" />',
  626.             'input:file': '<input type="file" name="" id="" />',
  627.             'input:f': '<input type="file" name="" id="" />',
  628.             'input:submit': '<input type="submit" value="" />',
  629.             'input:s': '<input type="submit" value="" />',
  630.             'input:image': '<input type="image" src="" alt="" />',
  631.             'input:i': '<input type="image" src="" alt="" />',
  632.             'input:reset': '<input type="reset" value="" />',
  633.             'input:button': '<input type="button" value="" />',
  634.             'input:b': '<input type="button" value="" />',
  635.             'select': '<select name="" id=""></select>',
  636.             'option': '<option value=""></option>',
  637.             'textarea': '<textarea name="" id="" cols="30" rows="10">',
  638.             'menu:context': '<menu type="context">',
  639.             'menu:c': '<menu type="context">',
  640.             'menu:toolbar': '<menu type="toolbar">',
  641.             'menu:t': '<menu type="toolbar">',
  642.             'video': '<video src="">',
  643.             'audio': '<audio src="">',
  644.             'html:xml': '<html xmlns="http://www.w3.org/1999/xhtml">',
  645.             'bq': '<blockquote>',
  646.             'acr': '<acronym>',
  647.             'fig': '<figure>',
  648.             'ifr': '<iframe>',
  649.             'emb': '<embed>',
  650.             'obj': '<object>',
  651.             'src': '<source>',
  652.             'cap': '<caption>',
  653.             'colg': '<colgroup>',
  654.             'fst': '<fieldset>',
  655.             'btn': '<button>',
  656.             'optg': '<optgroup>',
  657.             'opt': '<option>',
  658.             'tarea': '<textarea>',
  659.             'leg': '<legend>',
  660.             'sect': '<section>',
  661.             'art': '<article>',
  662.             'hdr': '<header>',
  663.             'ftr': '<footer>',
  664.             'adr': '<address>',
  665.             'dlg': '<dialog>',
  666.             'str': '<strong>',
  667.             'prog': '<progress>',
  668.             'fset': '<fieldset>',
  669.             'datag': '<datagrid>',
  670.             'datal': '<datalist>',
  671.             'kg': '<keygen>',
  672.             'out': '<output>',
  673.             'det': '<details>',
  674.             'cmd': '<command>',
  675.            
  676.             // expandos
  677.             'ol+': 'ol>li',
  678.             'ul+': 'ul>li',
  679.             'dl+': 'dl>dt+dd',
  680.             'map+': 'map>area',
  681.             'table+': 'table>tr>td',
  682.             'colgroup+': 'colgroup>col',
  683.             'colg+': 'colgroup>col',
  684.             'tr+': 'tr>td',
  685.             'select+': 'select>option',
  686.             'optgroup+': 'optgroup>option',
  687.             'optg+': 'optgroup>option'
  688.  
  689.         },
  690.        
  691.         'element_types': {
  692.             'empty': 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,keygen,command',
  693.             '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',
  694.             '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,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'
  695.         }
  696.     },
  697.    
  698.     'xml': {
  699.         'extends': 'html',
  700.         'filters': 'html'
  701.     },
  702.    
  703.     'xsl': {
  704.         'extends': 'html',
  705.         'filters': 'html, xsl',
  706.         'abbreviations': {
  707.             'tm': '<xsl:template match="" mode="">',
  708.             'tmatch': 'tm',
  709.             'tn': '<xsl:template name="">',
  710.             'tname': 'tn',
  711.             'xsl:when': '<xsl:when test="">',
  712.             'wh': 'xsl:when',
  713.             'var': '<xsl:variable name="">',
  714.             'vare': '<xsl:variable name="" select=""/>',
  715.             'if': '<xsl:if test="">',
  716.             'call': '<xsl:call-template name=""/>',
  717.             'attr': '<xsl:attribute name="">',
  718.             'wp': '<xsl:with-param name="" select=""/>',
  719.             'par': '<xsl:param name="" select=""/>',
  720.             'val': '<xsl:value-of select=""/>',
  721.             'co': '<xsl:copy-of select=""/>',
  722.             'each': '<xsl:for-each select="">',
  723.             'for': 'each',
  724.             'ap': '<xsl:apply-templates select="" mode=""/>',
  725.            
  726.             //expandos
  727.             'choose+': 'xsl:choose>xsl:when+xsl:otherwise'
  728.         }
  729.     },
  730.    
  731.     'haml': {
  732.         'filters': 'haml',
  733.         'extends': 'html'
  734.     }
  735. };/**
  736. * Parsed resources (snippets, abbreviations, variables, etc.) for Zen Coding.
  737. * Contains convenient method to get access for snippets with respect of
  738. * inheritance. Also provides abilitity to store data in different vocabularies
  739. * ('system' and 'user') for fast and safe resurce update
  740. * @author Sergey Chikuyonok ([email protected])
  741. * @link http://chikuyonok.ru
  742. */
  743. var zen_resources = (function(){
  744.     var TYPE_ABBREVIATION = 'zen-tag',
  745.         TYPE_EXPANDO = 'zen-expando',
  746.    
  747.         /** Reference to another abbreviation or tag */
  748.         TYPE_REFERENCE = 'zen-reference',
  749.        
  750.         VOC_SYSTEM = 'system',
  751.         VOC_USER = 'user',
  752.        
  753.         /** Regular expression for XML tag matching */
  754.         re_tag = /^<(\w+\:?[\w\-]*)((?:\s+[\w\:\-]+\s*=\s*(['"]).*?\3)*)\s*(\/?)>/,
  755.         re_attrs = /([\w\-]+)\s*=\s*(['"])(.*?)\2/g,
  756.        
  757.         system_settings = {},
  758.         user_settings = {};
  759.        
  760.     /**
  761.      * Trim whitespace from string
  762.      * @param {String} text
  763.      * @return {String}
  764.      */
  765.     function trim(text) {
  766.         return (text || "").replace( /^\s+|\s+$/g, "" );
  767.     }
  768.        
  769.     /**
  770.      * Check if specified resource is parsed by Zen Coding
  771.      * @param {Object} obj
  772.      * @return {Boolean}
  773.      */
  774.     function isParsed(obj) {
  775.         return obj && obj.__zen_parsed__;
  776.     }
  777.    
  778.     /**
  779.      * Marks object as parsed by Zen Coding
  780.      * @param {Object}
  781.      */
  782.     function setParsed(obj) {
  783.         obj.__zen_parsed__ = true;
  784.     }
  785.    
  786.     /**
  787.      * Returns resource vocabulary by its name
  788.      * @param {String} name Vocabulary name ('system' or 'user')
  789.      */
  790.     function getVocabulary(name) {
  791.         return name == VOC_SYSTEM ? system_settings : user_settings;
  792.     }
  793.        
  794.     /**
  795.      * Helper function that transforms string into hash
  796.      * @return {Object}
  797.      */
  798.     function stringToHash(str){
  799.         var obj = {}, items = str.split(",");
  800.         for ( var i = 0; i < items.length; i++ )
  801.             obj[ items[i] ] = true;
  802.         return obj;
  803.     }
  804.    
  805.     /**
  806.      * Creates resource inheritance chain for lookups
  807.      * @param {String} vocabulary Resource vocabulary
  808.      * @param {String} syntax Syntax name
  809.      * @param {String} name Resource name
  810.      * @return {Array}
  811.      */
  812.     function createResourceChain(vocabulary, syntax, name) {
  813.         var voc = getVocabulary(vocabulary),
  814.             result = [],
  815.             resource;
  816.        
  817.         if (voc && syntax in voc) {
  818.             resource = voc[syntax];
  819.             if (name in resource)
  820.                 result.push(resource[name]);
  821.         }
  822.        
  823.         // get inheritance definition
  824.         // in case of user-defined vocabulary, resource dependency
  825.         // may be defined in system vocabulary only, so we have to correctly
  826.         // handle this case
  827.         var chain_source;
  828.         if (resource && 'extends' in resource)
  829.             chain_source = resource;
  830.         else if (vocabulary == VOC_USER && syntax in system_settings
  831.             && 'extends' in system_settings[syntax] )
  832.             chain_source = system_settings[syntax];
  833.            
  834.         if (chain_source) {
  835.             if (!isParsed(chain_source['extends'])) {
  836.                 var ar = chain_source['extends'].split(',');
  837.                 for (var i = 0; i < ar.length; i++)
  838.                     ar[i] = trim(ar[i]);
  839.                 chain_source['extends'] = ar;
  840.                 setParsed(chain_source['extends']);
  841.             }
  842.            
  843.             // find resource in ancestors
  844.             for (var i = 0; i < chain_source['extends'].length; i++) {
  845.                 var type = chain_source['extends'][i];
  846.                 if (voc[type] && voc[type][name])
  847.                     result.push(voc[type][name]);
  848.             }
  849.         }
  850.        
  851.         return result;
  852.     }
  853.    
  854.     /**
  855.      * Get resource collection from settings vocbulary for specified syntax.
  856.      * It follows inheritance chain if resource wasn't directly found in
  857.      * syntax settings
  858.      * @param {String} vocabulary Resource vocabulary
  859.      * @param {String} syntax Syntax name
  860.      * @param {String} name Resource name
  861.      */
  862.     function getSubset(vocabulary, syntax, name) {
  863.         var chain = createResourceChain(vocabulary, syntax, name);
  864.         return chain[0];
  865.     }
  866.    
  867.     /**
  868.      * Returns parsed item located in specified vocabulary by its syntax and
  869.      * name
  870.      * @param {String} vocabulary Resource vocabulary
  871.      * @param {String} syntax Syntax name
  872.      * @param {String} name Resource name ('abbreviation', 'snippet')
  873.      * @param {String} item Abbreviation or snippet name
  874.      * @return {Object|null}
  875.      */
  876.     function getParsedItem(vocabulary, syntax, name, item) {
  877.         var chain = createResourceChain(vocabulary, syntax, name),
  878.             result = null,
  879.             res;
  880.            
  881.         for (var i = 0, il = chain.length; i < il; i++) {
  882.             res = chain[i];
  883.             if (item in res) {
  884.                 if (name == 'abbreviations' && !isParsed(res[item])) {
  885.                     // parse abbreviation
  886.                     var value = res[item];
  887.                     res[item] = parseAbbreviation(item, value);
  888.                     res[item].__ref = value;
  889.                     setParsed(res[item]);
  890.                 }
  891.                
  892.                 result = res[item];
  893.                 break;
  894.             }
  895.         }
  896.        
  897.         return result;
  898.     }
  899.    
  900.     /**
  901.      * Unified object for parsed data
  902.      */
  903.     function entry(type, key, value) {
  904.         return {
  905.             type: type,
  906.             key: key,
  907.             value: value
  908.         };
  909.     }
  910.    
  911.     /**
  912.      * Make expando from string
  913.      * @param {String} key
  914.      * @param {String} value
  915.      * @return {Object}
  916.      */
  917.     function makeExpando(key, value) {
  918.         return entry(TYPE_EXPANDO, key, value);
  919.     }
  920.    
  921.     /**
  922.      * Make abbreviation from string
  923.      * @param {String} key Abbreviation key
  924.      * @param {String} tag_name Expanded element's tag name
  925.      * @param {String} attrs Expanded element's attributes
  926.      * @param {Boolean} is_empty Is expanded element empty or not
  927.      * @return {Object}
  928.      */
  929.     function makeAbbreviation(key, tag_name, attrs, is_empty) {
  930.         var result = {
  931.             name: tag_name,
  932.             is_empty: !!is_empty
  933.         };
  934.        
  935.         if (attrs) {
  936.             var m;
  937.             result.attributes = [];
  938.             while (m = re_attrs.exec(attrs)) {
  939.                 result.attributes.push({
  940.                     name: m[1],
  941.                     value: m[3]
  942.                 });
  943.             }
  944.         }
  945.        
  946.         return entry(TYPE_ABBREVIATION, key, result);
  947.     }
  948.    
  949.     /**
  950.      * Parses single abbreviation
  951.      * @param {String} key Abbreviation name
  952.      * @param {String} value = Abbreviation value
  953.      * @return {Object}
  954.      */
  955.     function parseAbbreviation(key, value) {
  956.         key = trim(key);
  957.         var m;
  958.         if (key.substr(-1) == '+') {
  959.             // this is expando, leave 'value' as is
  960.             return makeExpando(key, value);
  961.         } else if (m = re_tag.exec(value)) {
  962.             return makeAbbreviation(key, m[1], m[2], m[4] == '/');
  963.         } else {
  964.             // assume it's reference to another abbreviation
  965.             return entry(TYPE_REFERENCE, key, value);
  966.         }
  967.     }
  968.    
  969.     return {
  970.         /**
  971.          * Sets new unparsed data for specified settings vocabulary
  972.          * @param {Object} data
  973.          * @param {String} type Vocabulary type ('system' or 'user')
  974.          */
  975.         setVocabulary: function(data, type) {
  976.             if (type == VOC_SYSTEM)
  977.                 system_settings = data;
  978.             else
  979.                 user_settings = data;
  980.         },
  981.        
  982.         /**
  983.          * Get data from specified vocabulary. Can contain parsed entities
  984.          * @param {String} name Vocabulary type ('system' or 'user')
  985.          * @return {Object}
  986.          */
  987.         getVocabulary: getVocabulary,
  988.        
  989.         /**
  990.          * Returns resource value from data set with respect of inheritance
  991.          * @param {String} syntax Resource syntax (html, css, ...)
  992.          * @param {String} name Resource name ('snippets' or 'abbreviation')
  993.          * @param {String} abbr Abbreviation name
  994.          * @return {Object|null}
  995.          */
  996.         getResource: function(syntax, name, item) {
  997.             return getParsedItem(VOC_USER, syntax, name, item)
  998.                 || getParsedItem(VOC_SYSTEM, syntax, name, item);
  999.         },
  1000.        
  1001.         /**
  1002.          * Returns abbreviation value from data set
  1003.          * @param {String} type Resource type (html, css, ...)
  1004.          * @param {String} name Abbreviation name
  1005.          * @return {Object|null}
  1006.          */
  1007.         getAbbreviation: function(type, name) {
  1008.             return this.getResource(type, 'abbreviations', name)
  1009.                 || this.getResource(type, 'abbreviations', name.replace(/\-/g, ':'));
  1010.         },
  1011.        
  1012.         /**
  1013.          * Returns snippet value from data set
  1014.          * @param {String} type Resource type (html, css, ...)
  1015.          * @param {String} name Snippet name
  1016.          * @return {Object|null}
  1017.          */
  1018.         getSnippet: function(type, name) {
  1019.             return this.getResource(type, 'snippets', name)
  1020.                 || this.getResource(type, 'snippets', name.replace(/\-/g, ':'));
  1021.         },
  1022.        
  1023.         /**
  1024.          * Returns variable value
  1025.          * @return {String}
  1026.          */
  1027.         getVariable: function(name) {
  1028.             return getSubset(VOC_USER, 'variables', name)
  1029.                 || getSubset(VOC_SYSTEM, 'variables', name);
  1030.         },
  1031.        
  1032.         /**
  1033.          * Returns resource subset from settings vocabulary
  1034.          * @param {String} syntax Syntax name
  1035.          * @param {String} name Resource name
  1036.          * @return {Object}
  1037.          */
  1038.         getSubset: function(syntax, name) {
  1039.             return getSubset(VOC_USER, syntax, name)
  1040.                 || getSubset(VOC_SYSTEM, syntax, name);
  1041.         },
  1042.        
  1043.         /**
  1044.          * Check if specified item exists in specified resource collection
  1045.          * (like 'empty', 'block_level')
  1046.          * @param {String} syntax
  1047.          * @param {String} collection Collection name
  1048.          * @param {String} item Item name
  1049.          */
  1050.         isItemInCollection: function(syntax, collection, item) {
  1051.             return item in this.getElementsCollection(getVocabulary(VOC_USER)[syntax], collection)
  1052.                 || item in this.getElementsCollection(getVocabulary(VOC_SYSTEM)[syntax], collection);
  1053.         },
  1054.        
  1055.         /**
  1056.          * Returns specified elements collection (like 'empty', 'block_level') from
  1057.          * <code>resource</code>. If collections wasn't found, returns empty object
  1058.          * @param {Object} resource
  1059.          * @param {String} type
  1060.          * @return {Object}
  1061.          */
  1062.         getElementsCollection: function(resource, type) {
  1063.             if (resource && resource.element_types) {
  1064.                 // if it's not parsed yet – do it
  1065.                 var res = resource.element_types;
  1066.                 if (!isParsed(res)) {
  1067.                     for (var p in res)
  1068.                         res[p] = stringToHash(res[p]);
  1069.                        
  1070.                     setParsed(res);
  1071.                 }
  1072.                 return res[type] || {}
  1073.             }
  1074.             else
  1075.                 return {};
  1076.         },
  1077.        
  1078.         /**
  1079.          * Check if there are resources for specified syntax
  1080.          * @param {String} syntax
  1081.          * @return {Boolean}
  1082.          */
  1083.         hasSyntax: function(syntax) {
  1084.             return syntax in getVocabulary(VOC_USER)
  1085.                 || syntax in getVocabulary(VOC_SYSTEM);
  1086.         }
  1087.     }
  1088. })();
  1089.  
  1090. try {
  1091.     zen_resources.setVocabulary(zen_settings, 'system');
  1092.     zen_resources.setVocabulary(my_zen_settings, 'user');
  1093. } catch(e) {}/**
  1094.  * Class that parses abbreviation into tree with respect of groups, attributes
  1095.  * and text nodes
  1096.  * @author Sergey Chikuyonok ([email protected])
  1097.  * @link http://chikuyonok.ru
  1098.  *
  1099.  * @include "zen_coding.js"
  1100.  */
  1101. var zen_parser = (function(){
  1102.    
  1103.     var re_valid_name = /^[\w\d\-_\$\:@!]+\+?$/i;
  1104.    
  1105.     /**
  1106.      * @class
  1107.      */
  1108.     function TreeNode(parent) {
  1109.         this.abbreviation = '';
  1110.         /** @type {TreeNode} */
  1111.         this.parent = null;
  1112.         this.children = [];
  1113.         this.count = 1;
  1114.         this.name = null;
  1115.         this.text = null;
  1116.         this.attributes = [];
  1117.         this.is_repeating = false;
  1118.         this.has_implict_name = false;
  1119.     }
  1120.    
  1121.     TreeNode.prototype = {
  1122.         /**
  1123.          * Adds passed or creates new child
  1124.          * @param {TreeNode} [child]
  1125.          * @return {TreeNode}
  1126.          */
  1127.         addChild: function(child) {
  1128.             child = child || new TreeNode;
  1129.             child.parent = this;
  1130.             this.children.push(child);
  1131.             return child;
  1132.         },
  1133.        
  1134.         /**
  1135.          * Replace current node in parent's child list with another node
  1136.          * @param {TreeNode} node
  1137.          */
  1138.         replace: function(node) {
  1139.             if (this.parent) {
  1140.                 var children = this.parent.children;
  1141.                 for (var i = 0, il = children.length; i < il; i++) {
  1142.                     if (children[i] === this) {
  1143.                         children[i] = node;
  1144.                         this.parent = null;
  1145.                         return;
  1146.                     }
  1147.                 }
  1148.             }
  1149.         },
  1150.        
  1151.         /**
  1152.          * Sets abbreviation that belongs to current node
  1153.          * @param {String} abbr
  1154.          */
  1155.         setAbbreviation: function(abbr) {
  1156.             this.abbreviation = abbr;
  1157.             var m = abbr.match(/\*(\d+)?$/);
  1158.             if (m) {
  1159.                 this.count = parseInt(m[1] || 1, 10);
  1160.                 this.is_repeating = !m[1];
  1161.                 abbr = abbr.substr(0, abbr.length - m[0].length);
  1162.             }
  1163.            
  1164.             if (abbr) {
  1165.                 var name_text = splitExpression(abbr);
  1166.                 var name = name_text[0];
  1167.                 if (name_text.length == 2)
  1168.                     this.text = name_text[1];
  1169.                    
  1170.                 if (name) {
  1171.                     var attr_result = parseAttributes(name);
  1172.                     this.name = attr_result[0] || 'div';
  1173.                     this.has_implict_name = !attr_result[0];
  1174.                     this.attributes = attr_result[1];
  1175.                 }
  1176.             }
  1177.            
  1178.             // validate name
  1179.             if (this.name && !re_valid_name.test(this.name)) {
  1180.                 throw new Error('InvalidAbbreviation');
  1181.             }
  1182.         },
  1183.        
  1184.         /**
  1185.          * @return {String}
  1186.          */
  1187.         getAbbreviation: function() {
  1188.             return this.expr;
  1189.         },
  1190.        
  1191.         /**
  1192.          * Dump current tree node into a foramtted string
  1193.          * @return {String}
  1194.          */
  1195.         toString: function(level) {
  1196.             level = level || 0;
  1197.             var output = '(empty)';
  1198.             if (this.abbreviation) {
  1199.                 output = '';
  1200.                 if (this.name)
  1201.                     output = this.name;
  1202.                    
  1203.                 if (this.text !== null)
  1204.                     output += (output ? ' ' : '') + '{text: "' + this.text + '"}';
  1205.                    
  1206.                 if (this.attributes.length) {
  1207.                     var attrs = [];
  1208.                     for (var i = 0, il = this.attributes.length; i < il; i++) {
  1209.                         attrs.push(this.attributes[i].name + '="' + this.attributes[i].value + '"');
  1210.                     }
  1211.                     output += ' [' + attrs.join(', ') + ']';
  1212.                 }
  1213.             }
  1214.             var result = zen_coding.repeatString('-', level)
  1215.                 + output
  1216.                 + '\n';
  1217.             for (var i = 0, il = this.children.length; i < il; i++) {
  1218.                 result += this.children[i].toString(level + 1);
  1219.             }
  1220.            
  1221.             return result;
  1222.         },
  1223.        
  1224.         /**
  1225.          * Check if current node contains children with empty <code>expr</code>
  1226.          * property
  1227.          * @return {Boolean}
  1228.          */
  1229.         hasEmptyChildren: function() {
  1230.             for (var i = 0, il = this.children.length; i < il; i++) {
  1231.                 if (this.children[i].isEmpty())
  1232.                     return true;
  1233.             }
  1234.            
  1235.             return false;
  1236.         },
  1237.        
  1238.         /**
  1239.          * @return {Boolean}
  1240.          */
  1241.         isEmpty: function() {
  1242.             return !this.abbreviation;
  1243.         },
  1244.        
  1245.         /**
  1246.          * Check if current node is a text-only node
  1247.          * @return {Boolean}
  1248.          */
  1249.         isTextNode: function() {
  1250.             return !this.name && this.text;
  1251.         }
  1252.     };
  1253.    
  1254.     /**
  1255.      * Check if character is numeric
  1256.      * @requires {Stirng} ch
  1257.      * @return {Boolean}
  1258.      */
  1259.     function isNumeric(ch) {
  1260.         if (typeof(ch) == 'string')
  1261.             ch = ch.charCodeAt(0);
  1262.            
  1263.         return (ch && ch > 47 && ch < 58);
  1264.     }
  1265.    
  1266.     /**
  1267.      * Optimizes tree node: replaces empty nodes with their children
  1268.      * @param {TreeNode} node
  1269.      * @return {TreeNode}
  1270.      */
  1271.     function squash(node) {
  1272.         for (var i = node.children.length - 1; i >=0; i--) {
  1273.             /** @type {TreeNode} */
  1274.             var n = node.children[i];
  1275.             if (n.isEmpty()) {
  1276.                 var args = [i, 1];
  1277.                 for (var j = 0, jl = n.children.length; j < jl; j++) {
  1278.                     args.push(n.children[j]);
  1279.                 }
  1280.                
  1281.                 Array.prototype.splice.apply(node.children, args);
  1282.             }
  1283.         }
  1284.        
  1285.         return node;
  1286.     }
  1287.    
  1288.     /**
  1289.      * Trim whitespace from string
  1290.      * @param {String} text
  1291.      * @return {String}
  1292.      */
  1293.     function trim(text) {
  1294.         return (text || "").replace( /^\s+|\s+$/g, "" );
  1295.     }
  1296.    
  1297.     /**
  1298.      * Get word, starting at <code>ix</code> character of <code>str</code>
  1299.      */
  1300.     function getWord(ix, str) {
  1301.         var m = str.substring(ix).match(/^[\w\-:\$]+/);
  1302.         return m ? m[0] : '';
  1303.     }
  1304.    
  1305.     /**
  1306.      * Extract attributes and their values from attribute set
  1307.      * @param {String} attr_set
  1308.      */
  1309.     function extractAttributes(attr_set) {
  1310.         attr_set = trim(attr_set);
  1311.         var loop_count = 100, // endless loop protection
  1312.             re_string = /^(["'])((?:(?!\1)[^\\]|\\.)*)\1/,
  1313.             result = [],
  1314.             attr;
  1315.            
  1316.         while (attr_set && loop_count--) {
  1317.             var attr_name = getWord(0, attr_set);
  1318.             attr = null;
  1319.             if (attr_name) {
  1320.                 attr = {name: attr_name, value: ''};
  1321. //              result[attr_name] = '';
  1322.                 // let's see if attribute has value
  1323.                 var ch = attr_set.charAt(attr_name.length);
  1324.                 switch (ch) {
  1325.                     case '=':
  1326.                         var ch2 = attr_set.charAt(attr_name.length + 1);
  1327.                         if (ch2 == '"' || ch2 == "'") {
  1328.                             // we have a quoted string
  1329.                             var m = attr_set.substring(attr_name.length + 1).match(re_string);
  1330.                             if (m) {
  1331.                                 attr.value = m[2];
  1332.                                 attr_set = trim(attr_set.substring(attr_name.length + m[0].length + 1));
  1333.                             } else {
  1334.                                 // something wrong, break loop
  1335.                                 attr_set = '';
  1336.                             }
  1337.                         } else {
  1338.                             // unquoted string
  1339.                             var m = attr_set.substring(attr_name.length + 1).match(/(.+?)(\s|$)/);
  1340.                             if (m) {
  1341.                                 attr.value = m[1];
  1342.                                 attr_set = trim(attr_set.substring(attr_name.length + m[1].length + 1));
  1343.                             } else {
  1344.                                 // something wrong, break loop
  1345.                                 attr_set = '';
  1346.                             }
  1347.                         }
  1348.                         break;
  1349.                     default:
  1350.                         attr_set = trim(attr_set.substring(attr_name.length));
  1351.                         break;
  1352.                 }
  1353.             } else {
  1354.                 // something wrong, can't extract attribute name
  1355.                 break;
  1356.             }
  1357.            
  1358.             if (attr) result.push(attr);
  1359.         }
  1360.         return result;
  1361.     }
  1362.    
  1363.     /**
  1364.      * Parses tag attributes extracted from abbreviation
  1365.      * @param {String} str
  1366.      */
  1367.     function parseAttributes(str) {
  1368.         /*
  1369.          * Example of incoming data:
  1370.          * #header
  1371.          * .some.data
  1372.          * .some.data#header
  1373.          * [attr]
  1374.          * #item[attr=Hello other="World"].class
  1375.          */
  1376.         var result = [],
  1377.             name = '',
  1378.             collect_name = true,
  1379.             class_name,
  1380.             char_map = {'#': 'id', '.': 'class'};
  1381.        
  1382.         // walk char-by-char
  1383.         var i = 0,
  1384.             il = str.length,
  1385.             val;
  1386.            
  1387.         while (i < il) {
  1388.             var ch = str.charAt(i);
  1389.             switch (ch) {
  1390.                 case '#': // id
  1391.                     val = getWord(i, str.substring(1));
  1392.                     result.push({name: char_map[ch], value: val});
  1393.                     i += val.length + 1;
  1394.                     collect_name = false;
  1395.                     break;
  1396.                 case '.': // class
  1397.                     val = getWord(i, str.substring(1));
  1398.                     if (!class_name) {
  1399.                         // remember object pointer for value modification
  1400.                         class_name = {name: char_map[ch], value: ''};
  1401.                         result.push(class_name);
  1402.                     }
  1403.                    
  1404.                     class_name.value += ((class_name.value) ? ' ' : '') + val;
  1405.                     i += val.length + 1;
  1406.                     collect_name = false;
  1407.                     break;
  1408.                 case '[': //begin attribute set
  1409.                     // search for end of set
  1410.                     var end_ix = str.indexOf(']', i);
  1411.                     if (end_ix == -1) {
  1412.                         // invalid attribute set, stop searching
  1413.                         i = str.length;
  1414.                     } else {
  1415.                         var attrs = extractAttributes(str.substring(i + 1, end_ix));
  1416.                         for (var j = 0, jl = attrs.length; j < jl; j++) {
  1417.                             result.push(attrs[j]);
  1418.                         }
  1419.                         i = end_ix;
  1420.                     }
  1421.                     collect_name = false;
  1422.                     break;
  1423.                 default:
  1424.                     if (collect_name)
  1425.                         name += ch;
  1426.                     i++;
  1427.             }
  1428.         }
  1429.        
  1430.         return [name, result];
  1431.     }
  1432.    
  1433.     /**
  1434.      * @param {TreeNode} node
  1435.      * @return {TreeNode}
  1436.      */
  1437.     function optimizeTree(node) {
  1438.         while (node.hasEmptyChildren())
  1439.             squash(node);
  1440.            
  1441.         for (var i = 0, il = node.children.length; i < il; i++) {
  1442.             optimizeTree(node.children[i]);
  1443.         }
  1444.        
  1445.         return node;
  1446.     }
  1447.    
  1448.     /**
  1449.      * Split expression by node name and its content, if exists. E.g. if we pass
  1450.      * <code>a{Text}</code> expression, it will be splitted into <code>a</code>
  1451.      * and <code>Text</code>
  1452.      * @param {String} expr
  1453.      * @return {Array} Result with one or two elements (if expression contains
  1454.      * text node)
  1455.      */
  1456.     function splitExpression(expr) {
  1457.         // fast test on text node
  1458.         if (expr.indexOf('{') == -1)
  1459.             return [expr];
  1460.            
  1461.         var attr_lvl = 0,
  1462.             text_lvl = 0,
  1463.             brace_stack = [],
  1464.             i = 0,
  1465.             il = expr.length,
  1466.             ch;
  1467.            
  1468.         while (i < il) {
  1469.             ch = expr.charAt(i);
  1470.             switch (ch) {
  1471.                 case '[':
  1472.                     if (!text_lvl)
  1473.                         attr_lvl++;
  1474.                     break;
  1475.                 case ']':
  1476.                     if (!text_lvl)
  1477.                         attr_lvl--;
  1478.                     break;
  1479.                 case '{':
  1480.                     if (!attr_lvl) {
  1481.                         text_lvl++;
  1482.                         brace_stack.push(i);
  1483.                     }
  1484.                     break;
  1485.                 case '}':
  1486.                     if (!attr_lvl) {
  1487.                         text_lvl--;
  1488.                         var brace_start = brace_stack.pop();
  1489.                         if (text_lvl === 0) {
  1490.                             // found braces bounds
  1491.                             return [
  1492.                                 expr.substring(0, brace_start),
  1493.                                 expr.substring(brace_start + 1, i)
  1494.                             ];
  1495.                         }
  1496.                     }
  1497.                     break;
  1498.             }
  1499.             i++;
  1500.         }
  1501.        
  1502.         // if we are here, then no valid text node found
  1503.         return [expr];
  1504.     }
  1505.    
  1506.    
  1507.     return {
  1508.         /**
  1509.          * Parses abbreviation into tree with respect of groups,
  1510.          * text nodes and attributes. Each node of the tree is a single
  1511.          * abbreviation. Tree represents actual structure of the outputted
  1512.          * result
  1513.          * @param {String} abbr Abbreviation to parse
  1514.          * @return {TreeNode}
  1515.          */
  1516.         parse: function(abbr) {
  1517.             var root = new TreeNode,
  1518.                 context = root.addChild(),
  1519.                 i = 0,
  1520.                 il = abbr.length,
  1521.                 text_lvl = 0,
  1522.                 attr_lvl = 0,
  1523.                 group_lvl = 0,
  1524.                 group_stack = [root],
  1525.                 ch, prev_ch,
  1526.                 token = '';
  1527.                
  1528.             group_stack.last = function() {
  1529.                 return this[this.length - 1];
  1530.             };
  1531.            
  1532.             var dumpToken = function() {
  1533.                 if (token)
  1534.                     context.setAbbreviation(token);
  1535.                 token = '';
  1536.             };
  1537.                
  1538.             while (i < il) {
  1539.                 ch = abbr.charAt(i);
  1540.                 prev_ch = i ? abbr.charAt(i - 1) : '';
  1541.                 switch (ch) {
  1542.                     case '{':
  1543.                         if (!attr_lvl)
  1544.                             text_lvl++;
  1545.                         token += ch;
  1546.                         break;
  1547.                     case '}':
  1548.                         if (!attr_lvl)
  1549.                             text_lvl--;
  1550.                         token += ch;
  1551.                         break;
  1552.                     case '[':
  1553.                         if (!text_lvl)
  1554.                             attr_lvl++;
  1555.                         token += ch;
  1556.                         break;
  1557.                     case ']':
  1558.                         if (!text_lvl)
  1559.                             attr_lvl--;
  1560.                         token += ch;
  1561.                         break;
  1562.                     case '(':
  1563.                         if (!text_lvl && !attr_lvl) {
  1564.                             // beginning of the new group
  1565.                             dumpToken();
  1566.                            
  1567.                             if (prev_ch != '+' && prev_ch != '>') {
  1568.                                 // previous char is not an operator, assume it's
  1569.                                 // a sibling
  1570.                                 context = context.parent.addChild();
  1571.                             }
  1572.                            
  1573.                             group_stack.push(context);
  1574.                             context = context.addChild();
  1575.                         } else {
  1576.                             token += ch;
  1577.                         }
  1578.                         break;
  1579.                     case ')':
  1580.                         if (!text_lvl && !attr_lvl) {
  1581.                             // end of the group, pop stack
  1582.                             dumpToken();
  1583.                             context = group_stack.pop();
  1584.                            
  1585.                             if (i < il - 1 && abbr.charAt(i + 1) == '*') {
  1586.                                 // group multiplication
  1587.                                 var group_mul = '', n_ch;
  1588.                                 for (var j = i + 2; j < il; j++) {
  1589.                                     n_ch = abbr.charAt(j);
  1590.                                     if (isNumeric(n_ch))
  1591.                                         group_mul += n_ch;
  1592.                                     else
  1593.                                         break;
  1594.                                 }
  1595.                                
  1596.                                 i += group_mul.length + 1;
  1597.                                 group_mul = parseInt(group_mul || 1, 10);
  1598.                                 while (1 < group_mul--)
  1599.                                     context.parent.addChild(context);
  1600. //                                  last_parent.addChild(cur_item);
  1601.                             }
  1602.                            
  1603.                         } else {
  1604.                             token += ch;
  1605.                         }
  1606.                         break;
  1607.                        
  1608.                     case '^': // Ascension operator
  1609.                         if (!text_lvl && !attr_lvl) {
  1610.                             dumpToken();
  1611.                             context = context.parent.parent.addChild();
  1612.                            
  1613.                         } else {
  1614.                             token += ch;
  1615.                         }
  1616.                         break;
  1617.                        
  1618.                     case '+': // sibling operator
  1619.                         if (!text_lvl && !attr_lvl && i != il - 1 /* expando? */) {
  1620.                             dumpToken();
  1621.                             context = context.parent.addChild();
  1622.                         } else {
  1623.                             token += ch;
  1624.                         }
  1625.                         break;
  1626.                     case '>': // child operator
  1627.                         if (!text_lvl && !attr_lvl) {
  1628.                             dumpToken();
  1629.                             context = context.addChild();
  1630.                         } else {
  1631.                             token += ch;
  1632.                         }
  1633.                         break;
  1634.                     default:
  1635.                         token += ch;
  1636.                 }
  1637.                
  1638.                 i++;
  1639.             }
  1640.             // put the final token
  1641.             dumpToken();
  1642.            
  1643.             return optimizeTree(root);
  1644.         },
  1645.        
  1646.         TreeNode: TreeNode,
  1647.         optimizeTree: optimizeTree
  1648.     }
  1649. })();/**
  1650.  * Core library that do all Zen Coding magic
  1651.  * @author Sergey Chikuyonok ([email protected])
  1652.  * @link http://chikuyonok.ru
  1653.  * @include "settings.js"
  1654.  * @include "zen_parser.js"
  1655.  * @include "zen_resources.js"
  1656.  */
  1657. var zen_coding = (function(){
  1658.     var re_tag = /<\/?[\w:\-]+(?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*\s*(\/?)>$/,
  1659.    
  1660.         caret_placeholder = '{%::zen-caret::%}',
  1661.         newline = '\n',
  1662.        
  1663.         /** List of registered filters */
  1664.         filters = {},
  1665.         /** Filters that will be applied for unknown syntax */
  1666.         basic_filters = 'html',
  1667.        
  1668.         profiles = {},
  1669.         default_profile = {
  1670.             tag_case: 'lower',
  1671.             attr_case: 'lower',
  1672.             attr_quotes: 'double',
  1673.            
  1674.             // each tag on new line
  1675.             tag_nl: 'decide',
  1676.            
  1677.             place_cursor: true,
  1678.            
  1679.             // indent tags
  1680.             indent: true,
  1681.            
  1682.             // how many inline elements should be to force line break
  1683.             // (set to 0 to disable)
  1684.             inline_break: 3,
  1685.            
  1686.             // use self-closing style for writing empty elements, e.g. <br /> or <br>
  1687.             self_closing_tag: 'xhtml',
  1688.            
  1689.             // Profile-level output filters, re-defines syntax filters
  1690.             filters: ''
  1691.         };
  1692.    
  1693.     function isNumeric(ch) {
  1694.         if (typeof(ch) == 'string')
  1695.             ch = ch.charCodeAt(0);
  1696.            
  1697.         return (ch && ch > 47 && ch < 58);
  1698.     }
  1699.    
  1700.     /**
  1701.      * Проверяет, является ли символ допустимым в аббревиатуре
  1702.      * @param {String} ch
  1703.      * @return {Boolean}
  1704.      */
  1705.     function isAllowedChar(ch) {
  1706.         ch = String(ch); // convert Java object to JS
  1707.         var char_code = ch.charCodeAt(0),
  1708.             special_chars = '#.>+*:$-_!@[]()|^';
  1709.        
  1710.         return (char_code > 64 && char_code < 91)       // uppercase letter
  1711.                 || (char_code > 96 && char_code < 123)  // lowercase letter
  1712.                 || isNumeric(ch)                        // number
  1713.                 || special_chars.indexOf(ch) != -1;     // special character
  1714.     }
  1715.    
  1716.     /**
  1717.      * Возвращает символ перевода строки, используемый в редакторе
  1718.      * @return {String}
  1719.      */
  1720.     function getNewline() {
  1721.         return zen_coding.getNewline();
  1722.     }
  1723.    
  1724.     /**
  1725.      * Returns caret placeholder
  1726.      * @return {String}
  1727.      */
  1728.     function getCaretPlaceholder() {
  1729.         return (typeof(caret_placeholder) != 'string')
  1730.             ? caret_placeholder()
  1731.             : caret_placeholder
  1732.     }
  1733.    
  1734.     /**
  1735.      * Split text into lines. Set <code>remove_empty</code> to true to filter
  1736.      * empty lines
  1737.      * @param {String} text
  1738.      * @param {Boolean} [remove_empty]
  1739.      * @return {Array}
  1740.      */
  1741.     function splitByLines(text, remove_empty) {
  1742.         // IE fails to split string by regexp,
  1743.         // need to normalize newlines first
  1744.         // Also, Mozilla's Rhiho JS engine has a wierd newline bug
  1745.         var nl = getNewline();
  1746.         var lines = (text || '')
  1747.             .replace(/\r\n/g, '\n')
  1748.             .replace(/\n\r/g, '\n')
  1749.             .replace(/\r/g, '\n')
  1750.             .replace(/\n/g, nl)
  1751.             .split(nl);
  1752.        
  1753.         if (remove_empty) {
  1754.             for (var i = lines.length; i >= 0; i--) {
  1755.                 if (!trim(lines[i]))
  1756.                     lines.splice(i, 1);
  1757.             }
  1758.         }
  1759.        
  1760.         return lines;
  1761.     }
  1762.    
  1763.     /**
  1764.      * Trim whitespace from string
  1765.      * @param {String} text
  1766.      * @return {String}
  1767.      */
  1768.     function trim(text) {
  1769.         return (text || "").replace( /^\s+|\s+$/g, "" );
  1770.     }
  1771.    
  1772.     function createProfile(options) {
  1773.         var result = {};
  1774.         for (var p in default_profile)
  1775.             result[p] = (p in options) ? options[p] : default_profile[p];
  1776.        
  1777.         return result;
  1778.     }
  1779.    
  1780.     function setupProfile(name, options) {
  1781.         profiles[name.toLowerCase()] = createProfile(options || {});
  1782.     }
  1783.    
  1784.     /**
  1785.      * Repeats string <code>how_many</code> times
  1786.      * @param {String} str
  1787.      * @param {Number} how_many
  1788.      * @return {String}
  1789.      */
  1790.     function repeatString(str, how_many) {
  1791.         var result = '';
  1792.         for (var i = 0; i < how_many; i++)
  1793.             result += str;
  1794.            
  1795.         return result;
  1796.     }
  1797.    
  1798.     /**
  1799.      * Indents text with padding
  1800.      * @param {String} text Text to indent
  1801.      * @param {String|Number} pad Padding size (number) or padding itself (string)
  1802.      * @return {String}
  1803.      */
  1804.     function padString(text, pad) {
  1805.         var pad_str = (typeof(pad) == 'number')
  1806.                 ? repeatString(getIndentation(), pad)
  1807.                 : pad,
  1808.             result = '';
  1809.        
  1810.         var lines = splitByLines(text),
  1811.             nl = getNewline();
  1812.            
  1813.         result += lines[0];
  1814.         for (var j = 1; j < lines.length; j++)
  1815.             result += nl + pad_str + lines[j];
  1816.            
  1817.         return result;
  1818.     }
  1819.    
  1820.     /**
  1821.      * Class inheritance method
  1822.      * @param {Function} derived Derived class
  1823.      * @param {Function} from Base class
  1824.      */
  1825.     function inherit(derived, from) {
  1826.         var Inheritance = function(){};
  1827.    
  1828.         Inheritance.prototype = from.prototype;
  1829.    
  1830.         derived.prototype = new Inheritance();
  1831.         derived.prototype.constructor = derived;
  1832.         derived.baseConstructor = from;
  1833.         derived.superClass = from.prototype;
  1834.     };
  1835.    
  1836.     /**
  1837.      * Check if passed abbreviation is snippet
  1838.      * @param {String} abbr
  1839.      * @param {String} type
  1840.      * @return {Boolean}
  1841.      */
  1842.     function isShippet(abbr, type) {
  1843.         return getSnippet(type, filterNodeName(abbr)) ? true : false;
  1844.     }
  1845.    
  1846.     /**
  1847.      * Test if passed string ends with XHTML tag. This method is used for testing
  1848.      * '>' character: it belongs to tag or it's a part of abbreviation?
  1849.      * @param {String} str
  1850.      * @return {Boolean}
  1851.      */
  1852.     function isEndsWithTag(str) {
  1853.         return re_tag.test(str);
  1854.     }
  1855.    
  1856.     /**
  1857.      * Replace variables like ${var} in string
  1858.      * @param {String} str
  1859.      * @param {Object|Function} [vars] Variable set (default is <code>zen_settings.variables</code>)
  1860.      * @return {String}
  1861.      */
  1862.     function replaceVariables(str, vars) {
  1863.         var callback;
  1864.        
  1865.         if (typeof vars == 'function')
  1866.             callback = vars;
  1867.         else if (vars)
  1868.             callback = function(str, p1) {
  1869.                 return (p1 in vars) ? vars[p1] : str;
  1870.             };
  1871.         else
  1872.             callback = function(str, p1) {
  1873.                 var v = getVariable(p1);
  1874.                 return (v !== null && typeof v != 'undefined') ? v : str;
  1875.             }
  1876.        
  1877.         return str.replace(/\$\{([\w\-]+)\}/g, callback);
  1878.     }
  1879.    
  1880.     /**
  1881.      * Removes any unnecessary characters from node name
  1882.      * @param {String} name
  1883.      * @return {String}
  1884.      */
  1885.     function filterNodeName(name) {
  1886.         return (name || '').replace(/(.+)\!$/, '$1');
  1887.     }
  1888.    
  1889.     /**
  1890.      * Test if text contains output placeholder $#
  1891.      * @param {String} text
  1892.      * @return {Boolean}
  1893.      */
  1894.     function hasOutputPlaceholder(/* String */ text) {
  1895.         for (var i = 0, il = text.length; i < il; i++) {
  1896.             var ch = text.charAt(i);
  1897.             if (ch == '\\') { // escaped char
  1898.                 i++;
  1899.                 continue;
  1900.             } else if (ch == '$' && text.charAt(i + 1) == '#') {
  1901.                 return true;
  1902.             }
  1903.         }
  1904.        
  1905.         return false;
  1906.     }
  1907.    
  1908.     /**
  1909.      * Tag
  1910.      * @class
  1911.      * @param {zen_parser.TreeNode} node Parsed tree node
  1912.      * @param {String} type Tag type (html, xml)
  1913.      */
  1914.     function Tag(node, type) {
  1915.         type = type || 'html';
  1916.        
  1917.         var abbr = null;
  1918.         if (node.name) {
  1919.             abbr = getAbbreviation(type, filterNodeName(node.name));
  1920.             if (abbr && abbr.type == 'zen-reference')
  1921.                 abbr = getAbbreviation(type, filterNodeName(abbr.value));
  1922.         }
  1923.        
  1924.         this.name = (abbr) ? abbr.value.name : node.name;
  1925.         this.real_name = node.name;
  1926.         this.count = node.count || 1;
  1927.         this._abbr = abbr;
  1928.         this.syntax = type;
  1929.         this._content = '';
  1930.         this._paste_content = '';
  1931.         this.repeat_by_lines = node.is_repeating;
  1932.         this.is_repeating = node && node.count > 1;
  1933.         this.parent = null;
  1934.         this.has_implicit_name = node.has_implict_name;
  1935.        
  1936.         this.setContent(node.text);
  1937.        
  1938.         // add default attributes
  1939.         if (this._abbr)
  1940.             this.copyAttributes(this._abbr.value);
  1941.        
  1942.         this.copyAttributes(node);
  1943.     }
  1944.    
  1945.     Tag.prototype = {
  1946.         /**
  1947.          * Adds new child tag to current one
  1948.          * @param {Tag} tag
  1949.          */
  1950.         addChild: function(tag) {
  1951.             if (!this.children)
  1952.                 this.children = [];
  1953.                
  1954.             tag.parent = this;
  1955.             this.children.push(tag);
  1956.         },
  1957.        
  1958.         /**
  1959.          * Adds new attribute
  1960.          * @param {String} name Attribute's name
  1961.          * @param {String} value Attribute's value
  1962.          */
  1963.         addAttribute: function(name, value) {
  1964.             if (!this.attributes)
  1965.                 this.attributes = [];
  1966.                
  1967.             if (!this._attr_hash)
  1968.                 this._attr_hash = {};
  1969.            
  1970.             // escape pipe (caret) character with internal placeholder
  1971.             value = replaceUnescapedSymbol(value, '|', getCaretPlaceholder());
  1972.            
  1973.             var a;
  1974.             if (name in this._attr_hash) {
  1975.                 // attribute already exists, decide what to do
  1976.                 a = this._attr_hash[name];
  1977.                 if (name == 'class') {
  1978.                     // 'class' is a magic attribute
  1979.                     a.value += ((a.value) ? ' ' : '') + value;
  1980.                 } else {
  1981.                     a.value = value;
  1982.                 }
  1983.             } else {
  1984.                 a = {name: name, value: value};
  1985.                 this._attr_hash[name] = a
  1986.                 this.attributes.push(a);
  1987.             }
  1988.         },
  1989.        
  1990.         /**
  1991.          * Copy attributes from parsed node
  1992.          */
  1993.         copyAttributes: function(node) {
  1994.             if (node && node.attributes)
  1995.                 for (var i = 0, il = node.attributes.length; i < il; i++) {
  1996.                     var attr = node.attributes[i];
  1997.                     this.addAttribute(attr.name, attr.value);
  1998.                 }
  1999.         },
  2000.        
  2001.         /**
  2002.          * This function tests if current tags' content contains xHTML tags.
  2003.          * This function is mostly used for output formatting
  2004.          */
  2005.         hasTagsInContent: function() {
  2006.             return this.getContent() && re_tag.test(this.getContent());
  2007.         },
  2008.        
  2009.         /**
  2010.          * Set textual content for tag
  2011.          * @param {String} str Tag's content
  2012.          */
  2013.         setContent: function(str) {
  2014.             this._content = replaceUnescapedSymbol(str || '', '|', getCaretPlaceholder());
  2015.         },
  2016.        
  2017.         /**
  2018.          * Returns tag's textual content
  2019.          * @return {String}
  2020.          */
  2021.         getContent: function() {
  2022.             return this._content || '';
  2023.         },
  2024.        
  2025.         /**
  2026.          * Set content that should be pasted to the output
  2027.          * @param {String} val
  2028.          */
  2029.         setPasteContent: function(val) {
  2030.             this._paste_content = zen_coding.escapeText(val);
  2031.         },
  2032.        
  2033.         /**
  2034.          * Get content that should be pasted to the output
  2035.          * @return {String}
  2036.          */
  2037.         getPasteContent: function() {
  2038.             return this._paste_content;
  2039.         },
  2040.        
  2041.         /**
  2042.          * Search for deepest and latest child of current element
  2043.          * @return {Tag|null} Returns null if there's no children
  2044.          */
  2045.         findDeepestChild: function() {
  2046.             if (!this.children || !this.children.length)
  2047.                 return null;
  2048.                
  2049.             var deepest_child = this;
  2050.             while (true) {
  2051.                 deepest_child = deepest_child.children[ deepest_child.children.length - 1 ];
  2052.                 if (!deepest_child.children || !deepest_child.children.length)
  2053.                     break;
  2054.             }
  2055.            
  2056.             return deepest_child;
  2057.         }
  2058.     };
  2059.    
  2060.     /**
  2061.      * Snippet
  2062.      * @param {zen_parser.TreeNode} node
  2063.      * @param {String} type Tag type (html, xml)
  2064.      */
  2065.     function Snippet(node, type) {
  2066.         /** @type {String} */
  2067.         this.name = filterNodeName(node.name);
  2068.         this.real_name = node.name;
  2069.         this.count = node.count;
  2070.         this.children = [];
  2071.         this._content = node.text || '';
  2072.         this.repeat_by_lines = node.is_repeating;
  2073.         this.is_repeating = node && node.count > 1;
  2074.         this.attributes = [];
  2075.         this.value = replaceUnescapedSymbol(getSnippet(type, this.name), '|', getCaretPlaceholder());
  2076.         this.parent = null;
  2077.         this.syntax = type;
  2078.        
  2079.         this.addAttribute('id', getCaretPlaceholder());
  2080.         this.addAttribute('class', getCaretPlaceholder());
  2081.         this.copyAttributes(node);
  2082.     }
  2083.    
  2084.     inherit(Snippet, Tag);
  2085.    
  2086.     /**
  2087.      * Returns abbreviation value from data set
  2088.      * @param {String} type Resource type (html, css, ...)
  2089.      * @param {String} abbr Abbreviation name
  2090.      * @return {Object|null}
  2091.      */
  2092.     function getAbbreviation(type, abbr) {
  2093.         return zen_resources.getAbbreviation(type, abbr);
  2094.     }
  2095.    
  2096.     /**
  2097.      * Returns snippet value from data set
  2098.      * @param {String} type Resource type (html, css, ...)
  2099.      * @param {String} snippet_name Snippet name
  2100.      * @return {Object|null}
  2101.      */
  2102.     function getSnippet(type, snippet_name) {
  2103.         return zen_resources.getSnippet(type, snippet_name);
  2104.     }
  2105.    
  2106.     /**
  2107.      * Returns variable value
  2108.      * @return {String}
  2109.      */
  2110.     function getVariable(name) {
  2111.         return zen_resources.getVariable(name);
  2112.     }
  2113.    
  2114.     /**
  2115.      * Returns indentation string
  2116.      * @return {String}
  2117.      */
  2118.     function getIndentation() {
  2119.         return getVariable('indentation');
  2120.     }
  2121.    
  2122.     /**
  2123.      * @class
  2124.      * Creates simplified tag from Zen Coding tag
  2125.      * @param {Tag} tag
  2126.      */
  2127.     function ZenNode(tag) {
  2128.         this.type = (tag instanceof Snippet) ? 'snippet' : 'tag';
  2129.         this.name = tag.name;
  2130.         this.real_name = tag.real_name;
  2131.         this.children = [];
  2132.         this.counter = 1;
  2133.         this.is_repeating = tag.is_repeating;
  2134.         this.repeat_by_lines = tag.repeat_by_lines;
  2135.         this.has_implicit_name = this.type == 'tag' && tag.has_implicit_name;
  2136.        
  2137.         // create deep copy of attribute list so we can change
  2138.         // their values in runtime without affecting other nodes
  2139.         // created from the same tag
  2140.         this.attributes = [];
  2141.         if (tag.attributes) {
  2142.             for (var i = 0, il = tag.attributes.length; i < il; i++) {
  2143.                 var a =  tag.attributes[i];
  2144.                 this.attributes.push({
  2145.                     name: a.name,
  2146.                     value: a.value
  2147.                 });
  2148.             }
  2149.         }
  2150.        
  2151.         /** @type {Tag} Source element from which current tag was created */
  2152.         this.source = tag;
  2153.        
  2154.         // relations
  2155.         /** @type {ZenNode} */
  2156.         this.parent = null;
  2157.         /** @type {ZenNode} */
  2158.         this.nextSibling = null;
  2159.         /** @type {ZenNode} */
  2160.         this.previousSibling = null;
  2161.        
  2162.         // output params
  2163.         this.start = '';
  2164.         this.end = '';
  2165.         this.content = tag.getContent() || '';
  2166.         this.padding = '';
  2167.     }
  2168.    
  2169.     ZenNode.prototype = {
  2170.         /**
  2171.          * @type {ZenNode} tag
  2172.          */
  2173.         addChild: function(tag) {
  2174.             tag.parent = this;
  2175.            
  2176.             // check for implicit name
  2177.             if (tag.has_implicit_name && this.isInline())
  2178.                 tag.name = 'span';
  2179.            
  2180.             var last_child = this.children[this.children.length - 1];
  2181.             if (last_child) {
  2182.                 tag.previousSibling = last_child;
  2183.                 last_child.nextSibling = tag;
  2184.             }
  2185.            
  2186.             this.children.push(tag);
  2187.         },
  2188.        
  2189.         /**
  2190.          * Get attribute's value.
  2191.          * @param {String} name
  2192.          * @return {String|null} Returns <code>null</code> if attribute wasn't found
  2193.          */
  2194.         getAttribute: function(name) {
  2195.             name = name.toLowerCase();
  2196.             for (var i = 0, il = this.attributes.length; i < il; i++) {
  2197.                 if (this.attributes[i].name.toLowerCase() == name)
  2198.                     return this.attributes[i].value;
  2199.             }
  2200.            
  2201.             return null;
  2202.         },
  2203.        
  2204.         /**
  2205.          * Test if current tag is unary (no closing tag)
  2206.          * @return {Boolean}
  2207.          */
  2208.         isUnary: function() {
  2209.             if (this.type == 'snippet')
  2210.                 return false;
  2211.                
  2212.             return (this.source._abbr && this.source._abbr.value.is_empty)
  2213.                 || zen_resources.isItemInCollection(this.source.syntax, 'empty', this.name);
  2214.         },
  2215.        
  2216.         /**
  2217.          * Test if current tag is inline-level (like &lt;strong&gt;, &lt;img&gt;)
  2218.          * @return {Boolean}
  2219.          */
  2220.         isInline: function() {
  2221.             return this.type == 'text'
  2222.                 || zen_resources.isItemInCollection(this.source.syntax, 'inline_level', this.name);
  2223.         },
  2224.        
  2225.         /**
  2226.          * Test if current element is block-level
  2227.          * @return {Boolean}
  2228.          */
  2229.         isBlock: function() {
  2230.             return this.type == 'snippet' || !this.isInline();
  2231.         },
  2232.        
  2233.         /**
  2234.          * This function tests if current tags' content contains xHTML tags.
  2235.          * This function is mostly used for output formatting
  2236.          */
  2237.         hasTagsInContent: function() {
  2238.             return this.content && re_tag.test(this.content);
  2239.         },
  2240.        
  2241.         /**
  2242.          * Check if tag has child elements
  2243.          * @return {Boolean}
  2244.          */
  2245.         hasChildren: function() {
  2246.             return !!this.children.length;
  2247.         },
  2248.        
  2249.         /**
  2250.          * Test if current tag contains block-level children
  2251.          * @return {Boolean}
  2252.          */
  2253.         hasBlockChildren: function() {
  2254.             if (this.hasTagsInContent() && this.isBlock()) {
  2255.                 return true;
  2256.             }
  2257.            
  2258.             for (var i = 0; i < this.children.length; i++) {
  2259.                 if (this.children[i].isBlock())
  2260.                     return true;
  2261.             }
  2262.            
  2263.             return false;
  2264.         },
  2265.        
  2266.         /**
  2267.          * Search for deepest and latest child of current element
  2268.          * @return {ZenNode|null} Returns <code>null</code> if there's no children
  2269.          */
  2270.         findDeepestChild: function() {
  2271.             if (!this.children.length)
  2272.                 return null;
  2273.                
  2274.             var deepest_child = this;
  2275.             while (true) {
  2276.                 deepest_child = deepest_child.children[ deepest_child.children.length - 1 ];
  2277.                 if (!deepest_child.children.length)
  2278.                     break;
  2279.             }
  2280.            
  2281.             return deepest_child;
  2282.         },
  2283.        
  2284.         /**
  2285.          * @return {String}
  2286.          */
  2287.         toString: function() {
  2288.             var content = '';
  2289.             for (var i = 0, il = this.children.length; i < il; i++) {
  2290.                 content += this.children[i].toString();
  2291.             }
  2292.            
  2293.             return this.start + this.content + content + this.end;
  2294.         },
  2295.        
  2296.         /**
  2297.          * Test if current element contains output placeholder (aka $#)
  2298.          * @return {Boolean}
  2299.          */
  2300.         hasOutputPlaceholder: function() {
  2301.             if (hasOutputPlaceholder(this.content)) {
  2302.                 return true;
  2303.             } else {
  2304.                 // search inside attributes
  2305.                 for (var i = 0, il = this.attributes.length; i < il; i++) {
  2306.                     if (hasOutputPlaceholder(this.attributes[i].value))
  2307.                         return true;
  2308.                 }
  2309.             }
  2310.            
  2311.             return false;
  2312.         },
  2313.        
  2314.         /**
  2315.          * Recursively search for elements with output placeholders (aka $#)
  2316.          * inside current element (not included in result)
  2317.          * @param {Array} _arr
  2318.          * @return {Array} Array of elements with output placeholders.  
  2319.          */
  2320.         findElementsWithOutputPlaceholder: function(_arr) {
  2321.             _arr = _arr || [];
  2322.             for (var i = 0, il = this.children.length; i < il; i++) {
  2323.                 if (this.children[i].hasOutputPlaceholder()) {
  2324.                     _arr.push(this.children[i]);
  2325.                 }
  2326.                 this.children[i].findElementsWithOutputPlaceholder(_arr);
  2327.             }
  2328.             return _arr;
  2329.         },
  2330.        
  2331.         /**
  2332.          * Paste content in context of current node. Pasting is a special case
  2333.          * of recursive adding content in node.
  2334.          * This function will try to find $# placeholder inside node's
  2335.          * attributes and text content and replace in with <code>text</code>.
  2336.          * If it doesn't find $# placeholder, it will put <code>text</code>
  2337.          * value as the deepest child content
  2338.          * @param {String} text Text to paste
  2339.          */
  2340.         pasteContent: function(text) {
  2341.             var symbol = '$#',
  2342.                 r = [symbol, text],
  2343.                 replace_fn = function() {return r;},
  2344.                 /** @type {ZenNode[]} */
  2345.                 items = [];
  2346.                
  2347.             if (this.hasOutputPlaceholder())
  2348.                 items.push(this);
  2349.                
  2350.             items = items.concat(this.findElementsWithOutputPlaceholder());
  2351.            
  2352.             if (items.length) {
  2353.                 for (var i = 0, il = items.length; i < il; i++) {
  2354.                     /** @type {ZenNode} */
  2355.                     var item = items[i];
  2356.                     item.content = replaceUnescapedSymbol(item.content, symbol, replace_fn);
  2357.                     for (var j = 0, jl = item.attributes.length; j < jl; j++) {
  2358.                         var a = item.attributes[j];
  2359.                         a.value = replaceUnescapedSymbol(a.value, symbol, replace_fn);
  2360.                     }
  2361.                 }
  2362.             } else {
  2363.                 // no placeholders found, add content to the deepest child
  2364.                 var child = this.findDeepestChild() || this;
  2365.                 child.content += text;
  2366.             }
  2367.         }
  2368.     };
  2369.    
  2370.     /**
  2371.      * Roll outs basic Zen Coding tree into simplified, DOM-like tree.
  2372.      * The simplified tree, for example, represents each multiplied element
  2373.      * as a separate element sets with its own content, if exists.
  2374.      *
  2375.      * The simplified tree element contains some meta info (tag name, attributes,
  2376.      * etc.) as well as output strings, which are exactly what will be outputted
  2377.      * after expanding abbreviation. This tree is used for <i>filtering</i>:
  2378.      * you can apply filters that will alter output strings to get desired look
  2379.      * of expanded abbreviation.
  2380.      *
  2381.      * @param {Tag} tree
  2382.      * @param {ZenNode} [parent]
  2383.      */
  2384.     function rolloutTree(tree, parent) {
  2385.         parent = parent || new ZenNode(tree);
  2386.        
  2387.         var how_many = 1,
  2388.             tag_content = '';
  2389.            
  2390.         if (tree.children) {
  2391.             for (var i = 0, il = tree.children.length; i < il; i++) {
  2392.                 /** @type {Tag} */
  2393.                 var child = tree.children[i];
  2394.                 how_many = child.count;
  2395.                
  2396.                 if (child.repeat_by_lines) {
  2397.                     // it's a repeating element
  2398.                     tag_content = splitByLines(child.getPasteContent(), true);
  2399.                     how_many = Math.max(tag_content.length, 1);
  2400.                 } else {
  2401.                     tag_content = child.getPasteContent();
  2402.                 }
  2403.                
  2404.                 for (var j = 0; j < how_many; j++) {
  2405.                     var tag = new ZenNode(child);
  2406.                     parent.addChild(tag);
  2407.                     tag.counter = j + 1;
  2408.                    
  2409.                     if (child.children && child.children.length)
  2410.                         rolloutTree(child, tag);
  2411.                        
  2412.                     if (tag_content) {
  2413.                         var text = (typeof(tag_content) == 'string')
  2414.                             ? tag_content
  2415.                             : (tag_content[j] || '');
  2416.                         tag.pasteContent(trim(text));
  2417.                     }
  2418.                 }
  2419.             }
  2420.         }
  2421.        
  2422.         return parent;
  2423.     }
  2424.    
  2425.     /**
  2426.      * Runs filters on tree
  2427.      * @param {ZenNode} tree
  2428.      * @param {String|Object} profile
  2429.      * @param {String[]|String} filter_list
  2430.      * @return {ZenNode}
  2431.      */
  2432.     function runFilters(tree, profile, filter_list) {
  2433.         profile = processProfile(profile);
  2434.        
  2435.         if (typeof(filter_list) == 'string')
  2436.             filter_list = filter_list.split(/[\|,]/g);
  2437.            
  2438.         for (var i = 0, il = filter_list.length; i < il; i++) {
  2439.             var name = trim(filter_list[i].toLowerCase());
  2440.             if (name && name in filters) {
  2441.                 tree = filters[name](tree, profile);
  2442.             }
  2443.         }
  2444.        
  2445.         return tree;
  2446.     }
  2447.    
  2448.     /**
  2449.      * Transforms abbreviation into a primary internal tree. This tree should'n
  2450.      * be used ouside of this scope
  2451.      * @param {zen_parser.TreeNode} node Parsed tree node
  2452.      * @param {String} [type] Document type (xsl, html, etc.)
  2453.      * @return {Tag}
  2454.      */
  2455.     function transformTreeNode(node, type) {
  2456.         type = type || 'html';
  2457.         if (node.isEmpty()) return null;
  2458.        
  2459.         return isShippet(node.name, type)
  2460.                 ? new Snippet(node, type)
  2461.                 : new Tag(node, type);
  2462.     }
  2463.    
  2464.     /**
  2465.      * Process single tree node: expand it and its children
  2466.      * @param {zen_parser.TreeNode} node
  2467.      * @param {String} type
  2468.      * @param {Tag} parent
  2469.      */
  2470.     function processParsedNode(node, type, parent) {
  2471.         var t_node = transformTreeNode(node, type);
  2472.         parent.addChild(t_node);
  2473.            
  2474.         // set repeating element to the topmost node
  2475.         var root = parent;
  2476.         while (root.parent)
  2477.             root = root.parent;
  2478.        
  2479.         root.last = t_node;
  2480.         if (t_node.repeat_by_lines)
  2481.             root.multiply_elem = t_node;
  2482.            
  2483.         // process child groups
  2484.         for (var j = 0, jl = node.children.length; j < jl; j++) {
  2485.             processParsedNode(node.children[j], type, t_node);
  2486.         }
  2487.     }
  2488.    
  2489.     /**
  2490.      * Replaces expando nodes by its parsed content
  2491.      * @param {zen_parser.TreeNode} node
  2492.      * @param {String} type
  2493.      */
  2494.     function replaceExpandos(node, type) {
  2495.         for (var i = 0, il = node.children.length; i < il; i++) {
  2496.             var n = node.children[i];
  2497.             if (!n.isEmpty() && !n.isTextNode() && n.name.indexOf('+') != -1) {
  2498.                 // it's expando
  2499.                 var a = getAbbreviation(type, n.name);
  2500.                 if (a)
  2501.                     node.children[i] = zen_parser.parse(a.value);
  2502.             }
  2503.             replaceExpandos(node.children[i], type);
  2504.         }
  2505.     }
  2506.    
  2507.     /**
  2508.      * Replaces expandos and optimizes tree structure by removing empty nodes
  2509.      * @param {zen_parser.TreeNode} tree
  2510.      * @param {String} type
  2511.      */
  2512.     function preprocessParsedTree(tree, type) {
  2513.         replaceExpandos(tree, type);
  2514.         return zen_parser.optimizeTree(tree);
  2515.     }
  2516.    
  2517.     /**
  2518.      * Pad string with zeroes
  2519.      * @param {String} str
  2520.      * @param {Number} pad
  2521.      */
  2522.     function zeroPadString(str, pad) {
  2523.         var padding = '',
  2524.             il = str.length;
  2525.            
  2526.         while (pad > il++) padding += '0';
  2527.         return padding + str;
  2528.     }
  2529.    
  2530.     /**
  2531.      * Replaces unescaped symbols in <code>str</code>. For example, the '$' symbol
  2532.      * will be replaced in 'item$count', but not in 'item\$count'.
  2533.      * @param {String} str Original string
  2534.      * @param {String} symbol Symbol to replace
  2535.      * @param {String|Function} replace Symbol replacement
  2536.      * @return {String}
  2537.      */
  2538.     function replaceUnescapedSymbol(str, symbol, replace) {
  2539.         var i = 0,
  2540.             il = str.length,
  2541.             sl = symbol.length,
  2542.             match_count = 0;
  2543.            
  2544.         while (i < il) {
  2545.             if (str.charAt(i) == '\\') {
  2546.                 // escaped symbol, skip next character
  2547.                 i += sl + 1;
  2548.             } else if (str.substr(i, sl) == symbol) {
  2549.                 // have match
  2550.                 var cur_sl = sl;
  2551.                 match_count++;
  2552.                 var new_value = replace;
  2553.                 if (typeof(replace) !== 'string') {
  2554.                     var replace_data = replace(str, symbol, i, match_count);
  2555.                     if (replace_data) {
  2556.                         cur_sl = replace_data[0].length;
  2557.                         new_value = replace_data[1];
  2558.                     } else {
  2559.                         new_value = false;
  2560.                     }
  2561.                 }
  2562.                
  2563.                 if (new_value === false) { // skip replacement
  2564.                     i++;
  2565.                     continue;
  2566.                 }
  2567.                
  2568.                 str = str.substring(0, i) + new_value + str.substring(i + cur_sl);
  2569.                 // adjust indexes
  2570.                 il = str.length;
  2571.                 i += new_value.length;
  2572.             } else {
  2573.                 i++;
  2574.             }
  2575.         }
  2576.        
  2577.         return str;
  2578.     }
  2579.    
  2580.     /**
  2581.      * Processes profile argument, returning, if possible, profile object
  2582.      */
  2583.     function processProfile(profile) {
  2584.         var _profile = profile;
  2585.         if (typeof(profile) == 'string' && profile in profiles)
  2586.             _profile = profiles[profile];
  2587.        
  2588.         if (!_profile)
  2589.             _profile = profiles['plain'];
  2590.            
  2591.         return _profile;
  2592.     }
  2593.    
  2594.     // create default profiles
  2595.     setupProfile('xhtml');
  2596.     setupProfile('html', {self_closing_tag: false});
  2597.     setupProfile('xml', {self_closing_tag: true, tag_nl: true});
  2598.     setupProfile('plain', {tag_nl: false, indent: false, place_cursor: false});
  2599.    
  2600.    
  2601.     return {
  2602.         /** Hash of all available actions */
  2603.         actions: {},
  2604.        
  2605.         /**
  2606.          * Adds new Zen Coding action. This action will be available in
  2607.          * <code>zen_settings.actions</code> object.
  2608.          * @param {String} name Action's name
  2609.          * @param {Function} fn Action itself. The first argument should be
  2610.          * <code>zen_editor</code> instance.
  2611.          */
  2612.         registerAction: function(name, fn) {
  2613.             this.actions[name.toLowerCase()] = fn;
  2614.         },
  2615.        
  2616.         /**
  2617.          * Runs Zen Coding action. For list of available actions and their
  2618.          * arguments see <code>zen_actions.js</code> file.
  2619.          * @param {String} name Action name
  2620.          * @param {Array} args Additional arguments. It may be array of arguments
  2621.          * or inline arguments. The first argument should be <code>zen_editor</code> instance
  2622.          * @example
  2623.          * zen_coding.runActions('expand_abbreviation', zen_editor);  
  2624.          * zen_coding.runActions('wrap_with_abbreviation', [zen_editor, 'div']);  
  2625.          */
  2626.         runAction: function(name, args) {
  2627.             if (!(args instanceof Array))
  2628.                 args = Array.prototype.slice.call(arguments, 1);
  2629.                
  2630.             name = name.toLowerCase();
  2631.             if (name in this.actions)
  2632.                 return this.actions[name].apply(this, args);
  2633. //          try {
  2634. //          } catch(e){
  2635. //              if (window && window.console)
  2636. //                  console.error(e);
  2637. //              return false;
  2638. //          }
  2639.         },
  2640.        
  2641.         expandAbbreviation: function(abbr, type, profile) {
  2642.             type = type || 'html';
  2643.             var parsed_tree = this.parseIntoTree(abbr, type);
  2644.            
  2645.             if (parsed_tree) {
  2646.                 var tree = rolloutTree(parsed_tree);
  2647.                 this.applyFilters(tree, type, profile, parsed_tree.filters);
  2648.                 return replaceVariables(tree.toString());
  2649.             }
  2650.            
  2651.             return '';
  2652.         },
  2653.        
  2654.         /**
  2655.          * Extracts abbreviations from text stream, starting from the end
  2656.          * @param {String} str
  2657.          * @return {String} Abbreviation or empty string
  2658.          */
  2659.         extractAbbreviation: function(str) {
  2660.             var cur_offset = str.length,
  2661.                 start_index = -1,
  2662.                 group_count = 0,
  2663.                 brace_count = 0,
  2664.                 text_count = 0;
  2665.            
  2666.             while (true) {
  2667.                 cur_offset--;
  2668.                 if (cur_offset < 0) {
  2669.                     // moved to the beginning of the line
  2670.                     start_index = 0;
  2671.                     break;
  2672.                 }
  2673.                
  2674.                 var ch = str.charAt(cur_offset);
  2675.                
  2676.                 if (ch == ']') {
  2677.                     brace_count++;
  2678.                 } else if (ch == '[') {
  2679.                     if (!brace_count) { // unexpected brace
  2680.                         start_index = cur_offset + 1;
  2681.                         break;
  2682.                     }
  2683.                     brace_count--;
  2684.                 } else if (ch == '}') {
  2685.                     text_count++;
  2686.                 } else if (ch == '{') {
  2687.                     if (!text_count) { // unexpected brace
  2688.                         start_index = cur_offset + 1;
  2689.                         break;
  2690.                     }
  2691.                     text_count--;
  2692.                 } else if (ch == ')') {
  2693.                     group_count++;
  2694.                 } else if (ch == '(') {
  2695.                     if (!group_count) { // unexpected brace
  2696.                         start_index = cur_offset + 1;
  2697.                         break;
  2698.                     }
  2699.                     group_count--;
  2700.                 } else {
  2701.                     if (brace_count || text_count)
  2702.                         // respect all characters inside attribute sets or text nodes
  2703.                         continue;
  2704.                     else if (!isAllowedChar(ch) || (ch == '>' && isEndsWithTag(str.substring(0, cur_offset + 1)))) {
  2705.                         // found stop symbol
  2706.                         start_index = cur_offset + 1;
  2707.                         break;
  2708.                     }
  2709.                 }
  2710.             }
  2711.            
  2712.             if (start_index != -1 && !text_count && !brace_count && !group_count)
  2713.                 // found something, return abbreviation
  2714.                 return str.substring(start_index);
  2715.             else
  2716.                 return '';
  2717.         },
  2718.        
  2719.         /**
  2720.          * Parses abbreviation into a node set
  2721.          * @param {String} abbr Abbreviation
  2722.          * @param {String} type Document type (xsl, html, etc.)
  2723.          * @return {Tag}
  2724.          */
  2725.         parseIntoTree: function(abbr, type) {
  2726.             type = type || 'html';
  2727.             // remove filters from abbreviation
  2728.             var filter_list = '';
  2729.             abbr = abbr.replace(/\|([\w\|\-]+)$/, function(str, p1){
  2730.                 filter_list = p1;
  2731.                 return '';
  2732.             });
  2733.            
  2734.             // try to parse abbreviation
  2735.             try {
  2736.                 var abbr_tree = zen_parser.parse(abbr),
  2737.                     tree_root = new Tag({}, type);
  2738.                    
  2739.                 abbr_tree = preprocessParsedTree(abbr_tree, type);
  2740.             } catch(e) {
  2741.                 if (e.message == "InvalidAbbreviation")
  2742.                     return null;
  2743.             }
  2744.                
  2745.             // then recursively expand each group item
  2746.             for (var i = 0, il = abbr_tree.children.length; i < il; i++) {
  2747.                 processParsedNode(abbr_tree.children[i], type, tree_root);
  2748.             }
  2749.            
  2750.             tree_root.filters = filter_list;
  2751.             return tree_root;
  2752.         },
  2753.        
  2754.         /**
  2755.          * Indents text with padding
  2756.          * @param {String} text Text to indent
  2757.          * @param {String|Number} pad Padding size (number) or padding itself (string)
  2758.          * @return {String}
  2759.          */
  2760.         padString: padString,
  2761.         setupProfile: setupProfile,
  2762.         getNewline: function(){
  2763.             return newline;
  2764.         },
  2765.        
  2766.         setNewline: function(str) {
  2767.             newline = str;
  2768.             this.setVariable('newline', str);
  2769.             this.setVariable('nl', str);
  2770.         },
  2771.        
  2772.         /**
  2773.          * Wraps passed text with abbreviation. Text will be placed inside last
  2774.          * expanded element
  2775.          * @param {String} abbr Abbreviation
  2776.          * @param {String} text Text to wrap
  2777.          * @param {String} [type] Document type (html, xml, etc.). Default is 'html'
  2778.          * @param {String} [profile] Output profile's name. Default is 'plain'
  2779.          * @return {String}
  2780.          */
  2781.         wrapWithAbbreviation: function(abbr, text, type, profile) {
  2782.             type = type || 'html';
  2783.             var tree_root = this.parseIntoTree(abbr, type),
  2784.                 pasted = false;
  2785.                
  2786.             if (tree_root) {
  2787.                 if (tree_root.multiply_elem) {
  2788.                     // we have a repeating element, put content in
  2789.                     tree_root.multiply_elem.setPasteContent(text);
  2790.                     tree_root.multiply_elem.repeat_by_lines = pasted = true;
  2791.                 }
  2792.                
  2793.                 var tree = rolloutTree(tree_root);
  2794.                
  2795.                 if (!pasted)
  2796.                     tree.pasteContent(text);
  2797.                
  2798.                 this.applyFilters(tree, type, profile, tree_root.filters);
  2799.                 return replaceVariables(tree.toString());
  2800.             }
  2801.            
  2802.             return null;
  2803.         },
  2804.        
  2805.         splitByLines: splitByLines,
  2806.        
  2807.         /**
  2808.          * Check if cursor is placed inside xHTML tag
  2809.          * @param {String} html Contents of the document
  2810.          * @param {Number} cursor_pos Current caret position inside tag
  2811.          * @return {Boolean}
  2812.          */
  2813.         isInsideTag: function(html, cursor_pos) {
  2814.             var re_tag = /^<\/?\w[\w\:\-]*.*?>/;
  2815.            
  2816.             // search left to find opening brace
  2817.             var pos = cursor_pos;
  2818.             while (pos > -1) {
  2819.                 if (html.charAt(pos) == '<')
  2820.                     break;
  2821.                 pos--;
  2822.             }
  2823.            
  2824.             if (pos != -1) {
  2825.                 var m = re_tag.exec(html.substring(pos));
  2826.                 if (m && cursor_pos > pos && cursor_pos < pos + m[0].length)
  2827.                     return true;
  2828.             }
  2829.            
  2830.             return false;
  2831.         },
  2832.        
  2833.         /**
  2834.          * Returns caret placeholder
  2835.          * @return {String}
  2836.          */
  2837.         getCaretPlaceholder: getCaretPlaceholder,
  2838.        
  2839.         /**
  2840.          * Set caret placeholder: a string (like '|') or function.
  2841.          * You may use a function as a placeholder generator. For example,
  2842.          * TextMate uses ${0}, ${1}, ..., ${n} natively for quick Tab-switching
  2843.          * between them.
  2844.          * @param {String|Function} value
  2845.          */
  2846.         setCaretPlaceholder: function(value) {
  2847.             caret_placeholder = value;
  2848.         },
  2849.        
  2850.         rolloutTree: rolloutTree,
  2851.        
  2852.         /**
  2853.          * Register new filter
  2854.          * @param {String} name Filter name
  2855.          * @param {Function} fn Filter function
  2856.          */
  2857.         registerFilter: function(name, fn) {
  2858.             filters[name] = fn;
  2859.         },
  2860.        
  2861.         /**
  2862.          * Factory method that produces <code>ZenNode</code> instance
  2863.          * @param {String} name Node name
  2864.          * @param {Array} [attrs] Array of attributes as key/value objects  
  2865.          * @return {ZenNode}
  2866.          */
  2867.         nodeFactory: function(name, attrs) {
  2868.             return new ZenNode({name: name, attributes: attrs || []});
  2869.         },
  2870.        
  2871.         /**
  2872.          * Applies filters to tree according to syntax
  2873.          * @param {ZenNode} tree Tag tree to apply filters to
  2874.          * @param {String} syntax Syntax name ('html', 'css', etc.)
  2875.          * @param {String|Object} profile Profile or profile's name
  2876.          * @param {String|Array} [additional_filters] List or pipe-separated
  2877.          * string of additional filters to apply
  2878.          *
  2879.          * @return {ZenNode}
  2880.          */
  2881.         applyFilters: function(tree, syntax, profile, additional_filters) {
  2882.             profile = processProfile(profile);
  2883.             var _filters = profile.filters;
  2884.             if (!_filters)
  2885.                 _filters = zen_resources.getSubset(syntax, 'filters') || basic_filters;
  2886.                
  2887.             if (additional_filters)
  2888.                 _filters += '|' + ((typeof(additional_filters) == 'string')
  2889.                     ? additional_filters
  2890.                     : additional_filters.join('|'));
  2891.                
  2892.             if (!_filters)
  2893.                 // looks like unknown syntax, apply basic filters
  2894.                 _filters = basic_filters;
  2895.                
  2896.             return runFilters(tree, profile, _filters);
  2897.         },
  2898.        
  2899.         runFilters: runFilters,
  2900.        
  2901.         repeatString: repeatString,
  2902.         getVariable: getVariable,
  2903.         /**
  2904.          * Store runtime variable in user storage
  2905.          * @param {String} name Variable name
  2906.          * @param {String} value Variable value
  2907.          */
  2908.         setVariable: function(name, value){
  2909.             var voc = zen_resources.getVocabulary('user') || {};
  2910.             if (!('varaibles' in voc))
  2911.                 voc.variables = {};
  2912.                
  2913.             voc.variables[name] = value;
  2914.             zen_resources.setVocabulary(voc, 'user');
  2915.         },
  2916.         replaceVariables: replaceVariables,
  2917.        
  2918.         /**
  2919.          * Escapes special characters used in Zen Coding, like '$', '|', etc.
  2920.          * Use this method before passing to actions like "Wrap with Abbreviation"
  2921.          * to make sure that existing spacial characters won't be altered
  2922.          * @param {String} text
  2923.          * @return {String}
  2924.          */
  2925.         escapeText: function(text) {
  2926.             return text.replace(/([\$\|\\])/g, '\\$1');
  2927.         },
  2928.        
  2929.         /**
  2930.          * Unescapes special characters used in Zen Coding, like '$', '|', etc.
  2931.          * @param {String} text
  2932.          * @return {String}
  2933.          */
  2934.         unescapeText: function(text) {
  2935.             return text.replace(/\\(.)/g, '$1');
  2936.         },
  2937.        
  2938.         /**
  2939.          * Replaces '$' character in string assuming it might be escaped with '\'
  2940.          * @param {String} str
  2941.          * @param {String|Number} value
  2942.          * @return {String}
  2943.          */
  2944.         replaceCounter: function(str, value) {
  2945.             var symbol = '$';
  2946.             value = String(value);
  2947.             return replaceUnescapedSymbol(str, symbol, function(str, symbol, pos, match_num){
  2948.                 if (str.charAt(pos + 1) == '{' || isNumeric(str.charAt(pos + 1)) ) {
  2949.                     // it's a variable, skip it
  2950.                     return false;
  2951.                 }
  2952.                
  2953.                 // replace sequense of $ symbols with padded number  
  2954.                 var j = pos + 1;
  2955.                 while(str.charAt(j) == '$' && str.charAt(j + 1) != '{') j++;
  2956.                 return [str.substring(pos, j), zeroPadString(value, j - pos)];
  2957.             });
  2958.         },
  2959.        
  2960.         isNumeric: isNumeric,
  2961.        
  2962.         /**
  2963.          * Upgrades tabstops in zen node in order to prevent naming conflicts
  2964.          * @param {ZenNode} node
  2965.          * @param {Number} offset Tab index offset
  2966.          * @returns {Number} Maximum tabstop index in element
  2967.          */
  2968.         upgradeTabstops: function(node, offset) {
  2969.             var max_num = 0,
  2970.                 props = ['start', 'end', 'content'],
  2971.                 escape_fn = function(ch){ return '\\' + ch; },
  2972.                 tabstop_fn = function(i, num, value) {
  2973.                     num = parseInt(num);
  2974.                     if (num > max_num) max_num = num;
  2975.                        
  2976.                     if (value)
  2977.                         return '${' + (num + offset) + ':' + value + '}';
  2978.                     else
  2979.                         return '$' + (num + offset);
  2980.                 };
  2981.                
  2982.             for (var i = 0, il = props.length; i < il; i++)
  2983.                 node[props[i]] = this.processTextBeforePaste(node[props[i]], escape_fn, tabstop_fn);
  2984.            
  2985.             return max_num;
  2986.         },
  2987.        
  2988.         /**
  2989.          * Get profile by it's name. If profile wasn't found, returns 'plain'
  2990.          * profile
  2991.          */
  2992.         getProfile: function(name) {
  2993.             return (name in profiles) ? profiles[name] : profiles['plain'];
  2994.         },
  2995.        
  2996.         /**
  2997.          * Gets image size from image byte stream.
  2998.          * @author http://romeda.org/rePublish/
  2999.          * @param {String} stream Image byte stream (use <code>zen_file.read()</code>)
  3000.          * @return {Object} Object with <code>width</code> and <code>height</code> properties
  3001.          */
  3002.         getImageSize: function(stream) {
  3003.             var pngMagicNum = "\211PNG\r\n\032\n",
  3004.                 jpgMagicNum = "\377\330",
  3005.                 gifMagicNum = "GIF8",
  3006.                 nextByte = function() {
  3007.                     return stream.charCodeAt(pos++);
  3008.                 };
  3009.        
  3010.             if (stream.substr(0, 8) === pngMagicNum) {
  3011.                 // PNG. Easy peasy.
  3012.                 var pos = stream.indexOf('IHDR') + 4;
  3013.            
  3014.                 return { width:  (nextByte() << 24) | (nextByte() << 16) |
  3015.                                  (nextByte() <<  8) | nextByte(),
  3016.                          height: (nextByte() << 24) | (nextByte() << 16) |
  3017.                                  (nextByte() <<  8) | nextByte() };
  3018.            
  3019.             } else if (stream.substr(0, 4) === gifMagicNum) {
  3020.                 pos = 6;
  3021.            
  3022.                 return {
  3023.                     width:  nextByte() | (nextByte() << 8),
  3024.                     height: nextByte() | (nextByte() << 8)
  3025.                 };
  3026.            
  3027.             } else if (stream.substr(0, 2) === jpgMagicNum) {
  3028.                 // TODO need testing
  3029.                 pos = 2;
  3030.            
  3031.                 var l = stream.length;
  3032.                 while (pos < l) {
  3033.                     if (nextByte() != 0xFF) return;
  3034.                
  3035.                     var marker = nextByte();
  3036.                     if (marker == 0xDA) break;
  3037.                
  3038.                     var size = (nextByte() << 8) | nextByte();
  3039.                
  3040.                     if (marker >= 0xC0 && marker <= 0xCF && !(marker & 0x4) && !(marker & 0x8)) {
  3041.                         pos += 1;
  3042.                         return { height:  (nextByte() << 8) | nextByte(),
  3043.                                  width: (nextByte() << 8) | nextByte() };
  3044.                
  3045.                     } else {
  3046.                         pos += size - 2;
  3047.                     }
  3048.                 }
  3049.             }
  3050.         },
  3051.        
  3052.         /**
  3053.          * Returns context-aware node counter
  3054.          * @param {node} ZenNode
  3055.          * @return {Number}
  3056.          */
  3057.         getCounterForNode: function(node) {
  3058.             // find nearest repeating parent
  3059.             var counter = node.counter;
  3060.             if (!node.is_repeating && !node.repeat_by_lines) {
  3061.                 while (node = node.parent) {
  3062.                     if (node.is_repeating || node.repeat_by_lines)
  3063.                         return node.counter;
  3064.                 }
  3065.             }
  3066.            
  3067.             return counter;
  3068.         },
  3069.        
  3070.         /**
  3071.          * Process text that should be pasted into editor: clear escaped text and
  3072.          * handle tabstops
  3073.          * @param {String} text
  3074.          * @param {Function} escape_fn Handle escaped character. Must return
  3075.          * replaced value
  3076.          * @param {Function} tabstop_fn Callback function that will be called on every
  3077.          * tabstob occurance, passing <b>index</b>, <code>number</code> and
  3078.          * <b>value</b> (if exists) arguments. This function must return
  3079.          * replacement value
  3080.          * @return {String}
  3081.          */
  3082.         processTextBeforePaste: function(text, escape_fn, tabstop_fn) {
  3083.             var i = 0, il = text.length, start_ix, _i,
  3084.                 str_builder = [];
  3085.                
  3086.             var nextWhile = function(ix, fn) {
  3087.                 while (ix < il) if (!fn(text.charAt(ix++))) break;
  3088.                 return ix - 1;
  3089.             };
  3090.            
  3091.             while (i < il) {
  3092.                 var ch = text.charAt(i);
  3093.                 if (ch == '\\' && i + 1 < il) {
  3094.                     // handle escaped character
  3095.                     str_builder.push(escape_fn(text.charAt(i + 1)));
  3096.                     i += 2;
  3097.                     continue;
  3098.                 } else if (ch == '$') {
  3099.                     // looks like a tabstop
  3100.                     var next_ch = text.charAt(i + 1) || '';
  3101.                     _i = i;
  3102.                     if (this.isNumeric(next_ch)) {
  3103.                         // $N placeholder
  3104.                         start_ix = i + 1;
  3105.                         i = nextWhile(start_ix, this.isNumeric);
  3106.                         if (start_ix < i) {
  3107.                             str_builder.push(tabstop_fn(_i, text.substring(start_ix, i)));
  3108.                             continue;
  3109.                         }
  3110.                     } else if (next_ch == '{') {
  3111.                         // ${N:value} or ${N} placeholder
  3112.                         var brace_count = 1;
  3113.                         start_ix = i + 2;
  3114.                         i = nextWhile(start_ix, this.isNumeric);
  3115.                        
  3116.                         if (i > start_ix) {
  3117.                             if (text.charAt(i) == '}') {
  3118.                                 str_builder.push(tabstop_fn(_i, text.substring(start_ix, i)));
  3119.                                 i++; // handle closing brace
  3120.                                 continue;
  3121.                             } else if (text.charAt(i) == ':') {
  3122.                                 var val_start = i + 2;
  3123.                                 i = nextWhile(val_start, function(c) {
  3124.                                     if (c == '{') brace_count++;
  3125.                                     else if (c == '}') brace_count--;
  3126.                                     return !!brace_count;
  3127.                                 });
  3128.                                 str_builder.push(tabstop_fn(_i, text.substring(start_ix, val_start - 2), text.substring(val_start - 1, i)));
  3129.                                 i++; // handle closing brace
  3130.                                 continue;
  3131.                             }
  3132.                         }
  3133.                     }
  3134.                     i = _i;
  3135.                 }
  3136.                
  3137.                 // push current character to stack
  3138.                 str_builder.push(ch);
  3139.                 i++;
  3140.             }
  3141.            
  3142.             return str_builder.join('');
  3143.         }
  3144.     }
  3145. })();/**
  3146.  * Middleware layer that communicates between editor and Zen Coding.
  3147.  * This layer describes all available Zen Coding actions, like
  3148.  * "Expand Abbreviation".
  3149.  * @author Sergey Chikuyonok ([email protected])
  3150.  * @link http://chikuyonok.ru
  3151.  *
  3152.  * @include "zen_editor.js"
  3153.  * @include "html_matcher.js"
  3154.  * @include "zen_coding.js"
  3155.  * @include "zen_file.js"
  3156.  * @include "base64.js"
  3157.  */
  3158.  
  3159. /**
  3160.  * Search for abbreviation in editor from current caret position
  3161.  * @param {zen_editor} editor Editor instance
  3162.  * @return {String|null}
  3163.  */
  3164. function findAbbreviation(editor) {
  3165.     var range = editor.getSelectionRange(),
  3166.         content = String(editor.getContent());
  3167.     if (range.start != range.end) {
  3168.         // abbreviation is selected by user
  3169.         return content.substring(range.start, range.end);
  3170.     }
  3171.    
  3172.     // search for new abbreviation from current caret position
  3173.     var cur_line = editor.getCurrentLineRange();
  3174.     return zen_coding.extractAbbreviation(content.substring(cur_line.start, range.start));
  3175. }
  3176.  
  3177. /**
  3178.  * Find from current caret position and expand abbreviation in editor
  3179.  * @param {zen_editor} editor Editor instance
  3180.  * @param {String} [syntax] Syntax type (html, css, etc.)
  3181.  * @param {String} [profile_name] Output profile name (html, xml, xhtml)
  3182.  * @return {Boolean} Returns <code>true</code> if abbreviation was expanded
  3183.  * successfully
  3184.  */
  3185. function expandAbbreviation(editor, syntax, profile_name) {
  3186.     syntax = String(syntax || editor.getSyntax());
  3187.     profile_name = String(profile_name || editor.getProfileName());
  3188.    
  3189.     var caret_pos = editor.getSelectionRange().end,
  3190.         abbr,
  3191.         content = '';
  3192.        
  3193.     if ( (abbr = findAbbreviation(editor)) ) {
  3194.         content = zen_coding.expandAbbreviation(abbr, syntax, profile_name);
  3195.         if (content) {
  3196.             editor.replaceContent(content, caret_pos - abbr.length, caret_pos);
  3197.             return true;
  3198.         }
  3199.     }
  3200.    
  3201.     return false;
  3202. }
  3203.  
  3204. /**
  3205.  * A special version of <code>expandAbbreviation</code> function: if it can't
  3206.  * find abbreviation, it will place Tab character at caret position
  3207.  * @param {zen_editor} editor Editor instance
  3208.  * @param {String} syntax Syntax type (html, css, etc.)
  3209.  * @param {String} profile_name Output profile name (html, xml, xhtml)
  3210.  */
  3211. function expandAbbreviationWithTab(editor, syntax, profile_name) {
  3212.     syntax = String(syntax || editor.getSyntax());
  3213.     profile_name = String(profile_name || editor.getProfileName());
  3214.     if (!expandAbbreviation(editor, syntax, profile_name))
  3215.         editor.replaceContent(zen_coding.getVariable('indentation'), editor.getCaretPos());
  3216. }
  3217.  
  3218. /**
  3219.  * Find and select HTML tag pair
  3220.  * @param {zen_editor} editor Editor instance
  3221.  * @param {String} [direction] Direction of pair matching: 'in' or 'out'.
  3222.  * Default is 'out'
  3223.  */
  3224. function matchPair(editor, direction, syntax) {
  3225.     direction = String((direction || 'out').toLowerCase());
  3226.     syntax = String(syntax || editor.getProfileName());
  3227.    
  3228.     var range = editor.getSelectionRange(),
  3229.         cursor = range.end,
  3230.         range_start = range.start,
  3231.         range_end = range.end,
  3232. //      content = zen_coding.splitByLines(editor.getContent()).join('\n'),
  3233.         content = String(editor.getContent()),
  3234.         range = null,
  3235.         _r,
  3236.    
  3237.         old_open_tag = zen_coding.html_matcher.last_match['opening_tag'],
  3238.         old_close_tag = zen_coding.html_matcher.last_match['closing_tag'];
  3239.        
  3240.     if (direction == 'in' && old_open_tag && range_start != range_end) {
  3241. //      user has previously selected tag and wants to move inward
  3242.         if (!old_close_tag) {
  3243. //          unary tag was selected, can't move inward
  3244.             return false;
  3245.         } else if (old_open_tag.start == range_start) {
  3246.             if (content.charAt(old_open_tag.end) == '<') {
  3247. //              test if the first inward tag matches the entire parent tag's content
  3248.                 _r = zen_coding.html_matcher.find(content, old_open_tag.end + 1, syntax);
  3249.                 if (_r[0] == old_open_tag.end && _r[1] == old_close_tag.start) {
  3250.                     range = zen_coding.html_matcher(content, old_open_tag.end + 1, syntax);
  3251.                 } else {
  3252.                     range = [old_open_tag.end, old_close_tag.start];
  3253.                 }
  3254.             } else {
  3255.                 range = [old_open_tag.end, old_close_tag.start];
  3256.             }
  3257.         } else {
  3258.             var new_cursor = content.substring(0, old_close_tag.start).indexOf('<', old_open_tag.end);
  3259.             var search_pos = new_cursor != -1 ? new_cursor + 1 : old_open_tag.end;
  3260.             range = zen_coding.html_matcher(content, search_pos, syntax);
  3261.         }
  3262.     } else {
  3263.         range = zen_coding.html_matcher(content, cursor, syntax);
  3264.     }
  3265.    
  3266.     if (range !== null && range[0] != -1) {
  3267.         editor.createSelection(range[0], range[1]);
  3268.         return true;
  3269.     } else {
  3270.         return false;
  3271.     }
  3272. }
  3273.  
  3274. /**
  3275.  * Narrow down text indexes, adjusting selection to non-space characters
  3276.  * @param {String} text
  3277.  * @param {Number} start
  3278.  * @param {Number} end
  3279.  * @return {Array}
  3280.  */
  3281. function narrowToNonSpace(text, start, end) {
  3282.     // narrow down selection until first non-space character
  3283.     var re_space = /\s|\n|\r/;
  3284.     function isSpace(ch) {
  3285.         return re_space.test(ch);
  3286.     }
  3287.    
  3288.     while (start < end) {
  3289.         if (!isSpace(text.charAt(start)))
  3290.             break;
  3291.            
  3292.         start++;
  3293.     }
  3294.    
  3295.     while (end > start) {
  3296.         end--;
  3297.         if (!isSpace(text.charAt(end))) {
  3298.             end++;
  3299.             break;
  3300.         }
  3301.     }
  3302.    
  3303.     return [start, end];
  3304. }
  3305.  
  3306. /**
  3307.  * Wraps content with abbreviation
  3308.  * @param {zen_editor} Editor instance
  3309.  * @param {String} abbr Abbreviation to wrap with
  3310.  * @param {String} [syntax] Syntax type (html, css, etc.)
  3311.  * @param {String} [profile_name] Output profile name (html, xml, xhtml)
  3312.  */
  3313. function wrapWithAbbreviation(editor, abbr, syntax, profile_name) {
  3314.     syntax = String(syntax || editor.getSyntax());
  3315.     profile_name = String(profile_name || editor.getProfileName());
  3316.     abbr = abbr || editor.prompt("Enter abbreviation");
  3317.    
  3318.     var range = editor.getSelectionRange(),
  3319.         start_offset = range.start,
  3320.         end_offset = range.end,
  3321.         content = String(editor.getContent());
  3322.        
  3323.     if (!abbr || typeof abbr == 'undefined')
  3324.         return null;
  3325.        
  3326.     abbr = String(abbr);
  3327.    
  3328.     if (start_offset == end_offset) {
  3329.         // no selection, find tag pair
  3330.         range = zen_coding.html_matcher(content, start_offset, profile_name);
  3331.        
  3332.         if (!range || range[0] == -1) // nothing to wrap
  3333.             return null;
  3334.        
  3335.         var narrowed_sel = narrowToNonSpace(content, range[0], range[1]);
  3336.        
  3337.         start_offset = narrowed_sel[0];
  3338.         end_offset = narrowed_sel[1];
  3339.     }
  3340.    
  3341.     var new_content = zen_coding.escapeText(content.substring(start_offset, end_offset)),
  3342.         result = zen_coding.wrapWithAbbreviation(abbr, unindent(editor, new_content), syntax, profile_name);
  3343.    
  3344.     if (result) {
  3345.         editor.setCaretPos(end_offset);
  3346.         editor.replaceContent(result, start_offset, end_offset);
  3347.     }
  3348. }
  3349.  
  3350. /**
  3351.  * Unindent content, thus preparing text for tag wrapping
  3352.  * @param {zen_editor} editor Editor instance
  3353.  * @param {String} text
  3354.  * @return {String}
  3355.  */
  3356. function unindent(editor, text) {
  3357.     return unindentText(text, getCurrentLinePadding(editor));
  3358. }
  3359.  
  3360. /**
  3361.  * Removes padding at the beginning of each text's line
  3362.  * @param {String} text
  3363.  * @param {String} pad
  3364.  */
  3365. function unindentText(text, pad) {
  3366.     var lines = zen_coding.splitByLines(text);
  3367.     for (var i = 0; i < lines.length; i++) {
  3368.         if (lines[i].search(pad) == 0)
  3369.             lines[i] = lines[i].substr(pad.length);
  3370.     }
  3371.    
  3372.     return lines.join(zen_coding.getNewline());
  3373. }
  3374.  
  3375. /**
  3376.  * Returns padding of current editor's line
  3377.  * @param {zen_editor} Editor instance
  3378.  * @return {String}
  3379.  */
  3380. function getCurrentLinePadding(editor) {
  3381.     return getLinePadding(editor.getCurrentLine());
  3382. }
  3383.  
  3384. /**
  3385.  * Returns line padding
  3386.  * @param {String} line
  3387.  * @return {String}
  3388.  */
  3389. function getLinePadding(line) {
  3390.     return (line.match(/^(\s+)/) || [''])[0];
  3391. }
  3392.  
  3393. /**
  3394.  * Search for new caret insertion point
  3395.  * @param {zen_editor} editor Editor instance
  3396.  * @param {Number} inc Search increment: -1 — search left, 1 — search right
  3397.  * @param {Number} offset Initial offset relative to current caret position
  3398.  * @return {Number} Returns -1 if insertion point wasn't found
  3399.  */
  3400. function findNewEditPoint(editor, inc, offset) {
  3401.     inc = inc || 1;
  3402.     offset = offset || 0;
  3403.     var cur_point = editor.getCaretPos() + offset,
  3404.         content = String(editor.getContent()),
  3405.         max_len = content.length,
  3406.         next_point = -1,
  3407.         re_empty_line = /^\s+$/;
  3408.    
  3409.     function ch(ix) {
  3410.         return content.charAt(ix);
  3411.     }
  3412.    
  3413.     function getLine(ix) {
  3414.         var start = ix;
  3415.         while (start >= 0) {
  3416.             var c = ch(start);
  3417.             if (c == '\n' || c == '\r')
  3418.                 break;
  3419.             start--;
  3420.         }
  3421.        
  3422.         return content.substring(start, ix);
  3423.     }
  3424.        
  3425.     while (cur_point < max_len && cur_point > 0) {
  3426.         cur_point += inc;
  3427.         var cur_char = ch(cur_point),
  3428.             next_char = ch(cur_point + 1),
  3429.             prev_char = ch(cur_point - 1);
  3430.            
  3431.         switch (cur_char) {
  3432.             case '"':
  3433.             case '\'':
  3434.                 if (next_char == cur_char && prev_char == '=') {
  3435.                     // empty attribute
  3436.                     next_point = cur_point + 1;
  3437.                 }
  3438.                 break;
  3439.             case '>':
  3440.                 if (next_char == '<') {
  3441.                     // between tags
  3442.                     next_point = cur_point + 1;
  3443.                 }
  3444.                 break;
  3445.             case '\n':
  3446.             case '\r':
  3447.                 // empty line
  3448.                 if (re_empty_line.test(getLine(cur_point - 1))) {
  3449.                     next_point = cur_point;
  3450.                 }
  3451.                 break;
  3452.         }
  3453.        
  3454.         if (next_point != -1)
  3455.             break;
  3456.     }
  3457.    
  3458.     return next_point;
  3459. }
  3460.  
  3461. /**
  3462.  * Move caret to previous edit point
  3463.  * @param {zen_editor} editor Editor instance
  3464.  */
  3465. function prevEditPoint(editor) {
  3466.     var cur_pos = editor.getCaretPos(),
  3467.         new_point = findNewEditPoint(editor, -1);
  3468.        
  3469.     if (new_point == cur_pos)
  3470.         // we're still in the same point, try searching from the other place
  3471.         new_point = findNewEditPoint(editor, -1, -2);
  3472.    
  3473.     if (new_point != -1)
  3474.         editor.setCaretPos(new_point);
  3475. }
  3476.  
  3477. /**
  3478.  * Move caret to next edit point
  3479.  * @param {zen_editor} editor Editor instance
  3480.  */
  3481. function nextEditPoint(editor) {
  3482.     var new_point = findNewEditPoint(editor, 1);
  3483.     if (new_point != -1)
  3484.         editor.setCaretPos(new_point);
  3485. }
  3486.  
  3487. /**
  3488.  * Inserts newline character with proper indentation in specific positions only.
  3489.  * @param {zen_editor} editor
  3490.  * @return {Boolean} Returns <code>true</code> if line break was inserted
  3491.  */
  3492. function insertFormattedNewlineOnly(editor) {
  3493.     var caret_pos = editor.getCaretPos(),
  3494.         content = String(editor.getContent()),
  3495.         nl = zen_coding.getNewline(),
  3496.         pad = zen_coding.getVariable('indentation'),
  3497.         syntax = String(editor.getSyntax());
  3498.        
  3499.     if (syntax == 'html') {
  3500.         // let's see if we're breaking newly created tag
  3501.         var pair = zen_coding.html_matcher.getTags(content, caret_pos, String(editor.getProfileName()));
  3502.        
  3503.         if (pair[0] && pair[1] && pair[0].type == 'tag' && pair[0].end == caret_pos && pair[1].start == caret_pos) {
  3504.             editor.replaceContent(nl + pad + zen_coding.getCaretPlaceholder() + nl, caret_pos);
  3505.             return true;
  3506.         }
  3507.     } else if (syntax == 'css') {
  3508.         if (caret_pos && content.charAt(caret_pos - 1) == '{') {
  3509.             // look ahead for a closing brace
  3510.             for (var i = caret_pos, il = content.length, ch; i < il; i++) {
  3511.                 ch = content.charAt(i);
  3512.                 if (ch == '}') return false;
  3513.                 if (ch == '{') break;
  3514.             }
  3515.            
  3516.             // defining rule set
  3517.             var ins_value = nl + pad + zen_coding.getCaretPlaceholder() + nl,
  3518.                 has_close_brace = caret_pos < content.length && content.charAt(caret_pos) == '}';
  3519.                
  3520.             var user_close_brace = zen_coding.getVariable('close_css_brace');
  3521.             if (user_close_brace) {
  3522.                 // user defined how close brace should look like
  3523.                 ins_value += zen_coding.replaceVariables(user_close_brace);
  3524.             } else if (!has_close_brace) {
  3525.                 ins_value += '}';
  3526.             }
  3527.            
  3528.             editor.replaceContent(ins_value, caret_pos, caret_pos + (has_close_brace ? 1 : 0));
  3529.             return true;
  3530.         }
  3531.     }
  3532.        
  3533.     return false;
  3534. }
  3535.  
  3536. /**
  3537.  * Inserts newline character with proper indentation. This action is used in
  3538.  * editors that doesn't have indentation control (like textarea element) to
  3539.  * provide proper indentation
  3540.  * @param {zen_editor} editor Editor instance
  3541.  */
  3542. function insertFormattedNewline(editor) {
  3543.     if (!insertFormattedNewlineOnly(editor)) {
  3544.         var cur_padding = getCurrentLinePadding(editor),
  3545.             content = String(editor.getContent()),
  3546.             caret_pos = editor.getCaretPos(),
  3547.             c_len = content.length,
  3548.             nl = zen_coding.getNewline();
  3549.            
  3550.         // check out next line padding
  3551.         var line_range = editor.getCurrentLineRange(),
  3552.             next_padding = '';
  3553.            
  3554.         for (var i = line_range.end + 1, ch; i < c_len; i++) {
  3555.             ch = content.charAt(i);
  3556.             if (ch == ' ' || ch == '\t')
  3557.                 next_padding += ch;
  3558.             else
  3559.                 break;
  3560.         }
  3561.        
  3562.         if (next_padding.length > cur_padding.length)
  3563.             editor.replaceContent(nl + next_padding, caret_pos, caret_pos, true);
  3564.         else
  3565.             editor.replaceContent(nl, caret_pos);
  3566.     }
  3567. }
  3568.  
  3569. /**
  3570.  * Select line under cursor
  3571.  * @param {zen_editor} editor Editor instance
  3572.  */
  3573. function selectLine(editor) {
  3574.     var range = editor.getCurrentLineRange();
  3575.     editor.createSelection(range.start, range.end);
  3576. }
  3577.  
  3578. /**
  3579.  * Moves caret to matching opening or closing tag
  3580.  * @param {zen_editor} editor
  3581.  */
  3582. function goToMatchingPair(editor) {
  3583.     var content = String(editor.getContent()),
  3584.         caret_pos = editor.getCaretPos();
  3585.    
  3586.     if (content.charAt(caret_pos) == '<')
  3587.         // looks like caret is outside of tag pair  
  3588.         caret_pos++;
  3589.        
  3590.     var tags = zen_coding.html_matcher.getTags(content, caret_pos, String(editor.getProfileName()));
  3591.        
  3592.     if (tags && tags[0]) {
  3593.         // match found
  3594.         var open_tag = tags[0],
  3595.             close_tag = tags[1];
  3596.            
  3597.         if (close_tag) { // exclude unary tags
  3598.             if (open_tag.start <= caret_pos && open_tag.end >= caret_pos)
  3599.                 editor.setCaretPos(close_tag.start);
  3600.             else if (close_tag.start <= caret_pos && close_tag.end >= caret_pos)
  3601.                 editor.setCaretPos(open_tag.start);
  3602.         }
  3603.     }
  3604. }
  3605.  
  3606. /**
  3607.  * Merge lines spanned by user selection. If there's no selection, tries to find
  3608.  * matching tags and use them as selection
  3609.  * @param {zen_editor} editor
  3610.  */
  3611. function mergeLines(editor) {
  3612.     var selection = editor.getSelectionRange();
  3613.     if (selection.start == selection.end) {
  3614.         // find matching tag
  3615.         var pair = zen_coding.html_matcher(String(editor.getContent()), editor.getCaretPos(), String(editor.getProfileName()));
  3616.         if (pair) {
  3617.             selection.start = pair[0];
  3618.             selection.end = pair[1];
  3619.         }
  3620.     }
  3621.    
  3622.     if (selection.start != selection.end) {
  3623.         // got range, merge lines
  3624.         var text = String(editor.getContent()).substring(selection.start, selection.end),
  3625.             old_length = text.length;
  3626.         var lines =  zen_coding.splitByLines(text);
  3627.        
  3628.         for (var i = 1; i < lines.length; i++) {
  3629.             lines[i] = lines[i].replace(/^\s+/, '');
  3630.         }
  3631.        
  3632.         text = lines.join('').replace(/\s{2,}/, ' ');
  3633.         editor.replaceContent(text, selection.start, selection.end);
  3634.         editor.createSelection(selection.start, selection.start + text.length);
  3635.     }
  3636. }
  3637.  
  3638. /**
  3639.  * Toggle comment on current editor's selection or HTML tag/CSS rule
  3640.  * @param {zen_editor} editor
  3641.  */
  3642. function toggleComment(editor) {
  3643.     var syntax = String(editor.getSyntax());
  3644.     if (syntax == 'css') {
  3645.         // in case out editor is good enough and can recognize syntax from
  3646.         // current token, we have to make sure that cursor is not inside
  3647.         // 'style' attribute of html element
  3648.         var caret_pos = editor.getCaretPos();
  3649.         var pair = zen_coding.html_matcher.getTags(String(editor.getContent()), caret_pos);
  3650.         if (pair && pair[0] && pair[0].type == 'tag' &&
  3651.                 pair[0].start <= caret_pos && pair[0].end >= caret_pos) {
  3652.             syntax = 'html';
  3653.         }
  3654.     }
  3655.    
  3656.     switch (syntax) {
  3657.         case 'css':
  3658.             return toggleCSSComment(editor);
  3659.         default:
  3660.             return toggleHTMLComment(editor);
  3661.     }
  3662. }
  3663.  
  3664. /**
  3665.  * Toggle HTML comment on current selection or tag
  3666.  * @param {zen_editor} editor
  3667.  * @return {Boolean} Returns <code>true</code> if comment was toggled
  3668.  */
  3669. function toggleHTMLComment(editor) {
  3670.     var rng = editor.getSelectionRange(),
  3671.         content = String(editor.getContent());
  3672.        
  3673.     if (rng.start == rng.end) {
  3674.         // no selection, find matching tag
  3675.         var pair = zen_coding.html_matcher.getTags(content, editor.getCaretPos(), String(editor.getProfileName()));
  3676.         if (pair && pair[0]) { // found pair
  3677.             rng.start = pair[0].start;
  3678.             rng.end = pair[1] ? pair[1].end : pair[0].end;
  3679.         }
  3680.     }
  3681.    
  3682.     return genericCommentToggle(editor, '<!--', '-->', rng.start, rng.end);
  3683. }
  3684.  
  3685. /**
  3686.  * Simple CSS commenting
  3687.  * @param {zen_editor} editor
  3688.  * @return {Boolean} Returns <code>true</code> if comment was toggled
  3689.  */
  3690. function toggleCSSComment(editor) {
  3691.     var rng = editor.getSelectionRange();
  3692.        
  3693.     if (rng.start == rng.end) {
  3694.         // no selection, get current line
  3695.         rng = editor.getCurrentLineRange();
  3696.  
  3697.         // adjust start index till first non-space character
  3698.         var _r = narrowToNonSpace(String(editor.getContent()), rng.start, rng.end);
  3699.         rng.start = _r[0];
  3700.         rng.end = _r[1];
  3701.     }
  3702.    
  3703.     return genericCommentToggle(editor, '/*', '*/', rng.start, rng.end);
  3704. }
  3705.  
  3706. /**
  3707.  * Search for nearest comment in <code>str</code>, starting from index <code>from</code>
  3708.  * @param {String} text Where to search
  3709.  * @param {Number} from Search start index
  3710.  * @param {String} start_token Comment start string
  3711.  * @param {String} end_token Comment end string
  3712.  * @return {Array|null} Returns null if comment wasn't found
  3713.  */
  3714. function searchComment(text, from, start_token, end_token) {
  3715.     var start_ch = start_token.charAt(0),
  3716.         end_ch = end_token.charAt(0),
  3717.         comment_start = -1,
  3718.         comment_end = -1;
  3719.    
  3720.     function hasMatch(str, start) {
  3721.         return text.substr(start, str.length) == str;
  3722.     }
  3723.        
  3724.     // search for comment start
  3725.     while (from--) {
  3726.         if (text.charAt(from) == start_ch && hasMatch(start_token, from)) {
  3727.             comment_start = from;
  3728.             break;
  3729.         }
  3730.     }
  3731.    
  3732.     if (comment_start != -1) {
  3733.         // search for comment end
  3734.         from = comment_start;
  3735.         var content_len = text.length;
  3736.         while (content_len >= from++) {
  3737.             if (text.charAt(from) == end_ch && hasMatch(end_token, from)) {
  3738.                 comment_end = from + end_token.length;
  3739.                 break;
  3740.             }
  3741.         }
  3742.     }
  3743.    
  3744.     return (comment_start != -1 && comment_end != -1)
  3745.         ? [comment_start, comment_end]
  3746.         : null;
  3747. }
  3748.  
  3749. /**
  3750.  * Escape special regexp chars in string, making it usable for creating dynamic
  3751.  * regular expressions
  3752.  * @param {String} str
  3753.  * @return {String}
  3754.  */
  3755. function escapeForRegexp(str) {
  3756.   var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); // .*+?|()[]{}\
  3757.   return str.replace(specials, "\\$&");
  3758. }
  3759.  
  3760. /**
  3761.  * Generic comment toggling routine
  3762.  * @param {zen_editor} editor
  3763.  * @param {String} comment_start Comment start token
  3764.  * @param {String} comment_end Comment end token
  3765.  * @param {Number} range_start Start selection range
  3766.  * @param {Number} range_end End selection range
  3767.  * @return {Boolean}
  3768.  */
  3769. function genericCommentToggle(editor, comment_start, comment_end, range_start, range_end) {
  3770.     var content = String(editor.getContent()),
  3771.         caret_pos = editor.getCaretPos(),
  3772.         new_content = null;
  3773.        
  3774.     /**
  3775.      * Remove comment markers from string
  3776.      * @param {Sting} str
  3777.      * @return {String}
  3778.      */
  3779.     function removeComment(str) {
  3780.         return str
  3781.             .replace(new RegExp('^' + escapeForRegexp(comment_start) + '\\s*'), function(str){
  3782.                 caret_pos -= str.length;
  3783.                 return '';
  3784.             }).replace(new RegExp('\\s*' + escapeForRegexp(comment_end) + '$'), '');
  3785.     }
  3786.    
  3787.     function hasMatch(str, start) {
  3788.         return content.substr(start, str.length) == str;
  3789.     }
  3790.        
  3791.     // first, we need to make sure that this substring is not inside
  3792.     // comment
  3793.     var comment_range = searchComment(content, caret_pos, comment_start, comment_end);
  3794.    
  3795.     if (comment_range && comment_range[0] <= range_start && comment_range[1] >= range_end) {
  3796.         // we're inside comment, remove it
  3797.         range_start = comment_range[0];
  3798.         range_end = comment_range[1];
  3799.        
  3800.         new_content = removeComment(content.substring(range_start, range_end));
  3801.     } else {
  3802.         // should add comment
  3803.         // make sure that there's no comment inside selection
  3804.         new_content = comment_start + ' ' +
  3805.             content.substring(range_start, range_end)
  3806.                 .replace(new RegExp(escapeForRegexp(comment_start) + '\\s*|\\s*' + escapeForRegexp(comment_end), 'g'), '') +
  3807.             ' ' + comment_end;
  3808.            
  3809.         // adjust caret position
  3810.         caret_pos += comment_start.length + 1;
  3811.     }
  3812.  
  3813.     // replace editor content
  3814.     if (new_content !== null) {
  3815.         editor.setCaretPos(range_start);
  3816.         editor.replaceContent(unindent(editor, new_content), range_start, range_end);
  3817.         editor.setCaretPos(caret_pos);
  3818.         return true;
  3819.     }
  3820.    
  3821.     return false;
  3822. }
  3823.  
  3824. /**
  3825.  * Splits or joins tag, e.g. transforms it into a short notation and vice versa:<br>
  3826.  * &lt;div&gt;&lt;/div&gt; → &lt;div /&gt; : join<br>
  3827.  * &lt;div /&gt; → &lt;div&gt;&lt;/div&gt; : split
  3828.  * @param {zen_editor} editor Editor instance
  3829.  * @param {String} [profile_name] Profile name
  3830.  */
  3831. function splitJoinTag(editor, profile_name) {
  3832.     var caret_pos = editor.getCaretPos(),
  3833.         profile = zen_coding.getProfile(String(profile_name || editor.getProfileName())),
  3834.         caret = zen_coding.getCaretPlaceholder();
  3835.  
  3836.     // find tag at current position
  3837.     var pair = zen_coding.html_matcher.getTags(String(editor.getContent()), caret_pos, String(editor.getProfileName()));
  3838.     if (pair && pair[0]) {
  3839.         var new_content = pair[0].full_tag;
  3840.        
  3841.         if (pair[1]) { // join tag
  3842.             var closing_slash = ' /';
  3843.             if (profile.self_closing_tag === true)
  3844.                 closing_slash = '/';
  3845.                
  3846.             new_content = new_content.replace(/\s*>$/, closing_slash + '>');
  3847.            
  3848.             // add caret placeholder
  3849.             if (new_content.length + pair[0].start < caret_pos)
  3850.                 new_content += caret;
  3851.             else {
  3852.                 var d = caret_pos - pair[0].start;
  3853.                 new_content = new_content.substring(0, d) + caret + new_content.substring(d);
  3854.             }
  3855.            
  3856.             editor.replaceContent(new_content, pair[0].start, pair[1].end);
  3857.         } else { // split tag
  3858.             var nl = zen_coding.getNewline(),
  3859.                 pad = zen_coding.getVariable('indentation');
  3860.            
  3861.             // define tag content depending on profile
  3862.             var tag_content = (profile.tag_nl === true)
  3863.                     ? nl + pad +caret + nl
  3864.                     : caret;
  3865.                    
  3866.             new_content = new_content.replace(/\s*\/>$/, '>') + tag_content + '</' + pair[0].name + '>';
  3867.             editor.replaceContent(new_content, pair[0].start, pair[0].end);
  3868.         }
  3869.        
  3870.         return true;
  3871.     } else {
  3872.         return false;
  3873.     }
  3874. }
  3875.  
  3876. /**
  3877.  * Returns line bounds for specific character position
  3878.  * @param {String} text
  3879.  * @param {Number} from Where to start searching
  3880.  * @return {Object}
  3881.  */
  3882. function getLineBounds(text, from) {
  3883.     var len = text.length,
  3884.         start = 0,
  3885.         end = len - 1;
  3886.    
  3887.     // search left
  3888.     for (var i = from - 1; i > 0; i--) {
  3889.         var ch = text.charAt(i);
  3890.         if (ch == '\n' || ch == '\r') {
  3891.             start = i + 1;
  3892.             break;
  3893.         }
  3894.     }
  3895.     // search right
  3896.     for (var j = from; j < len; j++) {
  3897.         var ch = text.charAt(j);
  3898.         if (ch == '\n' || ch == '\r') {
  3899.             end = j;
  3900.             break;
  3901.         }
  3902.     }
  3903.    
  3904.     return {start: start, end: end};
  3905. }
  3906.  
  3907. /**
  3908.  * Gracefully removes tag under cursor
  3909.  * @param {zen_editor} editor
  3910.  */
  3911. function removeTag(editor) {
  3912.     var caret_pos = editor.getCaretPos(),
  3913.         content = String(editor.getContent());
  3914.        
  3915.     // search for tag
  3916.     var pair = zen_coding.html_matcher.getTags(content, caret_pos, String(editor.getProfileName()));
  3917.     if (pair && pair[0]) {
  3918.         if (!pair[1]) {
  3919.             // simply remove unary tag
  3920.             editor.replaceContent(zen_coding.getCaretPlaceholder(), pair[0].start, pair[0].end);
  3921.         } else {
  3922.             var tag_content_range = narrowToNonSpace(content, pair[0].end, pair[1].start),
  3923.                 start_line_bounds = getLineBounds(content, tag_content_range[0]),
  3924.                 start_line_pad = getLinePadding(content.substring(start_line_bounds.start, start_line_bounds.end)),
  3925.                 tag_content = content.substring(tag_content_range[0], tag_content_range[1]);
  3926.                
  3927.             tag_content = unindentText(tag_content, start_line_pad);
  3928.             editor.replaceContent(zen_coding.getCaretPlaceholder() + tag_content, pair[0].start, pair[1].end);
  3929.         }
  3930.        
  3931.         return true;
  3932.     } else {
  3933.         return false;
  3934.     }
  3935. }
  3936.  
  3937. /**
  3938.  * Test if <code>text</code> starts with <code>token</code> at <code>pos</code>
  3939.  * position. If <code>pos</code> is ommited, search from beginning of text
  3940.  * @param {String} token Token to test
  3941.  * @param {String} text Where to search
  3942.  * @param {Number} pos Position where to start search
  3943.  * @return {Boolean}
  3944.  * @since 0.65
  3945.  */
  3946. function startsWith(token, text, pos) {
  3947.     pos = pos || 0;
  3948.     return text.charAt(pos) == token.charAt(0) && text.substr(pos, token.length) == token;
  3949. }
  3950.  
  3951. /**
  3952.  * Encodes/decodes image under cursor to/from base64
  3953.  * @param {zen_editor} editor
  3954.  * @since 0.65
  3955.  */
  3956. function encodeDecodeBase64(editor) {
  3957.     var data = String(editor.getSelection()),
  3958.         caret_pos = editor.getCaretPos();
  3959.        
  3960.     if (!data) {
  3961.         // no selection, try to find image bounds from current caret position
  3962.         var text = String(editor.getContent()),
  3963.             ch,
  3964.             m;
  3965.         while (caret_pos-- >= 0) {
  3966.             if (startsWith('src=', text, caret_pos)) { // found <img src="">
  3967.                 if (m = text.substr(caret_pos).match(/^(src=(["'])?)([^'"<>\s]+)\1?/)) {
  3968.                     data = m[3];
  3969.                     caret_pos += m[1].length;
  3970.                 }
  3971.                 break;
  3972.             } else if (startsWith('url(', text, caret_pos)) { // found CSS url() pattern
  3973.                 if (m = text.substr(caret_pos).match(/^(url\((['"])?)([^'"\)\s]+)\1?/)) {
  3974.                     data = m[3];
  3975.                     caret_pos += m[1].length;
  3976.                 }
  3977.                 break;
  3978.             }
  3979.         }
  3980.     }
  3981.    
  3982.     if (data) {
  3983.         if (startsWith('data:', data))
  3984.             return decodeFromBase64(editor, data, caret_pos);
  3985.         else
  3986.             return encodeToBase64(editor, data, caret_pos);
  3987.     } else {
  3988.         return false;
  3989.     }
  3990. }
  3991.  
  3992. /**
  3993.  * Encodes image to base64
  3994.  * @requires zen_file
  3995.  *
  3996.  * @param {zen_editor} editor
  3997.  * @param {String} img_path Path to image
  3998.  * @param {Number} pos Caret position where image is located in the editor
  3999.  * @return {Boolean}
  4000.  */
  4001. function encodeToBase64(editor, img_path, pos) {
  4002.     var editor_file = editor.getFilePath(),
  4003.         default_mime_type = 'application/octet-stream';
  4004.        
  4005.     if (editor_file === null) {
  4006.         throw "You should save your file before using this action";
  4007.     }
  4008.    
  4009.     // locate real image path
  4010.     var real_img_path = zen_file.locateFile(editor_file, img_path);
  4011.     if (real_img_path === null) {
  4012.         throw "Can't find " + img_path + ' file';
  4013.     }
  4014.    
  4015.     var b64 = base64.encode(String(zen_file.read(real_img_path)));
  4016.     if (!b64) {
  4017.         throw "Can't encode file content to base64";
  4018.     }
  4019.    
  4020.     b64 = 'data:' + (base64.mime_types[String(zen_file.getExt(real_img_path))] || default_mime_type) +
  4021.         ';base64,' + b64;
  4022.        
  4023.     editor.replaceContent('$0' + b64, pos, pos + img_path.length);
  4024.     return true;
  4025. }
  4026.  
  4027. /**
  4028.  * Decodes base64 string back to file.
  4029.  * @requires zen_editor.prompt
  4030.  * @requires zen_file
  4031.  *
  4032.  * @param {zen_editor} editor
  4033.  * @param {String} data Base64-encoded file content
  4034.  * @param {Number} pos Caret position where image is located in the editor
  4035.  */
  4036. function decodeFromBase64(editor, data, pos) {
  4037.     // ask user to enter path to file
  4038.     var file_path = String(editor.prompt('Enter path to file (absolute or relative)'));
  4039.     if (!file_path)
  4040.         return false;
  4041.        
  4042.     var abs_path = zen_file.createPath(editor.getFilePath(), file_path);
  4043.     if (!abs_path) {
  4044.         throw "Can't save file";
  4045.     }
  4046.    
  4047.     zen_file.save(abs_path, base64.decode( data.replace(/^data\:.+?;.+?,/, '') ));
  4048.     editor.replaceContent('$0' + file_path, pos, pos + data.length);
  4049.     return true;
  4050. }
  4051.  
  4052. /**
  4053.  * Make decimal number look good: convert it to fixed precision end remove
  4054.  * traling zeroes
  4055.  * @param {Number} num
  4056.  * @param {Number} [fracion] Fraction numbers (default is 2)
  4057.  * @return {String}
  4058.  */
  4059. function prettifyNumber(num, fraction) {
  4060.     return num.toFixed(typeof fraction == 'undefined' ? 2 : fraction).replace(/\.?0+$/, '');
  4061. }
  4062.  
  4063. /**
  4064.  * Find expression bounds in current editor at caret position.
  4065.  * On each character a <code>fn</code> function will be caller which must
  4066.  * return <code>true</code> if current character meets requirements,
  4067.  * <code>false</code> otherwise
  4068.  * @param {zen_editor} editor
  4069.  * @param {Function} fn Function to test each character of expression
  4070.  * @return {Array} If expression found, returns array with start and end
  4071.  * positions
  4072.  */
  4073. function findExpressionBounds(editor, fn) {
  4074.     var content = String(editor.getContent()),
  4075.         il = content.length,
  4076.         expr_start = editor.getCaretPos() - 1,
  4077.         expr_end = expr_start + 1;
  4078.        
  4079.     // start by searching left
  4080.     while (expr_start >= 0 && fn(content.charAt(expr_start), expr_start, content)) expr_start--;
  4081.    
  4082.     // then search right
  4083.     while (expr_end < il && fn(content.charAt(expr_end), expr_end, content)) expr_end++;
  4084.    
  4085.     return expr_end > expr_start ? [++expr_start, expr_end] : null;
  4086. }
  4087.  
  4088. /**
  4089.  * Extract number from current caret position of the <code>editor</code> and
  4090.  * increment it by <code>step</code>
  4091.  * @param {zen_editor} editor
  4092.  * @param {Number} step Increment step (may be negative)
  4093.  */
  4094. function incrementNumber(editor, step) {
  4095.     var content = String(editor.getContent()),
  4096.         has_sign = false,
  4097.         has_decimal = false;
  4098.        
  4099.     var r = findExpressionBounds(editor, function(ch) {
  4100.         if (zen_coding.isNumeric(ch))
  4101.             return true;
  4102.         if (ch == '.')
  4103.             return has_decimal ? false : has_decimal = true;
  4104.         if (ch == '-')
  4105.             return has_sign ? false : has_sign = true;
  4106.            
  4107.         return false;
  4108.     });
  4109.        
  4110.     if (r) {
  4111.         var num = parseFloat(content.substring(r[0], r[1]));
  4112.         if (!isNaN(num)) {
  4113.             num = prettifyNumber(num + step);
  4114.             editor.replaceContent(num, r[0], r[1]);
  4115.             editor.createSelection(r[0], r[0] + num.length);
  4116.             return true;
  4117.         }
  4118.     }
  4119.    
  4120.     return false;
  4121. }
  4122.  
  4123. /**
  4124.  * Evaluates simple math expresison under caret
  4125.  * @param {zen_editor} editor
  4126.  */
  4127. function evaluateMathExpression(editor) {
  4128.     var content = String(editor.getContent()),
  4129.         chars = '.+-*/\\';
  4130.        
  4131.     var r = findExpressionBounds(editor, function(ch) {
  4132.         return zen_coding.isNumeric(ch) || chars.indexOf(ch) != -1;
  4133.     });
  4134.        
  4135.     if (r) {
  4136.         var expr = content.substring(r[0], r[1]);
  4137.        
  4138.         // replace integral division: 11\2 => Math.round(11/2)
  4139.         expr = expr.replace(/([\d\.\-]+)\\([\d\.\-]+)/g, 'Math.round($1/$2)');
  4140.        
  4141.         try {
  4142.             var result = new Function('return ' + expr)();
  4143.             result = prettifyNumber(result);
  4144.             editor.replaceContent(result, r[0], r[1]);
  4145.             editor.setCaretPos(r[0] + result.length);
  4146.             return true;
  4147.         } catch (e) {}
  4148.     }
  4149.    
  4150.     return false;
  4151. }
  4152.  
  4153. // register all actions
  4154. zen_coding.registerAction('expand_abbreviation', expandAbbreviation);
  4155. zen_coding.registerAction('expand_abbreviation_with_tab', expandAbbreviationWithTab);
  4156. zen_coding.registerAction('match_pair', matchPair);
  4157. zen_coding.registerAction('match_pair_inward', function(editor){
  4158.     matchPair(editor, 'in');
  4159. });
  4160.  
  4161. zen_coding.registerAction('match_pair_outward', function(editor){
  4162.     matchPair(editor, 'out');
  4163. });
  4164. zen_coding.registerAction('wrap_with_abbreviation', wrapWithAbbreviation);
  4165. zen_coding.registerAction('prev_edit_point', prevEditPoint);
  4166. zen_coding.registerAction('next_edit_point', nextEditPoint);
  4167. zen_coding.registerAction('insert_formatted_line_break', insertFormattedNewline);
  4168. zen_coding.registerAction('insert_formatted_line_break_only', insertFormattedNewlineOnly);
  4169. zen_coding.registerAction('select_line', selectLine);
  4170. zen_coding.registerAction('matching_pair', goToMatchingPair);
  4171. zen_coding.registerAction('merge_lines', mergeLines);
  4172. zen_coding.registerAction('toggle_comment', toggleComment);
  4173. zen_coding.registerAction('split_join_tag', splitJoinTag);
  4174. zen_coding.registerAction('remove_tag', removeTag);
  4175. zen_coding.registerAction('encode_decode_data_url', encodeDecodeBase64);
  4176. //zen_coding.registerAction('update_image_size', updateImageSize);
  4177.  
  4178. zen_coding.registerAction('increment_number_by_1', function(editor) {
  4179.     return incrementNumber(editor, 1);
  4180. });
  4181.  
  4182. zen_coding.registerAction('decrement_number_by_1', function(editor) {
  4183.     return incrementNumber(editor, -1);
  4184. });
  4185.  
  4186. zen_coding.registerAction('increment_number_by_10', function(editor) {
  4187.     return incrementNumber(editor, 10);
  4188. });
  4189.  
  4190. zen_coding.registerAction('decrement_number_by_10', function(editor) {
  4191.     return incrementNumber(editor, -10);
  4192. });
  4193.  
  4194. zen_coding.registerAction('increment_number_by_01', function(editor) {
  4195.     return incrementNumber(editor, 0.1);
  4196. });
  4197.  
  4198. zen_coding.registerAction('decrement_number_by_01', function(editor) {
  4199.     return incrementNumber(editor, -0.1);
  4200. });
  4201.  
  4202. zen_coding.registerAction('evaluate_math_expression', evaluateMathExpression);
  4203. /**
  4204.  * @author Sergey Chikuyonok ([email protected])
  4205.  * @link http://chikuyonok.ru
  4206.  */
  4207. (function(){
  4208.     // Regular Expressions for parsing tags and attributes
  4209.     var start_tag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
  4210.         end_tag = /^<\/([\w\:\-]+)[^>]*>/,
  4211.         attr = /([\w\-:]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
  4212.        
  4213.     // Empty Elements - HTML 4.01
  4214.     var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
  4215.  
  4216.     // Block Elements - HTML 4.01
  4217.     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");
  4218.  
  4219.     // Inline Elements - HTML 4.01
  4220.     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");
  4221.  
  4222.     // Elements that you can, intentionally, leave open
  4223.     // (and which close themselves)
  4224.     var close_self = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
  4225.    
  4226.     /** Current matching mode */
  4227.     var cur_mode = 'xhtml';
  4228.    
  4229.     /** Last matched HTML pair */
  4230.     var last_match = {
  4231.         opening_tag: null, // tag() or comment() object
  4232.         closing_tag: null, // tag() or comment() object
  4233.         start_ix: -1,
  4234.         end_ix: -1
  4235.     };
  4236.    
  4237.     function setMode(new_mode) {
  4238.         if (!new_mode || new_mode != 'html')
  4239.             new_mode = 'xhtml';
  4240.            
  4241.         cur_mode = new_mode;
  4242.     }
  4243.    
  4244.     function tag(match, ix) {
  4245.         var name = match[1].toLowerCase();
  4246.         return  {
  4247.             name: name,
  4248.             full_tag: match[0],
  4249.             start: ix,
  4250.             end: ix + match[0].length,
  4251.             unary: Boolean(match[3]) || (name in empty && cur_mode == 'html'),
  4252.             has_close: Boolean(match[3]),
  4253.             type: 'tag',
  4254.             close_self: (name in close_self && cur_mode == 'html')
  4255.         };
  4256.     }
  4257.    
  4258.     function comment(start, end) {
  4259.         return {
  4260.             start: start,
  4261.             end: end,
  4262.             type: 'comment'
  4263.         };
  4264.     }
  4265.    
  4266.     function makeMap(str){
  4267.         var obj = {}, items = str.split(",");
  4268.         for ( var i = 0; i < items.length; i++ )
  4269.             obj[ items[i] ] = true;
  4270.         return obj;
  4271.     }
  4272.    
  4273.     /**
  4274.      * Makes selection ranges for matched tag pair
  4275.      * @param {tag} opening_tag
  4276.      * @param {tag} closing_tag
  4277.      * @param {Number} ix
  4278.      */
  4279.     function makeRange(opening_tag, closing_tag, ix) {
  4280.         ix = ix || 0;
  4281.        
  4282.         var start_ix = -1,
  4283.             end_ix = -1;
  4284.        
  4285.         if (opening_tag && !closing_tag) { // unary element
  4286.             start_ix = opening_tag.start;
  4287.             end_ix = opening_tag.end;
  4288.         } else if (opening_tag && closing_tag) { // complete element
  4289.             if (
  4290.                 (opening_tag.start < ix && opening_tag.end > ix) ||
  4291.                 (closing_tag.start <= ix && closing_tag.end > ix)
  4292.             ) {
  4293.                 start_ix = opening_tag.start;
  4294.                 end_ix = closing_tag.end;
  4295.             } else {
  4296.                 start_ix = opening_tag.end;
  4297.                 end_ix = closing_tag.start;
  4298.             }
  4299.         }
  4300.        
  4301.         return [start_ix, end_ix];
  4302.     }
  4303.    
  4304.     /**
  4305.      * Save matched tag for later use and return found indexes
  4306.      * @param {tag} opening_tag
  4307.      * @param {tag} closing_tag
  4308.      * @param {Number} ix
  4309.      * @return {Array}
  4310.      */
  4311.     function saveMatch(opening_tag, closing_tag, ix) {
  4312.         ix = ix || 0;
  4313.         last_match.opening_tag = opening_tag;
  4314.         last_match.closing_tag = closing_tag;
  4315.        
  4316.         var range = makeRange(opening_tag, closing_tag, ix);
  4317.         last_match.start_ix = range[0];
  4318.         last_match.end_ix = range[1];
  4319.        
  4320.         return last_match.start_ix != -1 ? [last_match.start_ix, last_match.end_ix] : null;
  4321.     }
  4322.    
  4323.     /**
  4324.      * Handle unary tag: find closing tag if needed
  4325.      * @param {String} text
  4326.      * @param {Number} ix
  4327.      * @param {tag} open_tag
  4328.      * @return {tag|null} Closing tag (or null if not found)
  4329.      */
  4330.     function handleUnaryTag(text, ix, open_tag) {
  4331.         if (open_tag.has_close)
  4332.             return null;
  4333.         else {
  4334.             // TODO finish this method
  4335.         }
  4336.     }
  4337.    
  4338.     /**
  4339.      * Search for matching tags in <code>html</code>, starting from
  4340.      * <code>start_ix</code> position
  4341.      * @param {String} html Code to search
  4342.      * @param {Number} start_ix Character index where to start searching pair
  4343.      * (commonly, current caret position)
  4344.      * @param {Function} action Function that creates selection range
  4345.      * @return {Array|null}
  4346.      */
  4347.     function findPair(html, start_ix, mode, action) {
  4348.         action = action || makeRange;
  4349.         setMode(mode);
  4350.        
  4351.         var forward_stack = [],
  4352.             backward_stack = [],
  4353.             /** @type {tag()} */
  4354.             opening_tag = null,
  4355.             /** @type {tag()} */
  4356.             closing_tag = null,
  4357.             range = null,
  4358.             html_len = html.length,
  4359.             m,
  4360.             ix,
  4361.             tmp_tag;
  4362.            
  4363.         forward_stack.last = backward_stack.last = function() {
  4364.             return this[this.length - 1];
  4365.         }
  4366.        
  4367.         function hasMatch(str, start) {
  4368.             if (arguments.length == 1)
  4369.                 start = ix;
  4370.             return html.substr(start, str.length) == str;
  4371.         }
  4372.        
  4373.         function searchCommentStart(from) {
  4374.             while (from--) {
  4375.                 if (html.charAt(from) == '<' && hasMatch('<!--', from))
  4376.                     break;
  4377.             }
  4378.            
  4379.             return from;
  4380.         }
  4381.        
  4382.         // find opening tag
  4383.         ix = start_ix;
  4384.         while (ix-- && ix >= 0) {
  4385.             var ch = html.charAt(ix);
  4386.             if (ch == '<') {
  4387.                 var check_str = html.substring(ix, html_len);
  4388.                
  4389.                 if ( (m = check_str.match(end_tag)) ) { // found closing tag
  4390.                     tmp_tag = tag(m, ix);
  4391.                     if (tmp_tag.start < start_ix && tmp_tag.end > start_ix) // direct hit on searched closing tag
  4392.                         closing_tag = tmp_tag;
  4393.                     else
  4394.                         backward_stack.push(tmp_tag);
  4395.                 } else if ( (m = check_str.match(start_tag)) ) { // found opening tag
  4396.                     tmp_tag = tag(m, ix);
  4397.                    
  4398.                     if (tmp_tag.unary) {
  4399.                         if (tmp_tag.start < start_ix && tmp_tag.end > start_ix) // exact match
  4400.                             // TODO handle unary tag
  4401.                             return action(tmp_tag, null, start_ix);
  4402.                     } else if (backward_stack.last() && backward_stack.last().name == tmp_tag.name) {
  4403.                         backward_stack.pop();
  4404.                     } else { // found nearest unclosed tag
  4405.                         opening_tag = tmp_tag;
  4406.                         break;
  4407.                     }
  4408.                 } else if (check_str.indexOf('<!--') == 0) { // found comment start
  4409.                     var end_ix = check_str.search('-->') + ix + 3;
  4410.                     if (ix < start_ix && end_ix >= start_ix)
  4411.                         return action( comment(ix, end_ix) );
  4412.                 }
  4413.             } else if (ch == '-' && hasMatch('-->')) { // found comment end
  4414.                 // search left until comment start is reached
  4415.                 ix = searchCommentStart(ix);
  4416.             }
  4417.         }
  4418.        
  4419.         if (!opening_tag)
  4420.             return action(null);
  4421.        
  4422.         // find closing tag
  4423.         if (!closing_tag) {
  4424.             for (ix = start_ix; ix < html_len; ix++) {
  4425.                 var ch = html.charAt(ix);
  4426.                 if (ch == '<') {
  4427.                     var check_str = html.substring(ix, html_len);
  4428.                    
  4429.                     if ( (m = check_str.match(start_tag)) ) { // found opening tag
  4430.                         tmp_tag = tag(m, ix);
  4431.                         if (!tmp_tag.unary)
  4432.                             forward_stack.push( tmp_tag );
  4433.                     } else if ( (m = check_str.match(end_tag)) ) { // found closing tag
  4434.                         var tmp_tag = tag(m, ix);
  4435.                         if (forward_stack.last() && forward_stack.last().name == tmp_tag.name)
  4436.                             forward_stack.pop();
  4437.                         else { // found matched closing tag
  4438.                             closing_tag = tmp_tag;
  4439.                             break;
  4440.                         }
  4441.                     } else if (hasMatch('<!--')) { // found comment
  4442.                         ix += check_str.search('-->') + 2;
  4443.                     }
  4444.                 } else if (ch == '-' && hasMatch('-->')) {
  4445.                     // looks like cursor was inside comment with invalid HTML
  4446.                     if (!forward_stack.last() || forward_stack.last().type != 'comment') {
  4447.                         var end_ix = ix + 3;
  4448.                         return action(comment( searchCommentStart(ix), end_ix ));
  4449.                     }
  4450.                 }
  4451.             }
  4452.         }
  4453.        
  4454.         return action(opening_tag, closing_tag, start_ix);
  4455.     }
  4456.    
  4457.     /**
  4458.      * Search for matching tags in <code>html</code>, starting
  4459.      * from <code>start_ix</code> position. The result is automatically saved in
  4460.      * <code>last_match</code> property
  4461.      *
  4462.      * @return {Array|null}
  4463.      */
  4464.     var HTMLPairMatcher = function(/* String */ html, /* Number */ start_ix, /*  */ mode){
  4465.         return findPair(html, start_ix, mode, saveMatch);
  4466.     }
  4467.    
  4468.     HTMLPairMatcher.start_tag = start_tag;
  4469.     HTMLPairMatcher.end_tag = end_tag;
  4470.    
  4471.     /**
  4472.      * Search for matching tags in <code>html</code>, starting from
  4473.      * <code>start_ix</code> position. The difference between
  4474.      * <code>HTMLPairMatcher</code> function itself is that <code>find</code>
  4475.      * method doesn't save matched result in <code>last_match</code> property.
  4476.      * This method is generally used for lookups
  4477.      */
  4478.     HTMLPairMatcher.find = function(html, start_ix, mode) {
  4479.         return findPair(html, start_ix, mode);
  4480.     };
  4481.    
  4482.     /**
  4483.      * Search for matching tags in <code>html</code>, starting from
  4484.      * <code>start_ix</code> position. The difference between
  4485.      * <code>HTMLPairMatcher</code> function itself is that <code>getTags</code>
  4486.      * method doesn't save matched result in <code>last_match</code> property
  4487.      * and returns array of opening and closing tags
  4488.      * This method is generally used for lookups
  4489.      */
  4490.     HTMLPairMatcher.getTags = function(html, start_ix, mode) {
  4491.         return findPair(html, start_ix, mode, function(opening_tag, closing_tag){
  4492.             return [opening_tag, closing_tag];
  4493.         });
  4494.     };
  4495.    
  4496.     HTMLPairMatcher.last_match = last_match;
  4497.    
  4498.     try {
  4499.         zen_coding.html_matcher = HTMLPairMatcher;
  4500.     } catch(e){}
  4501.    
  4502. })();/**
  4503.  * @author Sergey Chikuyonok ([email protected])
  4504.  * @link http://chikuyonok.ru
  4505.  */
  4506. var base64 = {
  4507.     chars : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  4508.  
  4509.     mime_types : {
  4510.         'gif' : 'image/gif',
  4511.         'png' : 'image/png',
  4512.         'jpg' : 'image/jpeg',
  4513.         'jpeg' : 'image/jpeg',
  4514.         'svg' : 'image/svg+xml',
  4515.         'html' : 'text/html',
  4516.         'htm' : 'text/html'
  4517.     },
  4518.  
  4519.     encode : function(input) {
  4520.         var output = [];
  4521.         var chr1, chr2, chr3, enc1, enc2, enc3, enc4, cdp1, cdp2, cdp3;
  4522.         var i = 0, il = input.length, b64_str = this.chars;
  4523.  
  4524.         while (i < il) {
  4525.  
  4526.             cdp1 = input.charCodeAt(i++);
  4527.             cdp2 = input.charCodeAt(i++);
  4528.             cdp3 = input.charCodeAt(i++);
  4529.  
  4530.             chr1 = cdp1 & 0xff;
  4531.             chr2 = cdp2 & 0xff;
  4532.             chr3 = cdp3 & 0xff;
  4533.  
  4534.             enc1 = chr1 >> 2;
  4535.             enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  4536.             enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  4537.             enc4 = chr3 & 63;
  4538.  
  4539.             if (isNaN(cdp2)) {
  4540.                 enc3 = enc4 = 64;
  4541.             } else if (isNaN(cdp3)) {
  4542.                 enc4 = 64;
  4543.             }
  4544.  
  4545.             output.push(b64_str.charAt(enc1) + b64_str.charAt(enc2) + b64_str.charAt(enc3) + b64_str.charAt(enc4));
  4546.         }
  4547.  
  4548.         return output.join('');
  4549.     },
  4550.  
  4551.     /**
  4552.      * Decodes string using MIME base64 algorithm
  4553.      *
  4554.      * @author Tyler Akins (http://rumkin.com)
  4555.      * @param {String}
  4556.      *            data
  4557.      * @return {String}
  4558.      */
  4559.     decode : function(data) {
  4560.         var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, dec = "", tmp_arr = [];
  4561.         var b64 = this.chars, il = data.length;
  4562.  
  4563.         if (!data) {
  4564.             return data;
  4565.         }
  4566.  
  4567.         data += '';
  4568.  
  4569.         do { // unpack four hexets into three octets using index points in b64
  4570.             h1 = b64.indexOf(data.charAt(i++));
  4571.             h2 = b64.indexOf(data.charAt(i++));
  4572.             h3 = b64.indexOf(data.charAt(i++));
  4573.             h4 = b64.indexOf(data.charAt(i++));
  4574.  
  4575.             bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
  4576.  
  4577.             o1 = bits >> 16 & 0xff;
  4578.             o2 = bits >> 8 & 0xff;
  4579.             o3 = bits & 0xff;
  4580.  
  4581.             if (h3 == 64) {
  4582.                 tmp_arr[ac++] = String.fromCharCode(o1);
  4583.             } else if (h4 == 64) {
  4584.                 tmp_arr[ac++] = String.fromCharCode(o1, o2);
  4585.             } else {
  4586.                 tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
  4587.             }
  4588.         } while (i < il);
  4589.  
  4590.         return tmp_arr.join('');
  4591.     }
  4592. };/* This file defines an XML parser, with a few kludges to make it
  4593.  * useable for HTML. autoSelfClosers defines a set of tag names that
  4594.  * are expected to not have a closing tag, and doNotIndent specifies
  4595.  * the tags inside of which no indentation should happen (see Config
  4596.  * object). These can be disabled by passing the editor an object like
  4597.  * {useHTMLKludges: false} as parserConfig option.
  4598.  *
  4599.  * Original code by Marijn Haverbeke
  4600.  * from CodeMirror projet: http://codemirror.net/
  4601.  */
  4602.  
  4603. var XMLParser = (function() {
  4604.     // The value used to signal the end of a sequence in iterators.
  4605.     var StopIteration = {
  4606.         toString : function() {
  4607.             return "StopIteration"
  4608.         }
  4609.     };
  4610.    
  4611.     // Apply a function to each element in a sequence.
  4612.     function forEach(iter, f) {
  4613.         if (iter.next) {
  4614.             try {
  4615.                 while (true)
  4616.                     f(iter.next());
  4617.             } catch (e) {
  4618.                 if (e != StopIteration)
  4619.                     throw e;
  4620.             }
  4621.         } else {
  4622.             for (var i = 0; i < iter.length; i++)
  4623.                 f(iter[i]);
  4624.         }
  4625.     }
  4626.    
  4627.     // A framework for simple tokenizers. Takes care of newlines and
  4628.     // white-space, and of getting the text from the source stream into
  4629.     // the token object. A state is a function of two arguments -- a
  4630.     // string stream and a setState function. The second can be used to
  4631.     // change the tokenizer's state, and can be ignored for stateless
  4632.     // tokenizers. This function should advance the stream over a token
  4633.     // and return a string or object containing information about the next
  4634.     // token, or null to pass and have the (new) state be called to finish
  4635.     // the token. When a string is given, it is wrapped in a {style, type}
  4636.     // object. In the resulting object, the characters consumed are stored
  4637.     // under the content property. Any whitespace following them is also
  4638.     // automatically consumed, and added to the value property. (Thus,
  4639.     // content is the actual meaningful part of the token, while value
  4640.     // contains all the text it spans.)
  4641.    
  4642.     function tokenizer(source, state) {
  4643.         // Newlines are always a separate token.
  4644.         function isWhiteSpace(ch) {
  4645.             // The messy regexp is because IE's regexp matcher is of the
  4646.             // opinion that non-breaking spaces are no whitespace.
  4647.             return ch != "\n" && /^[\s\u00a0]*$/.test(ch);
  4648.         }
  4649.    
  4650.         var tokenizer = {
  4651.             state : state,
  4652.    
  4653.             take : function(type) {
  4654.                 if (typeof(type) == "string")
  4655.                     type = {
  4656.                         style : type,
  4657.                         type : type
  4658.                     };
  4659.    
  4660.                 type.content = (type.content || "") + source.get();
  4661.                 if (!/\n$/.test(type.content))
  4662.                     source.nextWhile(isWhiteSpace);
  4663.                 type.value = type.content + source.get();
  4664.                 return type;
  4665.             },
  4666.    
  4667.             next : function() {
  4668.                 if (!source.more())
  4669.                     throw StopIteration;
  4670.    
  4671.                 var type;
  4672.                 if (source.equals("\n")) {
  4673.                     source.next();
  4674.                     return this.take("whitespace");
  4675.                 }
  4676.    
  4677.                 if (source.applies(isWhiteSpace))
  4678.                     type = "whitespace";
  4679.                 else
  4680.                     while (!type)
  4681.                         type = this.state(source, function(s) {
  4682.                                     tokenizer.state = s;
  4683.                                 });
  4684.    
  4685.                 return this.take(type);
  4686.             }
  4687.         };
  4688.         return tokenizer;
  4689.     }
  4690.    
  4691.     /*
  4692.      * String streams are the things fed to parsers (which can feed them to a
  4693.      * tokenizer if they want). They provide peek and next methods for looking at
  4694.      * the current character (next 'consumes' this character, peek does not), and a
  4695.      * get method for retrieving all the text that was consumed since the last time
  4696.      * get was called.
  4697.      *
  4698.      * An easy mistake to make is to let a StopIteration exception finish the token
  4699.      * stream while there are still characters pending in the string stream (hitting
  4700.      * the end of the buffer while parsing a token). To make it easier to detect
  4701.      * such errors, the stringstreams throw an exception when this happens.
  4702.      */
  4703.    
  4704.     // Make a stringstream stream out of an iterator that returns strings.
  4705.     // This is applied to the result of traverseDOM (see codemirror.js),
  4706.     // and the resulting stream is fed to the parser.
  4707.     var stringStream = function(source) {
  4708.         // String that's currently being iterated over.
  4709.         var current = "";
  4710.         // Position in that string.
  4711.         var pos = 0;
  4712.         // Accumulator for strings that have been iterated over but not
  4713.         // get()-ed yet.
  4714.         var accum = "";
  4715.        
  4716.         // ZC fix: if we've passed a string, wrap it with traverseDOM-like interface
  4717.         if (typeof source == 'string') {
  4718.             var _source = source,
  4719.                 _fed = false;
  4720.             source = {
  4721.                 next: function() {
  4722.                     if (!_fed) {
  4723.                         _fed = true;
  4724.                         return _source;
  4725.                     } else {
  4726.                         throw StopIteration;
  4727.                     }
  4728.                 }
  4729.             }
  4730.         }
  4731.        
  4732.         // Make sure there are more characters ready, or throw
  4733.         // StopIteration.
  4734.         function ensureChars() {
  4735.             while (pos == current.length) {
  4736.                 accum += current;
  4737.                 current = ""; // In case source.next() throws
  4738.                 pos = 0;
  4739.                 try {
  4740.                     current = source.next();
  4741.                 } catch (e) {
  4742.                     if (e != StopIteration)
  4743.                         throw e;
  4744.                     else
  4745.                         return false;
  4746.                 }
  4747.             }
  4748.             return true;
  4749.         }
  4750.    
  4751.         return {
  4752.             // peek: -> character
  4753.             // Return the next character in the stream.
  4754.             peek : function() {
  4755.                 if (!ensureChars())
  4756.                     return null;
  4757.                 return current.charAt(pos);
  4758.             },
  4759.             // next: -> character
  4760.             // Get the next character, throw StopIteration if at end, check
  4761.             // for unused content.
  4762.             next : function() {
  4763.                 if (!ensureChars()) {
  4764.                     if (accum.length > 0)
  4765.                         throw "End of stringstream reached without emptying buffer ('" + accum + "').";
  4766.                     else
  4767.                         throw StopIteration;
  4768.                 }
  4769.                 return current.charAt(pos++);
  4770.             },
  4771.             // get(): -> string
  4772.             // Return the characters iterated over since the last call to
  4773.             // .get().
  4774.             get : function() {
  4775.                 var temp = accum;
  4776.                 accum = "";
  4777.                 if (pos > 0) {
  4778.                     temp += current.slice(0, pos);
  4779.                     current = current.slice(pos);
  4780.                     pos = 0;
  4781.                 }
  4782.                 return temp;
  4783.             },
  4784.             // Push a string back into the stream.
  4785.             push : function(str) {
  4786.                 current = current.slice(0, pos) + str + current.slice(pos);
  4787.             },
  4788.             lookAhead : function(str, consume, skipSpaces, caseInsensitive) {
  4789.                 function cased(str) {
  4790.                     return caseInsensitive ? str.toLowerCase() : str;
  4791.                 }
  4792.                 str = cased(str);
  4793.                 var found = false;
  4794.    
  4795.                 var _accum = accum, _pos = pos;
  4796.                 if (skipSpaces)
  4797.                     this.nextWhileMatches(/[\s\u00a0]/);
  4798.    
  4799.                 while (true) {
  4800.                     var end = pos + str.length, left = current.length - pos;
  4801.                     if (end <= current.length) {
  4802.                         found = str == cased(current.slice(pos, end));
  4803.                         pos = end;
  4804.                         break;
  4805.                     } else if (str.slice(0, left) == cased(current.slice(pos))) {
  4806.                         accum += current;
  4807.                         current = "";
  4808.                         try {
  4809.                             current = source.next();
  4810.                         } catch (e) {
  4811.                             if (e != StopIteration)
  4812.                                 throw e;
  4813.                             break;
  4814.                         }
  4815.                         pos = 0;
  4816.                         str = str.slice(left);
  4817.                     } else {
  4818.                         break;
  4819.                     }
  4820.                 }
  4821.    
  4822.                 if (!(found && consume)) {
  4823.                     current = accum.slice(_accum.length) + current;
  4824.                     pos = _pos;
  4825.                     accum = _accum;
  4826.                 }
  4827.    
  4828.                 return found;
  4829.             },
  4830.             // Wont't match past end of line.
  4831.             lookAheadRegex : function(regex, consume) {
  4832.                 if (regex.source.charAt(0) != "^")
  4833.                     throw new Error("Regexps passed to lookAheadRegex must start with ^");
  4834.    
  4835.                 // Fetch the rest of the line
  4836.                 while (current.indexOf("\n", pos) == -1) {
  4837.                     try {
  4838.                         current += source.next();
  4839.                     } catch (e) {
  4840.                         if (e != StopIteration)
  4841.                             throw e;
  4842.                         break;
  4843.                     }
  4844.                 }
  4845.                 var matched = current.slice(pos).match(regex);
  4846.                 if (matched && consume)
  4847.                     pos += matched[0].length;
  4848.                 return matched;
  4849.             },
  4850.    
  4851.             // Utils built on top of the above
  4852.             // more: -> boolean
  4853.             // Produce true if the stream isn't empty.
  4854.             more : function() {
  4855.                 return this.peek() !== null;
  4856.             },
  4857.             applies : function(test) {
  4858.                 var next = this.peek();
  4859.                 return (next !== null && test(next));
  4860.             },
  4861.             nextWhile : function(test) {
  4862.                 var next;
  4863.                 while ((next = this.peek()) !== null && test(next))
  4864.                     this.next();
  4865.             },
  4866.             matches : function(re) {
  4867.                 var next = this.peek();
  4868.                 return (next !== null && re.test(next));
  4869.             },
  4870.             nextWhileMatches : function(re) {
  4871.                 var next;
  4872.                 while ((next = this.peek()) !== null && re.test(next))
  4873.                     this.next();
  4874.             },
  4875.             equals : function(ch) {
  4876.                 return ch === this.peek();
  4877.             },
  4878.             endOfLine : function() {
  4879.                 var next = this.peek();
  4880.                 return next == null || next == "\n";
  4881.             }
  4882.         };
  4883.     };
  4884.  
  4885.    
  4886.    
  4887.   var Kludges = {
  4888.     autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
  4889.                       "meta": true, "col": true, "frame": true, "base": true, "area": true},
  4890.     doNotIndent: {"pre": true, "!cdata": true}
  4891.   };
  4892.   var NoKludges = {autoSelfClosers: {}, doNotIndent: {"!cdata": true}};
  4893.   var UseKludges = Kludges;
  4894.   var alignCDATA = false;
  4895.  
  4896.   // Simple stateful tokenizer for XML documents. Returns a
  4897.   // MochiKit-style iterator, with a state property that contains a
  4898.   // function encapsulating the current state. See tokenize.js.
  4899.   var tokenizeXML = (function() {
  4900.     function inText(source, setState) {
  4901.       var ch = source.next();
  4902.       if (ch == "<") {
  4903.         if (source.equals("!")) {
  4904.           source.next();
  4905.           if (source.equals("[")) {
  4906.             if (source.lookAhead("[CDATA[", true)) {
  4907.               setState(inBlock("xml-cdata", "]]>"));
  4908.               return null;
  4909.             }
  4910.             else {
  4911.               return "xml-text";
  4912.             }
  4913.           }
  4914.           else if (source.lookAhead("--", true)) {
  4915.             setState(inBlock("xml-comment", "-->"));
  4916.             return null;
  4917.           }
  4918.           else if (source.lookAhead("DOCTYPE", true)) {
  4919.             source.nextWhileMatches(/[\w\._\-]/);
  4920.             setState(inBlock("xml-doctype", ">"));
  4921.             return "xml-doctype";
  4922.           }
  4923.           else {
  4924.             return "xml-text";
  4925.           }
  4926.         }
  4927.         else if (source.equals("?")) {
  4928.           source.next();
  4929.           source.nextWhileMatches(/[\w\._\-]/);
  4930.           setState(inBlock("xml-processing", "?>"));
  4931.           return "xml-processing";
  4932.         }
  4933.         else {
  4934.           if (source.equals("/")) source.next();
  4935.           setState(inTag);
  4936.           return "xml-punctuation";
  4937.         }
  4938.       }
  4939.       else if (ch == "&") {
  4940.         while (!source.endOfLine()) {
  4941.           if (source.next() == ";")
  4942.             break;
  4943.         }
  4944.         return "xml-entity";
  4945.       }
  4946.       else {
  4947.         source.nextWhileMatches(/[^&<\n]/);
  4948.         return "xml-text";
  4949.       }
  4950.     }
  4951.  
  4952.     function inTag(source, setState) {
  4953.       var ch = source.next();
  4954.       if (ch == ">") {
  4955.         setState(inText);
  4956.         return "xml-punctuation";
  4957.       }
  4958.       else if (/[?\/]/.test(ch) && source.equals(">")) {
  4959.         source.next();
  4960.         setState(inText);
  4961.         return "xml-punctuation";
  4962.       }
  4963.       else if (ch == "=") {
  4964.         return "xml-punctuation";
  4965.       }
  4966.       else if (/[\'\"]/.test(ch)) {
  4967.         setState(inAttribute(ch));
  4968.         return null;
  4969.       }
  4970.       else {
  4971.         source.nextWhileMatches(/[^\s\u00a0=<>\"\'\/?]/);
  4972.         return "xml-name";
  4973.       }
  4974.     }
  4975.  
  4976.     function inAttribute(quote) {
  4977.       return function(source, setState) {
  4978.         while (!source.endOfLine()) {
  4979.           if (source.next() == quote) {
  4980.             setState(inTag);
  4981.             break;
  4982.           }
  4983.         }
  4984.         return "xml-attribute";
  4985.       };
  4986.     }
  4987.  
  4988.     function inBlock(style, terminator) {
  4989.       return function(source, setState) {
  4990.         while (!source.endOfLine()) {
  4991.           if (source.lookAhead(terminator, true)) {
  4992.             setState(inText);
  4993.             break;
  4994.           }
  4995.           source.next();
  4996.         }
  4997.         return style;
  4998.       };
  4999.     }
  5000.  
  5001.     return function(source, startState) {
  5002.       return tokenizer(source, startState || inText);
  5003.     };
  5004.   })();
  5005.  
  5006.   // The parser. The structure of this function largely follows that of
  5007.   // parseJavaScript in parsejavascript.js (there is actually a bit more
  5008.   // shared code than I'd like), but it is quite a bit simpler.
  5009.   function parseXML(source) {
  5010.     var tokens = tokenizeXML(source), token;
  5011.     var cc = [base];
  5012.     var tokenNr = 0, indented = 0;
  5013.     var currentTag = null, context = null;
  5014.     var consume;
  5015.    
  5016.     function push(fs) {
  5017.       for (var i = fs.length - 1; i >= 0; i--)
  5018.         cc.push(fs[i]);
  5019.     }
  5020.     function cont() {
  5021.       push(arguments);
  5022.       consume = true;
  5023.     }
  5024.     function pass() {
  5025.       push(arguments);
  5026.       consume = false;
  5027.     }
  5028.  
  5029.     function markErr() {
  5030.       token.style += " xml-error";
  5031.     }
  5032.     function expect(text) {
  5033.       return function(style, content) {
  5034.         if (content == text) cont();
  5035.         else {markErr(); cont(arguments.callee);}
  5036.       };
  5037.     }
  5038.  
  5039.     function pushContext(tagname, startOfLine) {
  5040.       var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent);
  5041.       context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent};
  5042.     }
  5043.     function popContext() {
  5044.       context = context.prev;
  5045.     }
  5046.     function computeIndentation(baseContext) {
  5047.       return function(nextChars, current) {
  5048.         var context = baseContext;
  5049.         if (context && context.noIndent)
  5050.           return current;
  5051.         if (alignCDATA && /<!\[CDATA\[/.test(nextChars))
  5052.           return 0;
  5053.         if (context && /^<\//.test(nextChars))
  5054.           context = context.prev;
  5055.         while (context && !context.startOfLine)
  5056.           context = context.prev;
  5057.         if (context)
  5058.           return context.indent + indentUnit;
  5059.         else
  5060.           return 0;
  5061.       };
  5062.     }
  5063.  
  5064.     function base() {
  5065.       return pass(element, base);
  5066.     }
  5067.     var harmlessTokens = {"xml-text": true, "xml-entity": true, "xml-comment": true, "xml-processing": true, "xml-doctype": true};
  5068.     function element(style, content) {
  5069.       if (content == "<") cont(tagname, attributes, endtag(tokenNr == 1));
  5070.       else if (content == "</") cont(closetagname, expect(">"));
  5071.       else if (style == "xml-cdata") {
  5072.         if (!context || context.name != "!cdata") pushContext("!cdata");
  5073.         if (/\]\]>$/.test(content)) popContext();
  5074.         cont();
  5075.       }
  5076.       else if (harmlessTokens.hasOwnProperty(style)) cont();
  5077.       else {markErr(); cont();}
  5078.     }
  5079.     function tagname(style, content) {
  5080.       if (style == "xml-name") {
  5081.         currentTag = content.toLowerCase();
  5082.         token.style = "xml-tagname";
  5083.         cont();
  5084.       }
  5085.       else {
  5086.         currentTag = null;
  5087.         pass();
  5088.       }
  5089.     }
  5090.     function closetagname(style, content) {
  5091.       if (style == "xml-name") {
  5092.         token.style = "xml-tagname";
  5093.         if (context && content.toLowerCase() == context.name) popContext();
  5094.         else markErr();
  5095.       }
  5096.       cont();
  5097.     }
  5098.     function endtag(startOfLine) {
  5099.       return function(style, content) {
  5100.         if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont();
  5101.         else if (content == ">") {pushContext(currentTag, startOfLine); cont();}
  5102.         else {markErr(); cont(arguments.callee);}
  5103.       };
  5104.     }
  5105.     function attributes(style) {
  5106.       if (style == "xml-name") {token.style = "xml-attname"; cont(attribute, attributes);}
  5107.       else pass();
  5108.     }
  5109.     function attribute(style, content) {
  5110.       if (content == "=") cont(value);
  5111.       else if (content == ">" || content == "/>") pass(endtag);
  5112.       else pass();
  5113.     }
  5114.     function value(style) {
  5115.       if (style == "xml-attribute") cont(value);
  5116.       else pass();
  5117.     }
  5118.  
  5119.     return {
  5120.       indentation: function() {return indented;},
  5121.  
  5122.       next: function(){
  5123.         token = tokens.next();
  5124.         if (token.style == "whitespace" && tokenNr == 0)
  5125.           indented = token.value.length;
  5126.         else
  5127.           tokenNr++;
  5128.         if (token.content == "\n") {
  5129.           indented = tokenNr = 0;
  5130.           token.indentation = computeIndentation(context);
  5131.         }
  5132.  
  5133.         if (token.style == "whitespace" || token.type == "xml-comment")
  5134.           return token;
  5135.  
  5136.         while(true){
  5137.           consume = false;
  5138.           cc.pop()(token.style, token.content);
  5139.           if (consume) return token;
  5140.         }
  5141.       },
  5142.  
  5143.       copy: function(){
  5144.         var _cc = cc.concat([]), _tokenState = tokens.state, _context = context;
  5145.         var parser = this;
  5146.        
  5147.         return function(input){
  5148.           cc = _cc.concat([]);
  5149.           tokenNr = indented = 0;
  5150.           context = _context;
  5151.           tokens = tokenizeXML(input, _tokenState);
  5152.           return parser;
  5153.         };
  5154.       }
  5155.     };
  5156.   }
  5157.  
  5158.   return {
  5159.     make: function(stream) {
  5160.         if (typeof stream == 'string')
  5161.             stream = stringStream(stream);
  5162.            
  5163.         return parseXML(stream);
  5164.     }
  5165.   };
  5166. })();
  5167. /**
  5168.  * @author Stoyan Stefanov
  5169.  * @link https://github.com/stoyan/etc/tree/master/cssex
  5170.  */
  5171. var CSSEX = (function () {
  5172.      
  5173.     var walker, tokens = [], isOp, isNameChar, isDigit;
  5174.    
  5175.     // walks around the source
  5176.     walker = {
  5177.         lines: null,
  5178.         total_lines: 0,
  5179.         linenum: -1,
  5180.         line: '',
  5181.         ch: '',
  5182.         chnum: -1,
  5183.         init: function (source) {
  5184.             var me = walker;
  5185.        
  5186.             // source, yumm
  5187.             me.lines = source
  5188.                 .replace(/\r\n/g, '\n')
  5189.                 .replace(/\r/g, '\n')
  5190.                 .split('\n');
  5191.             me.total_lines = me.lines.length;
  5192.        
  5193.             // reset
  5194.             me.chnum = -1;
  5195.             me.linenum = -1;
  5196.             me.ch = '';
  5197.             me.line = '';
  5198.        
  5199.             // advance
  5200.             me.nextLine();
  5201.             me.nextChar();
  5202.         },
  5203.         nextLine: function () {
  5204.             var me = this;
  5205.             me.linenum += 1;
  5206.             if (me.total_lines <= me.linenum) {
  5207.                 me.line = false;
  5208.             } else {
  5209.                 me.line = me.lines[me.linenum];
  5210.             }
  5211.             if (me.chnum !== -1) {
  5212.                 me.chnum = 0;
  5213.             }
  5214.             return me.line;
  5215.         },
  5216.         nextChar: function () {
  5217.             var me = this;
  5218.             me.chnum += 1;
  5219.             while (me.line.charAt(me.chnum) === '') {
  5220.                 if (this.nextLine() === false) {
  5221.                     me.ch = false;
  5222.                     return false; // end of source
  5223.                 }
  5224.                 me.chnum = -1;
  5225.                 me.ch = '\n';
  5226.                 return '\n';
  5227.             }
  5228.             me.ch = me.line.charAt(me.chnum);
  5229.             return me.ch;
  5230.         },
  5231.         peek: function() {
  5232.             return this.line.charAt(this.chnum + 1);
  5233.         }
  5234.     };
  5235.  
  5236.     // utility helpers
  5237.     isNameChar = function (c) {
  5238.         return (c === '_' || c === '-' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
  5239.     };
  5240.  
  5241.     isDigit = function (ch) {
  5242.         return (ch !== false && ch >= '0' && ch <= '9');
  5243.     };  
  5244.  
  5245.     isOp = (function () {
  5246.         var opsa = "{}[]()+*=.,;:>~|\\%$#@^!".split(''),
  5247.             opsmatcha = "*^|$~".split(''),
  5248.             ops = {},
  5249.             opsmatch = {},
  5250.             i = 0;
  5251.         for (; i < opsa.length; i += 1) {
  5252.             ops[opsa[i]] = true;
  5253.         }
  5254.         for (i = 0; i < opsmatcha.length; i += 1) {
  5255.             opsmatch[opsmatcha[i]] = true;
  5256.         }
  5257.         return function (ch, matchattr) {
  5258.             if (matchattr) {
  5259.                 return !!opsmatch[ch];
  5260.             }
  5261.             return !!ops[ch];
  5262.         };
  5263.     }());
  5264.    
  5265.     // shorthands
  5266.     function isset(v) {
  5267.         return typeof v !== 'undefined';
  5268.     }
  5269.     function getConf() {
  5270.         return {
  5271.             'char': walker.chnum,
  5272.             line: walker.linenum
  5273.         };
  5274.     }
  5275.  
  5276.  
  5277.     // creates token objects and pushes them to a list
  5278.     function tokener(value, type, conf) {
  5279.         var w = walker, c = conf || {};
  5280.         tokens.push({
  5281.             charstart: isset(c['char']) ? c['char'] : w.chnum,
  5282.             charend:   isset(c.charend) ? c.charend : w.chnum,
  5283.             linestart: isset(c.line)    ? c.line    : w.linenum,
  5284.             lineend:   isset(c.lineend) ? c.lineend : w.linenum,
  5285.             value:     value,
  5286.             type:      type || value
  5287.         });
  5288.     }
  5289.    
  5290.     // oops
  5291.     function error(m, config) {
  5292.         var w = walker,
  5293.             conf = config || {},
  5294.             c = isset(conf['char']) ? conf['char'] : w.chnum,
  5295.             l = isset(conf.line) ? conf.line : w.linenum;
  5296.         return {
  5297.             name: "ParseError",
  5298.             message: m + " at line " + (l + 1) + ' char ' + (c + 1),
  5299.             walker: w,
  5300.             tokens: tokens
  5301.         };
  5302.     }
  5303.  
  5304.  
  5305.     // token handlers follow for:
  5306.     // white space, comment, string, identifier, number, operator
  5307.     function white() {
  5308.    
  5309.         var c = walker.ch,
  5310.             token = '',
  5311.             conf = getConf();
  5312.    
  5313.         while (c === " " || c === "\t") {
  5314.             token += c;
  5315.             c = walker.nextChar();
  5316.         }
  5317.    
  5318.         tokener(token, 'white', conf);
  5319.    
  5320.     }
  5321.  
  5322.     function comment() {
  5323.    
  5324.         var w = walker,
  5325.             c = w.ch,
  5326.             token = c,
  5327.             cnext,
  5328.             conf = getConf();    
  5329.      
  5330.         cnext = w.nextChar();
  5331.        
  5332.         if (cnext !== '*') {
  5333.             // oops, not a comment, just a /
  5334.             conf.charend = conf['char'];
  5335.             conf.lineend = conf.line;
  5336.             return tokener(token, token, conf);
  5337.         }
  5338.    
  5339.         while (!(c === "*" && cnext === "/")) {
  5340.             token += cnext;
  5341.             c = cnext;
  5342.             cnext = w.nextChar();        
  5343.         }
  5344.         token += cnext;
  5345.         w.nextChar();
  5346.         tokener(token, 'comment', conf);
  5347.     }
  5348.  
  5349.     function str() {
  5350.         var w = walker,
  5351.             c = w.ch,
  5352.             q = c,
  5353.             token = c,
  5354.             cnext,
  5355.             conf = getConf();
  5356.    
  5357.         c = w.nextChar();
  5358.    
  5359.         while (c !== q) {
  5360.            
  5361.             if (c === '\n') {
  5362.                 cnext = w.nextChar();
  5363.                 if (cnext === "\\") {
  5364.                     token += c + cnext;
  5365.                 } else {
  5366.                     // end of line with no \ escape = bad
  5367.                     throw error("Unterminated string", conf);
  5368.                 }
  5369.             } else {
  5370.                 if (c === "\\") {
  5371.                     token += c + w.nextChar();
  5372.                 } else {
  5373.                     token += c;
  5374.                 }
  5375.             }
  5376.        
  5377.             c = w.nextChar();
  5378.        
  5379.         }
  5380.         token += c;
  5381.         w.nextChar();
  5382.         tokener(token, 'string', conf);
  5383.     }
  5384.    
  5385.     function brace() {
  5386.         var w = walker,
  5387.             c = w.ch,
  5388.             depth = 0,
  5389.             token = c,
  5390.             conf = getConf();
  5391.    
  5392.         c = w.nextChar();
  5393.    
  5394.         while (c !== ')' && !depth) {
  5395.             if (c === '(') {
  5396.                 depth++;
  5397.             } else if (c === ')') {
  5398.                 depth--;
  5399.             } else if (c === false) {
  5400.                 throw error("Unterminated brace", conf);
  5401.             }
  5402.            
  5403.             token += c;
  5404.             c = w.nextChar();
  5405.         }
  5406.        
  5407.         token += c;
  5408.         w.nextChar();
  5409.         tokener(token, 'brace', conf);
  5410.     }
  5411.  
  5412.     function identifier(pre) {
  5413.         var w = walker,
  5414.             c = w.ch,
  5415.             conf = getConf(),
  5416.             token = (pre) ? pre + c : c;
  5417.            
  5418.         c = w.nextChar();
  5419.    
  5420.         if (pre) { // adjust token position
  5421.             conf['char'] -= pre.length;
  5422.         }
  5423.        
  5424.         while (isNameChar(c) || isDigit(c)) {
  5425.             token += c;
  5426.             c = w.nextChar();
  5427.         }
  5428.    
  5429.         tokener(token, 'identifier', conf);    
  5430.     }
  5431.  
  5432.     function num() {
  5433.         var w = walker,
  5434.             c = w.ch,
  5435.             conf = getConf(),
  5436.             token = c,
  5437.             point = token === '.',
  5438.             nondigit;
  5439.        
  5440.         c = w.nextChar();
  5441.         nondigit = !isDigit(c);
  5442.    
  5443.         // .2px or .classname?
  5444.         if (point && nondigit) {
  5445.             // meh, NaN, could be a class name, so it's an operator for now
  5446.             conf.charend = conf['char'];
  5447.             conf.lineend = conf.line;
  5448.             return tokener(token, '.', conf);    
  5449.         }
  5450.        
  5451.         // -2px or -moz-something
  5452.         if (token === '-' && nondigit) {
  5453.             return identifier('-');
  5454.         }
  5455.    
  5456.         while (c !== false && (isDigit(c) || (!point && c === '.'))) { // not end of source && digit or first instance of .
  5457.             if (c === '.') {
  5458.                 point = true;
  5459.             }
  5460.             token += c;
  5461.             c = w.nextChar();
  5462.         }
  5463.  
  5464.         tokener(token, 'number', conf);    
  5465.    
  5466.     }
  5467.  
  5468.     function op() {
  5469.         var w = walker,
  5470.             c = w.ch,
  5471.             conf = getConf(),
  5472.             token = c,
  5473.             next = w.nextChar();
  5474.            
  5475.         if (next === "=" && isOp(token, true)) {
  5476.             token += next;
  5477.             tokener(token, 'match', conf);
  5478.             w.nextChar();
  5479.             return;
  5480.         }
  5481.        
  5482.         conf.charend = conf['char'] + 1;
  5483.         conf.lineend = conf.line;    
  5484.         tokener(token, token, conf);
  5485.     }
  5486.  
  5487.  
  5488.     // call the appropriate handler based on the first character in a token suspect
  5489.     function tokenize() {
  5490.  
  5491.         var ch = walker.ch;
  5492.    
  5493.         if (ch === " " || ch === "\t") {
  5494.             return white();
  5495.         }
  5496.  
  5497.         if (ch === '/') {
  5498.             return comment();
  5499.         }
  5500.  
  5501.         if (ch === '"' || ch === "'") {
  5502.             return str();
  5503.         }
  5504.        
  5505.         if (ch === '(') {
  5506.             return brace();
  5507.         }
  5508.    
  5509.         if (ch === '-' || ch === '.' || isDigit(ch)) { // tricky - char: minus (-1px) or dash (-moz-stuff)
  5510.             return num();
  5511.         }
  5512.    
  5513.         if (isNameChar(ch)) {
  5514.             return identifier();
  5515.         }
  5516.  
  5517.         if (isOp(ch)) {
  5518.             return op();
  5519.         }
  5520.        
  5521.         if (ch === "\n") {
  5522.             tokener("line");
  5523.             walker.nextChar();
  5524.             return;
  5525.         }
  5526.        
  5527.         throw error("Unrecognized character");
  5528.     }
  5529.  
  5530.  
  5531.     return {
  5532.         lex: function (source) {
  5533.             walker.init(source);
  5534.             tokens = [];
  5535.             while (walker.ch !== false) {
  5536.                 tokenize();            
  5537.             }
  5538.             return tokens;
  5539.         },
  5540.         toSource: function (toks) {
  5541.             var i = 0, max = toks.length, t, src = '';
  5542.             for (; i < max; i += 1) {
  5543.                 t = toks[i];
  5544.                 if (t.type === 'line') {
  5545.                     src += '\n';
  5546.                 } else {
  5547.                     src += t.value;
  5548.                 }
  5549.             }
  5550.             return src;
  5551.         }
  5552.     };
  5553.  
  5554.  
  5555.  
  5556. }());/**
  5557.  * Some utility functions for CSS parser:
  5558.  * -- optimizes CSS lexer token, produced by Stoyan Stefanov's CSSEX parser,
  5559.  *    for Zen Coding needs
  5560.  * -- extracts full CSS rule (selector + style rules) from content
  5561.  *  
  5562.  * @author Sergey Chikuyonok ([email protected])
  5563.  * @link http://chikuyonok.ru
  5564.  *
  5565.  * @include "sex.js"
  5566.  */
  5567. var ParserUtils = (function() {
  5568.     var css_stop_chars = '{}/\\<>';
  5569.    
  5570.     function isStopChar(token) {
  5571.         var stop_chars = '{};:';
  5572.         return stop_chars.indexOf(token.type) != -1;
  5573.     }
  5574.    
  5575.     /**
  5576.      * Calculates newline width at specified position in content
  5577.      * @param {String} content
  5578.      * @param {Number} pos
  5579.      * @return {Number}
  5580.      */
  5581.     function calculateNlLength(content, pos) {
  5582.         return content.charAt(pos) == '\r' && content.charAt(pos + 1) == '\n' ? 2 : 1;
  5583.     }
  5584.    
  5585.     /**
  5586.      * Post-process optmized tokens: collapse tokens for complex values
  5587.      * @param {Array} optimized Optimized tokens
  5588.      * @param {Array} original Original preprocessed tokens
  5589.      */
  5590.     function postProcessOptimized(optimized, original) {
  5591.         var token, child;
  5592.         for (var i = 0, il = optimized.length; i < il; i++) {
  5593.             token = optimized[i];
  5594.             if (token.type == 'value') {
  5595.                 token.children = [];
  5596.                 child = null;
  5597.                
  5598.                 var subtoken_start = token.ref_start_ix;
  5599.                    
  5600.                 while (subtoken_start <= token.ref_end_ix) {
  5601.                     var subtoken = original[subtoken_start];
  5602.                     if (subtoken.type != 'white') {
  5603.                         if (!child)
  5604.                             child = [subtoken.start, subtoken.end];
  5605.                         else
  5606.                             child[1] = subtoken.end;
  5607.                     } else if (child) {
  5608.                         token.children.push(child);
  5609.                         child = null;
  5610.                     }
  5611.                    
  5612.                     subtoken_start++;  
  5613.                 }
  5614.                
  5615.                 if (child) // push last token
  5616.                     token.children.push(child);
  5617.             }
  5618.         }
  5619.        
  5620.         return optimized;
  5621.     }
  5622.    
  5623.     function makeToken(type, value, pos, ix) {
  5624.         value = value || '';
  5625.         return {
  5626.             type: type || '',
  5627.             content: value,
  5628.             start: pos,
  5629.             end: pos + value.length,
  5630.             /** Reference token index that starts current token */
  5631.             ref_start_ix: ix,
  5632.             /** Reference token index that ends current token */
  5633.             ref_end_ix: ix
  5634.         }
  5635.     }
  5636.    
  5637.     return {
  5638.         /**
  5639.          * Parses CSS and optimizes parsed chunks
  5640.          * @see ParserUtils#optimizeCSS
  5641.          * @param {String} source CSS source code fragment
  5642.          * @param {Number} offset Offset of CSS fragment inside whole document
  5643.          * @return {Array}
  5644.          */
  5645.         parseCSS: function(source, offset) {
  5646.             return this.optimizeCSS(CSSEX.lex(source), offset || 0, source);
  5647.         },
  5648.        
  5649.         /**
  5650.          * Parses HTML and optimizes parsed chunks
  5651.          * @param {String} source HTML source code fragment
  5652.          * @param {Number} offset Offset of HTML fragment inside whole document
  5653.          * @return {Array}
  5654.          */
  5655.         parseHTML: function(tag, offset) {
  5656.             var tokens = XMLParser.make(tag),
  5657.                 result = [],
  5658.                 t, i = 0;
  5659.                
  5660.             try {
  5661.                 while (t = tokens.next()) {
  5662. //                  result.push(tagDef(offset + i, t));
  5663.                     result.push(makeToken(t.style, t.content, offset + i, 0));
  5664.                     i += t.value.length;
  5665.                 }
  5666.             } catch (e) {
  5667.                 if (e != 'StopIteration') throw e;
  5668.             }
  5669.            
  5670.             return result;
  5671.         },
  5672.        
  5673.         /**
  5674.          * Optimizes parsed CSS tokens: combines selector chunks, complex values
  5675.          * into a single chunk
  5676.          * @param {Array} tokens Tokens produced by <code>CSSEX.lex()</code>
  5677.          * @param {Number} offset CSS rule offset in source code (character index)
  5678.          * @param {String} content Original CSS source code
  5679.          * @return {Array} Optimized tokens  
  5680.          */
  5681.         optimizeCSS: function(tokens, offset, content) {
  5682.             offset = offset || 0;
  5683.             var result = [], token, i, il, _o = 0,
  5684.                 in_rules = false,
  5685.                 in_value = false,
  5686.                 delta = 0,
  5687.                 acc_type,
  5688.                 acc_tokens = {
  5689.                     /** @type {makeToken} */
  5690.                     selector: null,
  5691.                     /** @type {makeToken} */
  5692.                     value: null
  5693.                 },
  5694.                 nl_size,
  5695.                 orig_tokens = [];
  5696.                
  5697.             function addToken(token, type) {
  5698.                 if (type && type in acc_tokens) {
  5699.                     if (!acc_tokens[type]) {
  5700.                         acc_tokens[type] = makeToken(type, token.value, offset + delta + token.charstart, i);
  5701.                         result.push(acc_tokens[type]);
  5702.                     } else {
  5703.                         acc_tokens[type].content += token.value;
  5704.                         acc_tokens[type].end += token.value.length;
  5705.                         acc_tokens[type].ref_end_ix = i;
  5706.                     }
  5707.                 } else {
  5708.                     result.push(makeToken(token.type, token.value, offset + delta + token.charstart, i));
  5709.                 }
  5710.             }
  5711.            
  5712.             for (i = 0, il = tokens.length; i < il; i++) {
  5713.                 token = tokens[i];
  5714.                 acc_type = null;
  5715.                
  5716.                 if (token.type == 'line') {
  5717.                     delta += _o;
  5718.                     nl_size = content ? calculateNlLength(content, delta) : 1;
  5719.                    
  5720.                     var tok_value = nl_size == 1 ? '\n' : '\r\n';
  5721.                     orig_tokens.push(makeToken(token.type, tok_value, offset + delta));
  5722.                    
  5723.                     result.push(makeToken(token.type, tok_value, offset + delta, i));
  5724.                     delta += nl_size;
  5725.                     _o = 0;
  5726.                    
  5727.                     continue;
  5728.                 }
  5729.                
  5730.                 orig_tokens.push(makeToken(token.type, token.value, offset + delta + token.charstart));
  5731.                
  5732. //              _o = token.charend;
  5733.                 // use charstart and length because of incorrect charend
  5734.                 // computation for whitespace
  5735.                 _o = token.charstart + token.value.length;
  5736.                
  5737.                 if (token.type != 'white') {
  5738.                     if (token.type == '{') {
  5739.                         in_rules = true;
  5740.                         acc_tokens.selector = null;
  5741.                     } else if (in_rules) {
  5742.                         if (token.type == ':') {
  5743.                             in_value = true;
  5744.                         } else if (token.type == ';') {
  5745.                             in_value = false;
  5746.                             acc_tokens.value = null;
  5747.                         }  else if (token.type == '}') {
  5748.                             in_value = in_rules = false;
  5749.                             acc_tokens.value = null;
  5750.                         } else if (in_value || acc_tokens.value) {
  5751.                             acc_type = 'value';
  5752.                         }
  5753.                     } else if (acc_tokens.selector || (!in_rules && !isStopChar(token))) {
  5754.                         // start selector token
  5755.                         acc_type = 'selector';
  5756.                     }
  5757.                    
  5758.                     addToken(token, acc_type);
  5759.                 } else {
  5760.                     // whitespace token, decide where it should be
  5761.                     if (i < il - 1 && isStopChar(tokens[i + 1])) continue;
  5762.                    
  5763.                     if (acc_tokens.selector || acc_tokens.value)
  5764.                         addToken(token, acc_tokens.selector ? 'selector' : 'value');
  5765.                 }
  5766.             }
  5767.            
  5768.             result.__original = orig_tokens;
  5769.             return postProcessOptimized(result, orig_tokens);
  5770.         },
  5771.        
  5772.         /**
  5773.          * Extracts single CSS selector definition from source code
  5774.          * @param {String} content CSS source code
  5775.          * @param {Number} pos Character position where to start source code extraction
  5776.          */
  5777.         extractCSSRule: function(content, pos, is_backward) {
  5778.             var result = '',
  5779.                 c_len = content.length,
  5780.                 offset = pos,
  5781.                 brace_pos = -1, ch;
  5782.            
  5783.             // search left until we find rule edge
  5784.             while (offset >= 0) {
  5785.                 ch = content.charAt(offset);
  5786.                 if (ch == '{') {
  5787.                     brace_pos = offset;
  5788.                     break;
  5789.                 }
  5790.                 else if (ch == '}' && !is_backward) {
  5791.                     offset++;
  5792.                     break;
  5793.                 }
  5794.                
  5795.                 offset--;
  5796.             }
  5797.            
  5798.             // search right for full rule set
  5799.             while (offset < c_len) {
  5800.                 ch = content.charAt(offset);
  5801.                 if (ch == '{')
  5802.                     brace_pos = offset;
  5803.                 else if (ch == '}') {
  5804.                     if (brace_pos != -1)
  5805.                         result = content.substring(brace_pos, offset + 1);
  5806.                     break;
  5807.                 }
  5808.                
  5809.                 offset++;
  5810.             }
  5811.            
  5812.             if (result) {
  5813.                 // find CSS selector
  5814.                 offset = brace_pos - 1;
  5815.                 var selector = '';
  5816.                 while (offset >= 0) {
  5817.                     ch = content.charAt(offset);
  5818.                     if (css_stop_chars.indexOf(ch) != -1) break;
  5819.                     offset--;
  5820.                 }
  5821.                
  5822.                 // also trim whitespace
  5823.                 selector = content.substring(offset + 1, brace_pos).replace(/^[\s\n\r]+/m, '');
  5824.                 return [brace_pos - selector.length, brace_pos + result.length];
  5825.             }
  5826.            
  5827.             return null;
  5828.         },
  5829.        
  5830.         token: makeToken
  5831.     };
  5832. })();
  5833. /**
  5834.  * @author Sergey Chikuyonok ([email protected])
  5835.  * @link http://chikuyonok.ru
  5836.  *
  5837.  * @include "../zen_editor.js"
  5838.  * @include "parserutils.js"
  5839.  * @include "../zen_coding.js"
  5840.  * @include "../zen_actions.js"
  5841.  */
  5842.  
  5843. /**
  5844.  * Reflect CSS value: takes rule's value under caret and pastes it for the same
  5845.  * rules with vendor prefixes
  5846.  * @param {zen_editor} editor
  5847.  */
  5848. function reflectCSSValue(editor) {
  5849.     if (editor.getSyntax() != 'css') return false;
  5850.    
  5851.     return compoundUpdate(editor, doCSSReflection(editor));
  5852. }
  5853.  
  5854. /**
  5855.  * Update image size: reads image from image/CSS rule under caret
  5856.  * and updates dimensions inside tag/rule
  5857.  * @param {zen_editor} editor
  5858.  */
  5859. function updateImageSize(editor) {
  5860.     var result;
  5861.     if (String(editor.getSyntax()) == 'css') {
  5862.         result = updateImageSizeCSS(editor);
  5863.     } else {
  5864.         result = updateImageSizeHTML(editor);
  5865.     }
  5866.    
  5867.     return compoundUpdate(editor, result);
  5868. }
  5869.  
  5870. function compoundUpdate(editor, data) {
  5871.     if (data) {
  5872.         var sel = editor.getSelectionRange();
  5873.         editor.replaceContent(data.data, data.start, data.end, true);
  5874.         editor.createSelection(data.caret, data.caret + sel.end - sel.start);
  5875.         return true;
  5876.     }
  5877.    
  5878.     return false;
  5879. }
  5880.  
  5881. /**
  5882.  * Updates image size of &lt;img src=""&gt; tag
  5883.  * @param {zen_editor} editor
  5884.  */
  5885. function updateImageSizeHTML(editor) {
  5886.     var offset = editor.getCaretPos();
  5887.        
  5888.     var image = findImage(editor);
  5889.     if (image) {
  5890.         var re = /\bsrc=(["'])(.+?)\1/i, m, src;
  5891.         if (m = re.exec(image.tag))
  5892.             src = m[2];
  5893.        
  5894.         if (src) {
  5895.             var size = getImageSizeForSource(editor, src);
  5896.             if (size) {
  5897.                 var new_tag = replaceOrAppend(image.tag, 'width', size.width);
  5898.                 new_tag = replaceOrAppend(new_tag, 'height', size.height);
  5899.                
  5900.                 return {
  5901.                     'data': new_tag,
  5902.                     'start': image.start,
  5903.                     'end': image.end,
  5904.                     'caret': offset
  5905.                 };
  5906.             }
  5907.         }
  5908.     }
  5909.    
  5910.     return null;
  5911. }
  5912.  
  5913. /**
  5914.  * Search for insertion point for new CSS properties
  5915.  * @param {ParserUtils.token[]} tokens
  5916.  * @param {Number} start_ix Token index where to start searching
  5917.  */
  5918. function findCSSInsertionPoint(tokens, start_ix) {
  5919.     var ins_point,
  5920.         ins_ix = -1,
  5921.         need_col = false;
  5922.        
  5923.     for (var i = start_ix, il = tokens.length; i < il; i++) {
  5924.         var t = tokens[i];
  5925.         if (t.type == 'value') {
  5926.             ins_point = t;
  5927.             ins_ix = i;
  5928.             // look ahead fo rule termination
  5929.             if (tokens[i + 1] && tokens[i + 1].type == ';') {
  5930.                 ins_point = tokens[i + 1];
  5931.                 ins_ix += 1;
  5932.             } else {
  5933.                 need_col = true;
  5934.             }
  5935.             break;
  5936.         }
  5937.     }
  5938.    
  5939.     return {
  5940.         token: ins_point,
  5941.         ix: ins_ix,
  5942.         need_col: need_col
  5943.     };
  5944. }
  5945.  
  5946. /**
  5947.  * Updates image size of CSS rule
  5948.  * @param {zen_editor} editor
  5949.  */
  5950. function updateImageSizeCSS(editor) {
  5951.     var caret_pos = editor.getCaretPos(),
  5952.         content = String(editor.getContent()),
  5953.         rule = ParserUtils.extractCSSRule(content, caret_pos, true);
  5954.        
  5955.    
  5956.     if (rule) {
  5957.         var css = ParserUtils.parseCSS(content.substring(rule[0], rule[1]), rule[0]),
  5958.             cur_token = findTokenFromPosition(css, caret_pos, 'identifier'),
  5959.             value = findValueToken(css, cur_token + 1),
  5960.             m;
  5961.            
  5962.         if (!value) return false;
  5963.        
  5964.         // find inserion point
  5965.         var ins_point = findCSSInsertionPoint(css, cur_token);
  5966.            
  5967.         if (m = /url\((["']?)(.+?)\1\)/i.exec(value.content)) {
  5968.             var size = getImageSizeForSource(editor, m[2]);
  5969.             if (size) {
  5970.                 var wh = {width: null, height: null},
  5971.                     updates = [],
  5972.                     styler = learnCSSStyle(css, cur_token);
  5973.                    
  5974.                 for (var i = 0, il = css.length; i < il; i++) {
  5975.                     if (css[i].type == 'identifier' && css[i].content in wh)
  5976.                         wh[css[i].content] = i;
  5977.                 }
  5978.                
  5979.                 function update(name, val) {
  5980.                     var v;
  5981.                     if (wh[name] !== null && (v = findValueToken(css, wh[name] + 1))) {
  5982.                         updates.push([v.start, v.end, val + 'px']);
  5983.                     } else {
  5984.                         updates.push([ins_point.token.end, ins_point.token.end, styler(name, val + 'px')]);
  5985.                     }
  5986.                 }
  5987.                
  5988.                 update('width', size.width);
  5989.                 update('height', size.height);
  5990.                
  5991.                 if (updates.length) {
  5992.                     updates.sort(function(a, b){return a[0] - b[0]});
  5993.                    
  5994.                     // some editors do not provide easy way to replace multiple code
  5995.                     // fragments so we have to squash all replace operations into one
  5996.                     var data = content.substring(updates[0][0], updates[updates.length - 1][1]),
  5997.                         offset = updates[0][0];
  5998.                        
  5999.                     for (var i = updates.length - 1; i >= 0; i--) {
  6000.                         var u = updates[i];
  6001.                         data = replaceSubstring(data, u[0] - offset, u[1] - offset, u[2]);
  6002.                            
  6003.                         // also calculate new caret position
  6004.                         if (u[0] < caret_pos)
  6005.                             caret_pos += u[2].length - u[1] + u[0];
  6006.                     }
  6007.                    
  6008.                     if (ins_point.need_col)
  6009.                         data = replaceSubstring(data, ins_point.token.end - offset, ins_point.token.end - offset, ';');
  6010.                    
  6011.                     return {
  6012.                         'data': data,
  6013.                         'start': offset,
  6014.                         'end': updates[updates.length - 1][1],
  6015.                         'caret': caret_pos
  6016.                     };
  6017.                    
  6018.                 }
  6019.             }
  6020.         }
  6021.     }
  6022.        
  6023.     return false;
  6024. }
  6025.  
  6026. /**
  6027.  * Learns formatting style from parsed tokens
  6028.  * @param {ParserUtils.token[]} tokens List of tokens
  6029.  * @param {Number} pos Identifier token position, from which style should be learned
  6030.  * @returns {Function} Function with <code>(name, value)</code> arguments that will create
  6031.  * CSS rule based on learned formatting
  6032.  */
  6033. function learnCSSStyle(tokens, pos) {
  6034.     var prefix = '', glue = '', i, il;
  6035.    
  6036.     // use original tokens instead of optimized ones
  6037.     pos = tokens[pos].ref_start_ix;
  6038.     tokens = tokens.__original;
  6039.    
  6040.     // learn prefix
  6041.     for (i = pos - 1; i >= 0; i--) {
  6042.         if (tokens[i].type == 'white') {
  6043.             prefix = tokens[i].content + prefix;
  6044.         } else if (tokens[i].type == 'line') {
  6045.             prefix = tokens[i].content + prefix;
  6046.             break;
  6047.         } else {
  6048.             break;
  6049.         }
  6050.     }
  6051.    
  6052.     // learn glue
  6053.     for (i = pos + 1, il = tokens.length; i < il; i++) {
  6054.         if (tokens[i].type == 'white' || tokens[i].type == ':')
  6055.             glue += tokens[i].content;
  6056.         else break;
  6057.     }
  6058.    
  6059.     if (glue.indexOf(':') == -1)
  6060.         glue = ':';
  6061.    
  6062.     return function(name, value) {
  6063.         return prefix + name + glue + value + ';';
  6064.     };
  6065. }
  6066.  
  6067. /**
  6068.  * Returns image dimentions for source
  6069.  * @param {zen_editor} editor
  6070.  * @param {String} src Image source (path or data:url)
  6071.  */
  6072. function getImageSizeForSource(editor, src) {
  6073.     var f_content;
  6074.     if (src) {
  6075.         // check if it is data:url
  6076.         if (startsWith('data:', src)) {
  6077.             f_content = base64.decode( src.replace(/^data\:.+?;.+?,/, '') );
  6078.         } else {
  6079.             var abs_path = zen_file.locateFile(editor.getFilePath(), src);
  6080.             if (abs_path === null) {
  6081.                 throw "Can't find " + src + ' file';
  6082.             }
  6083.            
  6084.             f_content = String(zen_file.read(abs_path));
  6085.         }
  6086.        
  6087.         return zen_coding.getImageSize(f_content);
  6088.     }
  6089. }
  6090.  
  6091. /**
  6092.  * Find image tag under caret
  6093.  * @param {zen_editor} editor
  6094.  * @return Image tag and its indexes inside editor source
  6095.  */
  6096. function findImage(editor) {
  6097.     var caret_pos = editor.getCaretPos(),
  6098.         content = String(editor.getContent()),
  6099.         content_len = content.length,
  6100.         start_ix = -1,
  6101.         end_ix = -1;
  6102.    
  6103.     // find the beginning of the tag
  6104.     do {
  6105.         if (caret_pos < 0)
  6106.             break;
  6107.         if (content.charAt(caret_pos) == '<') {
  6108.             if (content.substring(caret_pos, caret_pos + 4).toLowerCase() == '<img') {
  6109.                 // found the beginning of the image tag
  6110.                 start_ix = caret_pos;
  6111.                 break;
  6112.             } else {
  6113.                 // found some other tag
  6114.                 return null;
  6115.             }
  6116.         }
  6117.     } while(caret_pos--);
  6118.    
  6119.     // find the end of the tag
  6120.     caret_pos = editor.getCaretPos();
  6121.     do {
  6122.         if (caret_pos >= content_len)
  6123.             break;
  6124.            
  6125.         if (content.charAt(caret_pos) == '>') {
  6126.             end_ix = caret_pos + 1;
  6127.             break;
  6128.         }
  6129.     } while(caret_pos++);
  6130.    
  6131.     if (start_ix != -1 && end_ix != -1)
  6132.        
  6133.         return {
  6134.             start: start_ix,
  6135.             end: end_ix,
  6136.             tag: content.substring(start_ix, end_ix)
  6137.         };
  6138.    
  6139.     return null;
  6140. }
  6141.  
  6142. /**
  6143.  * Replaces or adds attribute to the tag
  6144.  * @param {String} img_tag
  6145.  * @param {String} attr_name
  6146.  * @param {String} attr_value
  6147.  */
  6148. function replaceOrAppend(img_tag, attr_name, attr_value) {
  6149.     if (img_tag.toLowerCase().indexOf(attr_name) != -1) {
  6150.         // attribute exists
  6151.         var re = new RegExp(attr_name + '=([\'"])(.*?)([\'"])', 'i');
  6152.         return img_tag.replace(re, function(str, p1, p2){
  6153.             return attr_name + '=' + p1 + attr_value + p1;
  6154.         });
  6155.     } else {
  6156.         return img_tag.replace(/\s*(\/?>)$/, ' ' + attr_name + '="' + attr_value + '" $1');
  6157.     }
  6158. }
  6159.  
  6160. function doCSSReflection(editor) {
  6161.     var content = String(editor.getContent()),
  6162.         caret_pos = editor.getCaretPos(),
  6163.         css = ParserUtils.extractCSSRule(content, caret_pos),
  6164.         v;
  6165.        
  6166.     if (!css || caret_pos < css[0] || caret_pos > css[1])
  6167.         // no matching CSS rule or caret outside rule bounds
  6168.         return false;
  6169.        
  6170.     var tokens = ParserUtils.parseCSS(content.substring(css[0], css[1]), css[0]),
  6171.         token_ix = findTokenFromPosition(tokens, caret_pos, 'identifier');
  6172.    
  6173.     if (token_ix != -1) {
  6174.         var cur_prop = tokens[token_ix].content,
  6175.             value_token = findValueToken(tokens, token_ix + 1),
  6176.             base_name = getBaseCSSName(cur_prop),
  6177.             re_name = new RegExp('^(?:\\-\\w+\\-)?' + base_name + '$'),
  6178.             re_name = getReflectedCSSName(base_name),
  6179.             values = [];
  6180.            
  6181.         if (!value_token) return false;
  6182.            
  6183.         // search for all vendor-prefixed properties
  6184.         for (var i = 0, token, il = tokens.length; i < il; i++) {
  6185.             token = tokens[i];
  6186.             if (token.type == 'identifier' && re_name.test(token.content) && token.content != cur_prop) {
  6187.                 v = findValueToken(tokens, i + 1);
  6188.                 if (v)
  6189.                     values.push({name: token, value: v});
  6190.             }
  6191.         }
  6192.        
  6193.         // some editors do not provide easy way to replace multiple code
  6194.         // fragments so we have to squash all replace operations into one
  6195.         if (values.length) {
  6196.             var data = content.substring(values[0].value.start, values[values.length - 1].value.end),
  6197.                 offset = values[0].value.start,
  6198.                 value = value_token.content,
  6199.                 rv;
  6200.                
  6201.             for (var i = values.length - 1; i >= 0; i--) {
  6202.                 v = values[i].value;
  6203.                 rv = getReflectedValue(cur_prop, value, values[i].name.content, v.content);
  6204.                 data = replaceSubstring(data, v.start - offset, v.end - offset, rv);
  6205.                    
  6206.                 // also calculate new caret position
  6207.                 if (v.start < caret_pos) {
  6208.                     caret_pos += rv.length - v.content.length;
  6209.                 }
  6210.             }
  6211.            
  6212.             return {
  6213.                 'data': data,
  6214.                 'start': offset,
  6215.                 'end': values[values.length - 1].value.end,
  6216.                 'caret': caret_pos
  6217.             };
  6218.         }
  6219.     }
  6220. }
  6221.  
  6222. zen_coding.actions.doCSSReflection = doCSSReflection;
  6223.  
  6224. /**
  6225.  * Removes vendor prefix from CSS property
  6226.  * @param {String} name CSS property
  6227.  * @return {String}
  6228.  */
  6229. function getBaseCSSName(name) {
  6230.     return name.replace(/^\s*\-\w+\-/, '');
  6231. }
  6232.  
  6233. /**
  6234.  * Returns regexp that should match reflected CSS property names
  6235.  * @param {String} name Current CSS property name
  6236.  * @return {RegExp}
  6237.  */
  6238. function getReflectedCSSName(name) {
  6239.     name = getBaseCSSName(name);
  6240.     var vendor_prefix = '^(?:\\-\\w+\\-)?', m;
  6241.    
  6242.     if (name == 'opacity' || name == 'filter') {
  6243.         return new RegExp(vendor_prefix + '(?:opacity|filter)$');
  6244.     } else if (m = name.match(/^border-radius-(top|bottom)(left|right)/)) {
  6245.         // Mozilla-style border radius
  6246.         return new RegExp(vendor_prefix + '(?:' + name + '|border-' + m[1] + '-' + m[2] + '-radius)$');
  6247.     } else if (m = name.match(/^border-(top|bottom)-(left|right)-radius/)) {
  6248.         return new RegExp(vendor_prefix + '(?:' + name + '|border-radius-' + m[1] + m[2] + ')$');
  6249.     }
  6250.    
  6251.     return new RegExp(vendor_prefix + name + '$');
  6252. }
  6253.  
  6254. /**
  6255.  * Returns value that should be reflected for <code>ref_name</code> CSS property
  6256.  * from <code>cur_name</code> property. This function is used for special cases,
  6257.  * when the same result must be achieved with different properties for different
  6258.  * browsers. For example: opаcity:0.5; -> filter:alpha(opacity=50);<br><br>
  6259.  *
  6260.  * This function does value conversion between different CSS properties
  6261.  *
  6262.  * @param {String} cur_name Current CSS property name
  6263.  * @param {String} cur_value Current CSS property value
  6264.  * @param {String} ref_name Receiver CSS property's name
  6265.  * @param {String} ref_value Receiver CSS property's value
  6266.  * @return {String} New value for receiver property
  6267.  */
  6268. function getReflectedValue(cur_name, cur_value, ref_name, ref_value) {
  6269.     cur_name = getBaseCSSName(cur_name);
  6270.     ref_name = getBaseCSSName(ref_name);
  6271.    
  6272.     if (cur_name == 'opacity' && ref_name == 'filter') {
  6273.         return ref_value.replace(/opacity=[^)]*/i, 'opacity=' + Math.floor(parseFloat(cur_value) * 100));
  6274.     } else if (cur_name == 'filter' && ref_name == 'opacity') {
  6275.         var m = cur_value.match(/opacity=([^)]*)/i);
  6276.         return m ? prettifyNumber(parseInt(m[1]) / 100) : ref_value;
  6277.     }
  6278.    
  6279.     return cur_value;
  6280. }
  6281.  
  6282. /**
  6283.  * Find value token, staring at <code>pos</code> index and moving right
  6284.  * @param {Array} tokens
  6285.  * @param {Number} pos
  6286.  * @return {ParserUtils.token}
  6287.  */
  6288. function findValueToken(tokens, pos) {
  6289.     for (var i = pos, il = tokens.length; i < il; i++) {
  6290.         var t = tokens[i];
  6291.         if (t.type == 'value')
  6292.             return t;
  6293.         else if (t.type == 'identifier' || t.type == ';')
  6294.             break;
  6295.     }
  6296.    
  6297.     return null;
  6298. }
  6299.  
  6300. /**
  6301.  * Replace substring of <code>text</code>, defined by <code>start</code> and
  6302.  * <code>end</code> indexes with <code>new_value</code>
  6303.  * @param {String} text
  6304.  * @param {Number} start
  6305.  * @param {Number} end
  6306.  * @param {String} new_value
  6307.  * @return {String}
  6308.  */
  6309. function replaceSubstring(text, start, end, new_value) {
  6310.     return text.substring(0, start) + new_value + text.substring(end);
  6311. }
  6312.  
  6313. /**
  6314.  * Search for token with specified type left to the specified position
  6315.  * @param {Array} tokens List of parsed tokens
  6316.  * @param {Number} pos Position where to start searching
  6317.  * @param {String} type Token type
  6318.  * @return {Number} Token index
  6319.  */
  6320. function findTokenFromPosition(tokens, pos, type) {
  6321.     // find token under caret
  6322.     var token_ix = -1;
  6323.     for (var i = 0, il = tokens.length; i < il; i++) {
  6324.         var token = tokens[i];
  6325.         if (token.start <= pos && token.end >= pos) {
  6326.             token_ix = i;
  6327.             break;
  6328.         }
  6329.     }
  6330.    
  6331.     if (token_ix != -1) {
  6332.         // token found, search left until we find token with specified type
  6333.         while (token_ix >= 0) {
  6334.             if (tokens[token_ix].type == type)
  6335.                 return token_ix;
  6336.             token_ix--;
  6337.         }
  6338.     }
  6339.    
  6340.     return -1;
  6341. }
  6342.  
  6343. zen_coding.registerAction('reflect_css_value', reflectCSSValue);
  6344. zen_coding.registerAction('update_image_size', updateImageSize);/**
  6345.  * Actions that use stream parsers and tokenizers for traversing:
  6346.  * -- Search for next/previous items in HTML
  6347.  * -- Search for next/previous items in CSS
  6348.  *
  6349.  * @author Sergey Chikuyonok ([email protected])
  6350.  * @link http://chikuyonok.ru
  6351.  *
  6352.  * @include "../zen_editor.js"
  6353.  * @include "utils.js"
  6354.  * @include "stringstream.js"
  6355.  * @include "parsexml.js"
  6356.  * @include "tokenize.js"
  6357.  * @include "sex.js"
  6358.  * @include "parserutils.js"
  6359.  */
  6360. (function(){
  6361.     var start_tag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
  6362.         known_xml_types = {
  6363.             'xml-tagname': 1,
  6364.             'xml-attname': 1,
  6365.             'xml-attribute': 1
  6366.         },
  6367.         known_css_types = {
  6368.             'selector': 1,
  6369.             'identifier': 1,
  6370.             'value': 1
  6371.         };
  6372.    
  6373.     /**
  6374.      * Find next HTML item
  6375.      * @param {zen_editor} editor
  6376.      */
  6377.     function findNextHTMLItem(editor) {
  6378.         var is_first = true;
  6379.         return findItem(editor, false, function(content, search_pos){
  6380.             if (is_first) {
  6381.                 is_first = false;
  6382.                 return findOpeningTagFromPosition(content, search_pos);
  6383.             } else {
  6384.                 return getOpeningTagFromPosition(content, search_pos);
  6385.             }
  6386.         }, getRangeForNextItemInHTML);
  6387.     }
  6388.    
  6389.     /**
  6390.      * Find previous HTML item
  6391.      * @param {zen_editor} editor
  6392.      */
  6393.     function findPrevHTMLItem(editor) {
  6394.         return findItem(editor, true, getOpeningTagFromPosition, getRangeForPrevItemInHTML);
  6395.     }
  6396.    
  6397.     /**
  6398.      * Returns range for item to be selected in tag after current caret position
  6399.      * @param {String} tag Tag declaration
  6400.      * @param {Number} offset Tag's position index inside content
  6401.      * @param {Number} sel_start Start index of user selection
  6402.      * @param {Number} sel_end End index of user selection
  6403.      * @return {Array} Returns array with two indexes if next item was found,
  6404.      * <code>null</code> otherwise
  6405.      */
  6406.     function getRangeForNextItemInHTML(tag, offset, sel_start, sel_end) {
  6407.         var tokens = ParserUtils.parseHTML(tag, offset),
  6408.             next = [];
  6409.                
  6410.         // search for token that is right to selection
  6411.         for (var i = 0, il = tokens.length; i < il; i++) {
  6412.             /** @type {ParserUtils.token} */
  6413.             var token = tokens[i], pos_test;
  6414.             if (token.type in known_xml_types) {
  6415.                 // check token position
  6416.                 pos_test = token.start >= sel_start;
  6417.                 if (token.type == 'xml-attribute' && isQuote(token.content.charAt(0)))
  6418.                     pos_test = token.start + 1 >= sel_start && token.end -1 != sel_end;
  6419.                
  6420.                 if (!pos_test && !(sel_start == sel_end && token.end > sel_start)) continue;
  6421.                
  6422.                 // found token that should be selected
  6423.                 if (token.type == 'xml-attname') {
  6424.                     next = handleFullAttributeHTML(tokens, i, sel_end <= token.end ? token.start : -1);
  6425.                     if (next) return next;
  6426.                 } else if (token.end > sel_end) {
  6427.                     next = [token.start, token.end];
  6428.                    
  6429.                     if (token.type == 'xml-attribute')
  6430.                         next = handleQuotesHTML(token.content, next);
  6431.                        
  6432.                     if (sel_start == next[0] && sel_end == next[1])
  6433.                         // in case of empty attribute
  6434.                         continue;
  6435.                    
  6436.                     return next;
  6437.                 }
  6438.             }
  6439.         }
  6440.        
  6441.         return null;
  6442.     }
  6443.    
  6444.     /**
  6445.      * Returns range for item to be selected in tag before current caret position
  6446.      * @param {String} tag Tag declaration
  6447.      * @param {Number} offset Tag's position index inside content
  6448.      * @param {Number} sel_start Start index of user selection
  6449.      * @param {Number} sel_end End index of user selection
  6450.      * @return {Array} Returns array with two indexes if next item was found,
  6451.      * <code>null</code> otherwise
  6452.      */
  6453.     function getRangeForPrevItemInHTML(tag, offset, sel_start, sel_end) {
  6454.         var tokens = ParserUtils.parseHTML(tag, offset),
  6455.             next;
  6456.                
  6457.         // search for token that is left to the selection
  6458.         for (var i = tokens.length - 1, il = tokens.length; i >= 0; i--) {
  6459.             /** @type {ParserUtils.token} */
  6460.             var token = tokens[i], pos_test;
  6461.             if (token.type in known_xml_types) {
  6462.                 // check token position
  6463.                 pos_test = token.start < sel_start;
  6464.                 if (token.type == 'xml-attribute' && isQuote(token.content.charAt(0))) {
  6465.                     pos_test = token.start + 1 < sel_start;
  6466.                 }
  6467.                
  6468.                 if (!pos_test) continue;
  6469.                
  6470.                 // found token that should be selected
  6471.                 if (token.type == 'xml-attname') {
  6472.                     next = handleFullAttributeHTML(tokens, i, token.start);
  6473.                     if (next) return next;
  6474.                 } else {
  6475.                     next = [token.start, token.end];
  6476.                    
  6477.                     if (token.type == 'xml-attribute')
  6478.                         next = handleQuotesHTML(token.content, next);
  6479.                    
  6480.                     return next;
  6481.                 }
  6482.             }
  6483.         }
  6484.        
  6485.         return null;
  6486.     }
  6487.    
  6488.     /**
  6489.      * Search for opening tag in content, starting at specified position
  6490.      * @param {String} html Where to search tag
  6491.      * @param {Number} pos Character index where to start searching
  6492.      * @return {Array} Returns array with tag indexes if valid opening tag was found,
  6493.      * <code>null</code> otherwise
  6494.      */
  6495.     function findOpeningTagFromPosition(html, pos) {
  6496.         var tag;
  6497.         while (pos >= 0) {
  6498.             if (tag = getOpeningTagFromPosition(html, pos))
  6499.                 return tag;
  6500.             pos--;
  6501.         }
  6502.        
  6503.         return null;
  6504.     }
  6505.    
  6506.     /**
  6507.      * @param {String} html Where to search tag
  6508.      * @param {Number} pos Character index where to start searching
  6509.      * @return {Array} Returns array with tag indexes if valid opening tag was found,
  6510.      * <code>null</code> otherwise
  6511.      */
  6512.     function getOpeningTagFromPosition(html, pos) {
  6513.         var m;
  6514.         if (html.charAt(pos) == '<' && (m = html.substring(pos, html.length).match(start_tag))) {
  6515.             return [pos, pos + m[0].length];
  6516.         }
  6517.     }
  6518.    
  6519.     function isQuote(ch) {
  6520.         return ch == '"' || ch == "'";
  6521.     }
  6522.    
  6523.     /**
  6524.      * Find item
  6525.      * @param {zen_editor} editor
  6526.      * @param {String} is_backward Search backward (search forward otherwise)
  6527.      * @param {Function} extract_fn Function that extracts item content
  6528.      * @param {Function} range_rn Function that search for next token range
  6529.      */
  6530.     function findItem(editor, is_backward, extract_fn, range_fn) {
  6531.         var content = String(editor.getContent()),
  6532.             c_len = content.length,
  6533.             item,
  6534.             item_def,
  6535.             rng,
  6536.             loop = 100000, // endless loop protection
  6537.             prev_range = [-1, -1],
  6538.             sel = editor.getSelectionRange(),
  6539.             sel_start = Math.min(sel.start, sel.end),
  6540.             sel_end = Math.max(sel.start, sel.end);
  6541.            
  6542.         var search_pos = sel_start;
  6543.         while (search_pos >= 0 && search_pos < c_len && loop > 0) {
  6544.             loop--;
  6545.             if ( (item = extract_fn(content, search_pos, is_backward)) ) {
  6546.                 if (prev_range[0] == item[0] && prev_range[1] == item[1]) {
  6547.                     break;
  6548.                 }
  6549.                
  6550.                 prev_range[0] = item[0];
  6551.                 prev_range[1] = item[1];
  6552.                 item_def = content.substring(item[0], item[1]);
  6553.                 rng = range_fn(item_def, item[0], sel_start, sel_end);
  6554.                    
  6555.                 if (rng) {
  6556.                     editor.createSelection(rng[0], rng[1]);
  6557.                     return true;
  6558.                 } else {
  6559.                     search_pos = is_backward ? item[0] : item[1] - 1;
  6560.                 }
  6561.             }
  6562.            
  6563.             search_pos += is_backward ? -1 : 1;
  6564.         }
  6565.        
  6566.         return false;
  6567.     }
  6568.    
  6569.     function findNextCSSItem(editor) {
  6570.         return findItem(editor, false, ParserUtils.extractCSSRule, getRangeForNextItemInCSS);
  6571.     }
  6572.    
  6573.     function findPrevCSSItem(editor) {
  6574.         return findItem(editor, true, ParserUtils.extractCSSRule, getRangeForPrevItemInCSS);
  6575.     }
  6576.    
  6577.     /**
  6578.      * Returns range for item to be selected in tag after current caret position
  6579.      * @param {String} rule CSS rule declaration
  6580.      * @param {Number} offset Rule's position index inside content
  6581.      * @param {Number} sel_start Start index of user selection
  6582.      * @param {Number} sel_end End index of user selection
  6583.      * @return {Array} Returns array with two indexes if next item was found,
  6584.      * <code>null</code> otherwise
  6585.      */
  6586.     function getRangeForNextItemInCSS(rule, offset, sel_start, sel_end) {
  6587.         var tokens = ParserUtils.parseCSS(rule, offset), pos_test,
  6588.             next = [];
  6589.            
  6590.         /**
  6591.          * Same range is used inside complex value processor
  6592.          * @return {Boolean}
  6593.          */
  6594.         function checkSameRange(r) {
  6595.             return r[0] == sel_start && r[1] == sel_end;
  6596.         }
  6597.                
  6598.         // search for token that is right to selection
  6599.         for (var i = 0, il = tokens.length; i < il; i++) {
  6600.             /** @type {ParserUtils.token} */
  6601.             var token = tokens[i], pos_test;
  6602.             if (token.type in known_css_types) {
  6603.                 // check token position
  6604.                 if (sel_start == sel_end)
  6605.                     pos_test = token.end > sel_start;
  6606.                 else {
  6607.                     pos_test = token.start >= sel_start;
  6608.                     if (token.type == 'value') // respect complex values
  6609.                         pos_test = pos_test || sel_start >= token.start && token.end >= sel_end;
  6610.                 }
  6611.                
  6612.                 if (!pos_test) continue;
  6613.                
  6614.                 // found token that should be selected
  6615.                 if (token.type == 'identifier') {
  6616.                     var rule_sel = handleFullRuleCSS(tokens, i, sel_end <= token.end ? token.start : -1);
  6617.                     if (rule_sel) return rule_sel;
  6618.                    
  6619.                 } else if (token.type == 'value' && sel_end > token.start && token.children) {
  6620.                     // looks like a complex value
  6621.                     var children = token.children;
  6622.                     for (var j = 0, jl = children.length; j < jl; j++) {
  6623.                         if (children[j][0] >= sel_start || (sel_start == sel_end && children[j][1] > sel_start)) {
  6624.                             next = [children[j][0], children[j][1]];
  6625.                             if (checkSameRange(next)) {
  6626.                                 var rule_sel = handleCSSSpecialCase(rule, next[0], next[1], offset);
  6627.                                 if (!checkSameRange(rule_sel))
  6628.                                     return rule_sel;
  6629.                                 else
  6630.                                     continue;
  6631.                             }
  6632.                            
  6633.                             return next;
  6634.                         }
  6635.                     }
  6636.                 } else if (token.end > sel_end) {
  6637.                     return [token.start, token.end];
  6638.                 }
  6639.             }
  6640.         }
  6641.        
  6642.         return null;
  6643.     }
  6644.    
  6645.     /**
  6646.      * Returns range for item to be selected in CSS rule before current caret position
  6647.      * @param {String} rule CSS rule declaration
  6648.      * @param {Number} offset Rule's position index inside content
  6649.      * @param {Number} sel_start Start index of user selection
  6650.      * @param {Number} sel_end End index of user selection
  6651.      * @return {Array} Returns array with two indexes if next item was found,
  6652.      * <code>null</code> otherwise
  6653.      */
  6654.     function getRangeForPrevItemInCSS(rule, offset, sel_start, sel_end) {
  6655.         var tokens = ParserUtils.parseCSS(rule, offset),
  6656.             next = [];
  6657.                
  6658.         /**
  6659.          * Same range is used inside complex value processor
  6660.          * @return {Boolean}
  6661.          */
  6662.         function checkSameRange(r) {
  6663.             return r[0] == sel_start && r[1] == sel_end;
  6664.         }
  6665.            
  6666.         // search for token that is left to the selection
  6667.         for (var i = tokens.length - 1, il = tokens.length; i >= 0; i--) {
  6668.             /** @type {ParserUtils.token} */
  6669.             var token = tokens[i], pos_test;
  6670.             if (token.type in known_css_types) {
  6671.                 // check token position
  6672.                 pos_test = token.start < sel_start;
  6673.                 if (token.type == 'value' && token.ref_start_ix != token.ref_end_ix) // respect complex values
  6674.                     pos_test = token.start <= sel_start;
  6675.                
  6676.                 if (!pos_test) continue;
  6677.                
  6678.                 // found token that should be selected
  6679.                 if (token.type == 'identifier') {
  6680.                     var rule_sel = handleFullRuleCSS(tokens, i, token.start);
  6681.                     if (rule_sel) return rule_sel;
  6682.                 } else if (token.type == 'value' && token.ref_start_ix != token.ref_end_ix) {
  6683.                     // looks like a complex value
  6684.                     var children = token.children;
  6685.                     for (var j = children.length - 1; j >= 0; j--) {
  6686.                         if (children[j][0] < sel_start) {
  6687.                             // create array copy
  6688.                             next = [children[j][0], children[j][1]];
  6689.                            
  6690.                             var rule_sel = handleCSSSpecialCase(rule, next[0], next[1], offset);
  6691.                             return !checkSameRange(rule_sel) ? rule_sel : next;
  6692.                         }
  6693.                     }
  6694.                    
  6695.                     // if we are here than we already traversed trough all
  6696.                     // child tokens, select full value
  6697.                     next = [token.start, token.end];
  6698.                     if (!checkSameRange(next))
  6699.                         return next;
  6700.                 } else {
  6701.                     return [token.start, token.end];
  6702.                 }
  6703.             }
  6704.         }
  6705.        
  6706.         return null;
  6707.     }
  6708.    
  6709.     function handleFullRuleCSS(tokens, i, start) {
  6710.         for (var j = i + 1, il = tokens.length; j < il; j++) {
  6711.             /** @type {ParserUtils.token} */
  6712.             var _t = tokens[j];
  6713.             if ((_t.type == 'value' && start == -1) || _t.type == 'identifier') {
  6714.                 return [_t.start, _t.end];
  6715.             } else if (_t.type == ';') {
  6716.                 return [start == -1 ? _t.start : start, _t.end];
  6717.             } else if (_t.type == '}') {
  6718.                 return [start == -1 ? _t.start : start, _t.start - 1];
  6719.             }
  6720.         }
  6721.        
  6722.         return null;
  6723.     }
  6724.    
  6725.     function handleFullAttributeHTML(tokens, i, start) {
  6726.         for (var j = i + 1, il = tokens.length; j < il; j++) {
  6727.             /** @type {ParserUtils.token} */
  6728.             var _t = tokens[j];
  6729.             if (_t.type == 'xml-attribute') {
  6730.                 if (start == -1)
  6731.                     return handleQuotesHTML(_t.content, [_t.start, _t.end]);
  6732.                 else
  6733.                     return [start, _t.end];
  6734.             } else if (_t.type == 'xml-attname') {
  6735.                 // moved to next attribute, adjust selection
  6736.                 return [_t.start, tokens[i].end];
  6737.             }
  6738.         }
  6739.            
  6740.         return null;
  6741.     }
  6742.    
  6743.     function handleQuotesHTML(attr, r) {
  6744.         if (isQuote(attr.charAt(0)))
  6745.             r[0]++;
  6746.         if (isQuote(attr.charAt(attr.length - 1)))
  6747.             r[1]--;
  6748.            
  6749.         return r;
  6750.     }
  6751.    
  6752.     function handleCSSSpecialCase(text, start, end, offset) {
  6753.         text = text.substring(start - offset, end - offset);
  6754.         var m;
  6755.         if (m = text.match(/^[\w\-]+\(['"]?/)) {
  6756.             start += m[0].length;
  6757.             if (m = text.match(/['"]?\)$/))
  6758.                 end -= m[0].length;
  6759.         }
  6760.        
  6761.         return [start, end];
  6762.     }
  6763.    
  6764.     // XXX register actions
  6765.     zen_coding.registerAction('select_next_item', function(/* zen_editor */ editor){
  6766.         if (editor.getSyntax() == 'css')
  6767.             return findNextCSSItem(editor);
  6768.         else
  6769.             return findNextHTMLItem(editor);
  6770.     });
  6771.    
  6772.     zen_coding.registerAction('select_previous_item', function(/* zen_editor */ editor){
  6773.         if (editor.getSyntax() == 'css')
  6774.             return findPrevCSSItem(editor);
  6775.         else
  6776.             return findPrevHTMLItem(editor);
  6777.     });
  6778. })();/**
  6779.  * Comment important tags (with 'id' and 'class' attributes)
  6780.  * @author Sergey Chikuyonok ([email protected])
  6781.  * @link http://chikuyonok.ru
  6782.  */
  6783. (function(){
  6784.     /**
  6785.      * Add comments to tag
  6786.      * @param {ZenNode} node
  6787.      */
  6788.     function addComments(node, i) {
  6789.         var id_attr = node.getAttribute('id'),
  6790.             class_attr = node.getAttribute('class'),
  6791.             nl = zen_coding.getNewline();
  6792.            
  6793.         if (id_attr || class_attr) {
  6794.             var comment_str = '',
  6795.                 padding = (node.parent) ? node.parent.padding : '';
  6796.             if (id_attr) comment_str += '#' + id_attr;
  6797.             if (class_attr) comment_str += '.' + class_attr;
  6798.            
  6799.             node.start = node.start.replace(/</, '<!-- ' + comment_str + ' -->' + nl + padding + '<');
  6800.             node.end = node.end.replace(/>/, '>' + nl + padding + '<!-- /' + comment_str + ' -->');
  6801.            
  6802.             // replace counters
  6803.             var counter = zen_coding.getCounterForNode(node);
  6804.             node.start = zen_coding.replaceCounter(node.start, counter);
  6805.             node.end = zen_coding.replaceCounter(node.end, counter);
  6806.         }
  6807.     }
  6808.    
  6809.     function process(tree, profile) {
  6810.         if (profile.tag_nl === false)
  6811.             return tree;
  6812.            
  6813.         for (var i = 0, il = tree.children.length; i < il; i++) {
  6814.             /** @type {ZenNode} */
  6815.             var item = tree.children[i];
  6816.            
  6817.             if (item.isBlock())
  6818.                 addComments(item, i);
  6819.            
  6820.             process(item, profile);
  6821.         }
  6822.        
  6823.         return tree;
  6824.     }
  6825.    
  6826.     zen_coding.registerFilter('c', process);
  6827. })();/**
  6828.  * Process CSS properties: replaces snippets, augumented with ! char, with
  6829.  * <em>!important</em> suffix
  6830.  * @author Sergey Chikuyonok ([email protected])
  6831.  * @link http://chikuyonok.ru
  6832.  */
  6833. (function(){
  6834.     var re_important = /(.+)\!$/;
  6835.     function process(tree, profile) {
  6836.         for (var i = 0, il = tree.children.length; i < il; i++) {
  6837.             /** @type {ZenNode} */
  6838.             var item = tree.children[i];
  6839.            
  6840.             // CSS properties are always snippets
  6841.             if (item.type == 'snippet' && re_important.test(item.real_name)) {
  6842.                 item.start = item.start.replace(/(;?)$/, ' !important$1');
  6843.             }
  6844.            
  6845.             process(item, profile);
  6846.         }
  6847.        
  6848.         return tree;
  6849.     }
  6850.    
  6851.     zen_coding.registerFilter('css', process);
  6852. })();/**
  6853.  * Filter for escaping unsafe XML characters: <, >, &
  6854.  * @author Sergey Chikuyonok ([email protected])
  6855.  * @link http://chikuyonok.ru
  6856.  */
  6857. (function(){
  6858.     var char_map = {
  6859.         '<': '&lt;',
  6860.         '>': '&gt;',
  6861.         '&': '&amp;'
  6862.     }
  6863.    
  6864.     function escapeChars(str) {
  6865.         return str.replace(/([<>&])/g, function(str, p1){
  6866.             return char_map[p1];
  6867.         });
  6868.     }
  6869.    
  6870.     function process(tree, profile, level) {
  6871.         for (var i = 0, il = tree.children.length; i < il; i++) {
  6872.             /** @type {ZenNode} */
  6873.             var item = tree.children[i];
  6874.            
  6875.             item.start = escapeChars(item.start);
  6876.             item.end = escapeChars(item.end);
  6877.            
  6878.             process(item);
  6879.         }
  6880.        
  6881.         return tree;
  6882.     }
  6883.    
  6884.     zen_coding.registerFilter('e', process);
  6885. })();/**
  6886.  * Format CSS properties: add space after property name:
  6887.  * padding:0; → padding: 0;
  6888.  * @author Sergey Chikuyonok ([email protected])
  6889.  * @link http://chikuyonok.ru
  6890.  */
  6891. (function(){
  6892.     function process(tree, profile) {
  6893.         for (var i = 0, il = tree.children.length; i < il; i++) {
  6894.             /** @type {ZenNode} */
  6895.             var item = tree.children[i];
  6896.            
  6897.             // CSS properties are always snippets
  6898.             if (item.type == 'snippet') {
  6899.                 item.start = item.start.replace(/([\w\-]+\s*:)(?!:)\s*/, '$1 ');
  6900.             }
  6901.            
  6902.             process(item, profile);
  6903.         }
  6904.        
  6905.         return tree;
  6906.     }
  6907.    
  6908.     zen_coding.registerFilter('fc', process);
  6909. })();/**
  6910.  * Generic formatting filter: creates proper indentation for each tree node,
  6911.  * placing "%s" placeholder where the actual output should be. You can use
  6912.  * this filter to preformat tree and then replace %s placeholder to whatever you
  6913.  * need. This filter should't be called directly from editor as a part
  6914.  * of abbreviation.
  6915.  * @author Sergey Chikuyonok ([email protected])
  6916.  * @link http://chikuyonok.ru
  6917.  *
  6918.  * @include "../zen_coding.js"
  6919.  */
  6920. (function(){
  6921.     var child_token = '${child}',
  6922.         placeholder = '%s';
  6923.    
  6924.     function getNewline() {
  6925.         return zen_coding.getNewline();
  6926.     }
  6927.    
  6928.     function getIndentation() {
  6929.         return zen_resources.getVariable('indentation');
  6930.     }
  6931.    
  6932.     /**
  6933.      * Test if passed node has block-level sibling element
  6934.      * @param {ZenNode} item
  6935.      * @return {Boolean}
  6936.      */
  6937.     function hasBlockSibling(item) {
  6938.         return (item.parent && item.parent.hasBlockChildren());
  6939.     }
  6940.    
  6941.     /**
  6942.      * Test if passed itrem is very first child of the whole tree
  6943.      * @param {ZenNode} tree
  6944.      */
  6945.     function isVeryFirstChild(item) {
  6946.         return item.parent && !item.parent.parent && !item.previousSibling;
  6947.     }
  6948.    
  6949.     /**
  6950.      * Need to add line break before element
  6951.      * @param {ZenNode} node
  6952.      * @param {Object} profile
  6953.      * @return {Boolean}
  6954.      */
  6955.     function shouldBreakLine(node, profile) {
  6956.         if (!profile.inline_break)
  6957.             return false;
  6958.            
  6959.         // find toppest non-inline sibling
  6960.         while (node.previousSibling && node.previousSibling.isInline())
  6961.             node = node.previousSibling;
  6962.        
  6963.         if (!node.isInline())
  6964.             return false;
  6965.            
  6966.         // calculate how many inline siblings we have
  6967.         var node_count = 1;
  6968.         while (node = node.nextSibling) {
  6969.             if (node.type == 'text' || !node.isInline())
  6970.                 node_count = 0;
  6971.             else if (node.isInline())
  6972.                 node_count++;
  6973.         }
  6974.        
  6975.         return node_count >= profile.inline_break;
  6976.     }
  6977.    
  6978.     /**
  6979.      * Need to add newline because <code>item</code> has too many inline children
  6980.      * @param {ZenNode} node
  6981.      * @param {Object} profile
  6982.      */
  6983.     function shouldBreakChild(node, profile) {
  6984.         // we need to test only one child element, because
  6985.         // hasBlockChildren() method will do the rest
  6986.         return (node.children.length && shouldBreakLine(node.children[0], profile));
  6987.     }
  6988.    
  6989.     /**
  6990.      * Processes element with <code>snippet</code> type
  6991.      * @param {ZenNode} item
  6992.      * @param {Object} profile
  6993.      * @param {Number} [level] Depth level
  6994.      */
  6995.     function processSnippet(item, profile, level) {
  6996.         var data = item.source.value;
  6997.            
  6998.         if (!data)
  6999.             // snippet wasn't found, process it as tag
  7000.             return processTag(item, profile, level);
  7001.            
  7002.         item.start = item.end = placeholder;
  7003.        
  7004.         var padding = (item.parent)
  7005.             ? item.parent.padding
  7006.             : zen_coding.repeatString(getIndentation(), level);
  7007.        
  7008.         if (!isVeryFirstChild(item)) {
  7009.             item.start = getNewline() + padding + item.start;
  7010.         }
  7011.        
  7012.         // adjust item formatting according to last line of <code>start</code> property
  7013.         var parts = data.split(child_token),
  7014.             lines = zen_coding.splitByLines(parts[0] || ''),
  7015.             padding_delta = getIndentation();
  7016.            
  7017.         if (lines.length > 1) {
  7018.             var m = lines[lines.length - 1].match(/^(\s+)/);
  7019.             if (m)
  7020.                 padding_delta = m[1];
  7021.         }
  7022.        
  7023.         item.padding = padding + padding_delta;
  7024.        
  7025.         return item;
  7026.     }
  7027.    
  7028.     /**
  7029.      * Processes element with <code>tag</code> type
  7030.      * @param {ZenNode} item
  7031.      * @param {Object} profile
  7032.      * @param {Number} [level] Depth level
  7033.      */
  7034.     function processTag(item, profile, level) {
  7035.         if (!item.name)
  7036.             // looks like it's a root element
  7037.             return item;
  7038.        
  7039.         item.start = item.end = placeholder;
  7040.        
  7041.         var is_unary = (item.isUnary() && !item.children.length);
  7042.            
  7043.         // formatting output
  7044.         if (profile.tag_nl !== false) {
  7045.             var padding = (item.parent)
  7046.                     ? item.parent.padding
  7047.                     : zen_coding.repeatString(getIndentation(), level),
  7048.                 force_nl = (profile.tag_nl === true),
  7049.                 should_break = shouldBreakLine(item, profile);
  7050.            
  7051.             // formatting block-level elements
  7052.             if (item.type != 'text') {
  7053.                 if (( (item.isBlock() || should_break) && item.parent) || force_nl) {
  7054.                     // snippet children should take different formatting
  7055.                     if (!item.parent || (item.parent.type != 'snippet' && !isVeryFirstChild(item)))
  7056.                         item.start = getNewline() + padding + item.start;
  7057.                        
  7058.                     if (item.hasBlockChildren() || shouldBreakChild(item, profile) || (force_nl && !is_unary))
  7059.                         item.end = getNewline() + padding + item.end;
  7060.                        
  7061.                     if (item.hasTagsInContent() || (force_nl && !item.hasChildren() && !is_unary))
  7062.                         item.start += getNewline() + padding + getIndentation();
  7063.                    
  7064.                 } else if (item.isInline() && hasBlockSibling(item) && !isVeryFirstChild(item)) {
  7065.                     item.start = getNewline() + padding + item.start;
  7066.                 } else if (item.isInline() && item.hasBlockChildren()) {
  7067.                     item.end = getNewline() + padding + item.end;
  7068.                 }
  7069.                
  7070.                 item.padding = padding + getIndentation();
  7071.             }
  7072.         }
  7073.        
  7074.         return item;
  7075.     }
  7076.    
  7077.     /**
  7078.      * Processes simplified tree, making it suitable for output as HTML structure
  7079.      * @param {ZenNode} tree
  7080.      * @param {Object} profile
  7081.      * @param {Number} [level] Depth level
  7082.      */
  7083.     function process(tree, profile, level) {
  7084.         level = level || 0;
  7085.        
  7086.         for (var i = 0, il = tree.children.length; i < il; i++) {
  7087.             /** @type {ZenNode} */
  7088.             var item = tree.children[i];
  7089.             item = (item.type == 'tag')
  7090.                 ? processTag(item, profile, level)
  7091.                 : processSnippet(item, profile, level);
  7092.                
  7093.             if (item.content)
  7094.                 item.content = zen_coding.padString(item.content, item.padding);
  7095.                
  7096.             process(item, profile, level + 1);
  7097.         }
  7098.        
  7099.         return tree;
  7100.     }
  7101.    
  7102.     zen_coding.registerFilter('_format', process);
  7103. })();/**
  7104.  * Filter that produces HAML tree
  7105.  * @author Sergey Chikuyonok ([email protected])
  7106.  * @link http://chikuyonok.ru
  7107.  *
  7108.  * @include "../zen_coding.js"
  7109.  */
  7110. (function(){
  7111.     var child_token = '${child}';
  7112.    
  7113.     /**
  7114.      * Creates HTML attributes string from tag according to profile settings
  7115.      * @param {ZenNode} tag
  7116.      * @param {default_profile} profile
  7117.      */
  7118.     function makeAttributesString(tag, profile) {
  7119.         // make attribute string
  7120.         var attrs = '',
  7121.             attr_quote = profile.attr_quotes == 'single' ? "'" : '"',
  7122.             cursor = profile.place_cursor ? zen_coding.getCaretPlaceholder() : '',
  7123.             attr_name,
  7124.             i,
  7125.             a;
  7126.            
  7127.         // use short notation for ID and CLASS attributes
  7128.         for (i = 0; i < tag.attributes.length; i++) {
  7129.             a = tag.attributes[i];
  7130.             switch (a.name.toLowerCase()) {
  7131.                 case 'id':
  7132.                     attrs += '#' + (a.value || cursor);
  7133.                     break;
  7134.                 case 'class':
  7135.                     attrs += '.' + (a.value || cursor);
  7136.                     break;
  7137.             }
  7138.         }
  7139.        
  7140.         var other_attrs = [];
  7141.        
  7142.         // process other attributes
  7143.         for (i = 0; i < tag.attributes.length; i++) {
  7144.             a = tag.attributes[i];
  7145.             var attr_name_lower = a.name.toLowerCase();
  7146.             if (attr_name_lower != 'id' && attr_name_lower != 'class') {
  7147.                 attr_name = (profile.attr_case == 'upper') ? a.name.toUpperCase() : attr_name_lower;
  7148.                 other_attrs.push(':' +attr_name + ' => ' + attr_quote + (a.value || cursor) + attr_quote);
  7149.             }
  7150.         }
  7151.        
  7152.         if (other_attrs.length)
  7153.             attrs += '{' + other_attrs.join(', ') + '}';
  7154.        
  7155.         return attrs;
  7156.     }
  7157.    
  7158.     /**
  7159.      * Processes element with <code>snippet</code> type
  7160.      * @param {ZenNode} item
  7161.      * @param {Object} profile
  7162.      * @param {Number} [level] Depth level
  7163.      */
  7164.     function processSnippet(item, profile, level) {
  7165.         var data = item.source.value;
  7166.            
  7167.         if (!data)
  7168.             // snippet wasn't found, process it as tag
  7169.             return processTag(item, profile, level);
  7170.            
  7171.         var parts = data.split(child_token),
  7172.             start = parts[0] || '',
  7173.             end = parts[1] || '',
  7174.             padding = item.parent ? item.parent.padding : '';
  7175.            
  7176.         item.start = item.start.replace('%s', zen_coding.padString(start, padding));
  7177.         item.end = item.end.replace('%s', zen_coding.padString(end, padding));
  7178.        
  7179.         // replace variables ID and CLASS
  7180.         var cb = function(str, var_name) {
  7181.             if (var_name == 'id' || var_name == 'class')
  7182.                 return item.getAttribute(var_name);
  7183.             else
  7184.                 return str;
  7185.         };
  7186.         item.start = zen_coding.replaceVariables(item.start, cb);
  7187.         item.end = zen_coding.replaceVariables(item.end, cb);
  7188.        
  7189.         return item;
  7190.     }
  7191.    
  7192.     /**
  7193.      * Test if passed node has block-level sibling element
  7194.      * @param {ZenNode} item
  7195.      * @return {Boolean}
  7196.      */
  7197.     function hasBlockSibling(item) {
  7198.         return (item.parent && item.parent.hasBlockChildren());
  7199.     }
  7200.    
  7201.     /**
  7202.      * Processes element with <code>tag</code> type
  7203.      * @param {ZenNode} item
  7204.      * @param {Object} profile
  7205.      * @param {Number} [level] Depth level
  7206.      */
  7207.     function processTag(item, profile, level) {
  7208.         if (!item.name)
  7209.             // looks like it's root element
  7210.             return item;
  7211.        
  7212.         var attrs = makeAttributesString(item, profile),
  7213.             content = '',
  7214.             cursor = profile.place_cursor ? zen_coding.getCaretPlaceholder() : '',
  7215.             self_closing = '',
  7216.             is_unary = (item.isUnary() && !item.children.length),
  7217.             start= '',
  7218.             end = '';
  7219.        
  7220.         if (profile.self_closing_tag && is_unary)
  7221.             self_closing = '/';
  7222.            
  7223.         // define tag name
  7224.         var tag_name = '%' + ((profile.tag_case == 'upper') ? item.name.toUpperCase() : item.name.toLowerCase());
  7225.         if (tag_name.toLowerCase() == '%div' && attrs && attrs.indexOf('{') == -1)
  7226.             // omit div tag
  7227.             tag_name = '';
  7228.            
  7229.         item.end = '';
  7230.         start = tag_name + attrs + self_closing;
  7231.        
  7232.         var placeholder = '%s';
  7233.         // We can't just replace placeholder with new value because
  7234.         // JavaScript will treat double $ character as a single one, assuming
  7235.         // we're using RegExp literal.
  7236.         var pos = item.start.indexOf(placeholder);
  7237.         item.start = item.start.substring(0, pos) + start + item.start.substring(pos + placeholder.length);
  7238.        
  7239.         if (!item.children.length && !is_unary)
  7240.             item.start += cursor;
  7241.        
  7242.         return item;
  7243.     }
  7244.    
  7245.     /**
  7246.      * Processes simplified tree, making it suitable for output as HTML structure
  7247.      * @param {ZenNode} tree
  7248.      * @param {Object} profile
  7249.      * @param {Number} [level] Depth level
  7250.      */
  7251.     function process(tree, profile, level) {
  7252.         level = level || 0;
  7253.         if (level == 0)
  7254.             // preformat tree
  7255.             tree = zen_coding.runFilters(tree, profile, '_format');
  7256.        
  7257.         for (var i = 0, il = tree.children.length; i < il; i++) {
  7258.             /** @type {ZenNode} */
  7259.             var item = tree.children[i];
  7260.             item = (item.type == 'tag')
  7261.                 ? processTag(item, profile, level)
  7262.                 : processSnippet(item, profile, level);
  7263.            
  7264.             // replace counters
  7265.             var counter = zen_coding.getCounterForNode(item);
  7266.             item.start = zen_coding.unescapeText(zen_coding.replaceCounter(item.start, counter));
  7267.             item.end = zen_coding.unescapeText(zen_coding.replaceCounter(item.end, counter));
  7268.            
  7269.             process(item, profile, level + 1);
  7270.         }
  7271.        
  7272.         return tree;
  7273.     }
  7274.    
  7275.     zen_coding.registerFilter('haml', process);
  7276. })();/**
  7277.  * Filter that produces HTML tree
  7278.  * @author Sergey Chikuyonok ([email protected])
  7279.  * @link http://chikuyonok.ru
  7280.  *
  7281.  * @include "../zen_coding.js"
  7282.  */
  7283. (function(){
  7284.     var child_token = '${child}',
  7285.         tabstops = 0;
  7286.        
  7287.     /**
  7288.      * Returns proper string case, depending on profile value
  7289.      * @param {String} val String to process
  7290.      * @param {String} case_param Profile's case value ('lower', 'upper', 'leave')
  7291.      */
  7292.     function processStringCase(val, case_param) {
  7293.         switch (String(case_param || '').toLowerCase()) {
  7294.             case 'lower':
  7295.                 return val.toLowerCase();
  7296.             case 'upper':
  7297.                 return val.toUpperCase();
  7298.         }
  7299.        
  7300.         return val;
  7301.     }
  7302.    
  7303.     /**
  7304.      * Creates HTML attributes string from tag according to profile settings
  7305.      * @param {ZenNode} tag
  7306.      * @param {default_profile} profile
  7307.      */
  7308.     function makeAttributesString(tag, profile) {
  7309.         // make attribute string
  7310.         var attrs = '',
  7311.             attr_quote = profile.attr_quotes == 'single' ? "'" : '"',
  7312.             cursor = profile.place_cursor ? zen_coding.getCaretPlaceholder() : '',
  7313.             attr_name;
  7314.            
  7315.         for (var i = 0; i < tag.attributes.length; i++) {
  7316.             var a = tag.attributes[i];
  7317.             attr_name = processStringCase(a.name, profile.attr_case);
  7318.             attrs += ' ' + attr_name + '=' + attr_quote + (a.value || cursor) + attr_quote;
  7319.         }
  7320.        
  7321.         return attrs;
  7322.     }
  7323.    
  7324.     /**
  7325.      * Processes element with <code>snippet</code> type
  7326.      * @param {ZenNode} item
  7327.      * @param {Object} profile
  7328.      * @param {Number} [level] Depth level
  7329.      */
  7330.     function processSnippet(item, profile, level) {
  7331.         var data = item.source.value;
  7332.            
  7333.         if (!data)
  7334.             // snippet wasn't found, process it as tag
  7335.             return processTag(item, profile, level);
  7336.            
  7337.         var parts = data.split(child_token),
  7338.             start = parts[0] || '',
  7339.             end = parts[1] || '',
  7340.             padding = item.parent ? item.parent.padding : '';
  7341.            
  7342.            
  7343.         item.start = item.start.replace('%s', zen_coding.padString(start, padding));
  7344.         item.end = item.end.replace('%s', zen_coding.padString(end, padding));
  7345.        
  7346.         // replace variables ID and CLASS
  7347.         var cb = function(str, var_name) {
  7348.             if (var_name == 'id' || var_name == 'class')
  7349.                 return item.getAttribute(var_name);
  7350.             else
  7351.                 return str;
  7352.         };
  7353.         item.start = zen_coding.replaceVariables(item.start, cb);
  7354.         item.end = zen_coding.replaceVariables(item.end, cb);
  7355.        
  7356.         return item;
  7357.     }
  7358.    
  7359.     /**
  7360.      * Test if passed node has block-level sibling element
  7361.      * @param {ZenNode} item
  7362.      * @return {Boolean}
  7363.      */
  7364.     function hasBlockSibling(item) {
  7365.         return (item.parent && item.parent.hasBlockChildren());
  7366.     }
  7367.    
  7368.     /**
  7369.      * Processes element with <code>tag</code> type
  7370.      * @param {ZenNode} item
  7371.      * @param {Object} profile
  7372.      * @param {Number} [level] Depth level
  7373.      */
  7374.     function processTag(item, profile, level) {
  7375.         if (!item.name)
  7376.             // looks like it's root element
  7377.             return item;
  7378.        
  7379.         var attrs = makeAttributesString(item, profile),
  7380.             content = '',
  7381.             cursor = profile.place_cursor ? zen_coding.getCaretPlaceholder() : '',
  7382.             self_closing = '',
  7383.             is_unary = (item.isUnary() && !item.children.length),
  7384.             start= '',
  7385.             end = '';
  7386.        
  7387.         if (profile.self_closing_tag == 'xhtml')
  7388.             self_closing = ' /';
  7389.         else if (profile.self_closing_tag === true)
  7390.             self_closing = '/';
  7391.            
  7392.         // define opening and closing tags
  7393.         if (item.type != 'text') {
  7394.             var tag_name = processStringCase(item.name, profile.tag_case);
  7395.             if (is_unary) {
  7396.                 start = '<' + tag_name + attrs + self_closing + '>';
  7397.                 item.end = '';
  7398.             } else {
  7399.                 start = '<' + tag_name + attrs + '>';
  7400.                 end = '</' + tag_name + '>';
  7401.             }
  7402.         }
  7403.        
  7404.         var placeholder = '%s';
  7405.         // We can't just replace placeholder with new value because
  7406.         // JavaScript will treat double $ character as a single one, assuming
  7407.         // we're using RegExp literal.
  7408.         var pos = item.start.indexOf(placeholder);
  7409.         item.start = item.start.substring(0, pos) + start + item.start.substring(pos + placeholder.length);
  7410.        
  7411.         pos = item.end.indexOf(placeholder);
  7412.         item.end = item.end.substring(0, pos) + end + item.end.substring(pos + placeholder.length);
  7413.        
  7414.         if (!item.children.length && !is_unary && item.content.indexOf(cursor) == -1)
  7415.             item.start += cursor;
  7416.        
  7417.         return item;
  7418.     }
  7419.    
  7420.     /**
  7421.      * Processes simplified tree, making it suitable for output as HTML structure
  7422.      * @param {ZenNode} tree
  7423.      * @param {Object} profile
  7424.      * @param {Number} [level] Depth level
  7425.      */
  7426.     function process(tree, profile, level) {
  7427.         level = level || 0;
  7428.         if (level == 0) {
  7429.             tree = zen_coding.runFilters(tree, profile, '_format');
  7430.             tabstops = 0;
  7431.         }
  7432.        
  7433.         for (var i = 0, il = tree.children.length; i < il; i++) {
  7434.             /** @type {ZenNode} */
  7435.    
  7436.             var item = tree.children[i];
  7437.             item = (item.type == 'tag')
  7438.                 ? processTag(item, profile, level)
  7439.                 : processSnippet(item, profile, level);
  7440.            
  7441.             // replace counters
  7442.             var counter = zen_coding.getCounterForNode(item);
  7443.             item.start = zen_coding.unescapeText(zen_coding.replaceCounter(item.start, counter));
  7444.             item.end = zen_coding.unescapeText(zen_coding.replaceCounter(item.end, counter));
  7445.             item.content = zen_coding.unescapeText(zen_coding.replaceCounter(item.content, counter));
  7446.            
  7447.             tabstops += zen_coding.upgradeTabstops(item, tabstops) + 1;
  7448.            
  7449.             process(item, profile, level + 1);
  7450.         }
  7451.        
  7452.         return tree;
  7453.     }
  7454.    
  7455.     zen_coding.registerFilter('html', process);
  7456. })();/**
  7457.  * Output abbreviation on a single line (i.e. no line breaks)
  7458.  * @author Sergey Chikuyonok ([email protected])
  7459.  * @link http://chikuyonok.ru
  7460.  */
  7461. (function(){
  7462.     function process(tree, profile, level) {
  7463.         for (var i = 0, il = tree.children.length; i < il; i++) {
  7464.             /** @type {ZenNode} */
  7465.             var item = tree.children[i];
  7466.             if (item.type == 'tag') {
  7467.                 // remove padding from item
  7468.                 var re_pad = /^\s+/;
  7469.                 item.start = item.start.replace(re_pad, '');
  7470.                 item.end = item.end.replace(re_pad, '');
  7471.             }
  7472.            
  7473.             // remove newlines
  7474.             var re_nl = /[\n\r]/g;
  7475.             item.start = item.start.replace(re_nl, '');
  7476.             item.end = item.end.replace(re_nl, '');
  7477.             item.content = item.content.replace(re_nl, '');
  7478.            
  7479.             process(item);
  7480.         }
  7481.        
  7482.         return tree;
  7483.     }
  7484.    
  7485.     zen_coding.registerFilter('s', process);
  7486. })();
  7487. /**
  7488.  * Trim filter: removes characters at the beginning of the text
  7489.  *  content that indicates lists: numbers, #, *, -, etc.
  7490.  * @author Sergey Chikuyonok ([email protected])
  7491.  * @link http://chikuyonok.ru
  7492.  */
  7493. (function(){
  7494.     function process(tree, profile, level) {
  7495.         for (var i = 0, il = tree.children.length; i < il; i++) {
  7496.             /** @type {ZenNode} */
  7497.             var item = tree.children[i];
  7498.            
  7499.             if (item.content)
  7500.                 item.content = item.content.replace(/^([\s|\u00a0])?[\d|#|\-|\*|\u2022]+\.?\s*/, '$1');
  7501.            
  7502.             process(item);
  7503.         }
  7504.        
  7505.         return tree;
  7506.     }
  7507.    
  7508.     zen_coding.registerFilter('t', process);
  7509. })();
  7510. /**
  7511.  * Filter for trimming "select" attributes from some tags that contains
  7512.  * child elements
  7513.  * @author Sergey Chikuyonok ([email protected])
  7514.  * @link http://chikuyonok.ru
  7515.  */
  7516. (function(){
  7517.     var tags = {
  7518.         'xsl:variable': 1,
  7519.         'xsl:with-param': 1
  7520.     };
  7521.    
  7522.     /**
  7523.      * Removes "select" attribute from node
  7524.      * @param {ZenNode} node
  7525.      */
  7526.     function trimAttribute(node) {
  7527.         node.start = node.start.replace(/\s+select\s*=\s*(['"]).*?\1/, '');
  7528.     }
  7529.    
  7530.     function process(tree) {
  7531.         for (var i = 0, il = tree.children.length; i < il; i++) {
  7532.             /** @type {ZenNode} */
  7533.             var item = tree.children[i];
  7534.             if (item.type == 'tag' && item.name.toLowerCase() in tags && item.children.length)
  7535.                 trimAttribute(item);
  7536.             process(item);
  7537.         }
  7538.     }
  7539.    
  7540.     zen_coding.registerFilter('xsl', process);
  7541. })();/**
  7542.  * Tests if passed keydown/keypress event corresponds to defied shortcut
  7543.  *
  7544.  * Based on http://www.openjs.com/scripts/events/keyboard_shortcuts/
  7545.  * By Binny V A
  7546.  * License : BSD
  7547.  */
  7548. var shortcut = (function(){
  7549.     var is_opera = !!window.opera,
  7550.         is_mac = /mac\s+os/i.test(navigator.userAgent),
  7551.         //Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
  7552.         shift_nums = {
  7553.             "`":"~",
  7554.             "1":"!",
  7555.             "2":"@",
  7556.             "3":"#",
  7557.             "4":"$",
  7558.             "5":"%",
  7559.             "6":"^",
  7560.             "7":"&",
  7561.             "8":"*",
  7562.             "9":"(",
  7563.             "0":")",
  7564.             "-":"_",
  7565.             "=":"+",
  7566.             ";":":",
  7567.             "'":"\"",
  7568.             ",":"<",
  7569.             ".":">",
  7570.             "/":"?",
  7571.             "\\":"|"
  7572.         },
  7573.        
  7574.         //Special Keys - and their codes
  7575.         special_keys = {
  7576.             'esc':27,
  7577.             'escape':27,
  7578.             'tab':9,
  7579.             'space':32,
  7580.             'return':13,
  7581.             'enter':13,
  7582.             'backspace':8,
  7583.  
  7584.             'scrolllock':145,
  7585.             'scroll_lock':145,
  7586.             'scroll':145,
  7587.             'capslock':20,
  7588.             'caps_lock':20,
  7589.             'caps':20,
  7590.             'numlock':144,
  7591.             'num_lock':144,
  7592.             'num':144,
  7593.            
  7594.             'pause':19,
  7595.             'break':19,
  7596.            
  7597.             'insert':45,
  7598.             'home':36,
  7599.             'delete':46,
  7600.             'end':35,
  7601.            
  7602.             'pageup':33,
  7603.             'page_up':33,
  7604.             'pu':33,
  7605.  
  7606.             'pagedown':34,
  7607.             'page_down':34,
  7608.             'pd':34,
  7609.            
  7610.             'plus': 187,
  7611.             'minus': 189,
  7612.  
  7613.             'left':37,
  7614.             'up':38,
  7615.             'right':39,
  7616.             'down':40,
  7617.  
  7618.             'f1':112,
  7619.             'f2':113,
  7620.             'f3':114,
  7621.             'f4':115,
  7622.             'f5':116,
  7623.             'f6':117,
  7624.             'f7':118,
  7625.             'f8':119,
  7626.             'f9':120,
  7627.             'f10':121,
  7628.             'f11':122,
  7629.             'f12':123
  7630.         },
  7631.        
  7632.         mac_char_map = {
  7633.             'ctrl': '⌃',
  7634.             'control': '⌃',
  7635.             'meta': '⌘',
  7636.             'shift': '⇧',
  7637.             'alt': '⌥',
  7638.             'enter': '⏎',
  7639.             'tab': '⇥',
  7640.             'left': '←',
  7641.             'right': '→',
  7642.             'up': '↑',
  7643.             'down': '↓'
  7644.         },
  7645.        
  7646.         pc_char_map = {
  7647.             'meta': 'Ctrl',
  7648.             'control': 'Ctrl',
  7649.             'left': '←',
  7650.             'right': '→',
  7651.             'up': '↑',
  7652.             'down': '↓'
  7653.         },
  7654.        
  7655.         MODIFIERS = {
  7656.             SHIFT: 1,
  7657.             CTRL:  2,
  7658.             ALT:   4,
  7659.             META:  8
  7660.         };
  7661.        
  7662.     /**
  7663.      * Makes first letter of string in uppercase
  7664.      * @param {String} str
  7665.      */
  7666.     function capitalize(str) {
  7667.         return str.charAt().toUpperCase() + str.substring(1);
  7668.     }
  7669.        
  7670.     return {
  7671.         /**
  7672.          * Compile keyboard combination for faster tests
  7673.          * @param {String|Object} combination
  7674.          */
  7675.         compile: function(combination) {
  7676.             if (typeof combination != 'string') //already compiled
  7677.                 return combination;
  7678.                
  7679.             var mask = 0,
  7680.                 keys = combination.toLowerCase().split('+'),
  7681.                 key,
  7682.                 k;
  7683.                
  7684.             for(var i = 0, il = keys.length; i < il; i++) {
  7685.                 k = keys[i];
  7686.                 // Due to stupid Opera bug I have to swap Ctrl and Meta keys
  7687.                 if (is_mac && is_opera) {
  7688.                     if (k == 'ctrl' || k == 'control')
  7689.                         k = 'meta';
  7690.                     else if (k == 'meta')
  7691.                         k = 'ctrl';
  7692.                 } else if (!is_mac && k == 'meta') {
  7693.                     k = 'ctrl';
  7694.                 }
  7695.                
  7696.                 //Modifiers
  7697.                 if(k == 'ctrl' || k == 'control')
  7698.                     mask |= MODIFIERS.CTRL;
  7699.                 else if (k == 'shift')
  7700.                     mask |= MODIFIERS.SHIFT;
  7701.                 else if (k == 'alt')
  7702.                     mask |= MODIFIERS.ALT;
  7703.                 else if (k == 'meta')
  7704.                     mask |= MODIFIERS.META;
  7705.                 else
  7706.                     key = k;
  7707.             }
  7708.            
  7709.             return {
  7710.                 mask: mask,
  7711.                 key: key
  7712.             };
  7713.         },
  7714.        
  7715.         /**
  7716.          * Test shortcut combination against event
  7717.          * @param {String} combination Keyboard shortcut
  7718.          * @param {Event} evt
  7719.          */
  7720.         test: function(combination, evt) {
  7721.             var mask = 0,
  7722.                 ccomb = this.compile(combination);
  7723.            
  7724.             if (evt.ctrlKey)  mask |= MODIFIERS.CTRL;
  7725.             if (evt.shiftKey) mask |= MODIFIERS.SHIFT;
  7726.             if (evt.altKey)   mask |= MODIFIERS.ALT;
  7727.             if (evt.metaKey)  mask |= MODIFIERS.META;
  7728.            
  7729.             var code = evt.keyCode ? evt.keyCode : evt.which,
  7730.                 character = String.fromCharCode(code).toLowerCase();
  7731.            
  7732.             // if mask doesn't match, no need to test further
  7733.             if (mask !== ccomb.mask) return false;
  7734.            
  7735.             if (ccomb.key.length > 1) { //If it is a special key
  7736.                 return special_keys[ccomb.key] == code;
  7737.             } else { //The special keys did not match
  7738.                 if(code == 188) character = ","; //If the user presses , when the type is onkeydown
  7739.                 if(code == 190) character = ".";
  7740.                 if(code == 191) character = "/";
  7741.                
  7742.                 if (character == ccomb.key) return true;
  7743.                 if (evt.shiftKey && shift_nums[character]) //Stupid Shift key bug created by using lowercase
  7744.                     return shift_nums[character] == ccomb.key;
  7745.             }
  7746.            
  7747.             return false;
  7748.         },
  7749.        
  7750.         /**
  7751.          * Format keystroke for better readability, considering current platform
  7752.          * mnemonics
  7753.          * @param {String} keystroke
  7754.          * @return {String}
  7755.          */
  7756.         format: function(keystroke) {
  7757.             var char_map = is_mac ? mac_char_map : pc_char_map,
  7758.                 glue = is_mac ? '' : '+',
  7759.                 keys = keystroke.toLowerCase().split('+'),
  7760.                 ar = [],
  7761.                 key;
  7762.                
  7763.             for (var i = 0; i < keys.length; i++) {
  7764.                 key = keys[i];
  7765.                 ar.push(key in char_map ? char_map[key] : capitalize(key));
  7766.             }
  7767.            
  7768.             return ar.join(glue);
  7769.         }
  7770.     };
  7771. })();/**
  7772.  * High-level editor interface which communicates with other editor (like
  7773.  * TinyMCE, CKEditor, etc.) or browser.
  7774.  * Before using any of editor's methods you should initialize it with
  7775.  * <code>editor.setContext(elem)</code> method and pass reference to
  7776.  * &lt;textarea&gt; element.
  7777.  * @example
  7778.  * var textarea = document.getElemenetsByTagName('textarea')[0];
  7779.  * zen_editor.setContext(textarea);
  7780.  * //now you are ready to use editor object
  7781.  * zen_editor.getSelectionRange()
  7782.  *
  7783.  * @author Sergey Chikuyonok ([email protected])
  7784.  * @link http://chikuyonok.ru
  7785.  *
  7786.  * @include "../../javascript/zen_coding.js"
  7787.  * @include "../codemirror/shortcut.js"
  7788.  */
  7789. var zen_editor = (function(){
  7790.     /** @param {Element} Source element */
  7791.     var target = null,
  7792.         /** Textual placeholder that identifies cursor position in pasted text */
  7793.         caret_placeholder = '|',
  7794.        
  7795.         default_options = {
  7796.             profile: 'xhtml',
  7797.             syntax: 'html',
  7798.             use_tab: false,
  7799.             pretty_break: false
  7800.         },
  7801.        
  7802.         keyboard_shortcuts = {},
  7803.        
  7804.         /** @type {default_options} Current options */
  7805.         options = null;
  7806.    
  7807.        
  7808.     // different browser uses different newlines, so we have to figure out
  7809.     // native browser newline and sanitize incoming text with them
  7810.     var tx = document.createElement('textarea');
  7811.     tx.value = '\n';
  7812.     zen_coding.setNewline(tx.value);
  7813.     tx = null;
  7814.    
  7815.     /**
  7816.      * Returns content of current target element
  7817.      */
  7818.     function getContent() {
  7819.         return target.value || '';
  7820.     }
  7821.    
  7822.     /**
  7823.      * Returns selection indexes from element
  7824.      */
  7825.     function getSelectionRange() {
  7826.         if ('selectionStart' in target) { // W3C's DOM
  7827.             var length = target.selectionEnd - target.selectionStart;
  7828.             return {
  7829.                 start: target.selectionStart,
  7830.                 end: target.selectionEnd
  7831.             };
  7832.         } else if (document.selection) { // IE
  7833.             target.focus();
  7834.      
  7835.             var range = document.selection.createRange();
  7836.            
  7837.             if (range === null) {
  7838.                 return {
  7839.                     start: 0,
  7840.                     end: getContent().length
  7841.                 };
  7842.             }
  7843.      
  7844.             var re = target.createTextRange();
  7845.             var rc = re.duplicate();
  7846.             re.moveToBookmark(range.getBookmark());
  7847.             rc.setEndPoint('EndToStart', re);
  7848.      
  7849.             return {
  7850.                 start: rc.text.length,
  7851.                 end: rc.text.length + range.text.length
  7852.             };
  7853.         } else {
  7854.             return null;
  7855.         }
  7856.     }
  7857.    
  7858.     /**
  7859.      * Creates text selection on target element
  7860.      * @param {Number} start
  7861.      * @param {Number} end
  7862.      */
  7863.     function createSelection(start, end) {
  7864.         // W3C's DOM
  7865.         if (typeof(end) == 'undefined')
  7866.             end = start;
  7867.            
  7868.         if ('setSelectionRange' in target) {
  7869.             target.setSelectionRange(start, end);
  7870.         } else if ('createTextRange' in target) {
  7871.             var t = target.createTextRange();
  7872.            
  7873.             t.collapse(true);
  7874.             var delta = zen_coding.splitByLines(getContent().substring(0, start)).length - 1;
  7875.            
  7876.             // IE has an issue with handling newlines while creating selection,
  7877.             // so we need to adjust start and end indexes
  7878.             end -= delta + zen_coding.splitByLines(getContent().substring(start, end)).length - 1;
  7879.             start -= delta;
  7880.            
  7881.             t.moveStart('character', start);
  7882.             t.moveEnd('character', end - start);
  7883.             t.select();
  7884.         }
  7885.     }
  7886.    
  7887.     /**
  7888.      * Find start and end index of text line for <code>from</code> index
  7889.      * @param {String} text
  7890.      * @param {Number} from
  7891.      */
  7892.     function findNewlineBounds(text, from) {
  7893.         var len = text.length,
  7894.             start = 0,
  7895.             end = len - 1;
  7896.        
  7897.         // search left
  7898.         for (var i = from - 1; i > 0; i--) {
  7899.             var ch = text.charAt(i);
  7900.             if (ch == '\n' || ch == '\r') {
  7901.                 start = i + 1;
  7902.                 break;
  7903.             }
  7904.         }
  7905.         // search right
  7906.         for (var j = from; j < len; j++) {
  7907.             var ch = text.charAt(j);
  7908.             if (ch == '\n' || ch == '\r') {
  7909.                 end = j;
  7910.                 break;
  7911.             }
  7912.         }
  7913.        
  7914.         return {start: start, end: end};
  7915.     }
  7916.    
  7917.     /**
  7918.      * Returns current caret position
  7919.      */
  7920.     function getCaretPos() {
  7921.         var selection = getSelectionRange();
  7922.         return selection ? selection.start : null;
  7923.     }
  7924.    
  7925.     /**
  7926.      * Returns whitrespace padding of string
  7927.      * @param {String} str String line
  7928.      * @return {String}
  7929.      */
  7930.     function getStringPadding(str) {
  7931.         return (str.match(/^(\s+)/) || [''])[0];
  7932.     }
  7933.    
  7934.     /**
  7935.      * Get Zen Coding options from element's class name
  7936.      */
  7937.     function getOptionsFromContext() {
  7938.         var param_str = target.className || '',
  7939.             re_param = /\bzc\-(\w+)\-(\w+)/g,
  7940.             m,
  7941.             result = copyOptions(options);
  7942.            
  7943.         while ( (m = re_param.exec(param_str)) ) {
  7944.             var key = m[1].toLowerCase(),
  7945.                 value = m[2].toLowerCase();
  7946.            
  7947.             if (value == 'true' || value == 'yes' || value == '1')
  7948.                 value = true;
  7949.             else if (value == 'false' || value == 'no' || value == '0')
  7950.                 value = false;
  7951.                
  7952.             result[key] = value;
  7953.         }
  7954.        
  7955.         return result;
  7956.     }
  7957.    
  7958.     function getOption(name) {
  7959.         return getOptionsFromContext()[name];
  7960.     }
  7961.    
  7962.     function copyOptions(opt) {
  7963.         opt = opt || {};
  7964.         var result = {};
  7965.         for (var p in default_options) if (default_options.hasOwnProperty(p)) {
  7966.             result[p] = (p in opt) ? opt[p] : default_options[p];
  7967.         }
  7968.        
  7969.         return result;
  7970.     }
  7971.    
  7972.     /**
  7973.      * Handle tab-stops (like $1 or ${1:label}) inside text: find first tab-stop,
  7974.      * marks it as selection, remove the rest. If tab-stop wasn't found, search
  7975.      * for caret placeholder and use it as selection
  7976.      * @param {String} text
  7977.      * @return {Array} Array with new text and selection indexes (['...', -1,-1]
  7978.      * if there's no selection)
  7979.      */
  7980.     function handleTabStops(text) {
  7981.         var selection_len = 0,
  7982.             caret_pos = text.indexOf(caret_placeholder),
  7983.             placeholders = {};
  7984.            
  7985.         // find caret position
  7986.         if (caret_pos != -1) {
  7987.             text = text.split(caret_placeholder).join('');
  7988.         } else {
  7989.             caret_pos = text.length;
  7990.         }
  7991.        
  7992.         text = zen_coding.processTextBeforePaste(text,
  7993.             function(ch){ return ch; },
  7994.             function(i, num, val) {
  7995.                 if (val) placeholders[num] = val;
  7996.                
  7997.                 if (i < caret_pos) {
  7998.                     caret_pos = i;
  7999.                     if (val)
  8000.                         selection_len = val.length;
  8001.                 }
  8002.                    
  8003.                 return placeholders[num] || '';
  8004.             });
  8005.        
  8006.         return [text, caret_pos, caret_pos + selection_len];
  8007.     }
  8008.    
  8009.     /**
  8010.      * Returns normalized action name
  8011.      * @param {String} name Action name (like 'Expand Abbreviation')
  8012.      * @return Normalized name for coding (like 'expand_abbreviation')
  8013.      */
  8014.     function normalizeActionName(name) {
  8015.         return name
  8016.             .replace(/(^\s+|\s+$)/g, '') // remove trailing spaces
  8017.             .replace(/[\s\\\/]+/g, '_')
  8018.             .replace(/\./g, '')
  8019.             .toLowerCase();
  8020.     }
  8021.    
  8022.     /**
  8023.      * Bind shortcut to Zen Coding action
  8024.      * @param {String} keystroke
  8025.      * @param {String} label
  8026.      * @param {String} action_name
  8027.      */
  8028.     function addShortcut(keystroke, label, action_name) {
  8029.         keyboard_shortcuts[keystroke.toLowerCase()] = {
  8030.             compiled: shortcut.compile(keystroke),
  8031.             label: label,
  8032.             action: normalizeActionName(action_name || label)
  8033.         };
  8034.     }
  8035.    
  8036.     function stopEvent(evt) {
  8037.         evt.cancelBubble = true;
  8038.         evt.returnValue = false;
  8039.  
  8040.         if (evt.stopPropagation) {
  8041.             evt.stopPropagation();
  8042.             evt.preventDefault();
  8043.         }
  8044.     }
  8045.    
  8046.     /**
  8047.      * Runs actions called by user
  8048.      * @param {Event} evt Event object
  8049.      */
  8050.     function runAction(evt) {
  8051.         evt = evt || window.event;
  8052.        
  8053.         /** @type {Element} */
  8054.         var target_elem = evt.target || evt.srcElement,
  8055.             key_code = evt.keyCode || evt.which,
  8056.             action_name;
  8057.            
  8058.         if (target_elem && target_elem.nodeType == 1 && target_elem.nodeName == 'TEXTAREA') {
  8059.             zen_editor.setContext(target_elem);
  8060.            
  8061.             // test if occured event corresponds to one of the defined shortcut
  8062.             var sh, name, result;
  8063.             for (var s in keyboard_shortcuts) if (keyboard_shortcuts.hasOwnProperty(s)) {
  8064.                 sh = keyboard_shortcuts[s];
  8065.                 if (shortcut.test(sh.compiled, evt)) {
  8066.                     action_name = sh.action;
  8067.                     switch (action_name) {
  8068.                         case 'expand_abbreviation':
  8069.                             if (key_code == 9) {
  8070.                                 if (getOption('use_tab'))
  8071.                                     action_name = 'expand_abbreviation_with_tab';
  8072.                                 else
  8073.                                     // user pressed Tab key but it's forbidden in
  8074.                                     // Zen Coding: bubble up event
  8075.                                     return true;
  8076.                             }
  8077.                             break;
  8078.                         case 'insert_formatted_line_break':
  8079.                             if (key_code == 13 && !getOption('pretty_break')) {
  8080.                                 // user pressed Enter but it's forbidden in
  8081.                                 // Zen Coding: bubble up event
  8082.                                 return true;
  8083.                             }
  8084.                             break;
  8085.                     }
  8086.                    
  8087.                     zen_coding.runAction(action_name, [zen_editor]);
  8088.                     stopEvent(evt);
  8089.                     return false;
  8090.                 }
  8091.             }
  8092.         }
  8093.            
  8094.         // allow event bubbling
  8095.         return true;
  8096.     }
  8097.    
  8098.     var doc = document,
  8099.         key_event = window.opera ? 'keypress' : 'keydown';
  8100.        
  8101.     //Attach the function with the event
  8102.     if (doc.addEventListener) doc.addEventListener(key_event, runAction, false);
  8103.     else if(doc.attachEvent) doc.attachEvent('on' + key_event, runAction);
  8104.     else doc['on' + key_event] = func;
  8105.    
  8106.     options = copyOptions();
  8107.    
  8108.     addShortcut('Meta+E', 'Expand Abbreviation');
  8109.     addShortcut('Tab', 'Expand Abbreviation');
  8110.     addShortcut('Meta+D', 'Balance Tag Outward', 'match_pair_outward');
  8111.     addShortcut('Shift+Meta+D', 'Balance Tag inward', 'match_pair_inward');
  8112.     addShortcut('Shift+Meta+A', 'Wrap with Abbreviation');
  8113.     addShortcut('Ctrl+Alt+RIGHT', 'Next Edit Point');
  8114.     addShortcut('Ctrl+Alt+LEFT', 'Previous Edit Point', 'prev_edit_point');
  8115.     addShortcut('Meta+L', 'Select Line');
  8116.     addShortcut('Meta+Shift+M', 'Merge Lines');
  8117.     addShortcut('Meta+/', 'Toggle Comment');
  8118.     addShortcut('Meta+J', 'Split/Join Tag');
  8119.     addShortcut('Meta+K', 'Remove Tag');
  8120.     addShortcut('Ctrl+I', 'Update image size');
  8121.    
  8122.     addShortcut('Enter', 'Insert Formatted Line Break');
  8123.    
  8124.     // v0.7
  8125.     addShortcut('Meta+Y', 'Evaluate Math Expression');
  8126.     addShortcut('Ctrl+UP', 'Increment number by 1');
  8127.     addShortcut('Ctrl+DOWN', 'Decrement number by 1');
  8128.     addShortcut('Alt+UP', 'Increment number by 0.1');
  8129.     addShortcut('Alt+DOWN', 'Decrement number by 0.1');
  8130.     addShortcut('Ctrl+Alt+UP', 'Increment number by 10');
  8131.     addShortcut('Ctrl+Alt+DOWN', 'Decrement number by 10');
  8132.    
  8133.     addShortcut('Meta+.', 'Select Next Item');
  8134.     addShortcut('Meta+,', 'Select Previous Item');
  8135.     addShortcut('Meta+Shift+B', 'Reflect CSS Value');
  8136.    
  8137.     return {
  8138.         setContext: function(elem) {
  8139.             target = elem;
  8140.             caret_placeholder = zen_coding.getCaretPlaceholder();
  8141.         },
  8142.        
  8143.         getSelectionRange: getSelectionRange,
  8144.         createSelection: createSelection,
  8145.        
  8146.         /**
  8147.          * Returns current line's start and end indexes
  8148.          */
  8149.         getCurrentLineRange: function() {
  8150.             var caret_pos = getCaretPos(),
  8151.                 content = getContent();
  8152.             if (caret_pos === null) return null;
  8153.            
  8154.             return findNewlineBounds(content, caret_pos);
  8155.         },
  8156.        
  8157.         /**
  8158.          * Returns current caret position
  8159.          * @return {Number}
  8160.          */
  8161.         getCaretPos: getCaretPos,
  8162.        
  8163.         /**
  8164.          * Set new caret position
  8165.          * @param {Number} pos Caret position
  8166.          */
  8167.         setCaretPos: function(pos) {
  8168.             createSelection(pos);
  8169.         },
  8170.        
  8171.         /**
  8172.          * Returns content of current line
  8173.          * @return {String}
  8174.          */
  8175.         getCurrentLine: function() {
  8176.             var range = this.getCurrentLineRange();
  8177.             return range.start < range.end ? this.getContent().substring(range.start, range.end) : '';
  8178.         },
  8179.        
  8180.         /**
  8181.          * Replace editor's content or it's part (from <code>start</code> to
  8182.          * <code>end</code> index). If <code>value</code> contains
  8183.          * <code>caret_placeholder</code>, the editor will put caret into
  8184.          * this position. If you skip <code>start</code> and <code>end</code>
  8185.          * arguments, the whole target's content will be replaced with
  8186.          * <code>value</code>.
  8187.          *
  8188.          * If you pass <code>start</code> argument only,
  8189.          * the <code>value</code> will be placed at <code>start</code> string
  8190.          * index of current content.
  8191.          *
  8192.          * If you pass <code>start</code> and <code>end</code> arguments,
  8193.          * the corresponding substring of current target's content will be
  8194.          * replaced with <code>value</code>.
  8195.          * @param {String} value Content you want to paste
  8196.          * @param {Number} [start] Start index of editor's content
  8197.          * @param {Number} [end] End index of editor's content
  8198.          * @param {Boolean} [no_indent] Do not auto indent <code>value</code>
  8199.          */
  8200.         replaceContent: function(value, start, end, no_indent) {
  8201.             var content = getContent(),
  8202.                 caret_pos = getCaretPos(),
  8203.                 has_start = typeof(start) !== 'undefined',
  8204.                 has_end = typeof(end) !== 'undefined';
  8205.                
  8206.             // indent new value
  8207.             if (!no_indent)
  8208.                 value = zen_coding.padString(value, getStringPadding(this.getCurrentLine()));
  8209.            
  8210.             // find new caret position
  8211.             var tabstop_res = handleTabStops(value);
  8212.             value = tabstop_res[0];
  8213.            
  8214.             start = start || 0;
  8215.             if (tabstop_res[1] !== -1) {
  8216.                 tabstop_res[1] += start;
  8217.                 tabstop_res[2] += start;
  8218.             } else {
  8219.                 tabstop_res[1] = tabstop_res[2] = value.length + start;
  8220.             }
  8221.            
  8222.             try {
  8223.                 if (has_start && has_end) {
  8224.                     content = content.substring(0, start) + value + content.substring(end);
  8225.                 } else if (has_start) {
  8226.                     content = content.substring(0, start) + value + content.substring(start);
  8227.                 }
  8228.                
  8229.                 target.value = content;
  8230.                 this.createSelection(tabstop_res[1], tabstop_res[2]);
  8231.             } catch(e){}
  8232.         },
  8233.        
  8234.         /**
  8235.          * Returns editor's content
  8236.          * @return {String}
  8237.          */
  8238.         getContent: getContent,
  8239.        
  8240.         /**
  8241.          * Returns current editor's syntax mode
  8242.          * @return {String}
  8243.          */
  8244.         getSyntax: function(){
  8245.             var syntax = this.getOption('syntax'),
  8246.                 caret_pos = this.getCaretPos();
  8247.                
  8248.             if (!zen_resources.hasSyntax(syntax))
  8249.                 syntax = 'html';
  8250.                
  8251.             if (syntax == 'html') {
  8252.                 // get the context tag
  8253.                 var pair = zen_coding.html_matcher.getTags(this.getContent(), caret_pos);
  8254.                 if (pair && pair[0] && pair[0].type == 'tag' && pair[0].name.toLowerCase() == 'style') {
  8255.                     // check that we're actually inside the tag
  8256.                     if (pair[0].end <= caret_pos && pair[1].start >= caret_pos)
  8257.                         syntax = 'css';
  8258.                 }
  8259.             }
  8260.             return syntax;
  8261.         },
  8262.        
  8263.         /**
  8264.          * Returns current output profile name (@see zen_coding#setupProfile)
  8265.          * @return {String}
  8266.          */
  8267.         getProfileName: function() {
  8268.             return this.getOption('profile');
  8269.         },
  8270.        
  8271.         /**
  8272.          * Ask user to enter something
  8273.          * @param {String} title Dialog title
  8274.          * @return {String} Entered data
  8275.          * @since 0.65
  8276.          */
  8277.         prompt: function(title) {
  8278.             return prompt(title);
  8279.         },
  8280.        
  8281.         /**
  8282.          * Returns current selection
  8283.          * @return {String}
  8284.          * @since 0.65
  8285.          */
  8286.         getSelection: function() {
  8287.             var sel = getSelectionRange();
  8288.             if (sel) {
  8289.                 try {
  8290.                     return getContent().substring(sel.start, sel.end);
  8291.                 } catch(e) {}
  8292.             }
  8293.            
  8294.             return '';
  8295.         },
  8296.        
  8297.         /**
  8298.          * Returns current editor's file path
  8299.          * @return {String}
  8300.          * @since 0.65
  8301.          */
  8302.         getFilePath: function() {
  8303.             return location.href;
  8304.         },
  8305.        
  8306.         /**
  8307.          * Custom editor method: set default options (like syntax, tabs,
  8308.          * etc.) for editor
  8309.          * @param {Object} opt
  8310.          */
  8311.         setOptions: function(opt) {
  8312.             options = copyOptions(opt);
  8313.         },
  8314.        
  8315.         /**
  8316.          * Custom method: returns current context's option value
  8317.          * @param {String} name Option name
  8318.          * @return {String}
  8319.          */
  8320.         getOption: getOption,
  8321.        
  8322.         addShortcut: addShortcut,
  8323.        
  8324.         /**
  8325.          * Removes shortcut binding
  8326.          * @param {String} keystroke
  8327.          */
  8328.         unbindShortcut: function(keystroke) {
  8329.             keystroke = keystroke.toLowerCase();
  8330.             if (keystroke in keyboard_shortcuts)
  8331.                 delete keyboard_shortcuts[keystroke];
  8332.         },
  8333.                
  8334.         /**
  8335.          * Returns array of binded actions and their keystrokes
  8336.          * @return {Array}
  8337.          */
  8338.         getShortcuts: function() {
  8339.             var result = [], lp;
  8340.            
  8341.             for (var p in keyboard_shortcuts) if (keyboard_shortcuts.hasOwnProperty(p)) {
  8342.                 lp = p.toLowerCase();
  8343.                
  8344.                 // skip some internal bindings
  8345.                 if (lp == 'tab' || lp == 'enter')
  8346.                     continue;
  8347.                    
  8348.                 result.push({
  8349.                     keystroke: shortcut.format(p),
  8350.                     compiled: keyboard_shortcuts[p].compiled,
  8351.                     label: keyboard_shortcuts[p].label,
  8352.                     action: keyboard_shortcuts[p].action
  8353.                 });
  8354.             }
  8355.            
  8356.             return result;
  8357.         },
  8358.        
  8359.         getInfo: function() {
  8360.             var message = 'This textareas on this page are powered by Zen Coding project: ' +
  8361.                     'a set of tools for fast HTML coding.\n\n' +
  8362.                     'Available shortcuts:\n';
  8363.                    
  8364.             var sh = this.getShortcuts(),
  8365.                 actions = [];
  8366.                
  8367.             for (var i = 0; i < sh.length; i++) {
  8368.                 actions.push(sh[i].keystroke + ' — ' + sh[i].label)
  8369.             }
  8370.            
  8371.             message += actions.join('\n') + '\n\n';
  8372.             message += 'More info on http://code.google.com/p/zen-coding/';
  8373.            
  8374.             return message;
  8375.         },
  8376.        
  8377.         /**
  8378.          * Show info window about Zen Coding
  8379.          */
  8380.         showInfo: function() {
  8381.             alert(this.getInfo());
  8382.         },
  8383.        
  8384.         /**
  8385.          * Setup editor. Pass object with values defined in
  8386.          * <code>default_options</code>
  8387.          */
  8388.         setup: function(opt) {
  8389.             this.setOptions(opt);
  8390.         },
  8391.        
  8392.         // expose some core Zen Coding objects
  8393.        
  8394.         /**
  8395.          * Returns core Zen Codind object
  8396.          */
  8397.         getCore: function() {
  8398.             return zen_coding;
  8399.         },
  8400.        
  8401.         /**
  8402.          * Returns Zen Coding resource manager. You can add new snippets and
  8403.          * abbreviations with this manager, as well as modify ones.<br><br>
  8404.          *
  8405.          * Zen Coding stores settings in two separate vocabularies: 'system'
  8406.          * and 'user'. The ultimate solution to add new abbreviations and
  8407.          * snippets is to setup a 'user' vocabulary, like this:
  8408.          *
  8409.          * @example
  8410.          * var my_settings = {
  8411.          *  html: {
  8412.          *      abbreviations: {
  8413.          *          'tag': '<div class="mytag">'
  8414.          *      }
  8415.          *  }
  8416.          * };
  8417.          * zen_editor.getResourceManager().setVocabulary(my_settings, 'user')
  8418.          *
  8419.          * @see zen_resources.js
  8420.          */
  8421.         getResourceManager: function() {
  8422.             return zen_resources;
  8423.         }
  8424.     }
  8425. })();
  8426.  return zen_editor;
  8427. })();
Add Comment
Please, Sign In to add comment