pax_global_header00006660000000000000000000000064136113636560014524gustar00rootroot0000000000000052 comment=115a8b9c1b6d5ac3397b68e1db9d283439a92510 go-org-1.0.0/000077500000000000000000000000001361136365600127145ustar00rootroot00000000000000go-org-1.0.0/.gitignore000066400000000000000000000000461361136365600147040ustar00rootroot00000000000000/gh-pages/ /go-org /fuzz /org-fuzz.zipgo-org-1.0.0/.travis.yml000066400000000000000000000004301361136365600150220ustar00rootroot00000000000000language: go go: "1.x" script: - make test - make generate-gh-pages deploy: provider: pages github-token: $GITHUB_TOKEN # From travis-ci.org repository settings local-dir: gh-pages target-branch: gh-pages skip-cleanup: true verbose: true on: branch: master go-org-1.0.0/LICENSE000066400000000000000000000020601361136365600137170ustar00rootroot00000000000000MIT License Copyright (c) 2018 Niklas Fasching Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-org-1.0.0/Makefile000066400000000000000000000015111361136365600143520ustar00rootroot00000000000000.PHONY: default default: test .PHONY: install install: go get -t ./... .PHONY: build build: install go build . .PHONY: test test: install go test ./... -v .PHONY: setup setup: git config core.hooksPath etc/githooks .PHONY: preview preview: generate xdg-open gh-pages/index.html .PHONY: generate generate: generate-gh-pages generate-fixtures .PHONY: generate-gh-pages generate-gh-pages: build ./etc/generate-gh-pages .PHONY: generate-fixtures generate-fixtures: build ./etc/generate-fixtures .PHONY: fuzz fuzz: build @echo also see "http://lcamtuf.coredump.cx/afl/README.txt" go get github.com/dvyukov/go-fuzz/go-fuzz go get github.com/dvyukov/go-fuzz/go-fuzz-build mkdir -p fuzz fuzz/corpus cp org/testdata/*.org fuzz/corpus go-fuzz-build github.com/niklasfasching/go-org/org go-fuzz -bin=./org-fuzz.zip -workdir=fuzz go-org-1.0.0/README.org000066400000000000000000000035271361136365600143710ustar00rootroot00000000000000* go-org [[https://travis-ci.org/niklasfasching/go-org.svg?branch=master]] An Org mode parser in go. Take a look at [[https://niklasfasching.github.io/go-org/][github pages]] for some examples and an online org -> html demo (wasm based). [[https://raw.githubusercontent.com/niklasfasching/go-org/master/etc/example.png]] Please note that the goal for the html export is to produce sensible html output, not to exactly reproduce output the output of =org-html-export=. * development 1. =make setup install= 2. change things 3. =make preview= (regenerates fixtures & shows output in a browser) in general, have a look at the Makefile - it's short enough. * not yet implemented ** deadlines and scheduling see https://orgmode.org/manual/Deadlines-and-scheduling.html ** more types of links see https://orgmode.org/manual/External-links.html & https://orgmode.org/manual/Internal-links.html - radio target <<>> - link target: <> - link: [[go-org]] - link to headline - links with image as description - MyTarget <- this will automatically become a link - not sure i want this... * resources - test files - [[https://raw.githubusercontent.com/kaushalmodi/ox-hugo/master/test/site/content-org/all-posts.org][ox-hugo all-posts.org]] - https://ox-hugo.scripter.co/doc/examples/ - https://orgmode.org/manual/ - https://orgmode.org/worg/dev/org-syntax.html - https://code.orgmode.org/bzg/org-mode/src/master/lisp/org.el - https://code.orgmode.org/bzg/org-mode/src/master/lisp/org-element.el - mostly those & ox-html.el, but yeah, all of [[https://code.orgmode.org/bzg/org-mode/src/master/lisp/]] - existing Org mode implementations: [[https://github.com/emacsmirror/org][org]], [[https://github.com/bdewey/org-ruby/blob/master/spec/html_examples][org-ruby]], [[https://github.com/chaseadamsio/goorgeous/][goorgeous]], [[https://github.com/jgm/pandoc/][pandoc]] go-org-1.0.0/etc/000077500000000000000000000000001361136365600134675ustar00rootroot00000000000000go-org-1.0.0/etc/_goorgeous.go000066400000000000000000000013311361136365600161640ustar00rootroot00000000000000package main import ( "io/ioutil" "log" "os" "github.com/chaseadamsio/goorgeous" "github.com/russross/blackfriday" ) func main() { defer func() { if err := recover(); err != nil { log.Print(err) } }() log.SetFlags(0) log.SetOutput(os.Stdout) path := os.Args[1] in, err := ioutil.ReadFile(path) if err != nil { log.Fatal(err) } flags := blackfriday.HTML_USE_XHTML flags |= blackfriday.LIST_ITEM_BEGINNING_OF_LIST flags |= blackfriday.HTML_FOOTNOTE_RETURN_LINKS parameters := blackfriday.HtmlRendererParameters{} parameters.FootnoteReturnLinkContents = "" renderer := blackfriday.HtmlRendererWithParameters(flags, "", "", parameters) log.Print(string(goorgeous.Org(in, renderer))) } go-org-1.0.0/etc/_wasm.go000066400000000000000000000011651361136365600151270ustar00rootroot00000000000000package main import ( "fmt" "strings" "syscall/js" "github.com/niklasfasching/go-org/org" ) func main() { js.Global().Call("initialized") doc := js.Global().Get("document") in, out := doc.Call("getElementById", "input"), doc.Call("getElementById", "output") js.Global().Set("run", js.FuncOf(func(js.Value, []js.Value) interface{} { in := strings.NewReader(in.Get("value").String()) html, err := org.New().Parse(in, "").Write(org.NewHTMLWriter()) if err != nil { out.Set("innerHTML", fmt.Sprintf("
%s
", err)) } else { out.Set("innerHTML", html) } return nil })) select {} // stay alive } go-org-1.0.0/etc/example.png000066400000000000000000005554051361136365600156460ustar00rootroot00000000000000PNG  IHDRCMsBIT|d IDATxw|[^`y4K,MCP=Kf]__풤77s=7t$4M=zt貗_~Y!Izx }ɗ|eO$[苾h|HmF>{RH,ґ#G.{{WvovoV/Izwx}~e$;^]vUUIm:|eoI]EY5//#?#w{wg|g ]gg1I{{u{enRV$9n޼${>S?u??OOJ[ɟ<?Ӓ}3<3tٍ7T%ITJI>.=$>qqC~~AAAzk_;ڵkj4t:١ˮ^__$}>?z//K>C?T9tٯWW%I~z貅/Ir]W333C]rEk&I}؇}__ooH>>J!2t//~$I1zj˗/+IyҥKmI~?.y$?~gV/IOD5ŋj6L&.\??$}'}}g貟I>S>E^5tOOOO$IiWΟ?V%Ifܹs?SIҧ݇.Q$3?3C/Rٟzw~^z%a(Irٳ+I|oFyyz˾Ow'I/۾egΜQݖ$y%ɡN>$}~z??JXoo9;;ϒ//ի^N:N#I* J$Ctى' ]oI~ .%I__X,6t1qܷYNN\QFeQFeQFeQŢ(z /O5&d Oq Y&4P @)zReƄ=6q!3CH$CH(ƍجxڭ1!cOxc\PPR,hqcf(id86+v^;jVRLÐZ,QP-K2 Ci+Uۛծz2bJ[ݰb&-43LAg~Y9בe22ņ‡ּS&ݿALZ=k(K헕M2ҵE5U6TʹJ٦ Ð夕-n밓:zƚ}Fo헕RMCaʲSruXNX𢡊_SO=fҥ.滕:tT,*rO]vuL +n12*UJYLU*當ML2S# fm]F(M+m,,2T]kG~>--dduZ3z|U3Y7)>&VZCB?9[Jj1B9ť(9{:of4y4: sHJDD29v%ZU ѕ˕oTtҚKG&[.Ds E͡ڏ/y8{9:;{ztAlttW<0t@#˖tӆӶޟt8Ihd'^>w(+](.ESǮ;1ⳚZ5pNJXu)")]Rj=+k{^V4/9K^(7(Z׃c\v[RQ12Sr?KRVʑ$Cc+.Cӟ;jڶ,unoT3A[cBƞdǸ=C \gmi2%uZIjRܑ^R\5OJz9=dZM%9nZk`(_AWЖ錼Ki,\FbUSR^ƕHVS;bn-1Ãz?/tik Cn砷 izAj0]TޘLƀ3$y=IC4% ے'(.۱ONG=eY3emk*IgαgrfrQݕ3Otfu/ʔʚku^xrJtZ^6|6cX[gi?׽+a JA|jN$fG Ce&4{4/w)֯&سs5=u _~˿xF  %D'lDŽ4qᾜju {~fH,gNG^ʔeťpi?uj/-jڛ/:'->v5-+.u,Obns,?U":gt&t&_R6s\ ՒrMEe o#:J1:2Wg)J+Psqwr,ג(h4SR=TJhj53%I+גzACٚPK+[͡rGe;-ť^[ɿY X^yӐlƄ=6qHP-;TO~pRXխ1?hR=].U_ E7 OYw눦sLJ=y%m5_3 ӊ/W1WQs}RRL)(;!(Thvj*ZۨvJ;q)ovjTjovC'%lml[zj}_߿גB+౶/nNɗtjY\P(t<&vWHC3/Yϑio&4W.֞)GTϜQrF #5ĵp 5UW5VY3nJ!uM5[\=VEzmJݶZͦnYTuLETNʞk)H4U-\:Lesj?S&SiKF7TXЭŸdMbKSaFiǔ!ʻ/d:Hg38sFsYWiH zpyw4kzw-$j9fe+RHqlVm0\ϫ;ٲ*cx)O5_i(5,9*D* Ų~]-UrH5J*U!vS&SvanI~UTVTّaZWG Y2k7zFgejt *PORc2b V˩ToiF[XLfH-2~̐ar܌ _+oASu'֋zM,XԆ[N-##J=s~QRO_Jb1ڙ[FVr_f /e4JgKj,Ww,ǖ*[^]a2RGt.T:2H8b qbx:eyhB պuCO^~f#/{ե(/-JĤRnJ: .*urggU-NFO'N[2O*Y]:^\'œL2N/j^SP=l)FS -*ܔnK:~jF֚pFXT\={Oh2kJvEZZF^,92]vGJLjM2 G?i=r9Uӿ[!mC='WZWr2RrZ7ʴRLҔnƭS17(rvL&lWxJKjC5@Vֹ9MHZ\Vyͺ um1ɹsj[  5ZMHb({)4T坸_TJ'Ϊ67j-sr[_'3t%lj4|aiwG9[[G ~"49 fIt륜kZ541sUm վyNsqݻ\|#V\SV\jG`[ jg=lKm#ݒ|9澆U8J{R|VZIRsIwM?|< T.]֢THk.3EgעFǘIJ9emti8YU*e8YR޹6ݾʞ^ -(K-G67rK jmm\`˙v!P7ֳbNzg:~}\v2}RN\ROr')fP2鸤EA1M*s7[ASmIqǓ1nVnrcyIq7]MǶxԬqKRU37\%IU:.Z1.a*.i11 my yeR봓W -a7 ]eKvWMF1Ҟ/ž.`o֘p/ƞryc{eۃc>H"jzr&\lql-uPmim5(5`ưRORv$f%Rv\/[w]ee %IPҚoO?W@=Q'I-5CI;WZ[gqyٜOnڑ 2mY Ðԓe&/c$Ǹpd(¦nܸ |&07YhXىNG&㙦 .$j`£ݎ:]iƣGݸߑ1FVV5Bx9]<Oj(/(q$ wجM]l߾\&&a4#Ons^bҚmqtnxl8oVO=prtPwO7.g^-+qW1R2%rɵ[c½{lz,JӻcLЙZlc/kC@4d.MvY;tzk񢑙vj~c'RrŊA7u䜦ҽE曻xvǸ,) ݗg6./պN7T+ۘ IDATWVʉKlm [$t67sН3K{unm/:N/lGI\l*h ݨq#PS4zjvNFJ ح1^=3.\% I٢W\ۻ^FUG8f\%kڮ$%zzS)%% 6jj,n,RTi V2J{9[&%j`,M{W{ZU/ ڤ)dY;ڻX_[4q!3CX%}u:EQ$$3tGRe͌NPVД9,E&$ݫP)\6Uʫ H$nN I+ke劁 휊{:ϪNu6 /e_NZTsU΄jgp͗GXx򹊚@岯$;~0k0fjm;i0ffX_d(Q))gliKSEͧ{T;dO^z;%sӲًw8rm^ W9MxR=R%J5.c&2M'{S+sv,9=k M$ٹG׿ZyrӶWN/)9SRm>`)W*PRsYƲ]+7ȲEŧ2^,*xYyVq䖶6ޠN'zWɾn='4e>ɨ(IwTV[`xR fn ;C2mh6Rʝ \YF?SLh^飇2ju9hI掝UЬ+l}f)h)Mhr=qI~-+4%]7u ]1:tD4\m=f)S ԼpBgdvZ|YA+kzNG^U/lH\qpr798:@ VWfꐎfP1#HVؑ,kNT2ӽ0T״V~j,.jOO˻z;c1.EQ!WF:J2'8ƅ @" @bP3CH#%CDZY)[cBƞdǸ$XE^PHqlV x<֘'<1.df(d()EQA134R2tO5&d Oq  @" nT1ŌJ_d{^?߫geb9EC]V5r+ƆMtժ9XLR)=Zt :{́SW֌)sT^1E?<&vkLHZeest'œr,k#1.df({(]uM%ww9E5 'f(Sn3ǜWVMlZ]m(gd= 2 W;=a6lI` VIi#S+)HRXVڈm_Y=IC6U-dM V:ST5ͻLnE:jV RL#aNgTvX)#g9X,&dc1y_Hܧ[Ɉԩ[g}U$#rr] J-Psd5|Fn^K93dZ^^F8Z* bVz C(};Uy7피|EA{6l[iUyJٖL#&ô= \:ͪYw%~ˑ-m^nKRN^ʖe,#7[P]#+soQ!0e<+UVT&KgUV۵{G켗FqEOG'o_9:I/:ftz6IOD3L4=5%HR=݌(nGEQb294N_tXhl4?gbP477}P+ѱ+M$&Cgo}sѡ"gӗESqEhzz*k)ѹHD415MO&RgWbd$ţ77~l,)8zegg^(0jEKѱCd<'fCss܉(x$ţcӉ~MM?%s̊}cXttxr2&MD3'Ghnv*JJf碹$y8t?9G}WNDssɥ6^#ٛQ ^=6I&N& Kg|%::_󬙙hjfNm̓3HJD]M׾swwhnBޡhvv:Lģ0{=}"tPw4ꝏG'7")JLNGT2+͜<%Iчث\ף=j `7l!z\?q417Es܅\>έ5х~#1s,rwEZrbNd4w/^O,'_хU}nn=7C$phf$X{ѥÉHG3*>nGmR$Z}T*_Uy.)\W[uR%Ur~VFd赎JEUe&$Z X^L{Jǥ^Wn Swrt]0 ,'ž&A\v,L[&IvDZw|mV/jlϮa(ɑJ xr@')4 5(t/ v5#`8rAW2LIZ=|dǥ[;=;t+qs#%CqxL֘p/ƞ qbeU*䊪^SI܌gLjZ;tCH1x6ggdђܔ$U:jAPi*h)K XeMk{Vv`;\Wrͥ5mAA0_'>;jE)qHSb45)=*f*/(9s472% r[u:$iQw-7nGZUsw)v}JN fn b933Tj/~kJy+:/ԥsJH.߰ݎcRN[w-'LW^*.  %;^ZnRu@~S <0 qxͻwiwˑ>lzVFy/![:k -*)/qӽߣ_QZOOiQW1mnDҹΦI|4e%joQҬ^g Yf->+maK2ciP{Q4q!3CXR(6=6??{ltjfWO-W!8)jWkMkZ͖R)?,'@8鴒 h4IewB]a?ekeg2岚POz]NDDF9wNu[dvm0i.O'T80V5fme9rpYSBZvZFN9TkۆV?Ai;-NJK)lOi5~[5jϑ `I&х 433D"!?[ʕ+r]wU/ jl23Dz>eiwRqIl`X(Tj<Ɣ*~Cߒ)Ut\j~SJ6;Ωzؗm Tk˻nj52RYW_"w2Bw{\qPu,XfHJ%$ [֍VieܤJMv*"N[ί̘n{F{]N奋lϓtj0WBkM$Mx6(R:mKQkle7]2j fn jd:($CV]/R>WQs}R\%_K^njLFVS+hM93IIt>U9X`Wal]~ԝ&vPQ5X_23-7W/+/=\݇2qCXUHJxy.PzYZܔԻJEM)KX{ߣJP]ej23K uW-\uǶbn^-3VbAqwc^|]PJ6S׮td:9[ZwČw_R*T\ҍ `Ւ]9j]%ww !q  b)W*PRsYƲ]+7ȲEŧ2*Q4QwlGni4t@uʕ89ջd*StBwY=eN%?zVY3\st_]jg_Sѹ0~Z8}年aؖfBQ) K,RH*_i؎X)Jrj01UDF>C3, C7gJZOϯJWr\<2 3%FF޸Z/Ti8躉mۘ?1Je~'^dH$DYbD"H$D"1ywŸ h>j+GT"#vZg>3*M28.a;vz2kC_ӹP[n1w"޿h4MvZeGv:{̉c;)p-fYrk\^VT\C>tTB]! sԅd&^D#?&1)T+ W:>F v_FZ>*6 zțuz&ͨjxT]#PZKK+jOF-ЈeIi=ni.Ͱgd_xetAV,ڥuQ,7DM7i첹AW{K%j|5 w#:m ϩ2hb=Du8ju i,+>)D"HUƄbTD"H$DsPS"y4}_rCʓv֟WscQ}D"H$U4D"H$D"H$'@\$ _x~޶VoWh<,:[e 5,H~}H$G 5JEg6D"H$c?YdWf{{+?S񟌷u'HT0 cK2FZm鱱&hǦG͕CCbscj%Z>pg4+5ܔH$D"Yv ߱ =_IV ~W3D"H$ɓg)g*>nL\\1oWe]*y mz̠n xlI$F/K4:jjJѽ[Tv+D:L8z~M#U+sBct OK28R qJ$ɏCdqt#c %H$D"y"V O= IDAT[Yxy뱥xN@yl)$J7L,4]?cU|!KZ`|x']FRD"H$NOWw=H/zD"H$?Ye@nX!p- MEU5t T|:?1`뚪&khSR$/g08~?ۯͯ{4)*it&cFuTrU4ái U3񪭅RDw1bfu}1w}m;[Ma*v*gzksUH5oc*Powi ME3\Ya\-44U32T">S/Э]իi1bfh^Ocـ^ Wr8?1fjFv\w F1#OU83y1m*c6^L3y\ݻG0xo\>\ނnDs,L}854sb1rkhs-6:ߦ^p/TV2w}g SEؿQg ,7 δ=2l0lBmz~D[،~AqbnV; (랸]]~/1Z!Z,W.'YBMʕH$D"H$D"H"CWQAΗq@f%9c{/9%Iptvڴ/(.ǽ0%l'S +6ck"phb:Vw5/$ۦGm9/h :rivGhK>6[z҈4lDOޮ)wqV=_سɋCNh3zpX ۵yVⶊz({.z;gx6{ݥ ZAd6(zW#A5+y4MmZXv&XSHlNq}qC36NSlehݞ7ãz}Z%_G49l4iW}1QiAG"|Yoq 4̎gwSoؗcG6QL$ضB"|ߚMG!9[6 xE㣨MsW,o7)-,}p7/y5N2*nv_%/;}<^(|ɟNHQu&hMwɿ;eOu-|BMZSև>k -,9y.2 BY#}um_l&(ie/b3%b^nHPD.ެ Mo!7@Yonh^NQDv7C?~'Q q547_h9?n oNt(%#_vb\!>q+e(kJBl8J Dj냘kDDr6l$u^.! 9;dWdj0d86y~#?$%.≌;k7qojN9/>]ޑpxup2ӗ_r" "ۛ~NA@Rޜ\]rLny. k\H `%m:y>OS">ӗ?y ڮ8v]@Cpdz;ÄBsf~Nnȍ礽8ɡ-Vzx=nb}z{;L-Ņ<' QA|}S"H$D"H$D"܎䮢Xߗ..T,f:cutjWQu:nѨeiC4@OLC 6Q-Tm2Uv zjn rrhxy])ik*U|cli6ObpD1yŰSP&2P]3a bi Nύ :^ 6k4Qԛ_!?PՠYQ0 s"{]}Ptk*L5]i;O3lMY2o*"e߸'h].Z3<\(*iCeiSб]{cDr@YTcyJ~ >7~Hr@R"sT 0)fW{%J㑊OEӨ͛= ڌ~Hq qbi,\s(]7)ɱ\ߖ) GU*%(8ϚTd\D7 廧D"H$D"ܬP ]9j{zޜTc ݚrh5MT ӝihti;?gQף@¹6=X֤T]GruRhF_Aec*C'=4mgHE*У?-bN;$5TۦYMkz{3H"M0x.àUXi~2&ORU˔e:>ͭ[Xuttnga9,4Q#,BK¦ 0lמIk86igS/j3-gXZAXKhUԜD x ]"H$D"H$D" ]9*^B.ǯnxyB?=>𕗿ĈŦg '.0X1hXʀV􌚴 aPQ=@}slDc=;("y{N??90׉5惞/3P4ǃ6tz ,;d3H.Z3%CDZP {9ϼϯTt Ԣ+ݐc`~OϞ=ٳ~S8U` hG h\@W2$iS4q:?flX-ƍXKP 9~zN tex鳠ulQ]S)`鷻`(djW]4iJD"H$D"H$D"ῖ9i%JK lQH%j&Q6!VZɃe,,޷[i7[^?*ҌaNݛb{ S Ek]lTʹ 6k4>Mc uViu4MaH{}Ļuo"CsP <:+ۼ nQ_?BOբl ͐( iFoyqZ}0,| s|9ُd+m~~>__SD+w4NxmH$z'D"H$Ds¥Pul/)ݰ=VQrT彡pB/R[JW=ݵhF*NǥL"ڃ8΃ȽGQ:Ҟ_(ޫd٧OmX+w!z' xRktp8PfnPzޟhktt3s`ꃁQ?uY'jNw0n~IxqT,PF7Eftb1X]ڝ3aè!S7Ѐ(ts#N,owDs U+(5wSݧxJ%,Pu~F϶D"H$D"H$D,&wJaAa6u@U`Х}3"Ml6/#1jpS-K۵QLh=h7k Z&QƵ5lFgO,;g!hNߣè\cIñt/j%JA%֮)4|7 ng/ׅ 9_Q]o`a E&uzQ bFOw<04} .vQFgm8f4A;dvzVc1l [AGBMZ%/Q2ĭt*>n%J)V Z:n6(WlQ/Tnr 4aТʷc~[zPO 8Vfs1 NfڏbZ"H$D"H$D"5.wy?v{TLk~7mSW: 'kB%կ!m@'yt Rr_]w8%\kh*`k9*N|SHe4ʍKn/j3`@Yܿ3p LɅ鵨5Iƶ0u~EUIYNU{{$_mLMt|QR4_% Z^G~r5b[uK ;:)Gw>@Q)2i~>#ݥ8K,ԩF/BvLu[|%ˮ;*ԩD.{ƿ>vDt A`ͣRI8ZųaJNUr\;o٘ ۀNؤ[#:wV>O'iǞjSl,owFu)i^U(~a/VB kD"H$D"H$D"O7qwCdq_ƎɭkH'BQJݩ֔E9Dm̘ļO5N&("Htv]l>#>_%r5fBRl}ӎHHߩ_nV_Wq'=_D;wu}>"Pn^%'Χ[/bk=#RPE$)Y;e7D(kFc~<8N!o}ɸ"xRdr;b;9;H%vy9 8Xb9Y;2=<.Pī/}[ɑߓ%rkiL(BAJ")2b̅O_mtRĕ &r|;ѼWHW㷏b'8ω9;WOě5NąE*!v?~۹:e >ۛ"FD*+r;{ӵj[FsYQ"ʈmqE|x%3s ʬbޔrV(J\$bՇ9^f퉹\|9f _\flMzk\WbMA(d>"ylK$D"H$D"H$BrJ$-~{w5|ޢo}]CPD"Y9}5>ߏ\?BfJH$G6Q/Noӫ[ǡMٶxvoW:>ooL'C{2~$SC-x`+Je;%|NJ˘J$ɣPS"H$D"H~nV^3T"H^X!x˳ H $Sp)]~H5R%D"H~:Uuvn֧O|jỳ@0c*^ؒ|ŧO- M-WrG?=jJ,zlQ$k\?c H$ j!$KⳠA"H$D"H$N(l1xsiʷȟNE s5HCZg#ݟQkRV"yHnsrS3W-D"yXۂ=AχxD"H$JxwB)hɽҧGZբͪir>(툨tJ$s?2x/ir%D"H$D"yz5\5FLiQfXx L4e*8~j؝=bNnM=`h*Y$MVt! )UC7]$}ڍ"m*f`)*j6u45Fl>'(ӸEh-XU}QQbXEh5X U1mB5dVS뵨<,CCUUTKOY!٘SQu (7\qj\*c*c:bPuh}S9Wg\ƈ>t ck*1UE7rΘ0@#7/N!#F,,tF>u<-F,f#1pe܂nT%Zcs!>ӪX9T:+ߦQ氏c*n`y*-اUvb1bGmL^N9p nP޾Mҫ{混gw7X ծЙ:Vcdg}S˰po]j6;~'݆6ek(W^/Vq3Щ`1__B,#L_r_?wS ǬWy.Ai ~ 2յys=ZNǴ}sPw1B8Ra3 (1b|ň4 "j6Ӵh 8T([s5srV|ee=q}Z>.nw[zkض~Hތy>Q)H4%NU;rת7mJ(#;7wp"Z"3|^bu|Qr'JeNH$D"H$߀z'۽{ eM:x.qOt&#ҩP@"+N'6RJBkbm-+2o$DGq>~P2;bo'sy}[B<W{b3%LFusoOYGTFd)P.!M5GǓ"Ɋl6#R bI9Aj{$ې[ɡN68Y)=<dΧE:H3lc6tRgWS}27b; %tR/ȉ/p%fz Lgښ^ʢƞظiOE& 9Y{r"%.LWJ)xfK/}9+|HMz-'rzs"Y)!'Ǥ&|>"@o^nئqtZ?nۅ^3b{wsב x6{["u1_Rob+}17"Ɋl6;&O|ֶ徼ɉ$|6["=ҵHLvmhWva`.ȭ$3"ˉm{#]esαv6qeg3I;$ʐvI_D.\lVe"9.2;~/s";z&(ɬXD.#_W{b-y)ɮ1Ȋn!ϠgHbލAI_fJ=/"2opWbw;#\ѫ\ʚ:݃Hn'VNgNzNrbgBb+ؚ1_nJ 8H'=yќ'"&ֲWxf[,0%/tKاC,g[kmmv9|ijbwJӎ(MSSc IDATo~P)NDr6;y5B|֯9և[#ܬP:C%D"H$Dr+3tI.V$E*}qrt.N.6HMmv~⣍M2M||6QғƒSYM&ξxE|92|@UD2鍽+yOī攒ʈt2#vl?튵phN"+>M쑝W[[:CtJ ecC+i?+`{GB7&u\N=R?8y6r'7 }pu}\*%r>]m~ QZ$Sbol\9%#&|E"ٝ1'ڹbNYLm5ěarsg}Tĸ^;#لHvn~N9drS5kbk >拒KW &"w"/GfSzS%Fmqe򷏻Mzi֞I8:P]kBqHo='D}LĄ_HTrM9 ΤDr˅>X}Ox<-NƜT \흡Clbc\o$;D.g"{6~[I>\ՉgB|[Q6b'}},i[emm&6tތ=Nnv\Oo}푭ػCN%e[Ek.>&g5?5ώtJ$D"H$%ܤ&S|۩M]"=rnOG!ƣBWO R[ḇhLD(g\FFNv?\m)- ( T'{"&67f9p]"'fvG/9EϴuظJf6j/7dI]lOE]n4^Tr鼌痛_Xshe%&XlIo`gC܈ǃr:s6#sIa1g蜈ѹ9v3dm6]5rZ4 ;C٭yw.>;o&6ehvyv4癋HufcϊE5}";sܓtL:=z=LЃMDfdOhl_pXnm g8ZO>݋>m?I1`~y>NO} /3N\ڞee[E_?\|Nz4}%x/\f*J$D"H$ Nc2`@wU)R\OmkdJf /pwIÙ:@5Ltex[Sg@7V-I4 ư݃.%inNԦkKvԣ]O_zWhҬ̞q^;vb p9 @7Z ?{AtV`.gr$<{zIޜx p 猹iTO 8 k4d5g݋-ͭq7սk4.̨hiY5bFI=xP&l 8ٝ&`e6p^RX8st,Ciθm77p^Mj6T"żY[!=F ͎=wQ8[mw1ώ?;//鮢T(&7TωZaNEs\,Q֘(p,AI4^ؤ51|,G4_$wtqS 9];<ӽuUk;6ͥ\ )BۤX|W%EZfrI qyzT'NN<[\CAxІyw\1WFV D"H$D"H$10y u{i: ŴoD3txiӡgrJ1nt0)1Gus6T&}CvnK7/!YQ!u6V#<k Wvv쯝V@_`{u~{ 7|ӷcj8~,OVgh;9_~gnÌ9 ᮎ0昏mןWs{AT9S+N.ܣ5E %j!ckF}WDa kuihZsUԧhbEӽcYۺ]mVno78=UHmU)O\սУ5g-\]e;gf\cPx>['[]?JVR+H$D"H$xw¿fS5MqUCңcѡih߻w~r/*%ޝy4؁O_7k4{ХQ f#$ߡ^)<2ҬSq98;G잩7ީh)RUB;y&~`S::$ =^HyyVq^Q e6)y*zz4W@A3sܩ&M-Oχyvx2}}n,_9!fjnpO%(T9(/r}>wf߱=snl?Mkk(X Iq(ꂥC?"l P,[ݶ18" Z@0 Jˊ^ӣCnz['OVVXmp+rx{z M9zTFBQf{oy0j(Y{Rir%D"H$D"<-Y ,w-|˷s{++'(p֤vsM9OXt ^5&|'_v..e^ quԜfc8 :Q^Xyv]*'="zbY[=O!N|/fo9ag+G6e`GLwu 1a h̏Dg`(C'm.1U2|s?w^O,hEw.GQ@7پ/٤e{ \h)" H$D"H$ɢBG=zg@jza6p+ Aa@X6)rS~0SZzM PX+7\,c22m~բ]um ν1]/K?rby7F֛6XPEAְJhJRAѳ1ٴ($sZ$ En,8~J=q ṯGhP^wN·Dt|:aGWqW@qNsm}|gmBgT%v|^e\AlG$QАVzQHkIǹJ/ih 0>l=[rĂ>qgۺ*V`|A\JPbƯ 30-ȶ5vM}q}, {)7bbWT|C> _8>ԞzGT؛&hƈ{rՄyX̠=$w]Rcn-ݦlTc2[uPjLſpS+{iIqؘޮJ"lxwB9İajvt|>P¸uHG@8 :ng"7M"ۦ}(k9CF^Ԅ=k-tth?am@tcN-p$ڍ^s"7"aX@z]Vn=܀6\y%za XVjl/ F0OYc3 5i>:״m\hCPmRY͇~2180k^vg}:j/];MٸQKlb^oԱm"QVqF{.ng=n ڧ{ǝm m*HkqJkrl&7>ߢzfپs(h9J?=v4ǭ\WoX*V^(#CxmΑnFoz%xvI$ߣGU44;=c3SW6-ƒU7C71\S>7 ШIe޾àN$,Cܸy#%`RTGL+72ц}qn 3j8w*.j@NazfGCnAA٦r]߆AH NK^L" *q;4>վϡ }w[o[\$gD*=SmKjT ۠X.D%|$굨y^ьzXz:tXmڶ~J@![y4R2pimYCHx=n]rkq ,qs"aprY{ H$OϓmfH$GL|>g=pyUD"܁Artjt ~5 ,s,9SRlTkǕI;~7Msm _"JB`ǁ3\0yKT';-?P q`.Us+j6(SOtq]@< -S/ ULMMuq,|2ewLmX71-ӫ8w,?n<}8VE)ZSz5~{BrBɽ]/%>gb`*eoZpVn[rUS:dJT VJU)QX\*Q+̲0t__q٬Uv=Q15)C]\űt__p<ުQQOO A>Ѫq-MUM|zDe*f`yS[sQc*NK3*T F_:s媏hC١cfUrpw#(i¼Pڥz^# !:hv4au%t̀y~AG/~~LW b[h1Zn'&IwpBGյˎBHRN)KGl40^(Jl>+)yA^t1]etu z41aV4Ri 1,z# 8 "b K;۠g?Sh(d Ncz  T3LlL3_^0xp\ 0G\>O̴OC[+a^5c4̪6 d|GŪt (1>D$Zi8WmN;5oiwW2G|V@ru"OmHpW2.HVqk"dEa}CD;íP@ΉRI" Prbי|]((bm(e(D!iQxY].)έR LVd39^ eM쏽 P2yQRIlɐ^O>^%kQJ#)ٟ+e]<-昬x)6r;bƯ7fnߖDDncCi-Q.|}+D:&6ʤdKy-íBIr9o'{booOD^AY{g{Oފſ]pӶ]||dT-Aos † w}c]䎯V IDAT3#O,ߎD!ȭmRiCЛ[Nl~y߰-SJ:+B!'v1 o7Fit}fا;x}y?=kJNl6N6fiC2ߔ6#6rC[3ֶD>"PDi#;b- S/xYʎP3qg Qz ;|b-=f6EicMri();9Xoœ6`-O\١.x~<9qODžXE6†(wvPLw6f4-s3mu:?n;"%+ %Q*(BgwaWW'b2?Ʒp;'b}?Yv=z"2uqf7em_Ljbh-DFIZI6Dn$Ԅu6XdžX/ e"!Ozshd|{yE@v)H2bB)ky_K$D"\(_3tbqMAK'7EM1v8tX[gt:#Oχ7q;B]̌65% (xK`__Dv$8Cs9/o26r +J/?_/8kQʌ>h|95v,￝ΜO tb&c' cwq7{&9D|,  XFxn?$D"<03T ]F&թrG(:|BaV9uv4|>~4[ՉiPb`*ibsa'/kv1.֋,σVs3Mk"WL3P,[&YTfeF},TjU,6 JFnO7uN1zmU/R]Skӹ@owFڥ62As9cANV4@ :n:Ɗ).C;ۇT}s vv}2nqe0\s:`S3 ëC$5P{}Ptk5wk z1:ŊKQ0y#:D(լ[01=@0c*\bz3 ch+tCŰ6QPĽ؀UgTk& όNӬi'1dј٧[oahR6&} ѺՀcjȼ 7CBcjSoVUDQJ~nfr(^A^fyj^xwl]|lP vC^N}ZI87;JjT. v[3C d}Wk_a)Y_\y Yʽ5CB(S~ ֜}*H$D"iYƺ-?!ix階I<ϧGŠ8FZ(o°eWp~fNUEI>qq6zÜpd)n[)}N˧n36`x,'u2_v :@¥*$ `ei."xX}=Hmp \ȼ)wգ=#9>A7`@8cY8&D! 7a;tlۄ(Mx冹 D=d$@?U~nw䶻W L&ɮ.,baO%QuB'A3*r,ǹWuZ4OBzSPQTU,?:yFEm}cYc٥-(\~DPlg| P6(NTT^!}[l.2Fʮޕ̋p9,>.]Q Q9nq.W;7xؠ[ؤ" B?zrH} ƞ.j /D!xϫCK$!"O' Pm5ḭDԢ\i">|9M-I̬Шv(?>o;޼h}ОE]=sRɗ+7Ҵ+CAéDO|Wxwޒ>-S?dj9X+ 2[%v#04Ӈ 6ȮT[ɰJQ4¾L=PdqBatВ8Eg^IӬa8 \QrWk¿S"H$D"H$Xƺp!g +cAFgl*kvO!RvkjHoOħViN仄m*Û촨 Nw Cg/>A+:iG5zpq~iҌ.ww PeW0V"Mcbr>4<m\Ҟ[ݥq4@)"i2hƀәhIPRw_fVrZ5ٶcV; Rmwb?)9:Yl0 o2[8ؐYٸ"gD"H$D"H$ovV@VlNr([O8H+PY_O^ׅ"FuIK}kB!+G' {ǟ.JM E(X~.{/]x)ֲi/+'TȊ2k{CfNߖDR q^ťGzoSDixVVF@Nl~E>k띍Q}qpQ+e\+gߊ'["EQD: 8VRmgcJmv9 ߎeu_ͪ{" ? .>+Gɶ(E6hV6|oYKB2VRz-.kx|Gr"VE6W;buq>;yar\z-Jʺx>^ΦX/ Hgb}8m3n?+=qUv/6dA" 3; qO'1/V_;? ٘ofAdӣ9Ǝx M|-66#CW?(\&-ElN7C^p.jRa9l|*^bɬY<_Ȉ3_u5͉Ҏx~VPPD{X e?Gn{r{dE39Vog5CK$D"H$DrbVߌcI`=! (WtqDcG5'zH$D"v.֓jwq$D"H$D"X(g2J~EG+KOԛD"H$d>jM(מD"H$DfB3Tr{.6a/{ r[eԛD"H$D"H$D"H$_ ܞ$4-v߅7D"H$D"H$D"H$82gD"H$D"H$D"H$D"%Y(gD"H$D"H$D"H$D"51t$ܞJRt]D"H$_]D"H$D"$՚W\{ۢۋ.5 T1S*C$S Sj년Am~nWc+e|>~HPwn,O*~y,"#61CBUI5;L}[CamdR|t%joPJ$ɏXƺ-D"H$D"Hn@' t^.ȵ$$ Ll:yGk#t9Ru*j_! = Üd Z>my8!!CKyfwҜoNJ$ݰe$+H$D"H$] ŵg !.Xiq%"gNPM?_\5K{Q@HEܒC!իD"e ePD"H$D"HVLrQS)WJH ؅Nõ tU%Rt\Ǘ)RNѮ8j._}Qv5_"J zSj@?P+ۘL[m^*eDTTuxSѾt=Tp M+?5!oDRI *>$~ػVchcum{ l5q8y2.#~ JyݱF4lTJIHZ24TUEMrNtEH%#{>jgz#}z ܧSHT aZ:|c^P{.qf;4t괣٣USqТVvFm05QCvX/~G)Rf:_)Xݗtϔ4+>Pn\x;#H$?!2LD"H$D"H$+F5%89t u'/9e^|!g;Xj$ 9zWmOt)2uLvH}Baq6  sĪBA~?iZՈA =OXPNvKqp>.RJb")l*;' MTw B̫w(e^}@:K޶с8 M{ǯYS@ }dpm }'Uώ >5q3M,-&>c~{.u\G ZCSaыG>u{q' ڽ*KAj IDATlu u~L&m5uM4Bc~yI[SPd {Cߩ`?lYe:ZjӱZw_ cͱUZnr|;.r'W(˒tNF>A\ q@91ϡ29 T'=‘;onN.؇>L˶PI8h > V7J6ek(<#:սn)!NȐ_w05Яo< 0>xhy ӌ>|ݫG#MȳX'oZqćOG˥GHPH$ϊB%.ʢn,-].)_oi9ŸWNAfSo'bkcMi(HgrP/ hYYug.|(L،ߥ]7]q,?˚owa$DLsF/_h.%~OEJ~rt5{nMebcx^SW-6dE.H%elnHïާēO>'E"֧vKBAnG]6&2-:Z& 缈~B-W(kb/[C2L=Hgs|V\Nd?>顾 [U ՙ}qK+yY{D.[/?ïVn9' -ދ׷ۢI\sί/7DzgǟMשּL:=,ȁ؟,VlGR\T5zˍ'b=s -&h".3 v.ܸ}͘gIy}QNO1SDa Ңz ̖8<{Qϋ6ΨoeDx~l'C^v'B|;kgnX+顽TƯVnrcÏ8i?2k64./~v=Ȉ|!'l7vkmo3D"J~AT.;;;lLmk= -W%@Ioix16^'h?[kð/bNrQ$ëQĄؼ;jQN]Ǚ84aUZ4JR˜tPeij t@ˬ`=,u^mjרLvW X4ڵkYVB?h~jXߠ;~P yaYћ9U4'tZ[NBQŻШ OI6hz:s%4YA}]kQ33%t->dY] ,pmM?IԢ=nkhV 5[e]{H=N WRN pmApiluuֽIE٥VL'gN.ԇa8ŢٓuSoIu08 +wʡ٭9(d!'=SųIf 3z!H$K, ]FRdzz}1gR4_~A~FT̍^%#0$c >Ro| j}d3ʓ=7f4 2kBI#r^Z*|D"/w&;={? +R)m$ؠQSpŮk@qWpC9b?o(G٘ T,FRAՑq=K4P㛸/ c8o^ O Nk#^DVAi3Nig|y;fآ).AfnS*y"'"^L/#jb3:JNM77ia(馧CaMw'(gWo LStLQ;P sI1kYh%,X`)8 |B8pT,[ Ϻ>ARehC;#f?ףK0;هKu?N.ڇpxyڏ?p.yE9,Ʃsbzg89Aj:#tØ1 2izD")0A 2ӿT)O2Z*Uc*n:c_gM ߠlhjjto^`6zg4yFZ z 5"uϤx^zԇ2k唩 ]?1`ߥboM( Gkųk?08/A5ukDV1u̎׸)n:)Qhj[eKGUu,"$$s3BMSS۵UMJ{֗gQURjNѩGvIð=2< HZj iuo\Z n^ADaZrTթѬnynT4å>uyvhT8y>~MjT~BЬ:&N&vGhJi8\czSq㿄)ͣ'l$t-ǫ`3V"H$wCdtLNմ6?Su#4 m%f򏹐p\l>MM۬eN?8^R挋<  o#=~ʳgφ^ܼ9^gF|o>\kKţ+\nِd]h=I@p$`6:c `:8=ſ=Y_}WN ;hBŮwmK6j ?^g6XϿE}={aF;xoRH$3?ϲ]X)/2/.Rt- Oq!͢O}b*#:1Ds~Lk_-֯eߣ]pxJbmƟMNnNUf|qEHp(]L"^= ؼ'Vpܿx$NV EZpo!(4B}o=6o40nSѫP,q,~n{Bfͩ?{{= (Kթ8>R=oujXٵ;lrl쌗v9 iܥX!&NcB/\LFڻm~QۭË J:իŴt8TBҶs:Dg_>aF.p6^IBW|8MSg}o[t/H$aB&|zRzmAqq"ߥV}lw}ga+6ew^5ԽA]wywInƫ/ >ڴM|ޫ/ht;6):򬲋6&C,]Mk7ƪםq!:1%]jGzۥy>[q(>QmzgW[(}?:KL~s4;υҍpvBa/x 4U>Ԏ<15jeJbHZ8fzqmBogPƴ+>v|$vWTknӟۡ[5HZ.sŽk` s`4j u)?إ%`]a@4Pl뼍Vo{'aDedfSͶy'#~6o1o:4;1ū ]V^K7BCV^xU^wv Sjm/~' ,H$e$+PjNVR  Oۢs.2֬أ#N5ʭ1G(j#GHߧ"ݱTa撾eօ#t |puNsv<$GiJDro^QN[A'PcپUn9{u<0^ R ۵ED-|֘c(שN:-& /iY. P!<Ѭy7H{y`аle4;آ ˺P\Q45PW>)V\2 WGڥ6p\b!a4owIvÉFj_ybX e(\f*e b1,VN#`Ip}Z#txUtgt+.=̭ƒd(vڞ[MçN+ܤCU/D"nMwZ{ ҋC01PaZP' w5kvil/Ny,a+L5UtBm9SAқ}0a{ RPfEWy"³1GVy;6$۴p= b-!ܢͨ'J]0zI4P?GDbϾGk>t%*]mNt]׀!D"He v9CE(dBz WN sEG4G/NN.lkXhĠE˱PZpxk{}n nѹfᘓ 8 әv 7+Nԫe;gX wc481iyT En YQ/A&3/Sda$ע^͐>b{ӥv]QN5+ټ!wMHr-Jjg.:pent6*V>ŤC0@zs)3gZvd^.ТVJ)kNGPߘA b cfS*Wē.fb&85\?!ՇxMhQ/)!댛'2+Z2i'CÞ'Ъwvu<: DsVzGvQ! '9^TJAe:A&i]֬qW3؊2n.jֈ*UszRy^/:Kd\M/z~4J~L ܋&>zno˦l|jXԪ>x5ȨH%$ ZEA\8~RHUtk| goF'96U%*G+B6)zBe6)rOB: ,-zﴝy 2Kҹ˽T_yUhV az9+HZ'|y=,Izi@W[k>;X(XVٽkW 4L~FXf&|;Baˆ@&r+: xl=0t4Zmb"ir} K/*vѧ`_O6N|,q0(Znӈ$(T|tȣSI o7fBswqn IDAT&Raͺܝ~ģM"РQw9hCxvrܧS۹oVt"4sJy3f˟KhPuJ$ {gpSh n%)]Mlo2w/}d3?f綍ژh|J]n]Êg)gՅwRtCPiѬׂoa 3Niij)wˬ`F o{unw{Eαqb CM& |C=[I.:H$0 BЫFx.meGoV%g :f?:68yV":.{èӨ͈C6z}̒=¸i(i4r@sT^kTt;INЪWΓf(E3H$B N=äUҪIorRb⇪5Qpv٣0z*v)Cbգy!W#М,%q28ⵈZrfg }hN*+jId5ҷ2-(|8bCu(]Hg/hdxФ >j UCsd2oOH$8<P.;-$Igćk_+WbP^G5'`lSS(x5P$L_뾵H9 %OⰭ sTb5^ SݯӈٜHU22:lrǫQ#&vzl6V>OR%j̑/Tpku}g#o g&YCe)T<ŰrrIpTt,zjݜ'ʐMWnq~q(z>N!Gw"23O|̫jd^P7 }}}/PHel7:N %ڌ`r0b  6uTYr IU 6|L@E.y4|sƲPҰ ]P5|/ؐ% g)*PdsEǣJ԰)'osEj~?j)S9*;*{/H$3rar$+eF;S !1q,|uV((bv{wmԚ$Or AxAl2{mۺيF3 X.3|],2-'*,Xh^tiELsX8ccKbH=3kbNGo"q﹵η Iw足k=n|.Ğ:z"Q10DAlY7|H0$Ӡ-Ᏼ!yxQDA6NH$kī dS"H$D"H$gzk3*M6n|'U- a%G7YLE% 6I' de%r8E:3rl?YZ,rpixdv hxe 垨Vx nZ~\yɢbAJ>єk"`թVjlS:*mjׂi1;D{+K* Bn]J9OLG6nDB~EL} u@fBe"\yk^Gt,C;4`B^K* kFRW˓oE&s?[ت ŞU͑qO8|ry'ʮAPfiRU,H$D"H$D"nrNǘFPiua.˾@S($n>c6n{0T${)x*N>O½[FҡVELb⸑)Sl?HY$B{0j<=b ~1<eަoȸ@[rUmʹDϏN; ׭I&)?ğ[;U;r㝺։mrۛުy`g=#2*`nGu{v4A!g+ bv:;%YX#֟|nrL_F׍buV*+ӣj/gi{s2nrx f>5N&۫zBǹznr< "V(" OċbvL]Mn'b)zWxPIz - ^C®{Xؐr%D"H$D"H$`J!&obH$DP`|[\u5vcfm7y/#J$D"H$D"H$B1C?I!H$u@X)xNi`eWX-d!T"q*PD"H$Dfz.fD"H$sYuK֡IB"H$D"H$D"yPD"H$D"H$D"H$D"yݑ1C%D"H$D"H$D"H$[<*H$D"H$D"H$D"HJ.d DRD"H$D"*PD"H$Dfz<*H$D"H$D"H$D"HJ1T"H$D"H$D"H$D"L !u !H$D"H$D"H$D"H$F H$D"H$D"H$D"H$o%"}gG?,D"H$D"yxU:=%OO-[oo_D"LB/1T"H$D"H$D"ouV-D"H$\D"H$D"H$D"H$Dr!c_)$qLmjijyLuΟPnN(F /mB}Bi_+vݢ\7>5q[7ҒTtKZisS'1T$E"_7:/3zҢ)f$mHg1u &H$xU:᛭{~iRrTT7Z@>{@v3:SSShnsRINM1ed@UXZ$<2zr[D"LB/mV&?f}݂GNIhLRlrmURBRAE_-ιSʤD"H$䭥UP)>FT ݰii}Rk+$7z0$DB'C?I ѱSKYksu 4$͒&1'5aWM,lYdy :*fAV)MJ0wt+3QF"H$WٺDruy^޾dJӪFa~/1/.)Q/'1ܺjG付VMs=Y5{JJ"Z7|@|-6K$mfz/ff-Ȥ,6OH$o6dtFnQ.fOI;o9D{܏]D"H$IjD0 e'>~,_}ꗔZIV%VyVAgK̄p.eU#A}}5VsK*կIu|+_+_|+_VmBIFQ}bovD"|C%WF-rLtMEU5t+N䏏؉&+Z53ND dSVZ ٤Xne2IT/SNFxγofr6mDT-b` S6*Z$NXl8NDUQaOF (0K.Y*Iݘ<=F3tEt;WMn_67諽qMu2g r NݾXccVIE!wnڪWɧlL]CUU"E2_ZT*Sj|1QtLN5T >$D"H$kJcP-8-| vn2RNc{5xr~دpQ!MXZZ"w[HZԼm@7vOXD"y[1tJ%orn͊wL#~dagQVb]fb-*ډQ&eC cʼnǸj?$bʼn-z7mwy_*^Q%mܼ;6 <`GlV'D88Y93-=ܺOt,t{/GaR~Z5q1tvQyVVVXYYd6'tL::+wɕz[ꦽ| JVV 뤷4GefN09Vzdʥ"M/m~[}ZM<`PXrܮ/40lݔv,SQ&Cu'H$ī :ݳ^RR1:[ejpcbg#㔚 #fzDEUU4$)ᏙU 8ASR5"E<]:jSc%:8٪QNC#KW"ӻ)ѰItd[̐ "IdLQhQH-:FtL'ER^8LMMzeSSÎ)=W=S߼NؽSSLMi %y7qԩCͣl v[W$Zh28fy8ԜTã1:8b$ f4i&3ɑtPf<=`;҃&ҩdeDê僱!OuxgG{gUP5)e٩M̵ [Cҟ{&̏~ď~#ҿs` aZsjH&~)cDN޾[I ɧL=M*_aT t/4})+ow ELjy+H)^|onnxWD"]LD/_h^9E@X,>nYVĴPW#`^(3ޤ aʴXzD"ts p,!֞Lll<G71DXV WAфxד'by" D(;IOva!1D&$DDxab9@ō_ѥ# mPmǽ+/ĜPfW󁤞 E̮^y*VRO'B]|,a]$Bt:O2 /tpL,=Sx96IwG(exқXF/6b_-n \l,ƄBHgH'Y~Rx0/B">.ޝ%D"Ht{ά"=!1 `[{)x,f::9177#b@"4#V7D2"Wf'$\ 𴘝s1V;byN;˳ =%,b3bnnVDCߢb~ٹ "sssbv:ڹ_!GGԹ/E:ZnչnYNb:"<ăv-&f"Xgv>:3/H,o*ܭ;""3}yRWb1f2i:}J%8ѵW󳵠Dg[k3"D(;c"}'ʌX1͋3bv&YOtƋҺXw/lk>ߤi_'+" lϷH'>N?qX! ,VW|8+%'TD""s!ch.ׄ4N$΄%X2ݙ ғQlDA(!5/>ґXOEb*(8(gBtAl(=[ \# W{Ό/%̭ (Gbt2̝M/l GxAlʫ2>+ӊ@Υؾn.0<iϳk0ODt/4J$Dt·GpYc(Q8yt!3Эz{.C`yL%:bǠbѨ_O{;s\Dx~/ݣb!Pװ+PGWCf2x!ܼRy[ s'+b62}|9G _xGDLҿzdm6vuHxk|a }xp9ijy8Ӌ=S2t=go?$fW7= Z"DlfIn,b gzg }'/)PBa1bo틊ǣӕH$$B3Trij! c m j],uUA1mo,sgbw#rvRا=hz.~tG458u6|`~i0mn_F1"d Uj#3bb[YئM~EaL ~U~]hU=ڄq6CF3 hh5MUEZͷ$D"HNR?_2SSS|ᇸ{u'o4弃\ӓY1:y@4I@bD/Snш(g=H(,B/]UOR,GdV"_9e|9ݧiD0bٯhVrnC\Z dH{HR,X'_ cl]s%=fgH pN _-s ' B}jpi(m&L<Fwh@wzZFRPpTCP MB9Փj&@._O?OӿT_|wKI׿G@U"O~y>, IDAT~0XB-~?{|{S?%˴EbpTJ$;t־Q!K3&PowVSJ&_(P٩F~ ٩i*F}bZ9T36۴ fZ PtLc_Z-09k6@=Y=y D" "o,>dx/M%i6Gt" h&/F"A#o~ߘ h}c@9F&Ї M= 0-?5Ň&]R٤B$2laFBdL<|j(M28Jlho&WjSS $bs#>/FLx|ٟP,׉&F 8އ#!h۽ K*nD"H$oJ7n.΄u `#'P6fwlhqKqވ9axjx^ 6{*=, ^w+mP$6 ;n cxi{<@&9bC,@IKW=h>&j(W:By4L9<\3`]\ Uwvze%O"љ,enoxUCòmZK^؆EE7`#صu~ LL qw/;O[=?=_J |=_+hk'6KoBO /U^!OroMxnJ)4ֱX38V;u<fV'c誅c'¶y.9G1~sIZײVN}'|#n~/+x0woqRZG#_o7h7nwX^>7q1A''$k7j$Mj` xUVVoial e?0 5[(ѳѪSɦ7l}tB=u-Ŋ }_z1Nrg6[qƝ6x;οs7kt65M?,H$䊸>lr4|V o^2 ƫzCRw=n7&fhFAُmХ2X6ƯTy/3᫹@L  3BQ,$ N߫z5 bcNxwɓ$bg8 cc #X,h 5Pؖ7b⌶<56$*wk"DH@։X_q+򕁿o hft[#l߯ S]&մ͍$Vy~d( ?]1ǜMCvwDff.p5{΍y{iuNG"H$o esШ1I`|Q$N+9rnQ&[آMxGT 7]rɦIS)Ӕ냚cR]\$;D{+K*?ln]JC"+_ W~3p)=c10UM,C n|)!\{h /rxjt,"]uw|jҙШRg# 5ܡԺ9ϐN!E:xcWP$H$D"9cX$7itF3p{fo>*Zxf9ffs¡is &Rk]Ɛ>4*PlWwL`3K*7B^]]]J ;yV5\lCk0T(c*x[VG`z>B9% KpOtG0l=~<9= ܚ] 큵">&QRƱtϐڍǩ:~Ew}afWx^\ᱴu"9`īuO J>}PLl5ؑ!H$ך/:Tb-<7kmI%mk*vJ~EG2 -NkZR T|{,CݭRĔә|rFeaDTZ:5giRvB!U*9I6zqhѨyx;WxK&"=9mc jNڞO1 Qf,#jJlj`RO7;2Yv|1 2Itq,}=Dui#n9m1pILϦWvUZ`pmNLv9iPn:D̴0jm{YcVtd{mߥJM,.jqr;i*1[>廷eo&CJ6E0(o60Y]Y]MfL.;05BL&L߭⹵Ԛգ6ѩ>xo;j(#oϯ/pq#:/|XO ω;O^aocĪ =^&bomN@DEbb!څq /Ĝx)(Fbm>$_O_ DcKON~Xkř~B;3;d]EGˆ<.v,:ObA9j9ZB3!2sGgSY1;B ~4zB( -ޒWYHXU:cH 2{G?\׿~g|bo=}C| {z] E{So̳?Kw@s~={fk&R?3z~ʹxWA(?}gbLO/;bFẢq@wg&bS2H6'"bbpߝo^<5=ؾN޷ ޷hTfΘ"fWzgbїbm [{/{6%H$.t2tJ%P+L,b˧ϓԮ-:D"H$D"y xU:ۧ{a`c̈iYXFȷ4Er$\vPmfmvMl'c[>JtB9ǹ­6BN>L#B۟P N>NiċegBp'ߎ6cce0r6JQe [;m:c3i[Xid܉Z _˟V(72}7SG??S~ڗ7=.isbL?|ʏ?=B|Ϙ?G0b/hOc+4ab^n ne͇]-TL&E20R>bxDڸ$l:>.ubbV2Cަ80f떉nXXAD&An)b [a蘶Cܱ0"&?ڇ-nݺ )H$D"H$sIq,1?Ek5k4TԚj`jVեyL>bӫ>Kby ϯ6οwX&J'$W6pI Mڴj6y"3:榇0Y}/j%Bᯯ07ī$XZ{f/I`OX[J0k4}-B3Y{ #PZd&}ؠބ%zZ+1Jz9)+I:Q9<<9:NrN=״-' csVj@iiXHpgah#VƚdY VV`u@6&Ď7l_":Xc (`S4\*O4&0Kl@?)K%:uΩCG:ץ7`OO?_x~???.{+nk^/T:=i]P_G_7S&=Θ4P!axı% =BװîIfcuѣFEE!WʓIjh@03&w)IְqoJRfZpz='NGo³<^zkU$ ڼmD::M lW! ŔR'o !B!C e);.ۿӟߖV^)vBf1/**B!B!H0tv$z{ݪ.̿?s!&W!B!B!#;˓j9BBTY$+B!Bmz9=BIT ۑB!f1/B!B!B!W uǮBB!B!B!~pFf?صB)cWB!B!B?v+ӟ B!OB!B!B!BT9CgT!B!ķ2B!B,SCB!B!B!k'P!B!B!B!wi`_YCZ{J3NbY1%g b1ֽ\8cvΘ9V,Ul?vMVb1\kCD7:ܻ8nƨE5~tCn$ iի>e ﻞ9הB!n2B!B,慲2eQ(0v+{V5(foJ$jWv#LjҹvkKd{#*sǮ%MrF ݹnSv2W[~8bB!B!B( "Y;/OR% i&~DTW6 8 y!R(T8OJ%JNUT|Dy›rbCQ; X=V݄!ۧ`?v%{hMj?ZY!B!B!H DAj1XK B"D;zGMgRpPTBMOy,3S/;_֞ptZ~I)b:1rT;oH]%٘a:dK-V)Q&c6^*u(Z1bVv?S+&6^qzv%Rlrji:Sl7ʿycQ@]C7,l'_i^F,S(ÕFzyǔ?NR/ۼq[͓r 3r9umΎs>t!w7{a'= C75^j ,HwB!B!BCje,3j^I/O hw*V^Ymq(:^bgTT!̳*Gٍ3]p_0f1vtmk*m~M/Av#mDah7j/8 Ab]:M IDAT,~&^ހxj/kB/7vzAnbe]d1!;a-urL-$/kDih\sevJ>])z/G;=Rzs4j ;4N6=Unг_zPWw>|K(PZI*ȨCuVƦ:5WUZC\T5;ZF=;ZܸG2p{[E2cKc RK\\A\-ګJ)Q-TnSj1kܨ 'ޭ䕱|խJ^3P$R'æZƍ~' !B!B!^LM,3vo_^Ghk5, g'X"~@-&hgz4K/5 e?ۓՋnnydO^i(gNr.yN -]^q/rO:4cԲֹrNzӪ$XY:p)s$ԛg{̆FP"ʗɧ'vͭ{=8ի't,ÙŊn5M#Fs~q`Ys`X:hXؖƠEvB!^5',פnV|-bFuzu|=FLwR{B!w6y jD&ZJ2~Ի6ah@nJ`@^'xZ=l<݉%$<5A^ltAt|i 1[Z8G! 0\6{ hw>V\w\l I`O `⺗u5׺9v584z].jp4>/Ӣ0k֘>2Lf|]Zl{œxI9.PYY:W Jո ׆ !B1]=v]0>:uDz:E?װ_G4s7WM~D7KsǪ=>A@= !BL4U0t&JPԤQ*mwkxt< EGvOgUj^ޖKu,W_Vɻ:CDZ&e73,cCX@2*|fIݡoM- -x _EW7BqjN(sϛu "H-GuVc`]I 4ne#ͷ~@'MOR|Hγuyk%B1sGo7h}jzҳQ Qw 2HP-ؠTUZOjEC>gi`tG+.MnzƸ%7a1;m(ih^RњUMXb4uD[ xe"i_mQu(TsGTɫfA4/yN{ oyuyq{B!>t%^̭6hT\Z~7{]+!fOYx?Љv.F xصB!:M3tJPa ̅6/@wͲNJBtk ,Vʸj؅2䀽Jz!c& BڝAN+`@g2/3 ۝K8OL 4<7Gml.6=+JZ6Ru/5 ^z.(Oc֍@3KkA)WN/NB!2cu3]a~Hw,L]'1L/WeLj9>A-cG{6ռc躎a9ڕc*DUuM= ~Ͼi}n[1@ڵ"Y2a= Rl9$b{|qS'lȺ>_v>e⯵Z%o8+B%N cXf—SPٽS Q{7{"#se*s rihr5 7泤U)<7#%ʭ#H;< *Ƈy^N|s/6]:~1OJ@Pqef#hVw^9\mnL\ue3ziPow/qOlܬV}TK4vZg~Hq?a_˶Gَ[ma@0ۅӳ,L ۏ=!B1{^A=QzMO^1\y[9UߋVܯo4`Џ5ɹ?۫-z:FӚ]Gox7yׄpO~vp͋ÛgrԎIBjO>mnx.<'v!Otp #(IE" O^mMlw9lz/Y]',HֳȈb,dS-,=6rK*uqk* *vBK |TIZZSm}Rj9"ͪ\N) _\+ -7ǗJCSs{ **=TRT*VqPɕKo6̰N j.3_;D\VW޻X64|FM-n_8jF\JŵJgxk}~94PZ2ҢZ$M.WXL(@i[OrQ-i .ŏjNCʨt\SOSTb~]MZM%9Ҋz}}F? )kkk}6ySK+juuUmLnÍܾ g\O7{k*27r[-'QKyT΢V2*ɨL&5qʌ^[Vr!PsB{Nz9?{'Nz9?έo}7LjACe\B|Rjs)9,76ϟ*]&1E55~\xy 3ewh$>M.ׇV)MA\-4ƕQVFuU<^lɽ_0W99/yǗj~Na_eKW}~a^Q׷Scw+k*RjŹM2jri\?2R uz<7QdFlu5m1p>yߐB!AN 3Z_Ss"5ߩϣ@u_dJ#6#`z7 \\q4yϯñGG0T)>wj>TqMSZpj})=[XUcqB0T߭齘_y8T 9(Hl{~yua:MOIƇsP }VkO'u cb ?B!ߠ{_UiPI`X< &o Շ>|wcq~|㾓S+)$./>7 }>Ӗ;u;&_K_ 4y{]=[Wg Ώw*uz`Z! i/KPgn3G ;mJƭh + ~@û(uhpćݝ/ܡQ`s&B!]1-bOS$,d}p;eRUw?W +̰t!B!hLhdhP[rwczԽ[grl}˵e2IOm;} g+5 {l~ч]< ?Y.f?I k_犰rt !B** !B!ď2阣e(<}o˱%߮)70GNMkrm Kt|N>IV|9zʤJMo$+Rm {^:EvӴ C=z}m!B1,慲2T!B!7f960 tÐoаnQE=InˣΤ MarmT ؠYCc@nrOxvK01?t,7GUg9 hufXM(g >ŅB!}*mB!Bx9=NckAzg\8K0.1Mm?p@<קPpӖ;m;&,EZ*oWWhUQ7j5QޣZ4,4%hh6{QF1%Waֿx{_.%%ZqͧߢdnK,B!ƚżPV !B!?>xNp@Pu!ѩ) >GW?lPȕm&cNݎItۭ5ѨCfX5zv1,-R/8*jyr7t1iy˛Rb>Fw񜳳t]P]ׇpq_*`W&_hrrDVU!B̌CB!BoYN40B{o{5G>.m#juVq{'|cYGZZur r ~M,u, ',բ6L+_F|_ixZWsei˝+U4]\3Ov+Y_`Nj,Lu,iQ4,GZ8 ފ[F;?zX&>`Z%odNi0)b{ݰB!_HV*B!PsB{T(:gpѤeT;ϖwyաAzκ}Vfg9RͯjZc)gu6XOcv}"+/8^7eSc;O3x˕El(谳ӡ1mtx緗]*ϖH't]n_c o:GcD7=Jz7$N;sty>tv_\廀Vb-Y[H|v&ߤwS/IF`g kV[.s,4?^$CӅǃGB!mƔRjuB!B!B!BT+CgT!B!ķ2B!B,慒3T!B!B!B]`B!B!B!4U0tJʼn#fh.UOGw+T3fN', G?Wv.1Oaۏ]XL'wAz5<=׉ntwqݲQ#qik辇6|Y^NŵM ]G7Ll/O !߁S!B!~loEحPjZ X[[fWmv 1)\ⱫsICw[~F O^e mcyœ]RFs!B!B!߾HV*NGT B_,Q*(xʵJ%J<1bDw** E1x)P]R}m Ydm== {tWď}Z],CB!C e)B!?Y 7zGe>SmcWHSw=K"1NӫP5fuB!B!BdܯIM%b:afK4gYl>Ѫ$z߱F9-@=y[^GxnRvx?bfv.nkW{6cRU irxJm0b1bWuj={د_S4:EKyFomn7O7G0{_؆tv쟟 'h +81PY:T6n`yEZ WzB!B!BT+CgT\d}8O9i%yIEh\ )Y(~6FN o%Hz-;7j/8 Ab]:,~&kQOeM_P4MRSzL8&Q7d"Neⓖ$eq/m:풋~NQ<|Chg5BjY_wFm]aF9gJqЭzmF[t +Kq$*8 IDAT7/p6Jk}F {<+Eӡ;ym` z&Y=Er8Bn;+辇kz[$`:S(g k}o,BܷS!B!~l3*ޫ4 PZzE|zV:xgzASVg_EM>(Rݬ|RH-g/^V) ErYmԏ/*O³sCmq{BA\|x?+KV *N+V^TIP V('2jyXol[s~U5E\>SMeԳ;9\\P Pō}t+/q \TqP+x84?*Nq뫽Rkݲ_/( MeN>m>(-v6xWU{k* J[x=TFCXRJ|ߩR!B!B!gmrgT\'[..'qKeqЬӞݧe@H8=Az4K j BُÇ&ɝʑS- :мe )R%֩>K+UVB1AHKJ.T!S!B!~lJЯ๗$hɧ)9.=lL4|^ڝOd[r ;L\rZ7f@[ `RއkWc@C7G:(Я@?i]$Wޅ2uk!B!B!% ~5L+7 "z,ma.7 sLZ@0QwRXòksFQ M0+nobW~~z &g²PG ﯖw3hJ2XcqӰ Txw5ٍWͰRc}6 @mjz ]|_C]04˽@ڬ^B!B!B9i&J̜AO/S />*5-C68{*atrxEW4[MovxhnQn,pΛ͏{#l(rIߓB!,<ԜPB!Bcżp` F+BqF:4gg`tG+.]o$W0 44^˯F(;>Z%/{ LSAD#Xь\6l/F!KZB=wI/ O^5 a~ w2}7o7c;T9^,lR%B!B!BÙjY$+ ڝKўC;aO^1HDwͲeӈc0iw.": ɝE`a+ ǻ6 hN}8xn4;7\Tqm4zWOQѥmr<6.= 腗^$a˷?Ӫ9Պ1Dkr]B6W tܷ{K=.̭hǮB]y9=B!6y j,W8T*4 MsD {zsJn>K[sA]:OnrdS0hW(ςiJvgS"_p9ﶩ(yR+){*+V6 حinL}o\4{}ffl >[Zy]|vC: LC۳Z0@=p|Ķ0 YQr*F4K,B!B!!~-4UdZv2 ^6{tZt'M|r4$_sxgZ׵0 ;t>1HߡWb.ӏ#X(?MVJ !u:V&ɧݻGa_~L:6Qx6E FB֛7䝐Љ97N  CYcu*^0 A-_9DwJ4kc*B!~xay /үş IT\{y:,poB!)ޫ48VkkJ'UfaUm_{ZZi$MkcQShi/ŏlԪz7r?wj>Tqmاel3>W[|̅㷟̰x"V7JJl_=x_m-:%T*=WN$Q$՘&i],dT*WD22Kj嶺:}4J MͫZZ7nxI꒚ϤT")TWKgª8<\՜_wji.uz/W^/},7BZ%4 'Mn68>T *+m }Vks*NͿTzh*sz#r|ǣ;wh3x]uuOB!x,j.1I_(ߊV PͻPhpǾ9q !bbJ)0aW1{%l-R6U}^Sczn`Z~B#<+?jdc7GX,VR;?nB!Ggaz7` rrmnxs~#.ճUnB!~$3*!B!Bgr^SJC^VټJBK:~4ZF%Tjq]m?J)u6rSKjj}!u_>|9H̫gϿwkj.Zx;ca& kYiKk$W4*sY?ʨ񸊧sV3'~M9/yǗj~Na_e4ʬj{un5u6|ޫ4 4b5@ZU/~Me4ZJ-WqSڟ\))i0أ^mFvt`NHQ-LЁE㗫ds6Z1PlqDZsqih8U޴>Ӗ;u;&!'g]\9j*k*䬱OepdgxTR`N=~H @s].WQ_[Y{OZewBzSӧZ/>44ly<>Zۤ[> `p sI}u;ONx@!Bb^(9CB!B_*KQI6:-4\vadmjd]AMcs 3mӶnG&1: @X 0|̒_Kt ): Bq9WmżuC Hg>~ thu>emecj)s G{ s !B\xtʾGul"@HЅ >grmu\ۃd~.O.6gkX| {\(4Ȓg<ztC Ym^on zIò,1ݥgPB!TЙ$+B!BMz9=V19h'"+GcL̫*r .O{;_ 0,_i˝2s4}=>-A˒+)dn+Ƅ QSdq+-ZFB-;x x2-P(+{("aw wh@Ի2s&B!33yl+Bwik[<3SBOne ʺʺIeRSSST[T!UHRWUp\ETUp\ET-86&v>os& m-3,^B!IL JʽeS u_wL@4.wox|<~OlN4Oq3*mG޾~s}6˥x w2dmߺ抸vq[!B_TY+B!BsPbO蘣iI?9.Ƕ||9v1n޴mwq\uLǧXi?eri8کP^:IrE'_N[x2V@q5 YXu%4 C=zRS!?Yą23T!B!˱рAOئ, ;)G_~C?myNmwqLŰ-ڵ4A0siR6+o;`b}̩XnZj8 gug(B!~< B!B!o}l ЦKui"@ p<40icu۴mF06D5<קi۝vuiW2 734Ѭ~7r5^>thhJ!<4Ͻ~ؠ\ShL0ߚM<˵/2bI:]|9eB!~@S%CgQTVOBJ 3&G'(=h R)ѽ8Sv~XJXV9L-9R:}z <=7C@᝿n8~+qi{|~1|[Qp,CO&܌B!&PbOm xfE*~@Ts.' @]BS31ەXP)V̘]JEaHKp%qR~@нI)۝z6;(DC`.eudT3).h$WBKwi*ݕ$^nphnµ3ݧ b:lV;g&t[5Z@sNu ӡW/$]Rf^AbG?Q(lNeUB!niq iY*֌(tJ@CƥlTnW.knl¼/ĈMЦ`НÍ4Ѭ>.qc&ty[ly\!Bܻ^i`'`8y,G^Oؽco&F\3mY8Op,=Xcb8{5Bb8mb9.caX`ؤC?1-\ xa@fF :mӏcD.h?ybjyT2 LpoB!nd,21۵%OfE3* %ϼר.ר1JC(8wT**"WtHVVQCSNeg+l%BGK>*S;]!uW1Ğ~ӿ')ӧFyʢ(`{'[QH`ݼ]H;|s"⾁[|N'hF)$37B VsIa`/r((x&ߔN= "hk,:(dw7$ gէo:tZӕr}no&lQaB=N벃ލ.&K;Tn~2G .c&]p0:/":Fdci6%1LI,Ro2M38i*׬,`=v. ,&/ $l]xppB!jqaJ)9,Wck..7]5itm6U_}kCyE_>d>^\ÝO"(a=xkoֽT?,o%ݛ+x_Kzg'"ÌMxH *da_~{n{kq#B! %B!B,BY&t;ԋ>e:j.8eivn]kV2|hRiv! U9J/R_2ca:a%2{M=5z$6etS_^P٘a:+Wv)U)&c6^.)D %<{x覍Wj]:^/hP,CGܚT'MC1mr+mM~DC7,lקXk_i;T viQZKoEW/iaBoۼ;͓vy .z1DmCكs'hֽpR:NNٳ0t; 6n`y f/L!B!B!?f'y2wA?cLw ~b J],h$yk5[٬Uwc4柞q%9y~L l4Wtۢf/}J7 IDAT0OxC+Sv~2<%>\N:z61q0o5peF8Q@TI+>}s,=^DGՍ{{<t!j'_AAY~ަ<{ :Ͳ7L@?jdn2fBe;hU790Y+{wݹS055mj/a~J,s>Mv#L߃3J21V!iUL(B!Bf*q>o mN{oO|F}z)ȩwֲrj|AAJ[S{_RojܺڻMg[Ss[Q[gN緫jNC]UoEJ3*T}>}Ƿj 𚭾9˻ ?lRFAZͯ3ͪUTf͹GQkYZZ3juYzN.͗w*. Zڼzz:~|N?s>\RPFF!R}<[,4ܥ^½p V|NWO j峪R7v1~~445n:毶r65?nSN쭩{{7Tzx|5R'ٜʢZXXPM[y}!B!B!UML,SVd L(åX ne@L<5Ao*n{O3Ufh,Vjjx5~4i4˸gwG䅰Ih^;Gh ͚{n֨UQ8j7إA]iȟ? ?Щ59dX7(XgNR iOט-FT=7#*V)&Iz=8٫'t,ÙڙωnSrL՞pgca,k90m,4,lKcrrQ.&"h.\!B]ń{ !B!mq w a'd@0ݒew.w f1$^=lSǯE!B!B!H2У4 ۚ](Ww9,2`Fy >01L5 Nꛆe[W&>4Lr0KBÓ?HR8'Ma-,%hͣ~bwY}g`9GƸ7 DD>o& LSAn.f3^rٰ=G $1RjR=I/ L&Ϛa}sW"1<5w'w{ F$CB!B!62(V*N踾V0Ų}lmWK`aVu.U)d*4w 6az}NĀ if:!珐ۻ6 jwN}ԫ8xnu"ǵ]*TщG'G)T@;Ћ/DųzFrR':MxF'WdYg1n EYozݯ~VB!ĵUL(B!BfJU(aD=Ǵ5$Q)NV1*iOvCڹb 1`V}&I*T;ǐ)xHEs0jTӑ%Qjvf[X /l&Q㗋i4K%ZjSu+V oTi9Gf9' [4 4 QM kG+vW:$YkqH]ղm Yޛ7WnpGq, S~r1e~T(Dr#4/BB!B!7 <qtjm@Ks-zBC,Mnoj6GZz*VJ G͹NnjW<#}e^r* ^6a=NH_&MBj 8$<8b]ɿŘkj1߯xdw.aćcX*Q|g䒸IG9\2td/,l)+lnRtbݐ_ܝpyvl^sj *\'E^R5KӮ&àNZr2/8o<|?G`..ūjq~SpV߱0t.P-:_7^D'[#3gc|8b7hLB!B!B!~)J0R+*M+MT:;w&~#42j͗ҧeM=V{9ʮQ'JJ/>WnVfP̭{{sY4*|>?\TZx>ysۿ}39F}|ko/XQ _QsTm{j-"ƴ6OҼ LvN/oK-e-FNyI>UWʤ5Jgsjqzq>ߩ+*]u^½ZPK~ZYz-^X||֗r*Y[j9}ބ?mQ<{/Ϧv~zY2L1>U j.}퓶rZ CS_/[=wn1O{ƚZ^ȩl6V!B!B!UR3bF"*G-JwciDy(Zl+,ctߧ -uo!5_>csGa$n>O!ߌﻗB!I2T!B!/ bp@:wQI^?ӻ48ڥZ2l۶0nR7a4eSv)#,I^O[/elJ4W!B̦J?u?B!B$*&oi~ҋ; it4UrM3n q Q1eS"mRoYVmjykW7=J&,0iO!BܫYą23T!B!89r2sp$]+;JW(WS$!jpLwnP/8_j~iBSwM=_֎4z$h{6aX^r#bA** )Ą-HvkpnL3f&9v/SG-jE21)bV|~GO/7ۏT*ֿ=SC*v?s0>zJP+O]!j_Ý'P䏙s N_ $}݂_p3]x^DbN߼؎HLo5!yN}>axL1ILwx3-8vy,+DIL#I.ca~ƟG9~je?(㹏xj81E\#yGi_Zd.=7;2y>/43=2y3t[\!_~ËU8?|agnS1lH8Ë?}q}X'{Cv/ѹ)u˴(~cS]YB!įa&qBZf7!B! %[W9PhY4I\L+@iU3}V. (Zw/ՒBW 5\}Q}I}R_ԛݹJG]|>yEEfQ=}w>m j flfs[/_9MAZ-ofex]n6{:s3Vϟ\ ~~7/~zG^mk 45wa9mȡ@SKϏJc?<(ջP mN-|6v͙Rcͩʖz|T/3a^jsmq6PUYd/!BRf2B!B!4 l2~ݛw9:uv晁SnR]`p@93;t`0)nl 41tߡ>:yLMQxLBɰhQvO5 t3'џi۝zu# )XgvFkSM z9`pФMpE Egī5hnl|񽻐Щ\AxaSoV:K3|SoztB' >CA? v,&2Cre* B!J΢X}IJu)Ճoh))t6%h2"eh_k":GC"oop NLsFuXtL%_M !BbŸ9ԣ]S=F[YXVn c<g\&.4Bq@x h.yoLɻף6@ڣ0~"`\ q0&^Пi۝vWu 8cR5r3a6Os}]oT6r̓v0MR\n-;G@??bs ^}s閍 {X09OPB,Pxr =OYфBE\?3O$?49[ӍwxS:2cc !EҩoP޸MeCqOߑN߯t6g>vFЦhw/B!'IL4;uG]f;#maQK7c]ڧkoM9 &ߟi۝vW %.^CBBsm[kX=.k(sS,<׺tc>@v#{nz.;Km]>fa9]qz|hOX/{#-jME~B!-M >k.ZP.أIM/jB'AB \MqdE+'?_a נ[, x'%1͂ϟ۔MvaB!tW1O{TyViug8?䤍n`G|4伒YqWkp>x~+п4o|mwq\,  AːJeJy{1=$j&[1Jj|}WJ{eys+4߻<3əoOKb8#tݡ6BӚE\ ui"@ky ڝp\1q %JϬ c6ѩOMQaAN+ !BI&1 `e;ggS u_wL@4.wox|<~OlN4Oq4*mG޾~s}6˥x w2dmߺ抸,BD}/ 0DB!f7KNf0CmS+xئK&)D %^eZw ~x2 _)ٲ4B!fgereްfZwe ΑFv'oA/!)uLO>z61q0QkQyx~B7 3F,^= ;n7 s<=O$u(y^ H-MEBa q1\SͿ=zݘS$}q!'jlx :/7ᕩmtI&ʟ-W)|}Tn b 7'lcix׺0氆?f8܂Gޅ^!AS:,C{.ϊmtl .iJG7ߢO#Bq*&cO 6|46Aݛ钩gYAS`ܸnl1 .ݱ({姀uf0&M㘊a[z] 0&e&./+o;`b}̩XnZǤo?aNpmkw{DQKK1 ڝʅڼB!$.TB)Ym-ghj;>'r1 S_N^^=_O{j-BKtf^n}yMͫgw8EJ3*T}>Ƿj 륌_Sg6S_/,ʛ3| 7Y\̩/z޼xajc^ShjbS7Tu暼S9F}];sn@eWߪ/RJ)7ZCYQo&ns1z144|it1E[T/?]E꛳~RoVFZ-~mZ߬ ?KJ#_rQA_!B1} IDAT^.e+[ꪯ3~cRJ}TO1gvLeK-i(ȩwcvZ-QVK|\-hp˻ 0V՟i۝z|T[+jiqM%柪ޭ |=^m~ ޞZvn]Ƈ/^9~z5:yZ[^R'EznxN:m@N's|4P̭s;į_ިyRzS|RoQc>y uFnxs\VV_!B|'S-II?P(U "W5| ~8fRuG/R1;*fb3>71[ UvM*NDyF FC`Ul! ճ\.RFbqU/tfr$OQ/784Hd1m6+EʝS 9{ %Wt]^^I:~̼*҅0Fij}:aUJI1s5DB!ƙE\(5C{mJ|橶jwG z!ѤoȚK1ŷ8">͹ܾ8"4[:x~ }.q 8 O74,l[.ZntSQ Xp Qv;#! a8xJ~:4(<̰XoR"(B!ӧhqkQ cUK!7iL &k4g۲pMOcPS3ڤULB!]$1`Y} 7}aě,:ЋwBX,l4 ۵7l,/0g@ "tUB[_l=4!hC6_pm A4M|ʴ,ta l` bK:epP/R\'V}zqB"N/3W!B!B!2*ML?Y'S5;I^.vǵ^ؠ^nѾ_܆z?fOoy5IS+j[qLGS =9_ޫ˪)-QsTm4c rٴ4_Vߩ2kVO6TNO;C^V[ߪ3XZW[N=_Q -}^/jkY^+/(l:7? O"V' Y!B!B!WJ)\B!B!B!NMU3tJB!B*&S!B!~o J !B!B!B!ďNB!B!B!B_ B!B!B!BdfB!B!B!4U2tJB!B*&S!B!~o efB!B!B!$P!B!B!B!/)R !B!B!B!5*B!B!B!%M ER!B!? %B!B,B*B!Bq⚋JMݙ[-RFszM|=EJwwgk !B$*B!BF h󸶉HQ(7 {;!%1z ߵ1 cX^B+B!M 3~ܙnNs, ]G? jm{'B!? SL):6n[=Zw-T[YN*:UVѩ[Tx*<:* Sa*LƩp* QT(JSTȕnaC98>k͚[[[%3cowV|evvqsɲ:nܽu|GtB dK叇leEɂloQߺ""" ]Ÿ' IfXd6Og"""""""_߰Czŵ<;b42xkoӨ|,uK1?&ǗѷЪTy;XɣoFCÌћl.xzv*"""߳WXa:<l4$Mb8a8|Ž6T""""""ƄStZlTۭۯX;]w)#\Y2Z^XPoN@f 85G*ƅ?YfV<9~'""""""".++kU[~_HI2.%UB6M v *uڃvB/ˈ<ĴZ!caN@ ks{[>maLL 4{ &W=o}.Zqf uʁczXnPp~բP(T  N3 z kcv:G').jqk;aI79,-c/O1B>=ԩG4eg ſ  jDޱZ}SM hVI?Rk%#5(e,/ӽT[]aZ }3L>{DDDDu/N|&~KM2u v1Q *<Qb̌Q>Ot=>4=bo)ƈ,;,=X`wiN0hgV~-p |$&D[եsˠScy3OjYa[`Y| FÔd{ۛt{k- f.Wc+y#xy& }v`|$f]]iP= /g/\rir$[!>ew Ņ%Rh/v79l9z,#K{j:C0Ә-32cG#E5I]vk8Š;8Vؤk5lڗXDDD~jWR u}DDDDDDD䘷On%)Gȍ[7G x:9yuO9rR_߾}{y>vsaR7G}?>ʏ6ͣռ9ޫ|b#_)C13ghY69 k7ǿ}yIhP׎tŏ[-^ܞֽX̋kGy~gCogWŷ9<7|΋?29K*X"#><|x'urWA=z}/|vo+6-,.K7?v7wgr~vx _-Nڶz:7"9,_\M"""sɦ=+A@ckm1Jcz Au}cVU$-Z["N9mWﰾbxNdvDY^F:ضe>P$\ot̨PD%]FZ~ [grn$a(U*o_LBb^3s,*f:A% JY m:ܫ|kZbMz"iu,{SS#.LJ=w_BM+<Ƨ[1I,bDaPg`VuREOn`U,Vm_?zֹ}10y 9 ?7GXm bW)KIoLśhr>e^rz:d@t>Yӯ!e8zܣ *g|9a|`ƚggrm'D=3봂zw_,3 RxPM 5W]ʍjs/)g qD?:]f:.HX9L9b=&W1) M┹$bA@YDDDkdC~F}KiTo];aI*W+K1`ޙSڮ6iJ,8dyΩ}FirpSokޱW$!"ÀH q ]y˝Tjm6wRv#JJ9:<ǀ1i2L$6uOK`@cf5M߫7gz!.9fGY|189X6^X=qg`szsE3#L45`&Tm6/ َ3nvzƣ[X^8gg!wn">q/@ =?K{/yz7TO/}hyN<97fZ8~Fw@dޙd8O*ƅsCL&Iq=MFߛu9O;ŃeONGCf%krm9KB6(e5T*eXGd7vYLR Gy;+ 0qNkdYX02!%`6H."""?WX3c0l$qL|K\cO9CD3\ZGv.q<'1ۤI2;.]rltfvH~ALtV0oc.KXk`0& >f~fgtxߔ8ss3q ~[ A~tşNW>y7|o}]/"""?\izM?9hVl3jx7t5Y1~ 帜1t?7' q `G'fIucBNxE`)uڣOtۃ,n!vti[8SJQQy{Ưӭi(Mf\ Τδۧ;kVO.QԦ^)Sia6?M|0&jw9t֧oqia3./37`.~`h6$j`.{nCiV)""""""Sq˔ZȜY:]3Ԃ鲈zVi%`vAXmDԬr +Z!٨MDu4cGZc"שR$\YҥVYgAzK,wvdܺO&>EZƀq}ILH'e)ZvZŔN]bZ6;W;}"t*Ԍۤ}p/41I\:j椿1ǓtMFe;Tk=vv/G/'x+E`1rыbo> k IDATUʦ"""=&mnK y0r(ŕG܀c%mEDDDDDD",EȁX*奒1r(+Gy:yX|,Tm,K+Օ|ik'oAKWgQZ( KB^4ȍ[ Z|yqa9_Y]W>ʽ#>{vJJ,/%ct;~/;Kk/// ErJ7G6eҭݞZȁ||cWWWTq%8'o>;r9r~͇o=KJ|xPWY-K?:Fq!_Z\W6Zb,p#?^##_כ{smOft덥ʗ >FJŏ|ƣ9ϕj:ހ8IfY6b 赪cǮ<P|<"""""""rEЌH^< K &>#kwx"f.vH+yv [)77;TTEX[aтNL2ڔi kڔ\]b {yۏ^x>s;w;V/xt{dGloGDIֽgQOJ&ݗin,Rup֚4F?ILcwxi\dNY96QگO;;lXdOS2NUZ[,/ IG`ۇq] wnC-}1ƞ""""""?*3TDDDDDDDDDDDDD~H i`߫|'֘PcO9)ifa+s ׹QݡP(`Uo!4 LVzWYg D߬"b,|D|׳ȦZ}`Ͳ1kn0oWOkb`U,V*""""""ߧ5&|RNŞ/N%UB6M v *uڳ)vB/ˈ<ĴZ!c}\1>iFoQ+Y01m/%jysr/ݎ3 dЮS\˜rp2B>]ܠ](PpjYkT]Ӝ/$~ocgIDDDZqz7V ļ\ {T w`X<3cl?.{}u4~.1"˭t0X]lsDڭ "_ <%3Ɇ cnu2/>O})R8KB; }k"O`hlor{n~ E le=/V05 (-&#IluDM;|##l{ 7:7/\ri[!>ew Ņ%Rh/v79lɤ2YڣV:n𷘑>a.zI첳u_VaVlNoP?WMgIDDD䇓a}}}DDDDDDƄ{^Ww%1VGobҭٛG{ X:' c9_Y)ko߾߽<?0)wfXȋqWj^j~w݋|DY%qMZkF|Ҟݜcم>9-V~tu/Z{Yo>;||uz}<e##_DLF~x7o<9Oy4 gN>6ec1_{:?v/|vo+6-,.K7?v7zXΗIV[ Pמ]|\CW1.Ԛ"""""""?vPm̞F`zE<W iL#;n(\۶L`ԧ-ΑbMZI E[PbݥO :$ϼݎI>P T~[nqIř\v{xV@Ucts% JY m:ܫ|kZbMz"iu,{SS#.LJ=w_BM+<MJ-,c(9Y\mtX*"""`iHZ}xN3cZ`[,SfhS~tTNO ç}1ƞ%tejOwaaNŜ;)cp3ק]mӔ4XpSҔ!`p]% )@fhD2HS8[R#lnVT!bĿs &CNsU>^LZ&|&$#F@8]ux ˆU=3 """}q\P2Z3b^هOkp<>\1ct*ov}-wv|];ȨWt^y6FLVVvtZYӲ(œ6,+o[5jMvsw1ŅerZB𥢼21r3=4f4<3o)W+Sy8Ӽ_?&?zwk 5,P Bzy@iu*˙ >{[͋<'?C:3ws [h7(z_>sK#mFdOq{mŒK8> r~yl,s}4$YL+ ,A4P TDDD~(W1.Ԛ"""""""?,P .,򘈉=MFߛu9O;hhhȬDy[8g^HѦ޽ٽ5ՠ~z3+eXGd7vYLR Gy;+ 0qNKh^eaxk{YD3 ckmzQqEDDDNR0TDDDDDD{7Q++tϠSBr<'1ۤIq9olrltfR$9am˃ arm\,ޥ\`L2L90(Lг7%Nǜ>LBpЧ]aa5=q~Q)JV6Z( \DDD些+zk 5Kűqj~$˗DEG;^*2޹s xXfC͹Hf_]=aVØSй1ov|0$1&uy˝T-n`owm۸~@m/w0n^?eujKwx}'[USյ6oP\aUo*f%v< |<柿>eՏ}ߟp*y^o[\u LUAƂ1f/c70nϟ|n@J+?y[^cvw*ƅ ne>2c>誦CZQ̳{X`܊HqXqD\\J/z  wb_}@ЦLCYv"gSnGopsu +"J25n?zA<]8orn*n⚌(ɰܺ8j4}>nT2$N3pgQkM'MG<{i`b|O>)59V8f;JⰺvG/bFƍEJƘ4%:X^(2>T\5[lE)+yB~H(>Lq~yFCú}W Cc D-\|U~zi8%'"ZcB=EDDDDD~nW1.+*׈QyߺM,F00 Ԃ/W'_qJ~T}n-QLQwm,Ĵ\?d'ʶ? O`s]*ԊJĚqpw?vf8hS =4-Lv4h|t!FcXNHs0jdMGӧa;,s~\eifş'4\#N?YӴF;:ђW0j-ꁃeZ60UvL '?ZM`N?i&QnN\#pmao9vG{ B$UZdƹܟtۏLUO1]ִ=FtveڵݐzwV=GOzdm"y9?rgX?4ihV\{|]ʩϞV{ɽx|Ljȧ̕i5n oW߷(.,%M"#rcfK1-SvUn>檙Qt7lF(mƆ5icڣ%,ߤQ>L'5NqLA u@4c[42?Z8}wm¸qx8&N2p3ˈx&½H?_߲>?;ORY΅{RJP}%rk {0 G41Y`{S[/})1C' , B<"UmM6ɗȍ;UU4ثVYne18D Jn . vz \˯(Iw;8^.Wc+9#6~BVp]gFL;E<cf#Q;1n5Y~Fl|at)aإ=吲eQ_~gR`0MHzY7CAx}ja;i?0E#,KP"ўΈ!_/9(.Z01_Kg= 4d#Ȁ~!"yE?.jgF,# ,Ϳ%N;rvHڔ̀a_Џ1񦗹Vl{'Vc_k 5]ɸ0|Ͽϛ{AnxDWo>y׊F5wOnF|g=9zNd?y^wc9Q[ nyo/b~ER?z;GqR^KF­|f=W r0[},Ho\[ KWGjȗ8iU~gD}?X^/oNzc'y.Y_^zvb~g6|1'?z}Cg^_vό7GNe#X:qyqyOnr0c>vs!#_<{^s6M9/ޟxUk1_}Zyσ7䞾u6~Q~~ff=/`M IDATLGG!|a}cO7&ZHoKL۵g9s,N1gmr3׏`7)ah=:X^;23RkVYdL9:uEX (C$tBLp\Lj|m_kL*ƅZ3'`^Z {b>>t|\5B4,Sܙ&0"ܙg0#&_[ܽ[ٟ~DcfLehtUɒ L8#ZC$=ZL{ZӜN7 7?q  8xc1/tvRq]keM3#KzڭffIk0d F3~`x񶚶 GG$&9 8yj,?3k¸Oa~E18aI<ς1aKa?H!.ɛ ?0=Oml`###bx^5N߮/|09-3`vH"1 $">L Iw3)#g)dοcDDDDDDDDd [UQklq(x~ZJ|:0ɦY-M~G_g4s lb'eJ& "!+<3;_{)ۿgn6?9K/mt,FϬVZݨQ.nVb{503##T)ן{0}k 3:l2בm]Lkq?(->aFrp:̣WH!c l9G}F cٖC8,0,[ \_9|bZ6V#Isꜟ 33mM^Nې ?5;=LB*A~^ҠYD 8\LjisCdR,fDR "-iǃAt_qL)jY^S'J2 zOL(JcizWdUtڌIP-!nDt{j0k4QȢߞ.`NwQά[u읯S9UqHc0 6١3S;TϼGL< YmHdXo w*-ʵ%?.bSnSQ,ܪ0kw,_kL*ƅ Xn@ ց,[+S}EQkaN3F.zC&S=%b4"ʄ^ N X#;Ѩ~/l1dzW}fP ,ަ\ .όc7]'nSS;uM_2_k4I|Nz@uzp۶1Hsf2LRDm@:@;q|8֬sm#f$2^*_ښtص>f 'ٟ?L&Y5\ d4+ڴNθV{=za ܪ'fE~Lj|Z3*+[r40<7Ϙk4~;:}zV/^tMjx6р^? \; Q2gg\577 a_i4t x]gɺ9V`GѢK*?O>=3~6y zYxxutCRN=4`<$9r ~k{}s>Y̭\ 7#~̘)&b0$>@fL?ޟܳX&+^_FQB N*zRMy |+rj Ís1cHs[Ժ 4~֝,2SDczoiwFټ ۫e >^BNqY!ZT{FMX f֙9 <*/}1gqL#njEP<(H~U|r ڑ[$D"H0 ͦa'6#PG/}FVg߮KwvJsg1"߮78f@נh} TXdI`qJ?,\$IVߥMҼ3f6t[U*Q=uBv֫"b5C-;D)woLT JhQ1MJס|Q܋%H `] vkb5=oc<KJJQ/\bc=Aj,,C빜~!Rqa4},Y CSVϹ@ϒ3|:$dTݮK>28>BE%tF"_$]-KW=\_J|wGؕ 7ab4mL:nv$ALƫ.ү͇%LFkF![dRT#u<;G%1M>ݶAyF'_9޽'FstMks(%-wjmu?sMLzJ{~3mz(ןrEV۔N?mu }~˞W掞^=z?'b; $ۛRy|WAg>x^JyoopS()r qӎ[`1bsm?o$?:]hH$ɳ!`cH$S2gm\J9hDD"H$WBo|F[}@oBO]:vw>Mۣ?~Ӵ+$Y}O>l_{.[=ڥfR{mt(m73{z9pAѮ$X?$;gm :1k#y,?s]a ѐH$gcOT"H~&z 8*H$/K}oeS ҩN>7P6fCs \kHV tMEPQ5r(ܱ\EcO~`cCcK ?˭8 P*A: y|/ ߅+:MQf}ޑM6''pF (|>]qXN%d|,uQHw!jD&atFd2sx)nm >Ή<2,2M $Ͷ8B\gðCݧROrQ|;;qeYTmߜOoR:<qeR{;cq$2Qd(Jt:)b!E(-fg0X 3"6,g(tZS1'3{wc"hh~q}@9v=Q~'1t5 ۣa,iؙ؉24,uftfHdBesOdB%-& r&vb1Nc|CݵW\^S'H/2[q=7Df(7ڎW7ÓF!/7LEfɰO2hsZWö_z[<,oWuMcu; ClAvȨhH0iD2p?Rb`ފ(mY{OG%>f\W>ʈx3/gYEˑgt# O;rfK$rJ$D"H$D"y 667¯%id/j(פ5'Ef)8 qP,Ndc]ף=s<.yQNEOD64kw^ F0-,+2qfD,,a'=E*|}6WdssD8+%ܸuS){ (fɃmrQ*iZ4r*c ̛a;̵]@1q9e8R Vo+fb;RacǀGnzk 5:բ^py٦n06bļ'Ep8ZӇyB*@*KplbԬTuP4~5'߬J"WbtߤYdgT`vr>^+IޠjR0utAμ!del1Vk^i1}Epr6!K1#Vwijܩ5X"u.ט0+06W z6N"G.YO.f\~d6]nPH(1^~LP }U*06;w}Ͽ_ TRugd^&B~0:\Ƕo:Gɶνo"OgGum.ް1%=o2gLgb>s_!q7vi5\($]}mcD-<ǩg#)?}A yǘ{~Ƽ?sz{0*= qWôMB]~AjCJD? a擔aJ\, 匽`͙>5tsin InӢi_rG%t"ٱKwrϧߓ~/DΧUeK|ƈ^I˜-,utݸfB|ЦjSL ͞ wJr»x6H$?+2LD"H$D"H$hvd70Be&`ⳣ9j^1m6QBk>_aWc(s?&ɪZ67{%acj2hzנ$_<B04'{n͢Я/ˌGWnR>:w{^jL)Vj*Iߑm!hW|)^R:cѝ*n! XnpO~E,!ǣ;}O_.uk'ճ:Y>|Npxrշr`i9]0Dbp9b-c S$H$D"H$]^P~{>Ih{ޜ\e/R" \Fv \,+VEW~dI4 ;@wk݀npLˀ._^U8WN{^!i"y;F p-ncfy<5j)=2ކF]^od\[i tWy6N%2?Pxn|Qk.q4XX:49@ksG$& pݢi稴~c¶3w[xn9"0h-0ZS-U1=Z `Z97v'x&*AX67׈rx8 %o Dx494ixXi+ Z)B'׹y:ܧ~Q^6|iRJfgDm""W9^}Z*X_u趨ƂyZ#QY ;F͝;~˴׼;ܧ׳*_ݨWғΝJVҳcT!M%4gSs6axMv{!+=fK$) gD"H$D"H$?-:قCb;.l#}Z 43Mn[*bϣZu05yt}̥CoDQt5YxtZ|^>H`X!z5XPx j^B\a ^fOklsE/-B6|:x3B=$jrUa9^n X=Y|Q(9 >M3|>+NJcL4!k=G>9 \}г@nZ5Բ6%la.Fg{ l$[$ ɾ%>܀SYraxM* BX9gf5X"5(J\&H$D"H$^P~{.7q!Ea+# ,Rsq;vA:8dV|܁H+xsyNT"u|Ł ˠE,TJ1Vߕ86vA(b"Un>_q6E,%F D&CwǣHS" DrDOMq1X7BI3G^Jw4~ݨ99㾞%} f~ WeDxx](X\Lu&Bq ꚚW6?c&-lDžBHD)NE27^rRxL6r}1z5 ƃ7x_j<^sM"PbJ\IDEr3#'ģd@xR$QRJl[uIZ:m%<Ȉx8&HE7L݊8TBQE44>zձ؉Tx*-6)ɮ[Smx<#-NVDlGߚ88ى^ɤ`9^sT\r~hKPmM+q}hRiJFp8هv,L'ETA(ь8"GW #vг<+TxwqJot*)ۻx+|ʈÅ]%ɋ߅ky>EGA`ccG#ך:S6؈84~l *S܂>v K76 .3FN=NO=v-c|5QotfNy3FXp"=N>'{l&u=Fbsb1wD03Bl܄e@!a[KCV*]"YWV˒w܏|B4aȣ2jPU(k:>jD[C6 f.;GbQ'p9$آuQK͎窔1Bt6s4cʙ1 }= 3G˭KM^=Ja]ӏ|:`Mr3qkUۛI"jϟTT̑Q~T=7ۤ]>|蠓 guZr1rf<{,$U^<ƈz{C1yGeqǏx>d3|@bzH>lj$Q:xn=\AVT*^n0Άhv=a S2=9}f& ~=Efo_WڌCv9kcDn #8Dzm\w.^å<6in I G nz<-CUlנsxwhd-rB88hϿ,QZ?FU*Q$ڴ?6qaUz^O7!b) пW\wܨ ethU9V+QnMjSs5@ܡto?vgJdϏX[\'XT_bl0sL}+3m%5OyhbX  KI?pkPX}^)&|R&B]CfV+.=Ĵ4|h~11~&^7iBpOǺVqL&B_[-x.B/ 2FQzg-NN :5ۧ[؎Asxfn2,mA!) GWw;&"89sU0eܲQ2g(̑Yu>eyaP,D7QdGR5~}Ol8wٮ(~n\HۂǷKڤJ2ӗWq}ʵ&nZ85hQ1)ipZ!qtz-+- n@b~rTU'}Cnfє2?1/MH$?/]:'>"̐/U(.D"H$Oϓ|褥?3EgK%7no\v+ B[Ǐ*8g;QQ}2}DlGDiwq{^J@2GVL\ b ml2Wi"F58\(1}|yYBݙ囔P@{|ٷ{"FgstQD(1Vquv$N.G;N㉳vL(D`>G"B(X|-X򾉃" &v۹8kY.IZe0bX%-&iUv(?1{x+,@7AO|; !9#|rVTJRI +1yv"TQo/D:4GggDDE]W7" fX]l0@oɥnR: ]L(PSɕu%Ͽ EĶsQNEhU/%Y*8;>X0^|z(# b;˽ NJ͂B"{6!3,g㭨 yY$ߝXQV/"B< ~|#P 1&r~lGD֝Wd.{Sx{q(.~鐀H9o ~jotS pS(Ir)ce%_֙2+"@V1whG[1u'b; 份D"H$D"H$ kx+OGl! @|#7<7DXJ bqPͪPCI]F ln~4b)'lt;JR=?ᙡyJ|iRuX"0+4*cL@tfWT퉳+E,.uxtv42qX: *]iN>u~%ɛVhId#ouf/SRE*cxj"O.@v7PKu+?MT3Kg<"N; 7c@C(L5AVÝta$`&OdסQmr(NWkB؅:5JHbN' uaWklǮPT%>T/Mz#%b 1tA3`~[УY:FT,yGۤ@4G)o[ _cW Rs+d-du0O-奾 ߞD"H$D"߅2gtiԚ|3c!ҭ!X~ eN/;kvYv]ߧ ¶JJL_v񺠘.2)X9E>^c@۱fõi ,mny:Jt C B+XP'k:T }!gu&D~ףըQVT*T*%`eM HLڌo>mg@Ӝ6?%᧬]ߥQQzOYYV׿a1=EBߛoz@f@4:>BĒ!r^逰S`g(:'@9RDB6޴RԶ>7mJT:iK}WU52,1m@{3mJģeqP, jLL' 3 ) IDAT?e~ձ*8!YQxcD"H$D"H$ì36 "W(FInλZZIrhhSc=FI" u0/FA7f=Vz PDf(HdSۡ3zྋu na/:Mux8*?ww㘦͓&~z, ÿ=i7Fhfj+B`i^UR#dnŇgfqFUE"&K1zz=J'AD-x2V4?巿E E1&%wvb_j/ӨиVH 9.˟;̳iVZ?$`!Ϭlo+9} z 9yтpHZXC|SD"H$D"H$˳1I>@ibO! , S*حV)>bl3+wO[${x:\tDE[j-Jz'C'\%9Mmaun 'g"kop8# q?|ov%KvZjZ%3GBkC=)$Lc9SLŸ?Kܟ]B6i\<ϥ{2[ :5Jrr}jSM 9g6w=0'%ݢtݢ.bC_>o9]j̹d8~ [vOss$D"H$D:yWVҎD"(Ѭ<2w{b_TYwsiK$>sӡӃUU-Oe<0@]UmɍUB.}nnH$D"H$DY+gS$+i6(N )Ft1R/Vϛ&ZDC32Ǩ\Aכ^U|IukYQMl?Škz[*m/uuxAAeE[Ao^: ѷHe4չ_]:=\cGJ4P$Zi׏?A3f۾{GnTijy#0G8}?ȭ2̹'& yo6mߧ>\^j IJ?ݡQip$1- elYE*B8]VQ/%S\ɛ Z`mowXf.e01\-kP i_u뿖ܴwy R'yA^/)H$D"H$p-c_R bxYfBnCz@c[k/Ԋkbb X)Z-Rj@&oX3[/*y/$QnS*{mZ#"Nl>WsZ٧56 1+1蘆wCğnn+0`2$' mܙ ݳr??;>|ۙl~V!GoώNBWKcbvh48;YLeZXu%":zncBs)kOW_7 hKMg+?pZ~Z5aܤgXY՞Z64? /ͩ?ISҼVH kTOߍk(X!EroXkSn|_ÛT9tT$|)[^RVW;Fn&qsz'/MI4B(7|(樵'47;?DrV)浻w+3,EЍ=o^dJ&Zw3mrH$D"H$D"U}- WnS'FV TZTx){j_$=:M:YǯO6zm\3DT+؏kbuϾmYky8O РY_"URV }{Nk"i6.mOS0u ]ھ7O(dG0a}tM&$rT 9={^;ZnOX%m}xNVWC& #Nr]R{޿u:9tHZߋq:cfY|WO'oPASt۟z(x[y퇡-8TN߳o'mC\N[tYWMێSefc4\љj`\NNWL wY c&fBGGw9rC(Gq=S =:_St!WY;z-ih= e.|_lÒ /mOWٻ4ADa@ڗhDLuH@:0`:$tXC@TȃU U$U$U/*b9`Cƪdyh Vu'c{/mٖ]朒^{k\yr&GYq /A.$#NEӾnjM3X;`h6_I/ S*Ruv5cqRfo@)k"7wɒ Wx4C[õLG"_֦V8k0[/QוE"yX)Rq= {sK)RJ)R䳵#AL~OVbW.IP6 ]0w~s69[XH!^Xk{% Al-T&/?t(; kbx/eB`-.V/_[+O0)[  .^̑ȡ-I*J0n56R@X {ʒoB\A!^P±9YXYaZ:/W @تBPv I8@ _Bє,Nܟgr*oH;7/ø#][eq ~z>?ޔl>9X1^A }({4h(8: K-jCq$K2{ܒlւg3%މsvxMueq}Ov aCR痿*$!uuW a8Fs 6Z?r/Kse3#&{;2| enq].^R`gM$džW 7_Jͷ-vtkm~azjKp'>z&}|Un^6fShn |c;mRJ)RJTW◿r?zM" L"4oS/SmngUJ)Щ)vpfwU64t'ŗ\K{O Ms ߢZӡG=ٺ.R,95^H7tFֆ^{8wڄTS)RJ)ԍ͢]s}//YC2N}uO9,/>??}~SS?OQk`wեO qYRJ*)RJVF_뼓75UP۞J6+.ˇiz0}l'Iܠӿ)ԇSΐ~o XJ)E3RJ)RJ)9{{{4-SVffc~͢K_6/_1}~^xo i;<_o<y $)kUX֣Qkt:-Z54sL)u Wu>RJ)RJ}$MmO0mcߺMf3%_NE9Ozm'^dOۥ?dw}޾xH&@`~wO?`'>,ƶ-:7ݓfc)mBRJ)RJ)1h7iάcV?ƞVjk PO`z}De(^yUJywvfzO~/SJ *RJ)RwS1=x(&[[W!azij}kV4M|VdBNR6c<̖OPx02~jvL@׬KDߙ>KT.rkMN*5}:٤azL̀E$Xk_3pr@#gxek11}$Wnpz4<C~q!klWMbz~/0ò]isT?b1nb=P/qQp-=Z"Ddxz)}ɧw?ׯ46\/~Đʹ4ocgQ xSq4d6'$Sy|?p-7w7仿4\&ȯ{ކȏ (5sq'C(*xLbi)Y?i_OIhWootIg*;4rz]\\£uUr{ǾƆFhaKCH [L)F4RJ)RJ݅C J(Ҧ(6HhqحQ/%#&3sL9D;-[Ϗbh_ႜ-Un +'(ǒZʪlC0u5v0pb.9?Pv7eu>| _ag+BjN%iL dqŠ2l&1@~0tcIRsq/O Fj/l]\yCi0D~Χ՗zǂ][~gcPmXp)+Y< P~1sH6W\Gqսi7)=E]]ezy0v[޳ve)l%.! *9EP UJ)RJ)Fj_~%F^o8aU/ho0p3gXDlc֣Yo2nw҄IҨ7'̩"9a.vE0`i\;nd`R|I6Z5K:ᛔ0DǀAA{r̊i0 s,\ddrާ[וN_1ev\*}L.x_᧿ rupVjS7 ;CZ^/N<V@"d&l.ߤ^3aTd:q"X"Zz9Ū7᧽nyxg'xxOo ?7̏/ͯsm 45'R A.#Gۛbf|Z $<0vD4O*߬ܤ*ΕYyl/ths^Ƌ3{˵RRJ)RCQ*.nyy5)0fb4O(wh\Ķ-x~CZ Ȅ Rf=!0 ɛs뵆*v+sDӦ%鷇op\=6b$<{$ΐN&pzW+EB$4M`@x$t0y00MVF:{?Ӽa:4 Z.N4`mgAK 09Ű.omhhw:t:Ze)՟N`qXKVK~)?~?O֔yX1!+ 6dL:߭bV|MУwiMLnwNùܨ*3Yu=ԜoywI_3)3'JT_g]ڤ6TI_~b Zki< ˏ^A1h'vhiv1rSJ)RjGs{{^ߞ IDAT|y94n=F[1}?Q5މބN> ELyV߄|Oz% 0{x:i׼~}q;]9f(䩼xɳGѷ` I٤=!͠kxvf??RM^f9_7.Ff5hNy6{yЧUΐ?)vFO&K47JC5&=ie~5wu|h!%%/>ӇA0 ɒ$8~1sף7x\}EǞ7i}Ӻ2YE=9*/xAlv U5v>KKK,-O{X]K%}; d;{f5fޭ$LڞX"q1߻a{]o=ߛ_`^w4}c3\дxL|ZCp͕a/y'ܺ<;g'I;f;G?azv r׶u//;֦ `9VͶ׫SbAXKgzd?~_c~o2?_7I1f!-R(02~J6[1S;e^]]O,go0)BoU?;Rf.Ԟ3Igf\W2Cc7GE)RJOO.[~ܳ:ܸ~vgV7mڭNϾdnI)ۣ%g{Ἔk84_A{ӎ _|}ۢy3Ӗ$rUjڍ5jIl;MDZ_6p>'N 2͛ Y{5:C3tffi5a{9)ϓxe~n @=^ IUsAN~qc;Хkg:ߗU>zV+W3x\*CM,I7 ظ"Ii|bnDƵ &f B"_AңY|INg;{E5mYNL:oe.D%ra'˴MJIrs'x:ŌK׀.ָ{69 d_xtt:ui7ܯte7^j>}R׾x.YߩQLg&_iѬHD"RQ= <2-@n屏#^}qNDeb2DdXI-ۧ`zLb^=HTFij+|||ӱcckN_dRJ)K&lllz.^gg3χt17*?kT~W+ Zu ֫'<$D\ꔓ8 I.+VI--lt3$9..@pdM~%^(W:ԩǸsNqy[ej,6 -zsB-sl{)^(RwϿt+P?9\^}Zdb a={9$]?0Y2TNqH*:Y{ݦTs9Prt)\ >B)*:JKQ$16d׆|$(]iL"Lo<&Y<9'H|' t|咽UiSN|mldk5.w.d 7m$էp\l3 xY)986z#)umn#gf7 'YCߞ-R((]w2~\"Ieb.$3ɺz6[NJ*iѼ.t7߮p:}ԣup~I$$6M%S;!3BX8AKg $ x'i ȹ9 "_|F$tu<%QoD+,Gr/^\ s+ћn>"cb@X$鋬RJ)6}=$F^h4n?~MX>"kY"k ~3< J.W=jE9̳`,t3$`lC^D&Ĝ2~ OiJgsxAAqxו 1vlz ө̦.L5=D6SuqAI`Xs{4Gnc>Lsԋy{%ɥC|t1v6+dy@0ItlЬTΜvl"Gם#^ݞE2`+9rA~Z6C{$#D|E<өIDRa%=?$/`~y>z41MϞRcK}LsF$3:-LQ/du(3G;벶/G>?HIěڐ/6SO]\W$f S~0ڜA- (!o|M|q$[[{xv񽕨 d0;~Z潂AW$j eHDdWB.:D0²9Vh9ͅx%6mlH{n#KQ.[pMư~ [pSBFT g6x s~Dv3c?y-;dgsGnFyKpkABBpA'[59d-\ܺr*%c=ܑ!QY9_{+"FxIvDDec>(ꡬ MRJ)`mޠQb`HxiKS!ձG2g IF@B8)H?ڞ?,cQ zGdu\CakaGl]Va euTh-YzaH0x<&ѐW ζKX@qE6=⏭ 3(eۖFTVNJk~U&sK(X,&hH<Em Iy}}Ca exݦjs~^hqg·r++q(|z~{ϼQYz/-݋YiAI</?*{2~aMDdw)<ӹ ϫDY Ɯ/p+:7*Kk67uZCѸ%W9Y>{`lNR+rܟ˯3&ӿ8'L[ɧZ~ge'jbj??~gH~|6B~sΟ޻G_GR^[ba ޘ]ou9vzm6!O?  \\&_i=e]]ez}Yw7z{ҍᆤ%q1k)2g,&+/&Z[ $$٘Cψ)t"Y2-Zc/kUpvX:+L&<2]:@ 97÷NsD&W̌-oImB[e o X&V-As2$AV3= $y\-UC|@ ű9!| 2 ZjW(`TΙ9$t\MBclnv>wpέlTj /KYNxuJC~?OXOT&m%rڹ3Q.0_,7Բ䞼&*SNv*RJIM1=/HWi/2 wyj5#d֚4.sغhJbuqK6X1RKk4[5ͻUaʁMIŁ|.FNϛz>"s nh',7im[l?N'MC#rSc k)bId{IcaeV;3JA/]:=,W(̅t:}2#mvחu7 H-jPr&S^_a>4yVo[$Whֳؾ˥~~E &KsDC>z=|"k:EwlQm\^9FneyI#srcmijw* ?~hF[l.20-4yѮ?wA|_iX*:K1lKk9ϛcijd;ʍ8SUqOQxsEJɋL^?% {L)IIp򬃲wHvd}mUV ) R(, ߚ=8ӑ1۸AYغխylzoRؕt9\A< aW0O % a8rv$)bEv~7~{ѕ 8fZ\ o]Z_xk`7(;{'ξcߎg,'Y'""iDztÚ!37RH?|΃/8D$\wG]G0ք>ХX3g?3Mo 7@ /˳&>LyCou]᥉@H&_7qpi${)G;&{̽N_w_.Ygi-߰ORORJ)RJ)RJTЙLVz/:T2InCtab.cFpnI;ӠVh4i4sVk6dO]w5^IbQI[>L%2`n9'٨Q7h6ԛOyJ@0dge>v>ߴQQ1y ,u5M,7MM'~2J3>zuq~Fm7`ql 绘n+6mgn{n Y77`87p0xE֚6a0wg1m htni%g ,"vmAz~"g%C _uAymJ<JrL,+?0M;RJ)&6GTJ)RJ)5hN h,,/ UjA^|̫lJq/g123oV4:ӪhmMiLG1/)+c>27Mdn5b # SnJ:-ۀNFXve'Sl^; *峁vL}~Z>,v:$!v7 v5:ztZ _[%qa/2eJ u"4/yCTxgg) x^&HD,|&`YDNSZl _u0:-^Fh~s /Q.:'eJ&_>͑-Գօ,9$Ǐyq]l p9>,RJ)RJ)RJ)a΁lR zJ!`dc|͔xA…sF˅PvV$aeq}Ov aCR痿-Y]x4$~!`7l_ᮬ/ m$* ƐId!59/$"Gp?FP±9YXY۹C]X+ FlU#zeŭ$$n\LGD`5&V#"G{QX8(^P8&=Κ,-I,`xI!{3=c/~ 5!W%f{p%TLB~.֪,E%`Hy)mɡt%!C0²3an| 稈ȑ-qŝY)RJ)RJ)R#"r_XRJ)RJ)RJ)z_3t*RJ)8UP۞J)RJ)yEp`RJ)RJ)RJ)RJ}4RJ)RJ)RJ)$霡J)RJ)RJ)RJO UJ)RJ)RJ)RJ} bRRJ)RjjS)RJ)>ohjPRJ)RJ)RJ)'IJ)RJ)RJ)RJOGD3RJ)RJ)RJ)R=CRJ)RJ)yxlr]RvM<ܹ|8i|;ύZsRJ)Ga`,&+UJ)RJ)q6=өq<bRJ)RSEP{*RJ)R~Z!l̨<ɧ-Q^=OdsJ)R *RJ)R~BƉRa㾳fݠ30ضm0?RJ)I*_rC]Z2\D% yW#mz0#E>z-ӽ|Z#<4iVpLfJoYSJ)Rwڄwn5Ky4-mjI;GjRJ7v3W'kO]O7RJ)n粴٢QLpWq~A9ıLgEp3EjF/z4>5I+`bM*&=٧]͓tli4ZM]WY!vX>LӃilt~r>9k4k (gID7}l7M~+f\0=L3I)8F/ 71T/˄~J.9,ǃp@ND6bb," Z kuO&01}.XUI+B"S>WQ=f١x<&nݣUɑt,|9c4ǃ s,qǎi7Cyb Ϗ-_r~(RJL d,>kq:H;sɗV)e#;/J)RVMq=[RVD y]+#Dl?y|\4GC5K$/)5%}.95ZE竇<{u# J_:$ӽBٮ$k>ݦEĉD;<}5|\4%w;2.o*v$L7~ʃKq> ֧Urk=ݦky IDATZ88~Γ_doϴqsBgF0\*E*0Ϧl$|]nDA̷y!_b]Y/@7 Ύ4Vwz7Oyf߻V0:_s'm?yW\cQ|G4>숍ex)tHZ㮵oy\j+b'|HRt]~\cZD,7~ n#Z)R2vLayyy>@7uy2#\{!)5x&q1brpWYJ!M{5M)RJ݋j~:mϻvE…z_VbU;h_RAĈ=.sA ]ڔud=%Az{+5J+c_l.J1a˜wftiLr! a] PHy#KzF|LG;̭ަ,GsnLtk?CYA0` ;c+H܋!sZ\ 㲲{&'rS_՝X/Ԫl;I!j r~_ˁl-ņ d$:76~| [[`35L7,r\7.cmd/܏% Ijm42dXѰCqzFTVRJ,څ0^F)ƍX>Xd=Z!yLK:aߩO;}ND33 :r C):pX^Z~4r2/ߦOXcCd;K\' |ӷ0xvNؾ4.uM5?w˷DLfFߦK  /]~۩ԣYΒpF `lzϓ-W 7iGN2OB=9C~N1s<9<5fA`yY4 D2T]ygg6RJ)R~J2#hZd F k-S-&ttCubVg)ώ4H(gܰ >xsv@弋nhMX+H+d#ck^`jףV Ε(-'TKT;7+^R531،dɧVs٤T|[Ur<o*cӖ*jf?Gŵ-s=U/i`DHgk2Bnm+_8^DHmPH[G^Ӌ)g'_H$OOtdi^:RJ)7YiSRo"D$٧jR{zFsqѧSJ;خkWI(c+tdܯx MtyLJТ/t0=:Soh5]qq:M_ջգڦVoFxW[Di]܇/Mih<^dIdfд)&zDHICluhVI)MzyTGϯgz5$xK(tj44Y%$ӣl 'Ь=AL||(RJ)ڄ@Je[rХӅE6Հ^x0tG B_&e3KX|no˜$bgx^^aߤ| sn1ߴ7znv'$.,nbo7,CU /@ 1n7hvg2lIϴ41}6Fmk$nM:9h1]'Ojg߷ޏ^$Kƒe>I*RJ}f.*fJ̧I?{4_<.hd}fV5vfjNM~<}m]j(:{ rnGDx6-ÃJr.A|kiM[ɏ]_=QuIfNks,xnf-3j4i]$kQC"˓Fv%Ͽ6xQPSvid_QΎK5KRz N5Sr ʤ 0 %k{2ǯQ)@AA0Fl0߮Ӝ>/iHU0>|Jn9Kmi.:&U)W'~~:9Գ.yr$c rBlΣW&%O=cjV7XVJ)RJMУhlvtzj+{jZX֤y<:@vhwe[#XEl4hut:@5 ?č<ۇ!U!T,%, dZHoׂ&=qx,s/#;8pFX&a-Ɍ|N+9Yx2ɖ [}`e;YvÄ +tnZn{ ,uU}O=ߧ=reݎn׬So%9mqf4 Xsץ_t9t~|£-]:ae97 DZ88\:TI< 4Ml;44acʻpόX6<[* /%C;7t(IXx4㧎οX܌ T/{s(t|՛K.9%(tn˲$ea:.C~""苒*dUHfDϝvN|1N^<8]+SmXwc$AI`Z QN nT/Sb\X*-V9R-w$EmTmQ"Hc&1e+`x8R|"I2~#14E7apbbqd#Q㗫4әqa;1cаqsa;6&oȂ {>ZAt*Ϲd $=0Ĩz5Rt,yq۶ta[uf[j65}M/ݼ֝;,&nFʐ)ڥt \ /6@Z}^+faNDZ:w_^~/0B }dUʥ"Ba:fGpe)T+6Xw`8ފv-7;: jR9&]_huNʌňV9B:sqgMk>qoE"Ѷc, H[O;ȶq y3$rc K䊵7s]$s hsonA-KD[(Z^% s?,ZzVDDDDneR%0G翢}^fOV5CYvj9|t#b0_8U;rUwNrxNF훗9~j#:F2VP$ʭFAΚO,c’e|Zh,M oӡ^N̞Z7jUK7!>3ob>z4inȲ5Y|\ډ/Īcz.+O쑯qAtu=,iwX>o3Ͽm|PIZ4ͩnj-? 4%CEDDDDVV\hԊhi a &XB4+{Jq`gU+X1,{Kw*`c_zyS WCKeRYC#R8qi,FԚNs.`bPu2P2T QJ&ysq^,KiL'ȑ IqbYN%ҹw^S ^^%yJؒ7DVQ}˜;]Jq\u݇3xZSڨP磨C#[{%&V&BJ:a5}W'C&/AV+ǶlzŊ:wdwOε3q2ݞ[)֤f6nB-F+UNݬQ *Yb%WAbG TgH]G0BU>dBmF_+S,sAsd͹7eCyo\#+ e"/klЄeҶh6l1@,àee wM,D!-RsT e\e}WZk4WtB]RDXdD|7њ_ɪ;\RV6ZLfَZ \YK+C""""2z;vllh5!a1$"`WX4;D[ ۽5u;@coW= m+{pvjA8n71a.1#nt ߣdII&f-S{ZA} eZ~+z5}Y}3q.Ch+""""kQ1{nx9{ЮXRJ%ȓ`wII%7\(kْtW82EAG^!^`+{\qdJ%qNQ![w$%V2zpx0.n"rty\2<.jQvgsiƲCR2 D\rppj"l5g 0$vm粔oĽBWEDDDTÈ J> Η̗4潷_ ˶VKj@pyD#*bOP²?&muEǧC!wak#Iv`t)ʋS2 DD,cmM)M֋н#]V$; riMr%WH˯#_29\^R[ShSSv mI.uvb,.֖oءT(V_"""""fd²~l2\,6w}oKUz˗ 3airFs%$.vdbHڍme=Ҥrr3t+bq\%~;_O XBr\#LpľXr)'<򺒑R|nrC,H&HqK%8bbI9D壸v'1N)/]Dϵ}qa.*yw8I<7O8F2o|wy.w^:M.IOID pb6cľ*wWf;?RMr_]D"k|icݮ_GLnr:MiV f\B +( 'sO% p ߯KeG,y3I _Qx5kCïQ9םs29vsKM?4IK"jxN 7FwS*VH@.1\ҹ䚖/F\&H8u+إ5 , x )aYt!?ͬ1\zB6ɌGN%ݩ%wP?ʓgPsu>쥐_4PR֘iv[P(4ndf%(_<_pm :Ma6уM ~EDDDD}Z6~v~?V%}bZHPk`$\7B!_Z8tj6|Zpa2wI,z@xzL n&L)S(!~ fyT:K.㭈kd&r [,"R? tbK:%vW "R?mDlvMfIKG*aH%VB!L[@k3=gÊ(:7>ۏzݏ?߻yz+{oߵoM;ypwo|$:lw;x;ߞc[{s-,z!+"""""""""""""@ 3tJEDDDDDibBŞ""""""Oaą%CEDDDDDDDDDDDDDuJcI=CEDDDDDDDDDDDD䱤Py, FRlڨPȓmq*CEDDDDDDDDDDDD䱤d<6zÞȰ2TDDDDDDDDDDDDDK%CѬTDDDDDD>6*&T)"""""dF\Py,)*"""""""""""""!t,6ϵŦAشiqJ~s.4֦,-/eIqbiƈIJyw6oq͹βQLMLR忹_鄃mwR M)e6}\;4j3 ;e.܋,%V +u"""""mTLSDDDDD6!CN1.0lc8.u;:Z:S:WN@%(;VcmsWb"K`Dw\hxcn̥Z\3qbˎ?fD7u$^MqׂN3$8;ͥj;Y%|Zqv:1L:t 8u$~5C|-?G"ym0Fq8V iq}j""""""""""""O*CѬTh ;@+į8IU&]Qը>AIW&gsy>%+~|){і-$G.}74Z?.Ksɒx ~YZ#,_=?h}|'h軜L/?95?lIvFaBleYeB:u^F87Fؤr< XDDDDѲQ1bO'0B }tB@`{NgUc,qf@o۵ K+7!R%8khI}*usat';mjЗu*Lw vJd)I+ [ʑXCr@/ZZCPsnFd%UN+f+EDDDDDDDDDDDd }4'>E`D 2u4 +e0ʮ|9GJNKT?bF0ֿ8DEIۀr0N&= A)Cµ&Î$3j4jR\TINVDDDDDDDDDDDdd0?cӦMlڴ/۽ġߚ|2%&6m2JxENXapI4teʕdY1Ms0^X֝z@.w-]9)LJfTʙujdI IM%.xPSϴ {<ن>5y:ļ;-:4O_9 4ݤ`_ rTf!B=] mo@K'EzG#A;æQ$N#E&"p{ IDATb{NB?ΤwkN ҜWS*MaeIn^#X{F`FɛU[#~S~J=C%oEDDDDDDDDDDD*CѬTSEJ2圇-Slb.|.zHv9MPJ w+)vϽ>w~rV-Z-0 k`J{תuR>xK9k|,GX: rt %JiίZm5iv8OW9 WTZ Q膄!^cyllTLSDDDDD6PfPn@Իq4ePH-JP4**]vr9 1Nfp&~Ds)+ _5is"F7~у|,c$r/Q-8NYj2O\vG.Qpg_? c݅jy}AW[BX"WO'}#Hv V EDDDDDDDDDDD}J>:n,5OCt)em8[K+нvKoXҰIjK<+/F*I,-Ԫ)O3,fMۉiQ4]qDs2Vb:+N"S K29^Γ)& gth5B.߼Ʌ\PZTkWޮ} Ng:m $ 1Zsݔ_ZHfNSx78;p47i/ڴBJ#7G:cfO3kr0V2TDDDDDDDDDDD(%CѬT5A<5_ O#8-% \dD Rrvk/?au- 5eZ8ɑK/>zKk`Eyc_1RKha뻱ejVJFPo!,bݼʐ&Gr:ʑ_ԥ" Mq.Ӎ |0cw$Ͳb`;AР\ ,$ɸ,Q1bO'0M^7cፇ=7Þ<U;vLo芈<6*&ThtwaOADDDDd螺P~S>!EDDDDDDDDDDDDD䑢d<3T/a/]Š$"""""xC䊈c e }L 3TÞ<Z&رcÞ|FlTLSDDDDD6P=CEDDDDDDDDDDDD䱤d<6zÞ<= {B{2TDDDDDDDDDDDDDK%CѬTDDDDDD>6*&T)"""""dF\Py,)*"""""""""""""!Pٴ&? IknLVh 똭2 sW9cnN5-״}anĦ?&=dMlTDDDDdU*y #.Te<檤MN&kk}hV*20#Wq\>L'MPP(M{B""""Q1bO'0§0e9$3ÞN"MZ_f!̍{d*u:l$mZ6N"C&^m-Y$+ gR#m-oy5ұmN勋cZ)Vki(\lkS߇5jU;ir¡5?V$1<2`hU;*G.a>glY g (9rCtffP=ZpW$k֥YMXGi.qP5DD;ąY 64gO1$~-|~VkPIydεZj]9ȺmTLSDDDDD6P>tM*4MvS+`6*)ΒͥIc6(eҜㇿK䣉_Ir!p d擐{RO'sWx/l)Ng%2R1B]ea؂_&͑hTKwK.ږ*a,G&#>sk4hݪq)L!w܅,\D|qӹWOɕAKݼ䅷Bi{FY)=葛9.FyҧOvꔒ.ȖKY]:f5 }IpZSXYFRW/Sb(/Jة2uA mR^\e}+ GӅۉPx*g@^h;\sZ^|"7*u<4VPP(YԻ׿zA$-NY QWCt2 RVyTlTLSDDDDD6PY+ `'}mǂu:$3 'R,xFb,\ԷAXosW)Z \@6 1@9gÀ:ibY;K[Pqp j1 Z-`-OPolͧ>>-jҁu/uEu2M=`6F=ޅL`: YhR&(ǩ'REDDDDDDDDDDDMЇl2NIhF.}#YXf:*[4] FO)fY@V \tljACjAh$:0LslgeH:n0ИY61j1hJӜ7L林z9U}7Nt/)JBEDDDDDDDDDD FRYjH%{L{#g`9KA~#W0kx:]s[#iU$p5+ErIxl%!f[qU F_PMop*#H(*"""HبPȓmq*CX,AːkǎapfCihl2|կpL%n% B$2Jaĝ~u Y}L9잵4ٯjՠ 5p2Դ,VUoky1-Emg5PJ72>dȚAvFRs=FZ}lk¡ƉǠ[~"t⬧Tvpc|<NZ'w:@db]ZCYvPVF%l5~-f`sQf1p\o2ԴmuZ}~;q|3̂wY\;j2[(7y:CvȆ٨Pȓmq@Px{)Gnjgk!,{PUP&3jptLU""""Fń=3e+$Wz0fo/n{=ρ ky؇b7bE9ea-y⃝ZŨވ4pS a."""""""xOw4Δwj&_۟x9S3toˆ902I&9 7˛o^Gar m[2 =iˋ[{#""p=uo= y FRlڨPžܷC{./r$'ϓ?0ƽr""""2<È 3TDDDDDDDa!""""2TDDDDDDDAi7kD/ӧpp&׿OK@WO׼_c$0{9p*rW;#o8N$Ϭ>M'jEyͅ-g8~w>ϟc}K*vl_[}[v3$GȮ_bHht/="N^VgH,^|e?/=2G߽pI^}~}K['9.o۟WOr~f`L}K/a_;ϐ*CѬTDDDDDD>6*&TdȍO-mAL77fx][S燜?O~O}m_C]> F^btNS [0ʄSMY>#^G8ӂO׷y\IwNov[3St5W۰̕law~u^Ňo1u|1s%31 }C__9FVx󜟉}+L͜x|O䷯:LQx(6m趹53řrq>xcnG6{թL3;X0f&Ghńƕsx[|>yG_O'&3۶kK>wqV~phh3uy^!ۼw:k&""" #.2"""""""hh`C}z]f/#߬.JTO~/'N`di'ș֯짜o7==`]g+31j͙l `_= oU_21m,gg"\iC9&Y.ľ#̥7gh{2{# '6u3GȮ{O:xd>!?μ5En%-|;..iy3/}w=6z9mg ;3'ǘ̞8eGS#Ⱦ.'Ԟ/21.>X6o%B[oc.owϹD苓3D\&wJEDDDDDibBŞO+So1)( `06g;|8ԪQOePw޲*y&v" =ZטYn9S7xjsں/9~y3#lx{A0INƕO|ɿp0\"s>'I '\?aS0&VLؿ 4gKlٶ(]fΧ>;Wy1֕f.lcL-nf@Yk7{j_gޜɓs?niLZ~2&vm#BWf;@mm[Q-kntg0x+|xؼO]c/O}DmZ&WDDDDDDD?/KU[S~Ȋdѝt$~ OMBn>O/kKM-2b;[f.2ͷ۷rkf6ug܍f̸2uU^-|ȭUlΠշE17Gfгkq`3[V^0D3x[7킱e-;| .+"""(:f""""""ٴQ1b'oͶUiƞ^wac\asy~8(3W5&x8k>pμ>9~_ хdL}o+|GĻ{*"""F\Py<3:90mmtաk |,/:ʍ[~8ÖӦpCf25Ӆc=5rY.3d|CVqn9`g"tBt0"={r4x-5ƭnuen78bױ9$b!"""xg0gFń=4lkW2lcOA1~T&c_5n13ug"Lܶ pffa[$טKu9,sc [7/]*xSgVs*m >\# twf&m~xuv+r vr IDATY?E1&-]W@<ɭMط}岯[1W lDs6d=0vqoϾ7-Wx}_3 gm7yobՋWՍ0q8V;ϼǁg?fĖ~uhQz~N)'&iscjl~[|OCowWuWwrrc [L]evxnĽro?ϮW?dcIDDD3Pe0J?)Mvs-m)y0A6ΦMc noM.>}$OzW|k^>Ugo936X32z.x`}ϝWtbcyۗ5ncǎ78_ux*Ka׷=;s3gt cG~opyΟw?m9Pua C.`=z\\p]fa&:l:>+묢(,((& C@*"U*ӋȫȫBBrONlYc,$ן'؝_?'<O /?uȿ?7=]2?2w?n*"""r1feY z4 z,"ߧ߫aȟ~{CH1?y*.UF?74}nX<`T w_\hA:f-ݤkYSgСVx=Ę_)1ӄ~!,zIp4ۯL KE$Mqě! wCZѠRf `0P̛$WۻnvQ9rOo,B dyܸB0W1~`Bx#-Jyaئӟf> UL?-~r]J0&.: !|JIG}mʠ""""""""""""TY,V*#rVMaaڴǯM ܽ5oHqҢVK2wmz>缳Z.^L%t!m^<v@l0y(TDDDDDDDDDDDDC )a/b/G d8_=R}L␭cGy֛GFbk]š}l]A&ꠖurLn[nJ<)JNPLҤOa=UbJ- `irgX$}s9v5[wn(jfx :[T9 """"""""""""@bhp0p=7KI5q]Z%T+vՉ%$I6@n&vSc~c~Xt͔.P``Tۋ͠Fk\Jm:OOy.<$qċ]r7:g;DDDDDDDDDDDDLU b26 cbb(]mϚ:+^lOy@ K?℡;x 5rz~FNC78I1- z{5bj˴~ܢUĭnf^J1 EVn6DDDDDZPȷmy\e bk7erawO?޼0 jY,V*""""""WE=EDDDDDm *|T kIkJV<6QuT/ Jk^_]v-(#J|&wtQ9rOo,B*"""""""""""""ג"""""""""""""r-eY]v"""""""Y\v"""""367/; 42TDDDDDDDDDDDDDXTDDDDDD {|fjd\K*ȵ4U1u&|&oTu)sNClIߜZ .;Zb17W1 d,ӝ![Ɯ3q&IM>m}DDD r)""""""7qDl79 EfB?~ʽޡ׫ᘗN=]{(Sp+x/;7\G)eEaR,opNOL㻧ADDDDDԦ*/W 2:OT2oթ|šJf"WQkؗ|JTQHhG-CEDD tyM?޼DDDDD2cvfDDZc UﲣTk^GV;4kenqo-<[ '\0&1z ǶF뷺Z t%\;iX"^A=*. _(N^2hVNApzIL^+( s%Mc:5'ob΍_Mke&t`liM\sL9u7]̃5C:*5?<)?}mj >K_=OЮS)8v}М m ӨRykVnЍ?uRN yT'~L'hT=0Gߓ.ǾDDDDDDDDDDDƦz)J}f~O_pp2Bn/"1M7Kx>Eŵb­-w`An|JvJ?㗜h~~\4!{c^%lXЩtC p![/~G?%w(! ix>_[X/I!^KccQlVYLjJڣpq=+ x6"U_A_ޫ!eR?!CΔƴJ~z:v ڢۋI-[T]{oX.'1Ap.M)E'/GD |[Y":ig `MVoUpm\{Ҿgg5R &$}w~z<'u*W'AߞX\۠"7- Tn?E\1!Do}ǝ0lC ~r=JNBޏ>Qԧ9=J. 'ZXDD]TNxsO9䅙dY>{6-}{gnc1=[__,d@Jv]re~Ȟ(d`dKYvsȀX^o'e/yls0C'x=zfcPr^Y WgoyN lq?0a/\ts} xL7E|6?p'O^wgf 7G.gقAFa={vSy=X52'ZF.ϟad'j}??x;r?&ܷsxڣOoG[{t>x|z>V0_d2l eO2\KSM/ I:; U /LT/Zf*,2$ &OPiP-*x17Ӏvwr%,cI lJ @ӡRq11A8iyʍځQyJUyi;c Ejʁ&nm #Ӄm:{hԫ]nP[2 NsעnS9p0v7jy 6lwiO I[F64}!#vqu/}u G?rbAD?)tTa/ '$ I4`$Nk8|<'~0.y0M5qLA;_s$Lc'IOlߟ05X"&4iׅv>Q<-Z8$EŢM!m^- JM\Q?sH!AF  .z!QD  }Hλ)<ÈB㗼[^1\4""""""""""uiHTvTBI\p 022'k'Q"?fgޚ4RvR!ov'w6Q2*L^"I2ixM?yG~=`$'kݓ0 < ՠeC2/͟8= &$ax~~H[!qSթVT`44vdG ϲ?Kg0.<ǽ63f """"""""""TЙ,Vzen}9a$]x,rnzɥ7_fW+!guJ?<_XeA-WN /OT 'L?Wf3u?rp>s{RrXYO ݰQ|-Z*c,<>iWJKiJEkG~rf]+7x'VSJDD z""""""2YЄVxݺ{i4ہ[m#L&}^QYĩ.*lwy<7Z+9zhTpƄ`2н/'ccyU5{2|ހ5i)}6y~^h4hBVH<]_RLanfA}jУb,ݡӪ)<'uҮF'ϭiYS~Z&`R,שSDDDDDDDDDDDQjfXI%JZimN_jHQDgRDZ)%^@'y{k@O:^4O>Ztl K=|^8;F69$ҼQ4DL8< ch*!E ǙzEaD.k.fأDE͑kM)}=( 7vF' OK]w C#aL(SNw1xC=zt,^SDDDDDDkyTЫߩPi nVaA?GB²m,DS66^hzti4kl4z'Qx`68>)qJ,9EJ%+57@ίO5O3SPr Txh.PVJ,0iN@[Fo >e hsq٦w|< LMz4o L? !f1A4Galtqt?QxF޶1m Ρ~DPxjwhå\'`R,a'}RFjAءٙr^־ir{ԫ擀7aEӣ4.("ӫQ[r8k "b5:: ւKzyŹ vNJv)|>sf8BY>a/)U#]&`T3Si 2n X M^VpVAsG|_XXILbkU^@NWnXz{jy΀[`z<h Q aA ۣZq[.QwQL W\rDc!|w))0`+ٝJy[ שz+dU {[ .Ub'Dͱ٤bOǩVYn IDATV`Z{m4۷K?t<\ۂt@Ee~hoʣl O3ֲG'Jf@}7ð?X l~a9qyl 3fOf!B聞e2 ?}x(rFf ڝٻ07l‡+ټadm>{}x-n玜leµβ,{ww930I7ly #3rrvN_of 훉x zvلX.g.ldPn>bFy>>ySCyg!{V4[٣g'|֧B63mp]wٳ͵gȌlȖ6_f/V3}}LL֖܇?}Y_ By=|,.+"""""""""""""Mf,+rB""""""߶YSCEDDDDDDDDDDDDDv*ȵ5CEDDDDDDDDDDDDZP*bR.*'T)"""""mE^"""""""""""""r-*"""""""""""""\ee!"""""""""""""2k*"""""""""""""TY,V*""""""WE=EDDDDDm 52TDDDDDDDDDDDDD%CEDDDDDDDDDDDDZ?:ssEeG2-d._7ãcqFkQ;0-TlP)^jbbmBsl{8yh?}j?1Mdn.O5m4M|<6pn Cɚcnn{!5{"xz?60ǿX; $j͑: *3 ΍[ NijmV"ydn۸~f{4*g_=zIܥ16{QDDD6666MNb ٭qgz̎.ldȞYl}>äݜY#L]Όmek {%""""ߚ {8#=M6f#2rlqi9[^^r=c1H6AjadɖsdF-.-e x;f/7lyq>3ew6g __4 rndO孅 n>X l~a)[ZZ {X֟w?/WX̖WVC{wwO[k1o9[][6ѡra#[(0泅ťliZ1=8҆,{=Y_ocd,-xfw|c1|}}gYeo;Fƃg٭Ep6>EOJf@f?>7% ,{w?[10;oG{#[[Yg>[\YֲF8fkG~_>ݸR-}>dG>'s}5˲ϲ{ lai9[^^ {;/V:\ͻZ}Xyy`-[0[=ӏ("""_Y*~e\1Cٝlss3\ϖͲ%FG?Ms_ FT /G+lqr&i^l~n`mv0*R,)C)d ҭ'ۏ~>X L(j3*(˖7_q=-ƅ3C?Vsc.z~8oz([v G0ֲ/yvki\Zջ/I>.ͯ>8Tp{0xX"W %+,,d?F#ʷWF監 e;BR<-͞~{.{t/Nlmw5;iwuJ mP/,$JH,Օ66(ۇ0pju*`7LujFʡ;5Z![)fCXhWyf"i=Ի""":JiWJ|&^rk㛝_"""""2*<J.M o6yRmw mlĢW)9#N$ގ a0*΄9%X5&\ ](Qe$L w3mzey` |fbm @.7@Σ||'~׀aNL,Π~xDC wlv#z;Ns }3RNGUp?Ll{n2y ަѩL96E/|3r^҄[iQ_LBbv~ CzE^ ␯٠G;XCU9l&ъWƀ hsOV~8)yy`'Ä_DDDDDdvҘvD(5;4현8A@>9Rƶ'cbӽceOIlޜut(@' ~_LD䙔 891q`S,tlEv>L,N2.;ǺlHC6|c&Cec2sv\%w!Q/"w#܅/qqG}?.ޙǓ3ϙ) }ɁV ˜~ ٻgx:EwǶ x5~k9:`BMZڽꘟ2Q/Winm37ef0!M?Qm]_l^1$hddy 6vAI@ͥ6x?IocD^\4*NILL~IDDDDDҸM.+ ۔<2|^r,هG^Cg-]$*j7]~l/Wp9߀wt+Xm ; "`@؋!9W@J ݯ쪥uʽYouy95"nmCaGyN︻0n$"""gir;D@ﷇT琧Ob`uw/;Ӛgاz.4&^c,ެzKcho5<5)|K]v"""""rEq o<.}:NM:57d 11-`%3>X: ̯JFqb `0813I:y-Z/`,R?6L6=az[$ L&㑽I#,k"""rM52T]%&^3c v v>AO3=:U$ Xwz/~EX~0| |v veJDDDDD *aԟ1ǟ4_pğ'MH10 aDDDb"/4߄<^COHtkVp,zJgxY֧)5D4M.4XGkZ$Ӡ\vb@Z7C 7 |arp>F:`um.ѱ:Ϭx~ &7~w3]J<0$hwL$ :;QF=7tZQ,2j9؉ށ9,h0`@@P G$ĬIР5.NN mK zy`!J?k}0lu&|iRdȵ3U1_~u""""""rE\TNt^(ܠugRz5uW `ע{D> _Ns% UZ`*%nWuRϴ`Cv@#;ToIRO¯WX4`V|(;Tki{ ڡ5^M1A. 9`]>KܡZ`E@П,ӷ:_7Z74h1C;4ݖ=*!pS.cK>aq F[k)% `؏G#/O᤾j9Ψ GwmfRǦ<Mj$j.C6C OG_dc0R\ 52TDDDDDDJ:e²NgW{Rqȋ/Xu`1)J%J#СnRqs.k~vhzVNfu]~|u)-CN;ˢW (JnB7AUŝ[lj\H٠\6t]7C74CzakqvM{}c@v|ϵa/ wfKNi}6"[/rl 9}ENb=q9գJ>^Ѧ-ZVm`;?x*):cȫ{SX?m*vIզbȕ2Cvww?/O+xĭ% {=]CSj4X[HbhpΩ?-f@p&̤ϋv[5k\:ۤ׻Í~^@ڔ<#U)Z>u ~D?3)VDskNcS8rVyQP=R3KM7YnyyJ&7V҈O 7?' j837]eX#whfx&{‘p~?[3+F;[G:+I^1{l< .[ysBQi}զ xps|JG/`5-?]i@Qϗ,,J'|†g10&/r>a3+ەQ[7)< P\f¨KEPki.˲첃}qSnir6ˀgc\ɠL:KA zw_L_v4L{<Ewo`DDDDDDDDDDDD.T#C~/|.*'T)"""""mE^5CEDDDDDDDDDDDDZR1TDDDDDDDDDDDDDid\KSCY!""""""WE=EDDDDDm 52TDDDDDDDDDDDDD%CEDDDDDDDDDDDDZ˲, d_p[/ceG#WW=]n*"""""""""""""TY,V*""""""WE=EDDDDDm 52Tf/MI8o2c _5ejס """"""""""""2U1u""""""rE\TNSDDDDD6P#CEDDDDDDDDDDDDZ:JEDDDDDjPȷmyw3Cer)hichx]61*^YAzf>!}O&/2B]VMiaENEδ:~J8|slԠSa[CI55QJNbR\8ֻdžmI!?Ng T|a* R%q_ӒpG~Zŗ}Us!' $T j@i1ڃLiqO嚧= XsNbRtY}BpM_8V2:np=]V'nuq` ^UkkyiG2.K^eUk+gڡ&샣\ە9Pd&˲/.R'y?M; `{5M_Ȝn$nCEe(id>8e {6~!)ɝ̌fܺi8nۤ\' $TJNbRtY}BpP?Y++,ŠL U!p%Oe,8 鴃~Qȵ LSVTk I :8\^/Ow>8֧wnSݰ05p Bzn8{6 6[ z풂> =A?fO+:$CkT~S[%ڍ6hgzv 0ka(g❶% 5UkV՜iG\?IC}PcvAmKBuC5śɲ,vϿޛv5eZGnY<նJ)N/C$8VX% \ F+:Je {6~!n$n,˲i{]iLuo.*CHc%C'1Y)z>!}O&/2D24eY6 /70Ee(id$&+\O' $THJoSQɜ֕L;[.ʜ15z[i$kfFn}ܙϮ}!ϜYlaA^ *m7gdk{q )3P+=iӗya9eK33mZ:p]V'nR ?ruTy7>k wUΛ婲]Pn2\+SZ]+4WX^Y+{ƽŇ\,j~aڡ+W]T5EWZ}9P/>JYwxCiWwC R+|0R+juDBIfGOTKE}}*iUےWZgGښ$jn蝵Ud͔2Ɗ91DCÕZA}I7ST[JRpW^yi:nhUA=TpKT"ŵU]x[C?+hk'E_EOJP~R>VحhQp(뫳T %[^)҃jSA\;>@qU5E 7'D-_YϛbVXwX –:ݲ3rTZݽPGt@qW6fY}%aW~ kޱr"۪$ {MX+(:JP,?igO9܂J|/ŊR.w:y;ju"KEz_ I[לvT<Ԗ9ySR(|__ /w+>)%J:jXX/GGTO\DJd%_OR~^~(S?T܍m" Sχ7IFϼ?Iʔ_NjYNrϳGd/f-{IʌDsdd,eOrf3)-g٬ϻ<]۵lPf̯dod'x-&YI7Y}6od>dlᏏެ?nM\rR&fJ6o({Ŷ1|~ݛU&c>r^ޛ 岥oh,˲79e\6:_wleNda%{wegn;8vNy޾\ϗr>fou]gwe7gl{{/g>۷o#1 tۅ?gƨ-}}kb_g,2)ys3?8ϟgwLƝى'5˾<_r33]YeﳵRnamXٓgo?/gK9}_ev7X#Z^ߟͤl,fxqWӾGy2[>˲\&3&i\>\c ;J)V88,ղ5Te8W2)UN K9|rEVUXVkrޙHZjteԪ{ڿvꂡNKၝ47ՊڽXʪ֪ˬs}9$ߪQKzso~(p1+Pg,TWd]CJc(NCOlFPLAA&Qj4lwבkau$ɔ2#)bmu_ի% '}贎L✴mjwGmӵ Zn4UiupKơ V1UUVKՎPc'O˒92o&4;IkG\q8H^zuVP(U.y*LJXQo3T;<*c1]a)zPz]oCĩwwSM %*LҜynJd.OHnI &$ %=oEP}I~thȜ\VAKR?zdG2#×Z^ xqD8Qx(Ap\גETܟI՛xۦhvP}{# U=hP_$R<4x.{‚{x IߗT8EgrAm+(GT\*hn[F[v_lT & y`|Ћ8Qܓωx*aWN_Ҩ-uwZ:]ۋFےqGa㩢(@=j8CנruGm9Ó7jqG>лT~!O}qOۘק$CuPPARs9A) c%iz+IJh[cJﺪW[*ۓ亶*vΪ6e%J@ Qex(񙄊R +QG:5:>$CpU QCRM/> }ސv74[}_(MKOIR/M55QoYd,U=&xRQ8|;V7(7@8V*KR/U8&QaJ:yɅճG4H?f~bF_Cٓ4?w$*K֑ă5JI[.i@vWRIRuZ] *s7حgF?.DrgrQmuaGMݶz䩸\c]QhPՏJy{D}8o_zS)c]6Kx/yyTQt8Î/R؍eG] %vQI{ /_N\o?:˲dh iK&oSHOڑ'e>Տ;jꄑ$U_ +XQVeC_qf'T'Jzvd*o;j`JNdR;Ջ>nV|הhfFwҧS}HݱhIWݤ&g "{U AWaV7*~hh/j"]ٶ:'Y::<ĝwު~IK*v8ᕵoٛ%1Ulo*?֘cc'O :tcuQ9ڸPfΪjkKQ+SWb0ɋ?)*RV+_ X~E QPފ*/sիŴnӹjU/k7 U+psCjQ~t.EAK<_naKڕ@姯T6UVp!q^4J1P>z'Pnva*:TuNI2'J1;3_[UeK 5MVUWٟخԋBulߗSPQ2TO:>L@ꙙO>(h(d8,P,+ Vڊ4*'ob+I;:mD҃ vņ}g{9M3GKc̨vkpU]yjz}_?T':b0UxݜIn |QǗ_QW3<ɻCe4~:઻>!}O)ѬZ Ml.{$vQ/*I_FEsvmt1 IDATPI2mUy4 oׅI Jbr,ۖNJ}}UObhh;xpm5#__BC)XqAZbE1r+-6f՗FѦjr\UToFYnڑo,N:mi@bCU fꥣ50 y zT ߨipdowIF[>~}qh(3g[L>ٽYe~ʝl6gdFn6[X#{lPf̯e'3_OgKYP&)fo[}|n8? #3rln~1[G|,ف̐ٻ/dž1{r'[evc-gϻey)3ml9/}\XʞYe֖Gm>ne,'ekIّ8||qo)gadl6x'dzǯ'bͽ{\?/fƁxf+sʔ=2[;p e/?~ɾ4<ۗٳKp69{d|Ȗ_@c?_?Y0,?;-,֞~thۅկғŹ,gY.?-?iOy<Ӵ?NjƑ{۵L2ng=o>,gk F&l^ʌ./+ٝ{d?~^>goe\flۓ3<nd%[^9#3\6;-]|ݸ|G=Ǘʝ,=u\&ٝg9:+_4,˲KΨo^]$eܛ;R8~g5eYv)Wb՜azW/-S7QNIO*qT?M; ] iInR" |%=ӎ:%S?mMf={s5g$&+YhǘWB"\m' &ZZpV˭P`B큲,SOA[?tڡኚD9C`aCV4Ż5/k E5T/ZG"gUCK_zTO*2.X$>Ugjcu%`ZJE=h~G `J!mw ӎ7sSa%`Pb}TXn+j:p,!->NNqLj>\Tnj4% \An4dP+:Je {1PTT|NrpukmECc>I* J&/p"Ճ:_d8zk+T)\cIC-PVoPAfG.1C)7;'`JOJ2tPQqAHW^_I탏|nңnP~c78 ?n7Rԋ{ڑ /RP3eY6 /705֜pՍ d=vDP7P7X~mq`":*32ݺi2"{fFv5GShPVwڡ\]RafF33iGp3]V'nRȔSZʊVVkin=3JiGU_aXe9 VY89]V'nuq7Uy*eJ6{-]v<`n0Q̌JxP{F3vEdȭJÆʾi, jNN6T\ٖ)Ӵd{EU['32K @n9c*5c ->uT+y[RCIHZU-[nPQ3:1y7]ՓҰg2gv_[SА^W/׶d R?VU=x^^ÓS:uT/r } \Z+:po4P?ߖ)~ޙ^wݿ'WuJWjtKk%NA>C!m2gluK(JJnAY[nO蜧jUW5KD*C'1Yu3VCOo)aIE+R\s,K<3UУu] _։J,뫁s\4ThPqe66>]T5EW+]U$=E~CHMĈvk꙳r(yQFvr[U4'cʁZ݊>#)jY^E ҶJz좯mj=\-XUfZ~UҰGR/*s)Rך?S͓Vn5oQ^|ӝ]U [_,Hi Hҁj[~ _ՃduiEbv|gߩi5TTr8ƾ'`D-l&f^~uvoVfezL͖}+ټL72W&c>>˖gG=&kY~n9{˾u^|;IF&y#k}bfHY~qjgKyeWϗLe\>[\y?<^=_1{y`役P.[zʜ2sٽ{۵l1LR6{1i'KF&c);ˤծ,dla}vy&;isF[~~xf/iџٳ;Le +o};l9ݗq'K!eғ2 eduvrKOv(}}U8_4p!{s5L$&+nŃonEeאXAݖ:RՖEw괎{t׎Rc=f: ݪAV瓔+RZܑFj4654UҜCڑu_ ^QH; >IkkZ VZ=\1i/Pבyyd~m)K+ÔeJ ?v- U/n hiJwAv;l$V蜆)ix~ڗdʲIl K& y?n$ G,XGK{ /_N^mt-|}@Ҏ13$RP S DW?z%U*E9jJyxRHڗ v84ИT6uGOVI[odD&+A/Rց<$B{%$ueUKF;S֩Z_b s㒃2]UaGn( Ս6hsC°v`XbΊi/}5[nKfOrPR_a-P`Y*6ڪ-g.Ox\M^PiPGw{βQiO~#iOPuLilYKw;|@Z.- $SN)ՖePiz>٫ɔTKAZG[uջU7'Z(d(WTg;z'J4+;pc:J*X2MDwο~sPQ7:셑zl?gr0 *:|iu=&|~ ;^u$͝:PƇG>w|aZw`Vdhp}]f7?:\YFG1 /*tջv?Sﳞ,:S+rY}$c%C3udiK 5k-}M /tVKmjN95}Yiz#`j|JuAΆjɮ~nBҬQVzv4jNTVqNh74~ 284&T79Vh(K sl[Jxb':LiNYU?fMdռ*R+:LE2JsOhN[c*#@K :eE?oQ(}9V;7Usϵ u-?k'ȳ^URp5glIF9ն/{EUi8m-q"}h()>ԣYٖ4Hı}QkUK_)uUkSQrTQ7+uܐs+-6f՗Xdz6+v*P__q org/testdata/$(basename $org_file .org).html ./go-org $org_file org > org/testdata/$(basename $org_file .org).pretty_org done go-org-1.0.0/etc/generate-gh-pages000077500000000000000000000075601361136365600167100ustar00rootroot00000000000000#!/bin/bash set -eu examples_style=" .source { display: grid; grid-template-columns: 1fr 1fr; grid-gap: 1rem; } .org, .html { border: 1px dashed grey; padding: 1em; overflow-x: auto; } .sections { margin-left: 2rem; } .sections a { display: block; padding: 0.25em 0; } .sections a:hover, .sections a:focus, .sections a:active { background: rgba(200, 200, 200, 0.2); }" org_files=org/testdata/*.org go_org_examples="

Sections

    " for org_file in $org_files; do name=$(basename $org_file) go_org_examples+="
  • ${name}" done go_org_examples+="

" for org_file in $org_files; do echo generating content for $org_file name=$(basename $org_file) go_org_examples+="

${name}

$(sed 's/&/\&/g; s//\>/g;' $org_file)
$(./go-org $org_file html-chroma)
" done convert="

Convert

or ctrl + return
" index=" $convert $go_org_examples " convert=" $convert " mkdir -p gh-pages go get github.com/chaseadamsio/goorgeous cp etc/_goorgeous.go gh-pages/goorgeous.go go build -o gh-pages/goorgeous gh-pages/goorgeous.go go_org_vs_goorgeous_examples=" " for org_file in $org_files; do echo generating content for $org_file name=$(basename $org_file) go_org_vs_goorgeous_examples+="

${name}

$(./gh-pages/goorgeous $org_file)
$(./go-org $org_file html-chroma)
" done go_org_vs_goorgeous_examples+="" rm gh-pages/goorgeous gh-pages/goorgeous.go echo "$index" > gh-pages/index.html echo "$convert" > gh-pages/convert.html echo "$go_org_vs_goorgeous_examples" > gh-pages/go-org-vs-goorgeous.html cp etc/_wasm.go gh-pages/wasm.go GOOS=js GOARCH=wasm go build -o gh-pages/main.wasm gh-pages/wasm.go rm gh-pages/wasm.go cp $(go env GOROOT)/misc/wasm/wasm_exec.js gh-pages/wasm_exec.js go-org-1.0.0/etc/githooks/000077500000000000000000000000001361136365600153165ustar00rootroot00000000000000go-org-1.0.0/etc/githooks/pre-push000077500000000000000000000000261361136365600170050ustar00rootroot00000000000000#!/bin/bash make testgo-org-1.0.0/etc/style.css000066400000000000000000000050011361136365600153350ustar00rootroot00000000000000* { box-sizing: border-box; margin: 0; padding: 0; } html { font: 100%/1.5 sans-serif; word-wrap: break-word; padding: 1.5em; } @media (min-width: 768px) { html { font-size: 125%; } } h1, h2, h3, h4 { margin: 2.5rem 0 1.5rem 0; line-height: 1.25; } .title { font-size: 2.5em; } .subtitle { font-weight: normal; font-size: 0.75em; color: #666; } a { color: #fa6432; text-decoration: none; } a:hover, a:focus, a:active { text-decoration: underline; } p { margin: 1em 0; } p code { background-color: #eee; padding: 0.05em 0.2em; border: 1px solid #ccc; } ol, ul { margin: 1em; } ol li ol, ol li ul, ul li ol, ul li ul { margin: 0 2em; } ol li p, ul li p { margin: 0; } img { max-width: 100%; display: block; margin: 0 auto; padding: 0.5em; } blockquote { padding-left: 1em; font-style: italic; border-left: solid 1px #fa6432; } table { font-family: monospace, monospace; /* https://github.com/necolas/normalize.css#extended-details-and-known-issues */ font-size: 1rem; text-align: left; caption-side: bottom; margin-bottom: 2em; } table * { border: none; } table thead, table tr { display: table; table-layout: fixed; width: 100%; } table tr:nth-child(even) { background-color: #ccc; } table tbody { display: block; max-height: 70vh; overflow-y: auto; } table td, table th { padding: 0.25em; } table, .highlight > pre, pre.example { max-height: 70vh; margin: 1em 0; padding: 1em; overflow: auto; font-family: monospace, monospace; /* https://github.com/necolas/normalize.css#extended-details-and-known-issues */ font-size: 0.85rem; border: 1px solid rgba(250, 100, 50, 0.5); } figure { margin: 1em 0; } figcaption { font-family: monospace, monospace; /* https://github.com/necolas/normalize.css#extended-details-and-known-issues */ font-size: 0.75em; text-align: center; color: grey; } .footnote-definition sup { float: left; } .footnote-definition .footnote-body { margin: 1em 0; padding: 0 1em; border: 1px solid rgba(250, 100, 50, 0.3); background-color: #ccc; } .footnote-definition .footnote-body p:only-child { margin: 0.2em 0; } .align-left { text-align: left; } .align-center { text-align: center; } .align-right { text-align: right; } dl { font-family: monospace, monospace; } dl > dt { font-weight: bold; } dl > dd { margin: -1em 0 1em 1em; } .todo, .priority, .tags { font-size: 0.8em; color: lightgrey; } .timestamp { background-color: #eee; padding: 0.05em 0.2em; border: 1px solid #ccc; } go-org-1.0.0/main.go000066400000000000000000000025101361136365600141650ustar00rootroot00000000000000package main import ( "bytes" "fmt" "io/ioutil" "log" "os" "strings" "github.com/alecthomas/chroma" "github.com/alecthomas/chroma/formatters/html" "github.com/alecthomas/chroma/lexers" "github.com/alecthomas/chroma/styles" "github.com/niklasfasching/go-org/org" ) func main() { log := log.New(os.Stderr, "", 0) if len(os.Args) < 3 { log.Println("USAGE: org FILE OUTPUT_FORMAT") log.Fatal("Supported output formats: org, html, html-chroma") } path := os.Args[1] bs, err := ioutil.ReadFile(path) if err != nil { log.Fatal(err) } out, err := "", nil d := org.New().Parse(bytes.NewReader(bs), path) switch strings.ToLower(os.Args[2]) { case "org": out, err = d.Write(org.NewOrgWriter()) case "html": out, err = d.Write(org.NewHTMLWriter()) case "html-chroma": writer := org.NewHTMLWriter() writer.HighlightCodeBlock = highlightCodeBlock out, err = d.Write(writer) default: log.Fatal("Unsupported output format") } if err != nil { log.Fatal(err) } fmt.Fprint(os.Stdout, out) } func highlightCodeBlock(source, lang string) string { var w strings.Builder l := lexers.Get(lang) if l == nil { l = lexers.Fallback } l = chroma.Coalesce(l) it, _ := l.Tokenise(nil, source) _ = html.New().Format(&w, styles.Get("friendly"), it) return `
` + "\n" + w.String() + "\n" + `
` } go-org-1.0.0/org/000077500000000000000000000000001361136365600135035ustar00rootroot00000000000000go-org-1.0.0/org/block.go000066400000000000000000000046241361136365600151320ustar00rootroot00000000000000package org import ( "regexp" "strings" "unicode" ) type Block struct { Name string Parameters []string Children []Node } type Example struct { Children []Node } var exampleLineRegexp = regexp.MustCompile(`^(\s*):(\s(.*)|\s*$)`) var beginBlockRegexp = regexp.MustCompile(`(?i)^(\s*)#\+BEGIN_(\w+)(.*)`) var endBlockRegexp = regexp.MustCompile(`(?i)^(\s*)#\+END_(\w+)`) func lexBlock(line string) (token, bool) { if m := beginBlockRegexp.FindStringSubmatch(line); m != nil { return token{"beginBlock", len(m[1]), strings.ToUpper(m[2]), m}, true } else if m := endBlockRegexp.FindStringSubmatch(line); m != nil { return token{"endBlock", len(m[1]), strings.ToUpper(m[2]), m}, true } return nilToken, false } func lexExample(line string) (token, bool) { if m := exampleLineRegexp.FindStringSubmatch(line); m != nil { return token{"example", len(m[1]), m[3], m}, true } return nilToken, false } func isRawTextBlock(name string) bool { return name == "SRC" || name == "EXAMPLE" || name == "EXPORT" } func (d *Document) parseBlock(i int, parentStop stopFn) (int, Node) { t, start := d.tokens[i], i name, parameters := t.content, strings.Fields(t.matches[3]) trim := trimIndentUpTo(d.tokens[i].lvl) stop := func(d *Document, i int) bool { return i >= len(d.tokens) || (d.tokens[i].kind == "endBlock" && d.tokens[i].content == name) } block, i := Block{name, parameters, nil}, i+1 if isRawTextBlock(name) { rawText := "" for ; !stop(d, i); i++ { rawText += trim(d.tokens[i].matches[0]) + "\n" } block.Children = d.parseRawInline(rawText) } else { consumed, nodes := d.parseMany(i, stop) block.Children = nodes i += consumed } if i < len(d.tokens) && d.tokens[i].kind == "endBlock" && d.tokens[i].content == name { return i + 1 - start, block } return 0, nil } func (d *Document) parseExample(i int, parentStop stopFn) (int, Node) { example, start := Example{}, i for ; !parentStop(d, i) && d.tokens[i].kind == "example"; i++ { example.Children = append(example.Children, Text{d.tokens[i].content, true}) } return i - start, example } func trimIndentUpTo(max int) func(string) string { return func(line string) string { i := 0 for ; i < len(line) && i < max && unicode.IsSpace(rune(line[i])); i++ { } return line[i:] } } func (n Example) String() string { return orgWriter.WriteNodesAsString(n) } func (n Block) String() string { return orgWriter.WriteNodesAsString(n) } go-org-1.0.0/org/document.go000066400000000000000000000173441361136365600156610ustar00rootroot00000000000000// Package org is an Org mode syntax processor. // // It parses plain text into an AST and can export it as HTML or pretty printed Org mode syntax. // Further export formats can be defined using the Writer interface. // // You probably want to start with something like this: // input := strings.NewReader("Your Org mode input") // html, err := org.New().Parse(input, "./").Write(org.NewHTMLWriter()) // if err != nil { // log.Fatalf("Something went wrong: %s", err) // } // log.Print(html) package org import ( "bufio" "fmt" "io" "io/ioutil" "log" "os" "strings" ) type Configuration struct { MaxEmphasisNewLines int // Maximum number of newlines inside an emphasis. See org-emphasis-regexp-components newline. AutoLink bool // Try to convert text passages that look like hyperlinks into hyperlinks. DefaultSettings map[string]string // Default values for settings that are overriden by setting the same key in BufferSettings. Log *log.Logger // Log is used to print warnings during parsing. ReadFile func(filename string) ([]byte, error) // ReadFile is used to read e.g. #+INCLUDE files. } // Document contains the parsing results and a pointer to the Configuration. type Document struct { *Configuration Path string // Path of the file containing the parse input - used to resolve relative paths during parsing (e.g. INCLUDE). tokens []token baseLvl int Nodes []Node NamedNodes map[string]Node Outline Outline // Outline is a Table Of Contents for the document and contains all sections (headline + content). BufferSettings map[string]string // Settings contains all settings that were parsed from keywords. Error error } // Node represents a parsed node of the document. type Node interface { String() string // String returns the pretty printed Org mode string for the node (see OrgWriter). } type lexFn = func(line string) (t token, ok bool) type parseFn = func(*Document, int, stopFn) (int, Node) type stopFn = func(*Document, int) bool type token struct { kind string lvl int content string matches []string } var lexFns = []lexFn{ lexHeadline, lexDrawer, lexBlock, lexList, lexTable, lexHorizontalRule, lexKeywordOrComment, lexFootnoteDefinition, lexExample, lexText, } var nilToken = token{"nil", -1, "", nil} var orgWriter = NewOrgWriter() // New returns a new Configuration with (hopefully) sane defaults. func New() *Configuration { return &Configuration{ AutoLink: true, MaxEmphasisNewLines: 1, DefaultSettings: map[string]string{ "TODO": "TODO | DONE", "EXCLUDE_TAGS": "noexport", "OPTIONS": "toc:t <:t e:t f:t pri:t todo:t tags:t", }, Log: log.New(os.Stderr, "go-org: ", 0), ReadFile: ioutil.ReadFile, } } // String returns the pretty printed Org mode string for the given nodes (see OrgWriter). func String(nodes []Node) string { return orgWriter.WriteNodesAsString(nodes...) } // Write is called after with an instance of the Writer interface to export a parsed Document into another format. func (d *Document) Write(w Writer) (out string, err error) { defer func() { if recovered := recover(); recovered != nil { err = fmt.Errorf("could not write output: %s", recovered) } }() if d.Error != nil { return "", d.Error } else if d.Nodes == nil { return "", fmt.Errorf("could not write output: parse was not called") } w.Before(d) WriteNodes(w, d.Nodes...) w.After(d) return w.String(), err } // Parse parses the input into an AST (and some other helpful fields like Outline). // To allow method chaining, errors are stored in document.Error rather than being returned. func (c *Configuration) Parse(input io.Reader, path string) (d *Document) { outlineSection := &Section{} d = &Document{ Configuration: c, Outline: Outline{outlineSection, outlineSection, 0}, BufferSettings: map[string]string{}, NamedNodes: map[string]Node{}, Path: path, } defer func() { if recovered := recover(); recovered != nil { d.Error = fmt.Errorf("could not parse input: %v", recovered) } }() if d.tokens != nil { d.Error = fmt.Errorf("parse was called multiple times") } d.tokenize(input) _, nodes := d.parseMany(0, func(d *Document, i int) bool { return i >= len(d.tokens) }) d.Nodes = nodes return d } // Silent disables all logging of warnings during parsing. func (c *Configuration) Silent() *Configuration { c.Log = log.New(ioutil.Discard, "", 0) return c } func (d *Document) tokenize(input io.Reader) { d.tokens = []token{} scanner := bufio.NewScanner(input) for scanner.Scan() { d.tokens = append(d.tokens, tokenize(scanner.Text())) } if err := scanner.Err(); err != nil { d.Error = fmt.Errorf("could not tokenize input: %s", err) } } // Get returns the value for key in BufferSettings or DefaultSettings if key does not exist in the former func (d *Document) Get(key string) string { if v, ok := d.BufferSettings[key]; ok { return v } if v, ok := d.DefaultSettings[key]; ok { return v } return "" } // GetOption returns the value associated to the export option key // Currently supported options: // - < (export timestamps) // - e (export org entities) // - f (export footnotes) // - toc (export table of content. an int limits the included org headline lvl) // - todo (export headline todo status) // - pri (export headline priority) // - tags (export headline tags) // see https://orgmode.org/manual/Export-settings.html for more information func (d *Document) GetOption(key string) string { get := func(settings map[string]string) string { for _, field := range strings.Fields(settings["OPTIONS"]) { if strings.HasPrefix(field, key+":") { return field[len(key)+1:] } } return "" } value := get(d.BufferSettings) if value == "" { value = get(d.DefaultSettings) } if value == "" { value = "nil" d.Log.Printf("Missing value for export option %s", key) } return value } func (d *Document) parseOne(i int, stop stopFn) (consumed int, node Node) { switch d.tokens[i].kind { case "unorderedList", "orderedList": consumed, node = d.parseList(i, stop) case "tableRow", "tableSeparator": consumed, node = d.parseTable(i, stop) case "beginBlock": consumed, node = d.parseBlock(i, stop) case "beginDrawer": consumed, node = d.parseDrawer(i, stop) case "text": consumed, node = d.parseParagraph(i, stop) case "example": consumed, node = d.parseExample(i, stop) case "horizontalRule": consumed, node = d.parseHorizontalRule(i, stop) case "comment": consumed, node = d.parseComment(i, stop) case "keyword": consumed, node = d.parseKeyword(i, stop) case "headline": consumed, node = d.parseHeadline(i, stop) case "footnoteDefinition": consumed, node = d.parseFootnoteDefinition(i, stop) } if consumed != 0 { return consumed, node } d.Log.Printf("Could not parse token %#v: Falling back to treating it as plain text.", d.tokens[i]) m := plainTextRegexp.FindStringSubmatch(d.tokens[i].matches[0]) d.tokens[i] = token{"text", len(m[1]), m[2], m} return d.parseOne(i, stop) } func (d *Document) parseMany(i int, stop stopFn) (int, []Node) { start, nodes := i, []Node{} for i < len(d.tokens) && !stop(d, i) { consumed, node := d.parseOne(i, stop) i += consumed nodes = append(nodes, node) } return i - start, nodes } func (d *Document) addHeadline(headline *Headline) int { current := &Section{Headline: headline} d.Outline.last.add(current) d.Outline.count++ d.Outline.last = current return d.Outline.count } func tokenize(line string) token { for _, lexFn := range lexFns { if token, ok := lexFn(line); ok { return token } } panic(fmt.Sprintf("could not lex line: %s", line)) } go-org-1.0.0/org/drawer.go000066400000000000000000000047771361136365600153350ustar00rootroot00000000000000package org import ( "regexp" "strings" ) type Drawer struct { Name string Children []Node } type PropertyDrawer struct { Properties [][]string } var beginDrawerRegexp = regexp.MustCompile(`^(\s*):(\S+):\s*$`) var endDrawerRegexp = regexp.MustCompile(`^(\s*):END:\s*$`) var propertyRegexp = regexp.MustCompile(`^(\s*):(\S+):(\s+(.*)$|$)`) func lexDrawer(line string) (token, bool) { if m := endDrawerRegexp.FindStringSubmatch(line); m != nil { return token{"endDrawer", len(m[1]), "", m}, true } else if m := beginDrawerRegexp.FindStringSubmatch(line); m != nil { return token{"beginDrawer", len(m[1]), strings.ToUpper(m[2]), m}, true } return nilToken, false } func (d *Document) parseDrawer(i int, parentStop stopFn) (int, Node) { name := strings.ToUpper(d.tokens[i].content) if name == "PROPERTIES" { return d.parsePropertyDrawer(i, parentStop) } drawer, start := Drawer{Name: name}, i i++ stop := func(d *Document, i int) bool { if parentStop(d, i) { return true } kind := d.tokens[i].kind return kind == "beginDrawer" || kind == "endDrawer" || kind == "headline" } for { consumed, nodes := d.parseMany(i, stop) i += consumed drawer.Children = append(drawer.Children, nodes...) if i < len(d.tokens) && d.tokens[i].kind == "beginDrawer" { p := Paragraph{[]Node{Text{":" + d.tokens[i].content + ":", false}}} drawer.Children = append(drawer.Children, p) i++ } else { break } } if i < len(d.tokens) && d.tokens[i].kind == "endDrawer" { i++ } return i - start, drawer } func (d *Document) parsePropertyDrawer(i int, parentStop stopFn) (int, Node) { drawer, start := PropertyDrawer{}, i i++ stop := func(d *Document, i int) bool { return parentStop(d, i) || (d.tokens[i].kind != "text" && d.tokens[i].kind != "beginDrawer") } for ; !stop(d, i); i++ { m := propertyRegexp.FindStringSubmatch(d.tokens[i].matches[0]) if m == nil { return 0, nil } k, v := strings.ToUpper(m[2]), strings.TrimSpace(m[4]) drawer.Properties = append(drawer.Properties, []string{k, v}) } if i < len(d.tokens) && d.tokens[i].kind == "endDrawer" { i++ } else { return 0, nil } return i - start, drawer } func (d *PropertyDrawer) Get(key string) (string, bool) { if d == nil { return "", false } for _, kvPair := range d.Properties { if kvPair[0] == key { return kvPair[1], true } } return "", false } func (n Drawer) String() string { return orgWriter.WriteNodesAsString(n) } func (n PropertyDrawer) String() string { return orgWriter.WriteNodesAsString(n) } go-org-1.0.0/org/footnote.go000066400000000000000000000017471361136365600157000ustar00rootroot00000000000000package org import ( "regexp" ) type FootnoteDefinition struct { Name string Children []Node Inline bool } var footnoteDefinitionRegexp = regexp.MustCompile(`^\[fn:([\w-]+)\](\s+(.+)|\s*$)`) func lexFootnoteDefinition(line string) (token, bool) { if m := footnoteDefinitionRegexp.FindStringSubmatch(line); m != nil { return token{"footnoteDefinition", 0, m[1], m}, true } return nilToken, false } func (d *Document) parseFootnoteDefinition(i int, parentStop stopFn) (int, Node) { start, name := i, d.tokens[i].content d.tokens[i] = tokenize(d.tokens[i].matches[2]) stop := func(d *Document, i int) bool { return parentStop(d, i) || (isSecondBlankLine(d, i) && i > start+1) || d.tokens[i].kind == "headline" || d.tokens[i].kind == "footnoteDefinition" } consumed, nodes := d.parseMany(i, stop) definition := FootnoteDefinition{name, nodes, false} return consumed, definition } func (n FootnoteDefinition) String() string { return orgWriter.WriteNodesAsString(n) } go-org-1.0.0/org/fuzz.go000066400000000000000000000011161361136365600150270ustar00rootroot00000000000000// +build gofuzz package org import ( "bytes" "strings" ) // Fuzz function to be used by https://github.com/dvyukov/go-fuzz func Fuzz(input []byte) int { conf := New().Silent() d := conf.Parse(bytes.NewReader(input), "") orgOutput, err := d.Write(NewOrgWriter()) if err != nil { panic(err) } htmlOutputA, err := d.Write(NewHTMLWriter()) if err != nil { panic(err) } htmlOutputB, err := conf.Parse(strings.NewReader(orgOutput), "").Write(NewHTMLWriter()) if htmlOutputA != htmlOutputB { panic("rendered org results in different html than original input") } return 0 } go-org-1.0.0/org/headline.go000066400000000000000000000046001361136365600156030ustar00rootroot00000000000000package org import ( "fmt" "regexp" "strings" "unicode" ) type Outline struct { *Section last *Section count int } type Section struct { Headline *Headline Parent *Section Children []*Section } type Headline struct { Index int Lvl int Status string Priority string Properties *PropertyDrawer Title []Node Tags []string Children []Node } var headlineRegexp = regexp.MustCompile(`^([*]+)\s+(.*)`) var tagRegexp = regexp.MustCompile(`(.*?)\s+(:[A-Za-z0-9_@#%:]+:\s*$)`) func lexHeadline(line string) (token, bool) { if m := headlineRegexp.FindStringSubmatch(line); m != nil { return token{"headline", len(m[1]), m[2], m}, true } return nilToken, false } func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) { t, headline := d.tokens[i], Headline{} headline.Lvl = t.lvl headline.Index = d.addHeadline(&headline) text := t.content todoKeywords := strings.FieldsFunc(d.Get("TODO"), func(r rune) bool { return unicode.IsSpace(r) || r == '|' }) for _, k := range todoKeywords { if strings.HasPrefix(text, k) && len(text) > len(k) && unicode.IsSpace(rune(text[len(k)])) { headline.Status = k text = text[len(k)+1:] break } } if len(text) >= 4 && text[0:2] == "[#" && strings.Contains("ABC", text[2:3]) && text[3] == ']' { headline.Priority = text[2:3] text = strings.TrimSpace(text[4:]) } if m := tagRegexp.FindStringSubmatch(text); m != nil { text = m[1] headline.Tags = strings.FieldsFunc(m[2], func(r rune) bool { return r == ':' }) } headline.Title = d.parseInline(text) stop := func(d *Document, i int) bool { return parentStop(d, i) || d.tokens[i].kind == "headline" && d.tokens[i].lvl <= headline.Lvl } consumed, nodes := d.parseMany(i+1, stop) if len(nodes) > 0 { if d, ok := nodes[0].(PropertyDrawer); ok { headline.Properties = &d nodes = nodes[1:] } } headline.Children = nodes return consumed + 1, headline } func (h Headline) ID() string { if customID, ok := h.Properties.Get("CUSTOM_ID"); ok { return customID } return fmt.Sprintf("headline-%d", h.Index) } func (parent *Section) add(current *Section) { if parent.Headline == nil || parent.Headline.Lvl < current.Headline.Lvl { parent.Children = append(parent.Children, current) current.Parent = parent } else { parent.Parent.add(current) } } func (n Headline) String() string { return orgWriter.WriteNodesAsString(n) } go-org-1.0.0/org/html_entity.go000066400000000000000000000203671361136365600164020ustar00rootroot00000000000000package org import "strings" var htmlEntityReplacer *strings.Replacer func init() { htmlEntities = append(htmlEntities, "---", "—", "--", "–", "...", "…", ) htmlEntityReplacer = strings.NewReplacer(htmlEntities...) } /* Generated & copied over using the following elisp (Setting up go generate seems like a waste for now - I call YAGNI on that one) (insert (mapconcat (lambda (entity) (concat "`\\" (car entity) "`, `" (nth 6 entity) "`")) ; entity -> utf8 (remove-if-not 'listp org-entities) ",\n")) */ var htmlEntities = []string{ `\Agrave`, `À`, `\agrave`, `à`, `\Aacute`, `Á`, `\aacute`, `á`, `\Acirc`, `Â`, `\acirc`, `â`, `\Amacr`, `Ã`, `\amacr`, `ã`, `\Atilde`, `Ã`, `\atilde`, `ã`, `\Auml`, `Ä`, `\auml`, `ä`, `\Aring`, `Å`, `\AA`, `Å`, `\aring`, `å`, `\AElig`, `Æ`, `\aelig`, `æ`, `\Ccedil`, `Ç`, `\ccedil`, `ç`, `\Egrave`, `È`, `\egrave`, `è`, `\Eacute`, `É`, `\eacute`, `é`, `\Ecirc`, `Ê`, `\ecirc`, `ê`, `\Euml`, `Ë`, `\euml`, `ë`, `\Igrave`, `Ì`, `\igrave`, `ì`, `\Iacute`, `Í`, `\iacute`, `í`, `\Icirc`, `Î`, `\icirc`, `î`, `\Iuml`, `Ï`, `\iuml`, `ï`, `\Ntilde`, `Ñ`, `\ntilde`, `ñ`, `\Ograve`, `Ò`, `\ograve`, `ò`, `\Oacute`, `Ó`, `\oacute`, `ó`, `\Ocirc`, `Ô`, `\ocirc`, `ô`, `\Otilde`, `Õ`, `\otilde`, `õ`, `\Ouml`, `Ö`, `\ouml`, `ö`, `\Oslash`, `Ø`, `\oslash`, `ø`, `\OElig`, `Œ`, `\oelig`, `œ`, `\Scaron`, `Š`, `\scaron`, `š`, `\szlig`, `ß`, `\Ugrave`, `Ù`, `\ugrave`, `ù`, `\Uacute`, `Ú`, `\uacute`, `ú`, `\Ucirc`, `Û`, `\ucirc`, `û`, `\Uuml`, `Ü`, `\uuml`, `ü`, `\Yacute`, `Ý`, `\yacute`, `ý`, `\Yuml`, `Ÿ`, `\yuml`, `ÿ`, `\fnof`, `ƒ`, `\real`, `ℜ`, `\image`, `ℑ`, `\weierp`, `℘`, `\ell`, `ℓ`, `\imath`, `ı`, `\jmath`, `ȷ`, `\Alpha`, `Α`, `\alpha`, `α`, `\Beta`, `Β`, `\beta`, `β`, `\Gamma`, `Γ`, `\gamma`, `γ`, `\Delta`, `Δ`, `\delta`, `δ`, `\Epsilon`, `Ε`, `\epsilon`, `ε`, `\varepsilon`, `ε`, `\Zeta`, `Ζ`, `\zeta`, `ζ`, `\Eta`, `Η`, `\eta`, `η`, `\Theta`, `Θ`, `\theta`, `θ`, `\thetasym`, `ϑ`, `\vartheta`, `ϑ`, `\Iota`, `Ι`, `\iota`, `ι`, `\Kappa`, `Κ`, `\kappa`, `κ`, `\Lambda`, `Λ`, `\lambda`, `λ`, `\Mu`, `Μ`, `\mu`, `μ`, `\nu`, `ν`, `\Nu`, `Ν`, `\Xi`, `Ξ`, `\xi`, `ξ`, `\Omicron`, `Ο`, `\omicron`, `ο`, `\Pi`, `Π`, `\pi`, `π`, `\Rho`, `Ρ`, `\rho`, `ρ`, `\Sigma`, `Σ`, `\sigma`, `σ`, `\sigmaf`, `ς`, `\varsigma`, `ς`, `\Tau`, `Τ`, `\Upsilon`, `Υ`, `\upsih`, `ϒ`, `\upsilon`, `υ`, `\Phi`, `Φ`, `\phi`, `ɸ`, `\varphi`, `φ`, `\Chi`, `Χ`, `\chi`, `χ`, `\acutex`, `𝑥́`, `\Psi`, `Ψ`, `\psi`, `ψ`, `\tau`, `τ`, `\Omega`, `Ω`, `\omega`, `ω`, `\piv`, `ϖ`, `\varpi`, `ϖ`, `\partial`, `∂`, `\alefsym`, `ℵ`, `\aleph`, `ℵ`, `\gimel`, `ℷ`, `\beth`, `ב`, `\dalet`, `ד`, `\ETH`, `Ð`, `\eth`, `ð`, `\THORN`, `Þ`, `\thorn`, `þ`, `\dots`, `…`, `\cdots`, `⋯`, `\hellip`, `…`, `\middot`, `·`, `\iexcl`, `¡`, `\iquest`, `¿`, `\shy`, ``, `\ndash`, `–`, `\mdash`, `—`, `\quot`, `"`, `\acute`, `´`, `\ldquo`, `“`, `\rdquo`, `”`, `\bdquo`, `„`, `\lsquo`, `‘`, `\rsquo`, `’`, `\sbquo`, `‚`, `\laquo`, `«`, `\raquo`, `»`, `\lsaquo`, `‹`, `\rsaquo`, `›`, `\circ`, `∘`, `\vert`, `|`, `\vbar`, `|`, `\brvbar`, `¦`, `\S`, `§`, `\sect`, `§`, `\amp`, `&`, `\lt`, `<`, `\gt`, `>`, `\tilde`, `~`, `\slash`, `/`, `\plus`, `+`, `\under`, `_`, `\equal`, `=`, `\asciicirc`, `^`, `\dagger`, `†`, `\dag`, `†`, `\Dagger`, `‡`, `\ddag`, `‡`, `\nbsp`, ` `, `\ensp`, ` `, `\emsp`, ` `, `\thinsp`, ` `, `\curren`, `¤`, `\cent`, `¢`, `\pound`, `£`, `\yen`, `¥`, `\euro`, `€`, `\EUR`, `€`, `\dollar`, `$`, `\USD`, `$`, `\copy`, `©`, `\reg`, `®`, `\trade`, `™`, `\minus`, `−`, `\pm`, `±`, `\plusmn`, `±`, `\times`, `×`, `\frasl`, `⁄`, `\colon`, `:`, `\div`, `÷`, `\frac12`, `½`, `\frac14`, `¼`, `\frac34`, `¾`, `\permil`, `‰`, `\sup1`, `¹`, `\sup2`, `²`, `\sup3`, `³`, `\radic`, `√`, `\sum`, `∑`, `\prod`, `∏`, `\micro`, `µ`, `\macr`, `¯`, `\deg`, `°`, `\prime`, `′`, `\Prime`, `″`, `\infin`, `∞`, `\infty`, `∞`, `\prop`, `∝`, `\propto`, `∝`, `\not`, `¬`, `\neg`, `¬`, `\land`, `∧`, `\wedge`, `∧`, `\lor`, `∨`, `\vee`, `∨`, `\cap`, `∩`, `\cup`, `∪`, `\smile`, `⌣`, `\frown`, `⌢`, `\int`, `∫`, `\therefore`, `∴`, `\there4`, `∴`, `\because`, `∵`, `\sim`, `∼`, `\cong`, `≅`, `\simeq`, `≅`, `\asymp`, `≈`, `\approx`, `≈`, `\ne`, `≠`, `\neq`, `≠`, `\equiv`, `≡`, `\triangleq`, `≜`, `\le`, `≤`, `\leq`, `≤`, `\ge`, `≥`, `\geq`, `≥`, `\lessgtr`, `≶`, `\lesseqgtr`, `⋚`, `\ll`, `≪`, `\Ll`, `⋘`, `\lll`, `⋘`, `\gg`, `≫`, `\Gg`, `⋙`, `\ggg`, `⋙`, `\prec`, `≺`, `\preceq`, `≼`, `\preccurlyeq`, `≼`, `\succ`, `≻`, `\succeq`, `≽`, `\succcurlyeq`, `≽`, `\sub`, `⊂`, `\subset`, `⊂`, `\sup`, `⊃`, `\supset`, `⊃`, `\nsub`, `⊄`, `\sube`, `⊆`, `\nsup`, `⊅`, `\supe`, `⊇`, `\setminus`, `⧵`, `\forall`, `∀`, `\exist`, `∃`, `\exists`, `∃`, `\nexist`, `∄`, `\nexists`, `∄`, `\empty`, `∅`, `\emptyset`, `∅`, `\isin`, `∈`, `\in`, `∈`, `\notin`, `∉`, `\ni`, `∋`, `\nabla`, `∇`, `\ang`, `∠`, `\angle`, `∠`, `\perp`, `⊥`, `\parallel`, `∥`, `\sdot`, `⋅`, `\cdot`, `⋅`, `\lceil`, `⌈`, `\rceil`, `⌉`, `\lfloor`, `⌊`, `\rfloor`, `⌋`, `\lang`, `⟨`, `\rang`, `⟩`, `\langle`, `⟨`, `\rangle`, `⟩`, `\hbar`, `ℏ`, `\mho`, `℧`, `\larr`, `←`, `\leftarrow`, `←`, `\gets`, `←`, `\lArr`, `⇐`, `\Leftarrow`, `⇐`, `\uarr`, `↑`, `\uparrow`, `↑`, `\uArr`, `⇑`, `\Uparrow`, `⇑`, `\rarr`, `→`, `\to`, `→`, `\rightarrow`, `→`, `\rArr`, `⇒`, `\Rightarrow`, `⇒`, `\darr`, `↓`, `\downarrow`, `↓`, `\dArr`, `⇓`, `\Downarrow`, `⇓`, `\harr`, `↔`, `\leftrightarrow`, `↔`, `\hArr`, `⇔`, `\Leftrightarrow`, `⇔`, `\crarr`, `↵`, `\hookleftarrow`, `↵`, `\arccos`, `arccos`, `\arcsin`, `arcsin`, `\arctan`, `arctan`, `\arg`, `arg`, `\cos`, `cos`, `\cosh`, `cosh`, `\cot`, `cot`, `\coth`, `coth`, `\csc`, `csc`, `\deg`, `deg`, `\det`, `det`, `\dim`, `dim`, `\exp`, `exp`, `\gcd`, `gcd`, `\hom`, `hom`, `\inf`, `inf`, `\ker`, `ker`, `\lg`, `lg`, `\lim`, `lim`, `\liminf`, `liminf`, `\limsup`, `limsup`, `\ln`, `ln`, `\log`, `log`, `\max`, `max`, `\min`, `min`, `\Pr`, `Pr`, `\sec`, `sec`, `\sin`, `sin`, `\sinh`, `sinh`, `\sup`, `sup`, `\tan`, `tan`, `\tanh`, `tanh`, `\bull`, `•`, `\bullet`, `•`, `\star`, `⋆`, `\lowast`, `∗`, `\ast`, `*`, `\odot`, `ʘ`, `\oplus`, `⊕`, `\otimes`, `⊗`, `\check`, `✓`, `\checkmark`, `✓`, `\para`, `¶`, `\ordf`, `ª`, `\ordm`, `º`, `\cedil`, `¸`, `\oline`, `‾`, `\uml`, `¨`, `\zwnj`, `‌`, `\zwj`, `‍`, `\lrm`, `‎`, `\rlm`, `‏`, `\smiley`, `☺`, `\blacksmile`, `☻`, `\sad`, `☹`, `\frowny`, `☹`, `\clubs`, `♣`, `\clubsuit`, `♣`, `\spades`, `♠`, `\spadesuit`, `♠`, `\hearts`, `♥`, `\heartsuit`, `♥`, `\diams`, `◆`, `\diamondsuit`, `◆`, `\diamond`, `◆`, `\Diamond`, `◆`, `\loz`, `⧫`, `\_ `, ` `, `\_ `, `  `, `\_ `, `   `, `\_ `, `    `, `\_ `, `     `, `\_ `, `      `, `\_ `, `       `, `\_ `, `        `, `\_ `, `         `, `\_ `, `          `, `\_ `, `           `, `\_ `, `            `, `\_ `, `             `, `\_ `, `              `, `\_ `, `               `, `\_ `, `                `, `\_ `, `                 `, `\_ `, `                  `, `\_ `, `                   `, `\_ `, `                    `, } go-org-1.0.0/org/html_writer.go000066400000000000000000000335761361136365600164100ustar00rootroot00000000000000package org import ( "fmt" "html" "log" "regexp" "strconv" "strings" "unicode" h "golang.org/x/net/html" "golang.org/x/net/html/atom" ) // HTMLWriter exports an org document into a html document. type HTMLWriter struct { ExtendingWriter Writer HighlightCodeBlock func(source, lang string) string strings.Builder document *Document htmlEscape bool log *log.Logger footnotes *footnotes } type footnotes struct { mapping map[string]int list []*FootnoteDefinition } var emphasisTags = map[string][]string{ "/": []string{"", ""}, "*": []string{"", ""}, "+": []string{"", ""}, "~": []string{"", ""}, "=": []string{``, ""}, "_": []string{``, ""}, "_{}": []string{"", ""}, "^{}": []string{"", ""}, } var listTags = map[string][]string{ "unordered": []string{"
    ", "
"}, "ordered": []string{"
    ", "
"}, "descriptive": []string{"
", "
"}, } var listItemStatuses = map[string]string{ " ": "unchecked", "-": "indeterminate", "X": "checked", } var cleanHeadlineTitleForHTMLAnchorRegexp = regexp.MustCompile(`]*>`) // nested a tags are not valid HTML func NewHTMLWriter() *HTMLWriter { defaultConfig := New() return &HTMLWriter{ document: &Document{Configuration: defaultConfig}, log: defaultConfig.Log, htmlEscape: true, HighlightCodeBlock: func(source, lang string) string { return fmt.Sprintf("
\n
\n%s\n
\n
", html.EscapeString(source)) }, footnotes: &footnotes{ mapping: map[string]int{}, }, } } func (w *HTMLWriter) WriteNodesAsString(nodes ...Node) string { original := w.Builder w.Builder = strings.Builder{} WriteNodes(w, nodes...) out := w.String() w.Builder = original return out } func (w *HTMLWriter) WriterWithExtensions() Writer { if w.ExtendingWriter != nil { return w.ExtendingWriter } return w } func (w *HTMLWriter) Before(d *Document) { w.document = d w.log = d.Log if title := d.Get("TITLE"); title != "" { w.WriteString(fmt.Sprintf(`

%s

`+"\n", title)) } w.WriteOutline(d) } func (w *HTMLWriter) After(d *Document) { w.WriteFootnotes(d) } func (w *HTMLWriter) WriteComment(Comment) {} func (w *HTMLWriter) WritePropertyDrawer(PropertyDrawer) {} func (w *HTMLWriter) WriteBlock(b Block) { content := "" if isRawTextBlock(b.Name) { builder, htmlEscape := w.Builder, w.htmlEscape w.Builder, w.htmlEscape = strings.Builder{}, false WriteNodes(w, b.Children...) out := w.String() w.Builder, w.htmlEscape = builder, htmlEscape content = strings.TrimRightFunc(out, unicode.IsSpace) } else { content = w.WriteNodesAsString(b.Children...) } switch name := b.Name; { case name == "SRC": lang := "text" if len(b.Parameters) >= 1 { lang = strings.ToLower(b.Parameters[0]) } content = w.HighlightCodeBlock(content, lang) w.WriteString(fmt.Sprintf("
\n%s\n
\n", lang, content)) case name == "EXAMPLE": w.WriteString(`
` + "\n" + content + "\n
\n") case name == "EXPORT" && len(b.Parameters) >= 1 && strings.ToLower(b.Parameters[0]) == "html": w.WriteString(content + "\n") case name == "QUOTE": w.WriteString("
\n" + content + "
\n") case name == "CENTER": w.WriteString(`
` + "\n") w.WriteString(content + "
\n") default: w.WriteString(fmt.Sprintf(`
`, strings.ToLower(b.Name)) + "\n") w.WriteString(content + "
\n") } } func (w *HTMLWriter) WriteDrawer(d Drawer) { WriteNodes(w, d.Children...) } func (w *HTMLWriter) WriteKeyword(k Keyword) { if k.Key == "HTML" { w.WriteString(k.Value + "\n") } } func (w *HTMLWriter) WriteInclude(i Include) { WriteNodes(w, i.Resolve()) } func (w *HTMLWriter) WriteFootnoteDefinition(f FootnoteDefinition) { w.footnotes.updateDefinition(f) } func (w *HTMLWriter) WriteFootnotes(d *Document) { if w.document.GetOption("f") == "nil" || len(w.footnotes.list) == 0 { return } w.WriteString(`
` + "\n") w.WriteString(`
` + "\n") w.WriteString(`
` + "\n") for i, definition := range w.footnotes.list { id := i + 1 if definition == nil { name := "" for k, v := range w.footnotes.mapping { if v == i { name = k } } w.log.Printf("Missing footnote definition for [fn:%s] (#%d)", name, id) continue } w.WriteString(`
` + "\n") w.WriteString(fmt.Sprintf(`%d`, id, id, id) + "\n") w.WriteString(`
` + "\n") WriteNodes(w, definition.Children...) w.WriteString("
\n
\n") } w.WriteString("
\n
\n") } func (w *HTMLWriter) WriteOutline(d *Document) { if w.document.GetOption("toc") != "nil" && len(d.Outline.Children) != 0 { maxLvl, _ := strconv.Atoi(w.document.GetOption("toc")) w.WriteString("\n") } } func (w *HTMLWriter) writeSection(section *Section, maxLvl int) { if maxLvl != 0 && section.Headline.Lvl > maxLvl { return } // NOTE: To satisfy hugo ExtractTOC() check we cannot use `
  • \n` here. Doesn't really matter, just a note. w.WriteString("
  • ") h := section.Headline title := cleanHeadlineTitleForHTMLAnchorRegexp.ReplaceAllString(w.WriteNodesAsString(h.Title...), "") w.WriteString(fmt.Sprintf("%s\n", h.ID(), title)) hasChildren := false for _, section := range section.Children { hasChildren = hasChildren || maxLvl == 0 || section.Headline.Lvl <= maxLvl } if hasChildren { w.WriteString("
      \n") for _, section := range section.Children { w.writeSection(section, maxLvl) } w.WriteString("
    \n") } w.WriteString("
  • \n") } func (w *HTMLWriter) WriteHeadline(h Headline) { for _, excludeTag := range strings.Fields(w.document.Get("EXCLUDE_TAGS")) { for _, tag := range h.Tags { if excludeTag == tag { return } } } w.WriteString(fmt.Sprintf(``, h.Lvl+1, h.ID()) + "\n") if w.document.GetOption("todo") != "nil" && h.Status != "" { w.WriteString(fmt.Sprintf(`%s`, h.Status) + "\n") } if w.document.GetOption("pri") != "nil" && h.Priority != "" { w.WriteString(fmt.Sprintf(`[%s]`, h.Priority) + "\n") } WriteNodes(w, h.Title...) if w.document.GetOption("tags") != "nil" && len(h.Tags) != 0 { tags := make([]string, len(h.Tags)) for i, tag := range h.Tags { tags[i] = fmt.Sprintf(`%s`, tag) } w.WriteString("   ") w.WriteString(fmt.Sprintf(`%s`, strings.Join(tags, " "))) } w.WriteString(fmt.Sprintf("\n\n", h.Lvl+1)) WriteNodes(w, h.Children...) } func (w *HTMLWriter) WriteText(t Text) { if !w.htmlEscape { w.WriteString(t.Content) } else if w.document.GetOption("e") == "nil" || t.IsRaw { w.WriteString(html.EscapeString(t.Content)) } else { w.WriteString(html.EscapeString(htmlEntityReplacer.Replace(t.Content))) } } func (w *HTMLWriter) WriteEmphasis(e Emphasis) { tags, ok := emphasisTags[e.Kind] if !ok { panic(fmt.Sprintf("bad emphasis %#v", e)) } w.WriteString(tags[0]) WriteNodes(w, e.Content...) w.WriteString(tags[1]) } func (w *HTMLWriter) WriteLatexFragment(l LatexFragment) { w.WriteString(l.OpeningPair) WriteNodes(w, l.Content...) w.WriteString(l.ClosingPair) } func (w *HTMLWriter) WriteStatisticToken(s StatisticToken) { w.WriteString(fmt.Sprintf(`[%s]`, s.Content)) } func (w *HTMLWriter) WriteLineBreak(l LineBreak) { w.WriteString(strings.Repeat("\n", l.Count)) } func (w *HTMLWriter) WriteExplicitLineBreak(l ExplicitLineBreak) { w.WriteString("
    \n") } func (w *HTMLWriter) WriteFootnoteLink(l FootnoteLink) { if w.document.GetOption("f") == "nil" { return } i := w.footnotes.add(l) id := i + 1 w.WriteString(fmt.Sprintf(`%d`, id, id, id)) } func (w *HTMLWriter) WriteTimestamp(t Timestamp) { if w.document.GetOption("<") == "nil" { return } w.WriteString(`<`) if t.IsDate { w.WriteString(t.Time.Format(datestampFormat)) } else { w.WriteString(t.Time.Format(timestampFormat)) } if t.Interval != "" { w.WriteString(" " + t.Interval) } w.WriteString(`>`) } func (w *HTMLWriter) WriteRegularLink(l RegularLink) { url := html.EscapeString(l.URL) if l.Protocol == "file" { url = url[len("file:"):] } description := url if l.Description != nil { description = w.WriteNodesAsString(l.Description...) } switch l.Kind() { case "image": w.WriteString(fmt.Sprintf(`%s`, url, description, description)) case "video": w.WriteString(fmt.Sprintf(``, url, description, description)) default: w.WriteString(fmt.Sprintf(`%s`, url, description)) } } func (w *HTMLWriter) WriteList(l List) { tags, ok := listTags[l.Kind] if !ok { panic(fmt.Sprintf("bad list kind %#v", l)) } w.WriteString(tags[0] + "\n") WriteNodes(w, l.Items...) w.WriteString(tags[1] + "\n") } func (w *HTMLWriter) WriteListItem(li ListItem) { if li.Status != "" { w.WriteString(fmt.Sprintf("
  • \n", listItemStatuses[li.Status])) } else { w.WriteString("
  • \n") } WriteNodes(w, li.Children...) w.WriteString("
  • \n") } func (w *HTMLWriter) WriteDescriptiveListItem(di DescriptiveListItem) { if di.Status != "" { w.WriteString(fmt.Sprintf("
    \n", listItemStatuses[di.Status])) } else { w.WriteString("
    \n") } if len(di.Term) != 0 { WriteNodes(w, di.Term...) } else { w.WriteString("?") } w.WriteString("\n
    \n") w.WriteString("
    \n") WriteNodes(w, di.Details...) w.WriteString("
    \n") } func (w *HTMLWriter) WriteParagraph(p Paragraph) { if len(p.Children) == 0 { return } w.WriteString("

    ") if _, ok := p.Children[0].(LineBreak); !ok { w.WriteString("\n") } WriteNodes(w, p.Children...) w.WriteString("\n

    \n") } func (w *HTMLWriter) WriteExample(e Example) { w.WriteString(`
    ` + "\n")
    	if len(e.Children) != 0 {
    		for _, n := range e.Children {
    			WriteNodes(w, n)
    			w.WriteString("\n")
    		}
    	}
    	w.WriteString("
    \n") } func (w *HTMLWriter) WriteHorizontalRule(h HorizontalRule) { w.WriteString("
    \n") } func (w *HTMLWriter) WriteNodeWithMeta(n NodeWithMeta) { out := w.WriteNodesAsString(n.Node) if p, ok := n.Node.(Paragraph); ok { if len(p.Children) == 1 && isImageOrVideoLink(p.Children[0]) { out = w.WriteNodesAsString(p.Children[0]) } } for _, attributes := range n.Meta.HTMLAttributes { out = w.withHTMLAttributes(out, attributes...) + "\n" } if len(n.Meta.Caption) != 0 { caption := "" for i, ns := range n.Meta.Caption { if i != 0 { caption += " " } caption += w.WriteNodesAsString(ns...) } out = fmt.Sprintf("
    \n%s
    \n%s\n
    \n
    \n", out, caption) } w.WriteString(out) } func (w *HTMLWriter) WriteNodeWithName(n NodeWithName) { WriteNodes(w, n.Node) } func (w *HTMLWriter) WriteTable(t Table) { w.WriteString("\n") beforeFirstContentRow := true for i, row := range t.Rows { if row.IsSpecial || len(row.Columns) == 0 { continue } if beforeFirstContentRow { beforeFirstContentRow = false if i+1 < len(t.Rows) && len(t.Rows[i+1].Columns) == 0 { w.WriteString("\n") w.writeTableColumns(row.Columns, "th") w.WriteString("\n\n") continue } else { w.WriteString("\n") } } w.writeTableColumns(row.Columns, "td") } w.WriteString("\n
    \n") } func (w *HTMLWriter) writeTableColumns(columns []Column, tag string) { w.WriteString("\n") for _, column := range columns { if column.Align == "" { w.WriteString(fmt.Sprintf("<%s>", tag)) } else { w.WriteString(fmt.Sprintf(`<%s class="align-%s">`, tag, column.Align)) } WriteNodes(w, column.Children...) w.WriteString(fmt.Sprintf("\n", tag)) } w.WriteString("\n") } func (w *HTMLWriter) withHTMLAttributes(input string, kvs ...string) string { if len(kvs)%2 != 0 { w.log.Printf("withHTMLAttributes: Len of kvs must be even: %#v", kvs) return input } context := &h.Node{Type: h.ElementNode, Data: "body", DataAtom: atom.Body} nodes, err := h.ParseFragment(strings.NewReader(strings.TrimSpace(input)), context) if err != nil || len(nodes) != 1 { w.log.Printf("withHTMLAttributes: Could not extend attributes of %s: %v (%s)", input, nodes, err) return input } out, node := strings.Builder{}, nodes[0] for i := 0; i < len(kvs)-1; i += 2 { node.Attr = setHTMLAttribute(node.Attr, strings.TrimPrefix(kvs[i], ":"), kvs[i+1]) } err = h.Render(&out, nodes[0]) if err != nil { w.log.Printf("withHTMLAttributes: Could not extend attributes of %s: %v (%s)", input, node, err) return input } return out.String() } func setHTMLAttribute(attributes []h.Attribute, k, v string) []h.Attribute { for i, a := range attributes { if strings.ToLower(a.Key) == strings.ToLower(k) { switch strings.ToLower(k) { case "class", "style": attributes[i].Val += " " + v default: attributes[i].Val = v } return attributes } } return append(attributes, h.Attribute{Namespace: "", Key: k, Val: v}) } func (fs *footnotes) add(f FootnoteLink) int { if i, ok := fs.mapping[f.Name]; ok && f.Name != "" { return i } fs.list = append(fs.list, f.Definition) i := len(fs.list) - 1 if f.Name != "" { fs.mapping[f.Name] = i } return i } func (fs *footnotes) updateDefinition(f FootnoteDefinition) { if i, ok := fs.mapping[f.Name]; ok { fs.list[i] = &f } } go-org-1.0.0/org/html_writer_test.go000066400000000000000000000021541361136365600174330ustar00rootroot00000000000000package org import ( "strings" "testing" ) type ExtendedHTMLWriter struct { *HTMLWriter callCount int } func (w *ExtendedHTMLWriter) WriteText(t Text) { w.callCount++ w.HTMLWriter.WriteText(t) } func TestHTMLWriter(t *testing.T) { for _, path := range orgTestFiles() { expected := fileString(path[:len(path)-len(".org")] + ".html") reader, writer := strings.NewReader(fileString(path)), NewHTMLWriter() actual, err := New().Silent().Parse(reader, path).Write(writer) if err != nil { t.Errorf("%s\n got error: %s", path, err) continue } if actual != expected { t.Errorf("%s:\n%s'", path, diff(actual, expected)) } else { t.Logf("%s: passed!", path) } } } func TestExtendedHTMLWriter(t *testing.T) { p := Paragraph{Children: []Node{Text{Content: "text"}, Text{Content: "more text"}}} htmlWriter := NewHTMLWriter() extendedWriter := &ExtendedHTMLWriter{htmlWriter, 0} htmlWriter.ExtendingWriter = extendedWriter WriteNodes(extendedWriter, p) if extendedWriter.callCount != 2 { t.Errorf("WriteText method of extending writer was not called: CallCount %d", extendedWriter.callCount) } } go-org-1.0.0/org/inline.go000066400000000000000000000255061361136365600153200ustar00rootroot00000000000000package org import ( "fmt" "path" "regexp" "strings" "time" "unicode" ) type Text struct { Content string IsRaw bool } type LineBreak struct{ Count int } type ExplicitLineBreak struct{} type StatisticToken struct{ Content string } type Timestamp struct { Time time.Time IsDate bool Interval string } type Emphasis struct { Kind string Content []Node } type LatexFragment struct { OpeningPair string ClosingPair string Content []Node } type FootnoteLink struct { Name string Definition *FootnoteDefinition } type RegularLink struct { Protocol string Description []Node URL string AutoLink bool } var validURLCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=" var autolinkProtocols = regexp.MustCompile(`^(https?|ftp|file)$`) var imageExtensionRegexp = regexp.MustCompile(`^[.](png|gif|jpe?g|svg|tiff?)$`) var videoExtensionRegexp = regexp.MustCompile(`^[.](webm|mp4)$`) var subScriptSuperScriptRegexp = regexp.MustCompile(`^([_^]){([^{}]+?)}`) var timestampRegexp = regexp.MustCompile(`^<(\d{4}-\d{2}-\d{2})( [A-Za-z]+)?( \d{2}:\d{2})?( \+\d+[dwmy])?>`) var footnoteRegexp = regexp.MustCompile(`^\[fn:([\w-]*?)(:(.*?))?\]`) var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`) var latexFragmentRegexp = regexp.MustCompile(`(?s)^\\begin{(\w+)}(.*)\\end{(\w+)}`) var timestampFormat = "2006-01-02 Mon 15:04" var datestampFormat = "2006-01-02 Mon" var latexFragmentPairs = map[string]string{ `\(`: `\)`, `\[`: `\]`, `$$`: `$$`, `$`: `$`, } func (d *Document) parseInline(input string) (nodes []Node) { previous, current := 0, 0 for current < len(input) { rewind, consumed, node := 0, 0, (Node)(nil) switch input[current] { case '^': consumed, node = d.parseSubOrSuperScript(input, current) case '_': consumed, node = d.parseSubScriptOrEmphasis(input, current) case '*', '/', '+': consumed, node = d.parseEmphasis(input, current, false) case '=', '~': consumed, node = d.parseEmphasis(input, current, true) case '[': consumed, node = d.parseOpeningBracket(input, current) case '<': consumed, node = d.parseTimestamp(input, current) case '\\': consumed, node = d.parseExplicitLineBreakOrLatexFragment(input, current) case '$': consumed, node = d.parseLatexFragment(input, current, 1) case '\n': consumed, node = d.parseLineBreak(input, current) case ':': rewind, consumed, node = d.parseAutoLink(input, current) current -= rewind } if consumed != 0 { if current > previous { nodes = append(nodes, Text{input[previous:current], false}) } if node != nil { nodes = append(nodes, node) } current += consumed previous = current } else { current++ } } if previous < len(input) { nodes = append(nodes, Text{input[previous:], false}) } return nodes } func (d *Document) parseRawInline(input string) (nodes []Node) { previous, current := 0, 0 for current < len(input) { if input[current] == '\n' { consumed, node := d.parseLineBreak(input, current) if current > previous { nodes = append(nodes, Text{input[previous:current], true}) } nodes = append(nodes, node) current += consumed previous = current } else { current++ } } if previous < len(input) { nodes = append(nodes, Text{input[previous:], true}) } return nodes } func (d *Document) parseLineBreak(input string, start int) (int, Node) { i := start for ; i < len(input) && input[i] == '\n'; i++ { } return i - start, LineBreak{i - start} } func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int) (int, Node) { switch { case start+2 >= len(input): case input[start+1] == '\\' && start != 0 && input[start-1] != '\n': for i := start + 2; i <= len(input)-1 && unicode.IsSpace(rune(input[i])); i++ { if input[i] == '\n' { return i + 1 - start, ExplicitLineBreak{} } } case input[start+1] == '(' || input[start+1] == '[': return d.parseLatexFragment(input, start, 2) case strings.Index(input[start:], `\begin{`) == 0: if m := latexFragmentRegexp.FindStringSubmatch(input[start:]); m != nil { if open, content, close := m[1], m[2], m[3]; open == close { openingPair, closingPair := `\begin{`+open+`}`, `\end{`+close+`}` i := strings.Index(input[start:], closingPair) return i + len(closingPair), LatexFragment{openingPair, closingPair, d.parseRawInline(content)} } } } return 0, nil } func (d *Document) parseLatexFragment(input string, start int, pairLength int) (int, Node) { if start+2 >= len(input) { return 0, nil } if pairLength == 1 && input[start:start+2] == "$$" { pairLength = 2 } openingPair := input[start : start+pairLength] closingPair := latexFragmentPairs[openingPair] if i := strings.Index(input[start+pairLength:], closingPair); i != -1 { content := d.parseRawInline(input[start+pairLength : start+pairLength+i]) return i + pairLength + pairLength, LatexFragment{openingPair, closingPair, content} } return 0, nil } func (d *Document) parseSubOrSuperScript(input string, start int) (int, Node) { if m := subScriptSuperScriptRegexp.FindStringSubmatch(input[start:]); m != nil { return len(m[2]) + 3, Emphasis{m[1] + "{}", []Node{Text{m[2], false}}} } return 0, nil } func (d *Document) parseSubScriptOrEmphasis(input string, start int) (int, Node) { if consumed, node := d.parseSubOrSuperScript(input, start); consumed != 0 { return consumed, node } return d.parseEmphasis(input, start, false) } func (d *Document) parseOpeningBracket(input string, start int) (int, Node) { if len(input[start:]) >= 2 && input[start] == '[' && input[start+1] == '[' { return d.parseRegularLink(input, start) } else if footnoteRegexp.MatchString(input[start:]) { return d.parseFootnoteReference(input, start) } else if statisticsTokenRegexp.MatchString(input[start:]) { return d.parseStatisticToken(input, start) } return 0, nil } func (d *Document) parseFootnoteReference(input string, start int) (int, Node) { if m := footnoteRegexp.FindStringSubmatch(input[start:]); m != nil { name, definition := m[1], m[3] if name == "" && definition == "" { return 0, nil } link := FootnoteLink{name, nil} if definition != "" { link.Definition = &FootnoteDefinition{name, []Node{Paragraph{d.parseInline(definition)}}, true} } return len(m[0]), link } return 0, nil } func (d *Document) parseStatisticToken(input string, start int) (int, Node) { if m := statisticsTokenRegexp.FindStringSubmatch(input[start:]); m != nil { return len(m[1]) + 2, StatisticToken{m[1]} } return 0, nil } func (d *Document) parseAutoLink(input string, start int) (int, int, Node) { if !d.AutoLink || start == 0 || len(input[start:]) < 3 || input[start:start+3] != "://" { return 0, 0, nil } protocolStart, protocol := start-1, "" for ; protocolStart > 0; protocolStart-- { if !unicode.IsLetter(rune(input[protocolStart])) { protocolStart++ break } } if m := autolinkProtocols.FindStringSubmatch(input[protocolStart:start]); m != nil { protocol = m[1] } else { return 0, 0, nil } end := start for ; end < len(input) && strings.ContainsRune(validURLCharacters, rune(input[end])); end++ { } path := input[start:end] if path == "://" { return 0, 0, nil } return len(protocol), len(path + protocol), RegularLink{protocol, nil, protocol + path, true} } func (d *Document) parseRegularLink(input string, start int) (int, Node) { input = input[start:] if len(input) < 3 || input[:2] != "[[" || input[2] == '[' { return 0, nil } end := strings.Index(input, "]]") if end == -1 { return 0, nil } rawLinkParts := strings.Split(input[2:end], "][") description, link := ([]Node)(nil), rawLinkParts[0] if len(rawLinkParts) == 2 { link, description = rawLinkParts[0], d.parseInline(rawLinkParts[1]) } if strings.ContainsRune(link, '\n') { return 0, nil } consumed := end + 2 protocol, linkParts := "", strings.SplitN(link, ":", 2) if len(linkParts) == 2 { protocol = linkParts[0] } return consumed, RegularLink{protocol, description, link, false} } func (d *Document) parseTimestamp(input string, start int) (int, Node) { if m := timestampRegexp.FindStringSubmatch(input[start:]); m != nil { ddmmyy, hhmm, interval, isDate := m[1], m[3], strings.TrimSpace(m[4]), false if hhmm == "" { hhmm, isDate = "00:00", true } t, err := time.Parse(timestampFormat, fmt.Sprintf("%s Mon %s", ddmmyy, hhmm)) if err != nil { return 0, nil } timestamp := Timestamp{t, isDate, interval} return len(m[0]), timestamp } return 0, nil } func (d *Document) parseEmphasis(input string, start int, isRaw bool) (int, Node) { marker, i := input[start], start if !hasValidPreAndBorderChars(input, i) { return 0, nil } for i, consumedNewLines := i+1, 0; i < len(input) && consumedNewLines <= d.MaxEmphasisNewLines; i++ { if input[i] == '\n' { consumedNewLines++ } if input[i] == marker && i != start+1 && hasValidPostAndBorderChars(input, i) { if isRaw { return i + 1 - start, Emphasis{input[start : start+1], d.parseRawInline(input[start+1 : i])} } return i + 1 - start, Emphasis{input[start : start+1], d.parseInline(input[start+1 : i])} } } return 0, nil } // see org-emphasis-regexp-components (emacs elisp variable) func hasValidPreAndBorderChars(input string, i int) bool { return (i+1 >= len(input) || isValidBorderChar(rune(input[i+1]))) && (i == 0 || isValidPreChar(rune(input[i-1]))) } func hasValidPostAndBorderChars(input string, i int) bool { return (i == 0 || isValidBorderChar(rune(input[i-1]))) && (i+1 >= len(input) || isValidPostChar(rune(input[i+1]))) } func isValidPreChar(r rune) bool { return unicode.IsSpace(r) || strings.ContainsRune(`-({'"`, r) } func isValidPostChar(r rune) bool { return unicode.IsSpace(r) || strings.ContainsRune(`-.,:!?;'")}[`, r) } func isValidBorderChar(r rune) bool { return !unicode.IsSpace(r) } func (l RegularLink) Kind() string { if p := l.Protocol; l.Description != nil || (p != "" && p != "file" && p != "http" && p != "https") { return "regular" } if imageExtensionRegexp.MatchString(path.Ext(l.URL)) { return "image" } if videoExtensionRegexp.MatchString(path.Ext(l.URL)) { return "video" } return "regular" } func (n Text) String() string { return orgWriter.WriteNodesAsString(n) } func (n LineBreak) String() string { return orgWriter.WriteNodesAsString(n) } func (n ExplicitLineBreak) String() string { return orgWriter.WriteNodesAsString(n) } func (n StatisticToken) String() string { return orgWriter.WriteNodesAsString(n) } func (n Emphasis) String() string { return orgWriter.WriteNodesAsString(n) } func (n LatexFragment) String() string { return orgWriter.WriteNodesAsString(n) } func (n FootnoteLink) String() string { return orgWriter.WriteNodesAsString(n) } func (n RegularLink) String() string { return orgWriter.WriteNodesAsString(n) } func (n Timestamp) String() string { return orgWriter.WriteNodesAsString(n) } go-org-1.0.0/org/keyword.go000066400000000000000000000112671361136365600155250ustar00rootroot00000000000000package org import ( "bytes" "path/filepath" "regexp" "strings" ) type Comment struct{ Content string } type Keyword struct { Key string Value string } type NodeWithName struct { Name string Node Node } type NodeWithMeta struct { Node Node Meta Metadata } type Metadata struct { Caption [][]Node HTMLAttributes [][]string } type Include struct { Keyword Resolve func() Node } var keywordRegexp = regexp.MustCompile(`^(\s*)#\+([^:]+):(\s+(.*)|$)`) var commentRegexp = regexp.MustCompile(`^(\s*)#(.*)`) var includeFileRegexp = regexp.MustCompile(`(?i)^"([^"]+)" (src|example|export) (\w+)$`) var attributeRegexp = regexp.MustCompile(`(?:^|\s+)(:[-\w]+)\s+(.*)$`) func lexKeywordOrComment(line string) (token, bool) { if m := keywordRegexp.FindStringSubmatch(line); m != nil { return token{"keyword", len(m[1]), m[2], m}, true } else if m := commentRegexp.FindStringSubmatch(line); m != nil { return token{"comment", len(m[1]), m[2], m}, true } return nilToken, false } func (d *Document) parseComment(i int, stop stopFn) (int, Node) { return 1, Comment{d.tokens[i].content} } func (d *Document) parseKeyword(i int, stop stopFn) (int, Node) { k := parseKeyword(d.tokens[i]) switch k.Key { case "NAME": return d.parseNodeWithName(k, i, stop) case "SETUPFILE": return d.loadSetupFile(k) case "INCLUDE": return d.parseInclude(k) case "CAPTION", "ATTR_HTML": consumed, node := d.parseAffiliated(i, stop) if consumed != 0 { return consumed, node } fallthrough default: if _, ok := d.BufferSettings[k.Key]; ok { d.BufferSettings[k.Key] = strings.Join([]string{d.BufferSettings[k.Key], k.Value}, "\n") } else { d.BufferSettings[k.Key] = k.Value } return 1, k } } func (d *Document) parseNodeWithName(k Keyword, i int, stop stopFn) (int, Node) { if stop(d, i+1) { return 0, nil } consumed, node := d.parseOne(i+1, stop) if consumed == 0 || node == nil { return 0, nil } d.NamedNodes[k.Value] = node return consumed + 1, NodeWithName{k.Value, node} } func (d *Document) parseAffiliated(i int, stop stopFn) (int, Node) { start, meta := i, Metadata{} for ; !stop(d, i) && d.tokens[i].kind == "keyword"; i++ { switch k := parseKeyword(d.tokens[i]); k.Key { case "CAPTION": meta.Caption = append(meta.Caption, d.parseInline(k.Value)) case "ATTR_HTML": attributes, rest := []string{}, k.Value for { if k, m := "", attributeRegexp.FindStringSubmatch(rest); m != nil { k, rest = m[1], m[2] attributes = append(attributes, k) if v, m := "", attributeRegexp.FindStringSubmatchIndex(rest); m != nil { v, rest = rest[:m[0]], rest[m[0]:] attributes = append(attributes, v) } else { attributes = append(attributes, strings.TrimSpace(rest)) break } } else { break } } meta.HTMLAttributes = append(meta.HTMLAttributes, attributes) default: return 0, nil } } if stop(d, i) { return 0, nil } consumed, node := d.parseOne(i, stop) if consumed == 0 || node == nil { return 0, nil } i += consumed return i - start, NodeWithMeta{node, meta} } func parseKeyword(t token) Keyword { k, v := t.matches[2], t.matches[4] return Keyword{strings.ToUpper(k), strings.TrimSpace(v)} } func (d *Document) parseInclude(k Keyword) (int, Node) { resolve := func() Node { d.Log.Printf("Bad include %#v", k) return k } if m := includeFileRegexp.FindStringSubmatch(k.Value); m != nil { path, kind, lang := m[1], m[2], m[3] if !filepath.IsAbs(path) { path = filepath.Join(filepath.Dir(d.Path), path) } resolve = func() Node { bs, err := d.ReadFile(path) if err != nil { d.Log.Printf("Bad include %#v: %s", k, err) return k } return Block{strings.ToUpper(kind), []string{lang}, d.parseRawInline(string(bs))} } } return 1, Include{k, resolve} } func (d *Document) loadSetupFile(k Keyword) (int, Node) { path := k.Value if !filepath.IsAbs(path) { path = filepath.Join(filepath.Dir(d.Path), path) } bs, err := d.ReadFile(path) if err != nil { d.Log.Printf("Bad setup file: %#v: %s", k, err) return 1, k } setupDocument := d.Configuration.Parse(bytes.NewReader(bs), path) if err := setupDocument.Error; err != nil { d.Log.Printf("Bad setup file: %#v: %s", k, err) return 1, k } for k, v := range setupDocument.BufferSettings { d.BufferSettings[k] = v } return 1, k } func (n Comment) String() string { return orgWriter.WriteNodesAsString(n) } func (n Keyword) String() string { return orgWriter.WriteNodesAsString(n) } func (n NodeWithMeta) String() string { return orgWriter.WriteNodesAsString(n) } func (n NodeWithName) String() string { return orgWriter.WriteNodesAsString(n) } func (n Include) String() string { return orgWriter.WriteNodesAsString(n) } go-org-1.0.0/org/list.go000066400000000000000000000066521361136365600150160ustar00rootroot00000000000000package org import ( "fmt" "regexp" "strings" "unicode" ) type List struct { Kind string Items []Node } type ListItem struct { Bullet string Status string Children []Node } type DescriptiveListItem struct { Bullet string Status string Term []Node Details []Node } var unorderedListRegexp = regexp.MustCompile(`^(\s*)([+*-])(\s+(.*)|$)`) var orderedListRegexp = regexp.MustCompile(`^(\s*)(([0-9]+|[a-zA-Z])[.)])(\s+(.*)|$)`) var descriptiveListItemRegexp = regexp.MustCompile(`\s::(\s|$)`) var listItemStatusRegexp = regexp.MustCompile(`\[( |X|-)\]\s`) func lexList(line string) (token, bool) { if m := unorderedListRegexp.FindStringSubmatch(line); m != nil { return token{"unorderedList", len(m[1]), m[4], m}, true } else if m := orderedListRegexp.FindStringSubmatch(line); m != nil { return token{"orderedList", len(m[1]), m[5], m}, true } return nilToken, false } func isListToken(t token) bool { return t.kind == "unorderedList" || t.kind == "orderedList" } func listKind(t token) (string, string) { kind := "" switch bullet := t.matches[2]; { case bullet == "*" || bullet == "+" || bullet == "-": kind = "unordered" case unicode.IsLetter(rune(bullet[0])), unicode.IsDigit(rune(bullet[0])): kind = "ordered" default: panic(fmt.Sprintf("bad list bullet '%s': %#v", bullet, t)) } if descriptiveListItemRegexp.MatchString(t.content) { return kind, "descriptive" } return kind, kind } func (d *Document) parseList(i int, parentStop stopFn) (int, Node) { start, lvl := i, d.tokens[i].lvl listMainKind, kind := listKind(d.tokens[i]) list := List{Kind: kind} stop := func(*Document, int) bool { if parentStop(d, i) || d.tokens[i].lvl != lvl || !isListToken(d.tokens[i]) { return true } itemMainKind, _ := listKind(d.tokens[i]) return itemMainKind != listMainKind } for !stop(d, i) { consumed, node := d.parseListItem(list, i, parentStop) i += consumed list.Items = append(list.Items, node) } return i - start, list } func (d *Document) parseListItem(l List, i int, parentStop stopFn) (int, Node) { start, nodes, bullet := i, []Node{}, d.tokens[i].matches[2] minIndent, dterm, content, status := d.tokens[i].lvl+len(bullet), "", d.tokens[i].content, "" originalBaseLvl := d.baseLvl d.baseLvl = minIndent + 1 if m := listItemStatusRegexp.FindStringSubmatch(content); m != nil { status, content = m[1], content[len("[ ] "):] } if l.Kind == "descriptive" { if m := descriptiveListItemRegexp.FindStringIndex(content); m != nil { dterm, content = content[:m[0]], content[m[1]:] d.baseLvl = strings.Index(d.tokens[i].matches[0], " ::") + 4 } } d.tokens[i] = tokenize(strings.Repeat(" ", minIndent) + content) stop := func(d *Document, i int) bool { if parentStop(d, i) { return true } t := d.tokens[i] return t.lvl < minIndent && !(t.kind == "text" && t.content == "") } for !stop(d, i) && (i <= start+1 || !isSecondBlankLine(d, i)) { consumed, node := d.parseOne(i, stop) i += consumed nodes = append(nodes, node) } d.baseLvl = originalBaseLvl if l.Kind == "descriptive" { return i - start, DescriptiveListItem{bullet, status, d.parseInline(dterm), nodes} } return i - start, ListItem{bullet, status, nodes} } func (n List) String() string { return orgWriter.WriteNodesAsString(n) } func (n ListItem) String() string { return orgWriter.WriteNodesAsString(n) } func (n DescriptiveListItem) String() string { return orgWriter.WriteNodesAsString(n) } go-org-1.0.0/org/org_writer.go000066400000000000000000000177771361136365600162400ustar00rootroot00000000000000package org import ( "fmt" "strings" "unicode" "unicode/utf8" ) // OrgWriter export an org document into pretty printed org document. type OrgWriter struct { ExtendingWriter Writer TagsColumn int strings.Builder indent string } var emphasisOrgBorders = map[string][]string{ "_": []string{"_", "_"}, "*": []string{"*", "*"}, "/": []string{"/", "/"}, "+": []string{"+", "+"}, "~": []string{"~", "~"}, "=": []string{"=", "="}, "_{}": []string{"_{", "}"}, "^{}": []string{"^{", "}"}, } func NewOrgWriter() *OrgWriter { return &OrgWriter{ TagsColumn: 77, } } func (w *OrgWriter) WriterWithExtensions() Writer { if w.ExtendingWriter != nil { return w.ExtendingWriter } return w } func (w *OrgWriter) Before(d *Document) {} func (w *OrgWriter) After(d *Document) {} func (w *OrgWriter) WriteNodesAsString(nodes ...Node) string { builder := w.Builder w.Builder = strings.Builder{} WriteNodes(w, nodes...) out := w.String() w.Builder = builder return out } func (w *OrgWriter) WriteHeadline(h Headline) { start := w.Len() w.WriteString(strings.Repeat("*", h.Lvl)) if h.Status != "" { w.WriteString(" " + h.Status) } if h.Priority != "" { w.WriteString(" [#" + h.Priority + "]") } w.WriteString(" ") WriteNodes(w, h.Title...) if len(h.Tags) != 0 { tString := ":" + strings.Join(h.Tags, ":") + ":" if n := w.TagsColumn - len(tString) - (w.Len() - start); n > 0 { w.WriteString(strings.Repeat(" ", n) + tString) } else { w.WriteString(" " + tString) } } w.WriteString("\n") if len(h.Children) != 0 { w.WriteString(w.indent) } if h.Properties != nil { WriteNodes(w, *h.Properties) } WriteNodes(w, h.Children...) } func (w *OrgWriter) WriteBlock(b Block) { w.WriteString(w.indent + "#+BEGIN_" + b.Name) if len(b.Parameters) != 0 { w.WriteString(" " + strings.Join(b.Parameters, " ")) } w.WriteString("\n") if isRawTextBlock(b.Name) { w.WriteString(w.indent) } WriteNodes(w, b.Children...) if !isRawTextBlock(b.Name) { w.WriteString(w.indent) } w.WriteString("#+END_" + b.Name + "\n") } func (w *OrgWriter) WriteDrawer(d Drawer) { w.WriteString(w.indent + ":" + d.Name + ":\n") WriteNodes(w, d.Children...) w.WriteString(w.indent + ":END:\n") } func (w *OrgWriter) WritePropertyDrawer(d PropertyDrawer) { w.WriteString(":PROPERTIES:\n") for _, kvPair := range d.Properties { k, v := kvPair[0], kvPair[1] if v != "" { v = " " + v } w.WriteString(fmt.Sprintf(":%s:%s\n", k, v)) } w.WriteString(":END:\n") } func (w *OrgWriter) WriteFootnoteDefinition(f FootnoteDefinition) { w.WriteString(fmt.Sprintf("[fn:%s]", f.Name)) content := w.WriteNodesAsString(f.Children...) if content != "" && !unicode.IsSpace(rune(content[0])) { w.WriteString(" ") } w.WriteString(content) } func (w *OrgWriter) WriteParagraph(p Paragraph) { content := w.WriteNodesAsString(p.Children...) if len(content) > 0 && content[0] != '\n' { w.WriteString(w.indent) } w.WriteString(content + "\n") } func (w *OrgWriter) WriteExample(e Example) { for _, n := range e.Children { w.WriteString(w.indent + ":") if content := w.WriteNodesAsString(n); content != "" { w.WriteString(" " + content) } w.WriteString("\n") } } func (w *OrgWriter) WriteKeyword(k Keyword) { w.WriteString(w.indent + "#+" + k.Key + ":") if k.Value != "" { w.WriteString(" " + k.Value) } w.WriteString("\n") } func (w *OrgWriter) WriteInclude(i Include) { w.WriteKeyword(i.Keyword) } func (w *OrgWriter) WriteNodeWithMeta(n NodeWithMeta) { for _, ns := range n.Meta.Caption { w.WriteString("#+CAPTION: ") WriteNodes(w, ns...) w.WriteString("\n") } for _, attributes := range n.Meta.HTMLAttributes { w.WriteString("#+ATTR_HTML: ") w.WriteString(strings.Join(attributes, " ") + "\n") } WriteNodes(w, n.Node) } func (w *OrgWriter) WriteNodeWithName(n NodeWithName) { w.WriteString(fmt.Sprintf("#+NAME: %s\n", n.Name)) WriteNodes(w, n.Node) } func (w *OrgWriter) WriteComment(c Comment) { w.WriteString(w.indent + "#" + c.Content + "\n") } func (w *OrgWriter) WriteList(l List) { WriteNodes(w, l.Items...) } func (w *OrgWriter) WriteListItem(li ListItem) { originalBuilder, originalIndent := w.Builder, w.indent w.Builder, w.indent = strings.Builder{}, w.indent+strings.Repeat(" ", len(li.Bullet)+1) WriteNodes(w, li.Children...) content := strings.TrimPrefix(w.String(), w.indent) w.Builder, w.indent = originalBuilder, originalIndent w.WriteString(w.indent + li.Bullet) if li.Status != "" { w.WriteString(fmt.Sprintf(" [%s]", li.Status)) } if len(content) > 0 && content[0] == '\n' { w.WriteString(content) } else { w.WriteString(" " + content) } } func (w *OrgWriter) WriteDescriptiveListItem(di DescriptiveListItem) { indent := w.indent + strings.Repeat(" ", len(di.Bullet)+1) w.WriteString(w.indent + di.Bullet) if di.Status != "" { w.WriteString(fmt.Sprintf(" [%s]", di.Status)) indent = indent + strings.Repeat(" ", len(di.Status)+3) } if len(di.Term) != 0 { term := w.WriteNodesAsString(di.Term...) w.WriteString(" " + term + " ::") indent = indent + strings.Repeat(" ", len(term)+4) } originalBuilder, originalIndent := w.Builder, w.indent w.Builder, w.indent = strings.Builder{}, indent WriteNodes(w, di.Details...) details := strings.TrimPrefix(w.String(), w.indent) w.Builder, w.indent = originalBuilder, originalIndent if len(details) > 0 && details[0] == '\n' { w.WriteString(details) } else { w.WriteString(" " + details) } } func (w *OrgWriter) WriteTable(t Table) { for _, row := range t.Rows { w.WriteString(w.indent) if len(row.Columns) == 0 { w.WriteString(`|`) for i := 0; i < len(t.ColumnInfos); i++ { w.WriteString(strings.Repeat("-", t.ColumnInfos[i].Len+2)) if i < len(t.ColumnInfos)-1 { w.WriteString("+") } } w.WriteString(`|`) } else { w.WriteString(`|`) for _, column := range row.Columns { w.WriteString(` `) content := w.WriteNodesAsString(column.Children...) if content == "" { content = " " } n := column.Len - utf8.RuneCountInString(content) if n < 0 { n = 0 } if column.Align == "center" { if n%2 != 0 { w.WriteString(" ") } w.WriteString(strings.Repeat(" ", n/2) + content + strings.Repeat(" ", n/2)) } else if column.Align == "right" { w.WriteString(strings.Repeat(" ", n) + content) } else { w.WriteString(content + strings.Repeat(" ", n)) } w.WriteString(` |`) } } w.WriteString("\n") } } func (w *OrgWriter) WriteHorizontalRule(hr HorizontalRule) { w.WriteString(w.indent + "-----\n") } func (w *OrgWriter) WriteText(t Text) { w.WriteString(t.Content) } func (w *OrgWriter) WriteEmphasis(e Emphasis) { borders, ok := emphasisOrgBorders[e.Kind] if !ok { panic(fmt.Sprintf("bad emphasis %#v", e)) } w.WriteString(borders[0]) WriteNodes(w, e.Content...) w.WriteString(borders[1]) } func (w *OrgWriter) WriteLatexFragment(l LatexFragment) { w.WriteString(l.OpeningPair) WriteNodes(w, l.Content...) w.WriteString(l.ClosingPair) } func (w *OrgWriter) WriteStatisticToken(s StatisticToken) { w.WriteString(fmt.Sprintf("[%s]", s.Content)) } func (w *OrgWriter) WriteLineBreak(l LineBreak) { w.WriteString(strings.Repeat("\n"+w.indent, l.Count)) } func (w *OrgWriter) WriteExplicitLineBreak(l ExplicitLineBreak) { w.WriteString(`\\` + "\n" + w.indent) } func (w *OrgWriter) WriteTimestamp(t Timestamp) { w.WriteString("<") if t.IsDate { w.WriteString(t.Time.Format(datestampFormat)) } else { w.WriteString(t.Time.Format(timestampFormat)) } if t.Interval != "" { w.WriteString(" " + t.Interval) } w.WriteString(">") } func (w *OrgWriter) WriteFootnoteLink(l FootnoteLink) { w.WriteString("[fn:" + l.Name) if l.Definition != nil { w.WriteString(":") WriteNodes(w, l.Definition.Children[0].(Paragraph).Children...) } w.WriteString("]") } func (w *OrgWriter) WriteRegularLink(l RegularLink) { if l.AutoLink { w.WriteString(l.URL) } else if l.Description == nil { w.WriteString(fmt.Sprintf("[[%s]]", l.URL)) } else { w.WriteString(fmt.Sprintf("[[%s][%s]]", l.URL, w.WriteNodesAsString(l.Description...))) } } go-org-1.0.0/org/org_writer_test.go000066400000000000000000000037561361136365600172670ustar00rootroot00000000000000package org import ( "fmt" "io/ioutil" "path/filepath" "strings" "testing" "github.com/pmezard/go-difflib/difflib" ) type ExtendedOrgWriter struct { *OrgWriter callCount int } func (w *ExtendedOrgWriter) WriteText(t Text) { w.callCount++ w.OrgWriter.WriteText(t) } func TestOrgWriter(t *testing.T) { for _, path := range orgTestFiles() { expected := fileString(path[:len(path)-len(".org")] + ".pretty_org") reader, writer := strings.NewReader(fileString(path)), NewOrgWriter() actual, err := New().Silent().Parse(reader, path).Write(writer) if err != nil { t.Errorf("%s\n got error: %s", path, err) continue } if actual != expected { t.Errorf("%s:\n%s'", path, diff(actual, expected)) } else { t.Logf("%s: passed!", path) } } } func TestExtendedOrgWriter(t *testing.T) { p := Paragraph{Children: []Node{Text{Content: "text"}, Text{Content: "more text"}}} orgWriter := NewOrgWriter() extendedWriter := &ExtendedOrgWriter{orgWriter, 0} orgWriter.ExtendingWriter = extendedWriter WriteNodes(extendedWriter, p) if extendedWriter.callCount != 2 { t.Errorf("WriteText method of extending writer was not called: CallCount %d", extendedWriter.callCount) } } func orgTestFiles() []string { dir := "./testdata" files, err := ioutil.ReadDir(dir) if err != nil { panic(fmt.Sprintf("Could not read directory: %s", err)) } orgFiles := []string{} for _, f := range files { name := f.Name() if filepath.Ext(name) != ".org" { continue } orgFiles = append(orgFiles, filepath.Join(dir, name)) } return orgFiles } func fileString(path string) string { bs, err := ioutil.ReadFile(path) if err != nil { panic(fmt.Sprintf("Could not read file %s: %s", path, err)) } return string(bs) } func diff(actual, expected string) string { diff := difflib.UnifiedDiff{ A: difflib.SplitLines(actual), B: difflib.SplitLines(expected), FromFile: "Actual", ToFile: "Expected", Context: 3, } text, _ := difflib.GetUnifiedDiffString(diff) return text } go-org-1.0.0/org/paragraph.go000066400000000000000000000026231361136365600160020ustar00rootroot00000000000000package org import ( "math" "regexp" "strings" ) type Paragraph struct{ Children []Node } type HorizontalRule struct{} var horizontalRuleRegexp = regexp.MustCompile(`^(\s*)-{5,}\s*$`) var plainTextRegexp = regexp.MustCompile(`^(\s*)(.*)`) func lexText(line string) (token, bool) { if m := plainTextRegexp.FindStringSubmatch(line); m != nil { return token{"text", len(m[1]), m[2], m}, true } return nilToken, false } func lexHorizontalRule(line string) (token, bool) { if m := horizontalRuleRegexp.FindStringSubmatch(line); m != nil { return token{"horizontalRule", len(m[1]), "", m}, true } return nilToken, false } func (d *Document) parseParagraph(i int, parentStop stopFn) (int, Node) { lines, start := []string{d.tokens[i].content}, i stop := func(d *Document, i int) bool { return parentStop(d, i) || d.tokens[i].kind != "text" || d.tokens[i].content == "" } for i += 1; !stop(d, i); i++ { lvl := math.Max(float64(d.tokens[i].lvl-d.baseLvl), 0) lines = append(lines, strings.Repeat(" ", int(lvl))+d.tokens[i].content) } consumed := i - start return consumed, Paragraph{d.parseInline(strings.Join(lines, "\n"))} } func (d *Document) parseHorizontalRule(i int, parentStop stopFn) (int, Node) { return 1, HorizontalRule{} } func (n Paragraph) String() string { return orgWriter.WriteNodesAsString(n) } func (n HorizontalRule) String() string { return orgWriter.WriteNodesAsString(n) } go-org-1.0.0/org/table.go000066400000000000000000000060511361136365600151230ustar00rootroot00000000000000package org import ( "regexp" "strconv" "strings" "unicode/utf8" ) type Table struct { Rows []Row ColumnInfos []ColumnInfo } type Row struct { Columns []Column IsSpecial bool } type Column struct { Children []Node *ColumnInfo } type ColumnInfo struct { Align string Len int } var tableSeparatorRegexp = regexp.MustCompile(`^(\s*)(\|[+-|]*)\s*$`) var tableRowRegexp = regexp.MustCompile(`^(\s*)(\|.*)`) var columnAlignRegexp = regexp.MustCompile(`^<(l|c|r)>$`) func lexTable(line string) (token, bool) { if m := tableSeparatorRegexp.FindStringSubmatch(line); m != nil { return token{"tableSeparator", len(m[1]), m[2], m}, true } else if m := tableRowRegexp.FindStringSubmatch(line); m != nil { return token{"tableRow", len(m[1]), m[2], m}, true } return nilToken, false } func (d *Document) parseTable(i int, parentStop stopFn) (int, Node) { rawRows, start := [][]string{}, i for ; !parentStop(d, i); i++ { if t := d.tokens[i]; t.kind == "tableRow" { rawRow := strings.FieldsFunc(d.tokens[i].content, func(r rune) bool { return r == '|' }) for i := range rawRow { rawRow[i] = strings.TrimSpace(rawRow[i]) } rawRows = append(rawRows, rawRow) } else if t.kind == "tableSeparator" { rawRows = append(rawRows, nil) } else { break } } table := Table{nil, getColumnInfos(rawRows)} for _, rawColumns := range rawRows { row := Row{nil, isSpecialRow(rawColumns)} if len(rawColumns) != 0 { for i := range table.ColumnInfos { column := Column{nil, &table.ColumnInfos[i]} if i < len(rawColumns) { column.Children = d.parseInline(rawColumns[i]) } row.Columns = append(row.Columns, column) } } table.Rows = append(table.Rows, row) } return i - start, table } func getColumnInfos(rows [][]string) []ColumnInfo { columnCount := 0 for _, columns := range rows { if n := len(columns); n > columnCount { columnCount = n } } columnInfos := make([]ColumnInfo, columnCount) for i := 0; i < columnCount; i++ { countNumeric, countNonNumeric := 0, 0 for _, columns := range rows { if i >= len(columns) { continue } if n := utf8.RuneCountInString(columns[i]); n > columnInfos[i].Len { columnInfos[i].Len = n } if m := columnAlignRegexp.FindStringSubmatch(columns[i]); m != nil && isSpecialRow(columns) { switch m[1] { case "l": columnInfos[i].Align = "left" case "c": columnInfos[i].Align = "center" case "r": columnInfos[i].Align = "right" } } else if _, err := strconv.ParseFloat(columns[i], 32); err == nil { countNumeric++ } else if strings.TrimSpace(columns[i]) != "" { countNonNumeric++ } } if columnInfos[i].Align == "" && countNumeric >= countNonNumeric { columnInfos[i].Align = "right" } } return columnInfos } func isSpecialRow(rawColumns []string) bool { isAlignRow := true for _, rawColumn := range rawColumns { if !columnAlignRegexp.MatchString(rawColumn) && rawColumn != "" { isAlignRow = false } } return isAlignRow } func (n Table) String() string { return orgWriter.WriteNodesAsString(n) } go-org-1.0.0/org/testdata/000077500000000000000000000000001361136365600153145ustar00rootroot00000000000000go-org-1.0.0/org/testdata/blocks.html000066400000000000000000000055201361136365600174610ustar00rootroot00000000000000
    echo "a bash source block"
    
    function hello {
        echo Hello World!
    }
    
    hello
    
    block caption
    a source block without a language
    
    an example block with
    multiple lines including
    
    
    empty lines!
    
    it also has multiple parameters
    
    src, example & export blocks treat their content as raw text
    /inline/ *markup* is ignored
          and whitespace is honored and not removed
    
    examples like this
    are also supported
    
    note that /inline/ *markup* ignored
    

    Mongodb is webscale. (source: mongodb-is-web-scale)

    blocks like the quote block parse their content and can contain

    • lists

    • inline markup

    • tables

      foo
      bar
      baz
    • paragraphs

    • whitespace is honored and not removed (but is not displayed because that's how html works by default) it can be made visible using css (e.g. white-space: pre).

    • list item 1 blocks can contain unindented lines that would normally end a list item

      this line is not indented - if it was outside of a block the list item would end
      

      this line is not indented - if it was outside of a block the list item would end

      now we're outside the block again and the following unindented line will be outside of the list item

    this unindented line is outside of the list item

    • list item 2

      #+BEGIN_EXAMPLE
      

      #+END_EXAMPLE

      #+END_QUOTE
      
    • verse blocks

      • emacs / ox-hugo rendering

        Great clouds overhead
        Tiny black birds rise and fall
        Snow covers Emacs

           ---AlexSchroeder

      • go-org rendering

        <style>
        .verse-block p { white-space: pre; }
        .verse-block p + p { margin: 0; }
        </style>
        

        Great clouds overhead Tiny black birds rise and fall Snow covers Emacs

        —AlexSchroeder

    go-org-1.0.0/org/testdata/blocks.org000066400000000000000000000046011361136365600173030ustar00rootroot00000000000000#+CAPTION: block caption #+BEGIN_SRC bash :results raw echo "a bash source block" function hello { echo Hello World! } hello #+END_SRC #+BEGIN_SRC a source block without a language #+END_SRC #+BEGIN_EXAMPLE foo bar baz an example block with multiple lines including empty lines! it also has multiple parameters src, example & export blocks treat their content as raw text /inline/ *markup* is ignored and whitespace is honored and not removed #+END_EXAMPLE : examples like this : are also supported : : note that /inline/ *markup* ignored #+BEGIN_QUOTE Mongodb is *webscale*. (source: [[http://www.mongodb-is-web-scale.com/][mongodb-is-web-scale]]) blocks like the quote block parse their content and can contain - lists - inline /markup/ - tables | foo | | bar | | baz | - paragraphs - ... whitespace is honored and not removed (but is not displayed because that's how html works by default) it can be made visible using css (e.g. =white-space: pre=). #+END_QUOTE #+BEGIN_EXPORT html #+END_EXPORT - list item 1 blocks can contain unindented lines that would normally end a list item #+BEGIN_EXAMPLE this line is not indented - if it was outside of a block the list item would end #+END_EXAMPLE #+BEGIN_QUOTE this line is not indented - if it was outside of a block the list item would end #+END_QUOTE now we're outside the block again and the following unindented line will be outside of the list item this unindented line is outside of the list item - list item 2 #+BEGIN_SRC #+BEGIN_EXAMPLE #+END_SRC #+END_EXAMPLE #+BEGIN_QUOTE #+BEGIN_EXAMPLE #+END_QUOTE #+END_EXAMPLE #+END_QUOTE - verse blocks - emacs / ox-hugo rendering #+BEGIN_EXPORT html

    Great clouds overhead
    Tiny black birds rise and fall
    Snow covers Emacs

       ---AlexSchroeder

    #+END_EXPORT - go-org rendering #+BEGIN_SRC html #+END_SRC #+BEGIN_EXPORT html #+END_EXPORT #+BEGIN_VERSE Great clouds overhead Tiny black birds rise and fall Snow covers Emacs ---AlexSchroeder #+END_VERSE go-org-1.0.0/org/testdata/blocks.pretty_org000066400000000000000000000046051361136365600207160ustar00rootroot00000000000000#+CAPTION: block caption #+BEGIN_SRC bash :results raw echo "a bash source block" function hello { echo Hello World! } hello #+END_SRC #+BEGIN_SRC a source block without a language #+END_SRC #+BEGIN_EXAMPLE foo bar baz an example block with multiple lines including empty lines! it also has multiple parameters src, example & export blocks treat their content as raw text /inline/ *markup* is ignored and whitespace is honored and not removed #+END_EXAMPLE : examples like this : are also supported : : note that /inline/ *markup* ignored #+BEGIN_QUOTE Mongodb is *webscale*. (source: [[http://www.mongodb-is-web-scale.com/][mongodb-is-web-scale]]) blocks like the quote block parse their content and can contain - lists - inline /markup/ - tables | foo | | bar | | baz | - paragraphs - ... whitespace is honored and not removed (but is not displayed because that's how html works by default) it can be made visible using css (e.g. =white-space: pre=). #+END_QUOTE #+BEGIN_EXPORT html #+END_EXPORT - list item 1 blocks can contain unindented lines that would normally end a list item #+BEGIN_EXAMPLE this line is not indented - if it was outside of a block the list item would end #+END_EXAMPLE #+BEGIN_QUOTE this line is not indented - if it was outside of a block the list item would end #+END_QUOTE now we're outside the block again and the following unindented line will be outside of the list item this unindented line is outside of the list item - list item 2 #+BEGIN_SRC #+BEGIN_EXAMPLE #+END_SRC #+END_EXAMPLE #+BEGIN_QUOTE #+BEGIN_EXAMPLE #+END_QUOTE #+END_EXAMPLE #+END_QUOTE - verse blocks - emacs / ox-hugo rendering #+BEGIN_EXPORT html

    Great clouds overhead
    Tiny black birds rise and fall
    Snow covers Emacs

       ---AlexSchroeder

    #+END_EXPORT - go-org rendering #+BEGIN_SRC html #+END_SRC #+BEGIN_EXPORT html #+END_EXPORT #+BEGIN_VERSE Great clouds overhead Tiny black birds rise and fall Snow covers Emacs ---AlexSchroeder #+END_VERSE go-org-1.0.0/org/testdata/captions.html000066400000000000000000000014121361136365600200200ustar00rootroot00000000000000

    Anything can be captioned.

    echo "i have a caption!"
    
    captioned soure block
    https://placekitten.com/200/200#.png
    captioned link (image in this case)

    note that the whole paragraph is captioned, so a linebreak is needed for images to caption correctly

    https://placekitten.com/200/200#.png see?

    captioned link (image in this case)
    go-org-1.0.0/org/testdata/captions.org000066400000000000000000000006151361136365600176470ustar00rootroot00000000000000Anything can be captioned. #+CAPTION: captioned soure block #+BEGIN_SRC sh echo "i have a caption!" #+END_SRC #+CAPTION: captioned link (image in this case) [[https://placekitten.com/200/200#.png]] note that the whole paragraph is captioned, so a linebreak is needed for images to caption correctly #+CAPTION: captioned link (image in this case) [[https://placekitten.com/200/200#.png]] see? go-org-1.0.0/org/testdata/captions.pretty_org000066400000000000000000000006151361136365600212560ustar00rootroot00000000000000Anything can be captioned. #+CAPTION: captioned soure block #+BEGIN_SRC sh echo "i have a caption!" #+END_SRC #+CAPTION: captioned link (image in this case) [[https://placekitten.com/200/200#.png]] note that the whole paragraph is captioned, so a linebreak is needed for images to caption correctly #+CAPTION: captioned link (image in this case) [[https://placekitten.com/200/200#.png]] see? go-org-1.0.0/org/testdata/footnotes.html000066400000000000000000000103451361136365600202250ustar00rootroot00000000000000

    Using some footnotes

    • normal footnote reference 1 2 3 (footnote names can be anything in the format [\w-])

    • further references to the same footnote should not 1 render duplicates in the footnote list

    • inline footnotes are also supported via 4.

    • anonymous inline footnotes are also supported via 5.

    • Footnote definitions are not printed where they appear. Rather, they are gathered and exported at the end of the document in the footnote section. 6

    • footnotes that reference a non-existant definition are rendered but log a warning 7

    Footnotes

    Please note that the footnotes section is not automatically excluded from the export like in emacs. 8

    this is not part of 8 anymore as there are 2 blank lines in between!


    1

    https://www.example.com

    • footnotes can contain markup

    • and other elements

      • like blocks

        other non-plain
        
      • and tables

        1 a
        2 b
        3 c
    2

    Footnotes break after two consecutive empty lines - just like paragraphs - see https://orgmode.org/worg/dev/org-syntax.html. This shouldn't happen when the definition line and the line after that are empty.

    3

    yolo

    4

    the inline footnote definition

    5

    the anonymous inline footnote definition

    6

    so this definition will not be at the end of this section in the exported document. Rather, it will be somewhere down below in the footnotes section.

    8

    There's multiple reasons for that. Among others, doing so requires i18n (to recognize the section) and silently hides content before and after the footnotes.

    go-org-1.0.0/org/testdata/footnotes.org000066400000000000000000000037371361136365600200570ustar00rootroot00000000000000* Using some footnotes - normal footnote reference [fn:1] [fn:6] [fn:foo-bar] (footnote names can be anything in the format =[\w-]=) - further references to the same footnote should not [fn:1] render duplicates in the footnote list - inline footnotes are also supported via [fn:2:the inline footnote definition]. - anonymous inline footnotes are also supported via [fn::the anonymous inline footnote definition]. - Footnote definitions are not printed where they appear. Rather, they are gathered and exported at the end of the document in the footnote section. [fn:4] - footnotes that reference a non-existant definition are rendered but log a warning [fn:does-not-exist] [fn:4] so this definition will not be at the end of this section in the exported document. Rather, it will be somewhere down below in the footnotes section. [fn:5] this definition will also not be exported here - not only that, it will be overwritten by a definition of the same name later on in the document. That will log a warning but carry on nonetheless. * Footnotes Please note that the footnotes section is not automatically excluded from the export like in emacs. [fn:7] [fn:foo-bar] yolo [fn:1] https://www.example.com - footnotes can contain *markup* - and other elements - like blocks #+BEGIN_SRC other non-plain #+END_SRC - and tables | 1 | a | | 2 | b | | 3 | c | [fn:3] [[http://example.com/unused-footnote][example.com/unused-footnote]] [fn:5] another unused footnote (this definition overwrites the previous definition of =fn:5=) [fn:6] Footnotes break after two consecutive empty lines - just like paragraphs - see https://orgmode.org/worg/dev/org-syntax.html. This shouldn't happen when the definition line and the line after that are empty. [fn:7] There's multiple reasons for that. Among others, doing so requires i18n (to recognize the section) and silently hides content before and after the footnotes. this is not part of [fn:7] anymore as there are 2 blank lines in between! go-org-1.0.0/org/testdata/footnotes.pretty_org000066400000000000000000000037371361136365600214660ustar00rootroot00000000000000* Using some footnotes - normal footnote reference [fn:1] [fn:6] [fn:foo-bar] (footnote names can be anything in the format =[\w-]=) - further references to the same footnote should not [fn:1] render duplicates in the footnote list - inline footnotes are also supported via [fn:2:the inline footnote definition]. - anonymous inline footnotes are also supported via [fn::the anonymous inline footnote definition]. - Footnote definitions are not printed where they appear. Rather, they are gathered and exported at the end of the document in the footnote section. [fn:4] - footnotes that reference a non-existant definition are rendered but log a warning [fn:does-not-exist] [fn:4] so this definition will not be at the end of this section in the exported document. Rather, it will be somewhere down below in the footnotes section. [fn:5] this definition will also not be exported here - not only that, it will be overwritten by a definition of the same name later on in the document. That will log a warning but carry on nonetheless. * Footnotes Please note that the footnotes section is not automatically excluded from the export like in emacs. [fn:7] [fn:foo-bar] yolo [fn:1] https://www.example.com - footnotes can contain *markup* - and other elements - like blocks #+BEGIN_SRC other non-plain #+END_SRC - and tables | 1 | a | | 2 | b | | 3 | c | [fn:3] [[http://example.com/unused-footnote][example.com/unused-footnote]] [fn:5] another unused footnote (this definition overwrites the previous definition of =fn:5=) [fn:6] Footnotes break after two consecutive empty lines - just like paragraphs - see https://orgmode.org/worg/dev/org-syntax.html. This shouldn't happen when the definition line and the line after that are empty. [fn:7] There's multiple reasons for that. Among others, doing so requires i18n (to recognize the section) and silently hides content before and after the footnotes. this is not part of [fn:7] anymore as there are 2 blank lines in between! go-org-1.0.0/org/testdata/footnotes_in_headline.html000066400000000000000000000027751361136365600225540ustar00rootroot00000000000000

    Title 1


    1

    this test file just exists to reproduce a bug with footnotes in headlines - that only happens in very specific circumstances. The TLDR is:

    • HTMLWriter.footnotes should be a pointer field. I didn't notice my error as go translated my pointer-method calls on non-pointer values rather than complaining - i.e. footnotes.add() transparently gets translated to (&footnotes).add() (docs).

    • Headlines have to be htmlified twice - once for the outline and once for the headline itself. To do so we have to copy the writer

    • Copying the writer copies footnotes - which contains a map and a slice. Changes to the map will always be reflected in the original map. Changes to the slice will only be reflected if the slice doesn't grow.

    • We can thus end up with a footnote being in the mapping but not the slice - and get an index out of range error.

    go-org-1.0.0/org/testdata/footnotes_in_headline.org000066400000000000000000000016171361136365600223710ustar00rootroot00000000000000* Title [fn:1] [fn:1] this test file just exists to reproduce a bug with footnotes in headlines - that only happens in very specific circumstances. The TLDR is: - HTMLWriter.footnotes should be a pointer field. I didn't notice my error as go translated my pointer-method calls on non-pointer values rather than complaining - i.e. =footnotes.add()= transparently gets translated to =(&footnotes).add()= ([[https://golang.org/ref/spec#Calls][docs]]). - Headlines have to be htmlified twice - once for the outline and once for the headline itself. To do so we have to copy the writer - Copying the writer copies footnotes - which contains a map and a slice. Changes to the map will always be reflected in the original map. Changes to the slice will only be reflected if the slice doesn't grow. - We can thus end up with a footnote being in the mapping but not the slice - and get an index out of range error. go-org-1.0.0/org/testdata/footnotes_in_headline.pretty_org000066400000000000000000000016171361136365600240000ustar00rootroot00000000000000* Title [fn:1] [fn:1] this test file just exists to reproduce a bug with footnotes in headlines - that only happens in very specific circumstances. The TLDR is: - HTMLWriter.footnotes should be a pointer field. I didn't notice my error as go translated my pointer-method calls on non-pointer values rather than complaining - i.e. =footnotes.add()= transparently gets translated to =(&footnotes).add()= ([[https://golang.org/ref/spec#Calls][docs]]). - Headlines have to be htmlified twice - once for the outline and once for the headline itself. To do so we have to copy the writer - Copying the writer copies footnotes - which contains a map and a slice. Changes to the map will always be reflected in the original map. Changes to the slice will only be reflected if the slice doesn't grow. - We can thus end up with a footnote being in the mapping but not the slice - and get an index out of range error. go-org-1.0.0/org/testdata/headlines.html000066400000000000000000000051531361136365600201420ustar00rootroot00000000000000

    Simple Headline [1/2]

    • checked

    • unchecked

    • note that statistic tokens are marked up anywhere not just where they are actually meant to be - even here > [100%] < (Org mode proper does the same)

    TODO [B] Headline with todo status & priority

    DONE Headline with TODO status

    we can link to headlines that define a custom_id: #this-will-be-the-id-of-the-headline

    [A] Headline with tags & priority   foo bar

    Still outside the drawer

    This is inside the drawer

    Still outside the drawer

    CUSTOM headline with custom status

    it's possible to use #+SETUPFILE - in this case the setup file contains the following

    #+TODO: TODO DONE CUSTOM
    #+EXCLUDE_TAGS: noexport custom_noexport
    

    malformed property drawer

    :PROPERTIES: not a property

    :END:

    level limit for headlines to be included in the table of contents

    The toc option allows setting a level limit. For this file we set it to 1 - which means that the following headlines won't be included in the table of contents.

    headline 2 not in toc

    headline 3 not in toc

    anoter headline 2 not in toc

    you get the gist…

    go-org-1.0.0/org/testdata/headlines.org000066400000000000000000000030441361136365600177620ustar00rootroot00000000000000#+SETUPFILE: setup_file_org #+OPTIONS: toc:1 * Simple Headline [1/2] - [X] checked - [ ] unchecked - note that statistic tokens are marked up anywhere not just where they are actually meant to be - even here > [100%] < (Org mode proper does the same) * TODO [#B] Headline with todo status & priority * DONE Headline with TODO status :PROPERTIES: :custom_id: this-will-be-the-id-of-the-headline :note: property drawers are not exported as html like other drawers :END: we can link to headlines that define a custom_id: [[#this-will-be-the-id-of-the-headline]] * [#A] Headline with tags & priority :foo:bar: Still outside the drawer :DRAWERNAME: This is inside the drawer :END: Still outside the drawer * CUSTOM headline with custom status it's possible to use =#+SETUPFILE= - in this case the setup file contains the following #+INCLUDE: "setup_file_org" src org * excluded headline :custom_noexport: this headline and it's content are not exported as it is marked with an =EXCLUDE_TAGS= tag. By default =EXCLUDE_TAGS= is just =:noexport:=. * malformed property drawer :PROPERTIES: not a property :END: * level limit for headlines to be included in the table of contents The toc option allows setting a [[https://orgmode.org/manual/Export-settings.html][level limit]]. For this file we set it to 1 - which means that the following headlines won't be included in the table of contents. ** headline 2 not in toc *** headline 3 not in toc ** anoter headline 2 not in toc you get the gist... go-org-1.0.0/org/testdata/headlines.pretty_org000066400000000000000000000030351361136365600213710ustar00rootroot00000000000000#+SETUPFILE: setup_file_org #+OPTIONS: toc:1 * Simple Headline [1/2] - [X] checked - [ ] unchecked - note that statistic tokens are marked up anywhere not just where they are actually meant to be - even here > [100%] < (Org mode proper does the same) * TODO [#B] Headline with todo status & priority * DONE Headline with TODO status :PROPERTIES: :CUSTOM_ID: this-will-be-the-id-of-the-headline :NOTE: property drawers are not exported as html like other drawers :END: we can link to headlines that define a custom_id: [[#this-will-be-the-id-of-the-headline]] * [#A] Headline with tags & priority :foo:bar: Still outside the drawer :DRAWERNAME: This is inside the drawer :END: Still outside the drawer * CUSTOM headline with custom status it's possible to use =#+SETUPFILE= - in this case the setup file contains the following #+INCLUDE: "setup_file_org" src org * excluded headline :custom_noexport: this headline and it's content are not exported as it is marked with an =EXCLUDE_TAGS= tag. By default =EXCLUDE_TAGS= is just =:noexport:=. * malformed property drawer :PROPERTIES: not a property :END: * level limit for headlines to be included in the table of contents The toc option allows setting a [[https://orgmode.org/manual/Export-settings.html][level limit]]. For this file we set it to 1 - which means that the following headlines won't be included in the table of contents. ** headline 2 not in toc *** headline 3 not in toc ** anoter headline 2 not in toc you get the gist... go-org-1.0.0/org/testdata/inline.html000066400000000000000000000055701361136365600174670ustar00rootroot00000000000000
    • emphasis and a hard line break
      see?
      also hard line breaks not followed by a newline get ignored, see \\

    • .emphasis with dot border chars.

    • emphasis with a slash/inside

    • emphasis followed by raw text with slash /

    • ->/not an emphasis/<-

    • links with slashes do not become emphasis: https://somelinkshouldntrenderaccidentalemphasis.com/ emphasis

    • underlined bold verbatim code strikethrough

    • bold string with an *asterisk inside

    • multiline emphasis is supported - and respects MaxEmphasisNewLines (default: 1) so this is emphasized

      /but this is not emphasized/

    • empty emphasis markers like ++ // __ and so on are ignored

    • use _{} for subscriptsub and ^{} for superscriptsuper

    • links

      1. regular link https://example.com link without description

      2. regular link example.com link with description

      3. regular link to a file (image) my-img.png

      4. regular link to a file (video)

      5. regular link to http (image) http://placekitten.com/200/200#.png

      6. regular link to https (image) https://placekitten.com/200/200#.png

      7. regular link enclosed in [] [https://www.example.com] [example.com]

      8. auto link, i.e. not inside \[[square brackets]\] https://www.example.com

    • timestamps

      • <2019-01-06 Sun>

      • <2019-01-06 Sun>

      • <2019-01-06 Sun 18:00>

      • <2019-01-06 Sun 18:00 +1w>

      • <2019-01-06 Sun 18:00>

      • <2019-01-06 Sun 18:00 +1w>

    go-org-1.0.0/org/testdata/inline.org000066400000000000000000000027401361136365600173060ustar00rootroot00000000000000- /emphasis/ and a hard line break \\ see? \\ also hard line breaks not followed by a newline get ignored, see \\ - /.emphasis with dot border chars./ - /emphasis with a slash/inside/ - /emphasis/ followed by raw text with slash / - ->/not an emphasis/<- - links with slashes do not become /emphasis/: [[https://somelinkshouldntrenderaccidentalemphasis.com]]/ /emphasis/ - _underlined_ *bold* =verbatim= ~code~ +strikethrough+ - *bold string with an *asterisk inside* - =multiline emphasis is supported - and respects MaxEmphasisNewLines (default: 1)= /so this is emphasized/ /but this is not emphasized/ - empty emphasis markers like ++ // __ and so on are ignored - use _{} for subscript_{sub} and ^{} for superscript^{super} - links 1. regular link [[https://example.com]] link without description 2. regular link [[https://example.com][example.com]] link with description 3. regular link to a file (image) [[file:my-img.png]] 4. regular link to a file (video) [[my-video.mp4]] 5. regular link to http (image) [[http://placekitten.com/200/200#.png]] 6. regular link to https (image) [[https://placekitten.com/200/200#.png]] 7. regular link enclosed in [] [[[https://www.example.com]]] [[[https://www.example.com][example.com]]] 8. auto link, i.e. not inside =\[[square brackets]\]= https://www.example.com - timestamps - <2019-01-06> - <2019-01-06 Sun> - <2019-01-06 Sun 18:00> - <2019-01-06 Sun 18:00 +1w> - <2019-01-06 18:00> - <2019-01-06 18:00 +1w> go-org-1.0.0/org/testdata/inline.pretty_org000066400000000000000000000027541361136365600207220ustar00rootroot00000000000000- /emphasis/ and a hard line break \\ see? \\ also hard line breaks not followed by a newline get ignored, see \\ - /.emphasis with dot border chars./ - /emphasis with a slash/inside/ - /emphasis/ followed by raw text with slash / - ->/not an emphasis/<- - links with slashes do not become /emphasis/: [[https://somelinkshouldntrenderaccidentalemphasis.com]]/ /emphasis/ - _underlined_ *bold* =verbatim= ~code~ +strikethrough+ - *bold string with an *asterisk inside* - =multiline emphasis is supported - and respects MaxEmphasisNewLines (default: 1)= /so this is emphasized/ /but this is not emphasized/ - empty emphasis markers like ++ // __ and so on are ignored - use _{} for subscript_{sub} and ^{} for superscript^{super} - links 1. regular link [[https://example.com]] link without description 2. regular link [[https://example.com][example.com]] link with description 3. regular link to a file (image) [[file:my-img.png]] 4. regular link to a file (video) [[my-video.mp4]] 5. regular link to http (image) [[http://placekitten.com/200/200#.png]] 6. regular link to https (image) [[https://placekitten.com/200/200#.png]] 7. regular link enclosed in [] [[[https://www.example.com]]] [[[https://www.example.com][example.com]]] 8. auto link, i.e. not inside =\[[square brackets]\]= https://www.example.com - timestamps - <2019-01-06 Sun> - <2019-01-06 Sun> - <2019-01-06 Sun 18:00> - <2019-01-06 Sun 18:00 +1w> - <2019-01-06 Sun 18:00> - <2019-01-06 Sun 18:00 +1w> go-org-1.0.0/org/testdata/keywords.html000066400000000000000000000013331361136365600200510ustar00rootroot00000000000000
    echo "a bash source block with custom html attributes"
    
    and multiple lines of captions!

    and an image with custom html attributes and a caption

    https://placekitten.com/200/200#.png
    kittens!

    named paragraph

    named block
    
    go-org-1.0.0/org/testdata/keywords.org000066400000000000000000000007571361136365600177050ustar00rootroot00000000000000 #+CAPTION: and _multiple_ #+CAPTION: lines of *captions*! #+ATTR_HTML: :class a b #+ATTR_HTML: :id it :class c d #+BEGIN_SRC sh echo "a bash source block with custom html attributes" #+END_SRC and an image with custom html attributes and a caption #+CAPTION: kittens! #+ATTR_HTML: :style height: 100%; :id overwritten #+ATTR_HTML: :style border: 10px solid black; :id kittens [[https://placekitten.com/200/200#.png]] #+NAME: foo named paragraph #+NAME: bar #+begin_src named block #+end_src go-org-1.0.0/org/testdata/keywords.pretty_org000066400000000000000000000007571361136365600213140ustar00rootroot00000000000000 #+CAPTION: and _multiple_ #+CAPTION: lines of *captions*! #+ATTR_HTML: :class a b #+ATTR_HTML: :id it :class c d #+BEGIN_SRC sh echo "a bash source block with custom html attributes" #+END_SRC and an image with custom html attributes and a caption #+CAPTION: kittens! #+ATTR_HTML: :style height: 100%; :id overwritten #+ATTR_HTML: :style border: 10px solid black; :id kittens [[https://placekitten.com/200/200#.png]] #+NAME: foo named paragraph #+NAME: bar #+BEGIN_SRC named block #+END_SRC go-org-1.0.0/org/testdata/latex.html000066400000000000000000000013301361136365600173140ustar00rootroot00000000000000

    without latex delimiters the _{i=1} in \sum_{i=1}^n a_n is interpreted as subscript. we support \(...\), \[...\], $$...$$ and \begin{$env}...\end{$env} as latex fragment delimiters.

    • i=1^n a_n (without latex delimiter)

    • \(\sum_{i=1}^n a_n\)

    • \[\sum_{i=1}^n a_n\]

    • $$\sum_{i=1}^n a_n$$

    • \begin{xyz}\sum_{i=1}^n a_n\end{xyz}

    • \begin{xyz} \sum_{i=1}^n a_n \end{xyz}

    • $2 + 2$, $3 - 3$

    go-org-1.0.0/org/testdata/latex.org000066400000000000000000000006341361136365600171450ustar00rootroot00000000000000without latex delimiters the =_{i=1}= in =\sum_{i=1}^n a_n= is interpreted as subscript. we support =\(...\)=, =\[...\]=, =$$...$$= and =\begin{$env}...\end{$env}= as latex fragment delimiters. - \sum_{i=1}^n a_n (without latex delimiter) - \(\sum_{i=1}^n a_n\) - \[\sum_{i=1}^n a_n\] - $$\sum_{i=1}^n a_n$$ - \begin{xyz}\sum_{i=1}^n a_n\end{xyz} - \begin{xyz} \sum_{i=1}^n a_n \end{xyz} - $2 + 2$, $3 - 3$ go-org-1.0.0/org/testdata/latex.pretty_org000066400000000000000000000006341361136365600205540ustar00rootroot00000000000000without latex delimiters the =_{i=1}= in =\sum_{i=1}^n a_n= is interpreted as subscript. we support =\(...\)=, =\[...\]=, =$$...$$= and =\begin{$env}...\end{$env}= as latex fragment delimiters. - \sum_{i=1}^n a_n (without latex delimiter) - \(\sum_{i=1}^n a_n\) - \[\sum_{i=1}^n a_n\] - $$\sum_{i=1}^n a_n$$ - \begin{xyz}\sum_{i=1}^n a_n\end{xyz} - \begin{xyz} \sum_{i=1}^n a_n \end{xyz} - $2 + 2$, $3 - 3$ go-org-1.0.0/org/testdata/lists.html000066400000000000000000000052061361136365600173430ustar00rootroot00000000000000
    • unordered list item 1

    • list item with empty first and second line
      normally an empty line breaks the list item - but we make an exception for the first line and don't count it towards that limit

    • unordered list item 2 - with inline markup

      1. ordered sublist item 1

        1. ordered sublist item 1

        2. ordered sublist item 2

        3. ordered sublist item 3

      2. ordered sublist item 2

      3. list item with empty first and second line - see above

    • unordered list item 3 - and a link and some lines of text

      1. and another subitem

        echo with a block
        
      2. and another one with a table

        a b c
        1 2 3

        and text with an empty line in between as well!

    • unordered list item 4

      with an example
      
      that spans multiple lines
      

    descriptive lists

    term

    details continued details

    ?

    details without a term

    term

    details on a new line

    term

    details on a new line (with an empty line in between) continued

    echo "Hello World!"
    

    some list termination tests

    • unordered 1

    • unordered 2

    1. ordered 1

    2. ordered 2

    1. ordered 1

    2. ordered 2

    • unordered 1

    • unordered 2

    1. ordered 1

    2. ordered 2

    unordered descriptive

    1

    unordered descriptive

    2

    ordered descriptive

    1

    ordered descriptive

    2

    • unordered 1

    • unordered 2

    go-org-1.0.0/org/testdata/lists.org000066400000000000000000000027561361136365600171750ustar00rootroot00000000000000- [ ] unordered list item 1 - list item with empty first and second line \\ normally an empty line breaks the list item - but we make an exception for the first line and don't count it towards that limit - unordered list item 2 - with ~inline~ /markup/ 1. [-] ordered sublist item 1 a) [X] ordered sublist item 1 b) [ ] ordered sublist item 2 c) [X] ordered sublist item 3 2. ordered sublist item 2 3. list item with empty first and second line - see above - [X] unordered list item 3 - and a [[https://example.com][link]] and some lines of text 1. and another subitem #+BEGIN_SRC sh echo with a block #+END_SRC 2. and another one with a table | a | b | c | |---+---+---| | 1 | 2 | 3 | and text with an empty line in between as well! - unordered list item 4 : with an example : : that spans multiple lines descriptive lists - [ ] term :: details continued details - [ ] details without a term - [X] term :: details on a new line - term :: details on a new line (with an empty line in between) *continued* #+BEGIN_SRC bash echo "Hello World!" #+END_SRC some list termination tests - unordered 1 - unordered 2 1. ordered 1 2. ordered 2 1. ordered 1 2. ordered 2 - unordered 1 - unordered 2 1. ordered 1 2. ordered 2 - unordered descriptive :: 1 - unordered descriptive :: 2 1. ordered descriptive :: 1 2. ordered descriptive :: 2 - unordered 1 - unordered 2 go-org-1.0.0/org/testdata/lists.pretty_org000066400000000000000000000027651361136365600206040ustar00rootroot00000000000000- [ ] unordered list item 1 - list item with empty first and second line \\ normally an empty line breaks the list item - but we make an exception for the first line and don't count it towards that limit - unordered list item 2 - with ~inline~ /markup/ 1. [-] ordered sublist item 1 a) [X] ordered sublist item 1 b) [ ] ordered sublist item 2 c) [X] ordered sublist item 3 2. ordered sublist item 2 3. list item with empty first and second line - see above - [X] unordered list item 3 - and a [[https://example.com][link]] and some lines of text 1. and another subitem #+BEGIN_SRC sh echo with a block #+END_SRC 2. and another one with a table | a | b | c | |---+---+---| | 1 | 2 | 3 | and text with an empty line in between as well! - unordered list item 4 : with an example : : that spans multiple lines descriptive lists - [ ] term :: details continued details - [ ] details without a term - [X] term :: details on a new line - term :: details on a new line (with an empty line in between) *continued* #+BEGIN_SRC bash echo "Hello World!" #+END_SRC some list termination tests - unordered 1 - unordered 2 1. ordered 1 2. ordered 2 1. ordered 1 2. ordered 2 - unordered 1 - unordered 2 1. ordered 1 2. ordered 2 - unordered descriptive :: 1 - unordered descriptive :: 2 1. ordered descriptive :: 1 2. ordered descriptive :: 2 - unordered 1 - unordered 2 go-org-1.0.0/org/testdata/misc.html000066400000000000000000000317671361136365600171530ustar00rootroot00000000000000

    Misc

    issues from goorgeous (free test cases, yay!)

    DONE #19: Support #+HTML

    neato!

    DONE #29: Support verse block

    This is verse

    or even a totally custom kind of block crazy ain't it?

    DONE #30: Support #+SETUPFILE

    see ./headlines.org

    DONE #31: Support #+INCLUDE

    Note that only src/example/export block inclusion is supported for now. There's quite a lot more to include (see the org manual for include files) but I don't have a use case for this yet and stuff like namespacing footnotes of included files adds quite a bit of complexity.

    for now files can be included as:

    • src block

      #+SETUPFILE: setup_file_org
      #+OPTIONS: toc:1
      * Simple Headline [1/2]
      - [X] checked
      - [ ] unchecked
      - note that statistic tokens are marked up anywhere
        not just where they are actually meant to be - even here > [100%] <
        (Org mode proper does the same)
      * TODO [#B] Headline with todo status & priority
      * DONE Headline with TODO status
      :PROPERTIES:
      :custom_id: this-will-be-the-id-of-the-headline
      :note: property drawers are not exported as html like other drawers
      :END:
      
      we can link to headlines that define a custom_id: [[#this-will-be-the-id-of-the-headline]]
      * [#A] Headline with tags & priority                                :foo:bar:
      Still outside the drawer
      :DRAWERNAME:
      This is inside the drawer
      :END:
      Still outside the drawer
      * CUSTOM headline with custom status
      it's possible to use =#+SETUPFILE= - in this case the setup file contains the following
      
      #+INCLUDE: "setup_file_org" src org
      * excluded headline                                                :custom_noexport:
      this headline and it's content are not exported as it is marked with an =EXCLUDE_TAGS= tag.
      By default =EXCLUDE_TAGS= is just =:noexport:=.
      
      * malformed property drawer
      :PROPERTIES:
      not a property
      :END:
      * level limit for headlines to be included in the table of contents
      The toc option allows setting a [[https://orgmode.org/manual/Export-settings.html][level limit]]. For this file we set it to 1 - which means that the following headlines
      won't be included in the table of contents.
      ** headline 2 not in toc
      *** headline 3 not in toc
      ** anoter headline 2 not in toc
      you get the gist...
      
    • export block

      Paragraphs are the default element.

      Empty lines and other elements end paragraphs - but paragraphs can obviously span multiple lines.

      Paragraphs can contain inline markup like emphasis strong and links example.com and stuff.

    • example block

      language: go
      go: "1.x"
      script:
        - make test
        - make generate-gh-pages
      deploy:
        provider: pages
        github-token: $GITHUB_TOKEN  # From travis-ci.org repository settings
        local-dir: gh-pages
        target-branch: gh-pages
        skip-cleanup: true
        verbose: true
        on:
          branch: master
      

    DONE #33: Wrong output when mixing html with Org mode

    foo foo
    bar bar

    DONE #41: Support Table Of Contents

    DONE #46: Support for symbols like ndash and mdash

    • ndash –

    • mdash —

    • ellipsis …

    • acute Á and so on

    • note that —— is replaced with 2 mdashes and …. becomes ellipsis+. and so on - that's how org also does it

    DONE #47: Consecutive code wrapped text gets joined

    either this or that foo. either this or that foo.

    DONE #50: LineBreaks in lists are preserved

    • this list item has multiple linbreaks - but it's still just one paragraph (i.e. no line breaks are rendered)

    • foobar

    1. same goes for ordered lists

    2. foo

    DONE #68: Quote block with inline markup

    this is markup!

    DONE #72: Support for #+ATTR_HTML

    Go is fine though.

    DONE #75: Not parsing nested lists correctly

    • bullet 1

      • sub bullet

    DONE #77: Recognize code— as code plus dash

    DONE #78: Emphasis at beginning of line

    italics

    Text italics

    DONE #82: Crash on empty headline

    just a space as title…

    DONE #84: Paragraphs that are not followed by an empty line are not parsed correctly

    Foo

    Foo paragraph.

    Bar

    Bar paragraph

    DONE #86: Multiple hyphens not converted to dashes

    just like #46

    • -- -> – (en dash)

    • --- -> — (em dash)

    also, consecutive dashes inside

    • inline code -- --- and verbatim -- ---

    • src/example/export blocks should not be converted!

      --, ---
      
      --, ---
      
      --, ---
      --, ---
      

    DONE #87: Markup in footnotes is rendered literally

    footnotes can contain markup - and other elements and stuff 1 2

    DONE #92: src blocks only render in caps

    The behaviour of Org mode <s TAB changed and it now inserts lowercased src blocks (go-org already handled this one)

    • lowercased:

      uname -a
      
    • uppercased

      uname -a
      

    issues (wrongly) filed with hugo

    #3874 exporting images in org mode

    Hello, I'm writing hugo blogs using org-mode.

    When inserting an image link like /home/amos/Pictures/Screenshots/img-2017-09-11-165647.png, hugo doesn't export the image.

    #4006 source code blocks in org not rendered correctly

    (defun small-shell ()
      (interactive)
      (split-window-vertically)
      (other-window 1)
      (shrink-window (- (window-height) 12))
      (ansi-term))
    

    misc fuzz / regression / edge case

    index out of range in headline priority parsing

    [#B

    index out of range in explicit line break parsing

    0\\

    Footnotes


    1

    a footnote with markup

    • and a list

    • because that's possible

    2

    that also goes for inline footnote definitions

    go-org-1.0.0/org/testdata/misc.org000066400000000000000000000121601361136365600167600ustar00rootroot00000000000000#+TITLE: Misc ** issues from goorgeous (free test cases, yay!) *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/19][#19]]: Support #+HTML #+HTML:

    neato!

    *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/29][#29:]] Support verse block #+BEGIN_VERSE This *is* verse #+END_VERSE #+BEGIN_CUSTOM or even a *totally* /custom/ kind of block crazy ain't it? #+END_CUSTOM *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/30][#30]]: Support #+SETUPFILE see =./headlines.org= *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/31][#31]]: Support #+INCLUDE Note that only src/example/export block inclusion is supported for now. There's quite a lot more to include (see the [[https://orgmode.org/manual/Include-files.html][org manual for include files]]) but I don't have a use case for this yet and stuff like namespacing footnotes of included files adds quite a bit of complexity. for now files can be included as: - src block #+INCLUDE: "./headlines.org" src org - export block #+INCLUDE: "./paragraphs.html" export html - example block #+INCLUDE: "../../.travis.yml" example yaml *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/33][#33]]: Wrong output when mixing html with Org mode #+HTML:
    | *foo* | foo | | *bar* | bar | #+HTML:
    *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/41][#41]]: Support Table Of Contents *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/46][#46]]: Support for symbols like ndash and mdash - ndash -- - mdash --- - ellipsis ... - acute \Aacute and so on - note that ------ is replaced with 2 mdashes and .... becomes ellipsis+. and so on - that's how org also does it *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/47][#47:]] Consecutive ~code~ wrapped text gets joined either ~this~ or ~that~ foo. either ~this~ or ~that~ foo. *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/50][#50]]: LineBreaks in lists are preserved - this list item has multiple linbreaks - but it's still just one paragraph (i.e. no line breaks are rendered) - foobar 1. same goes for ordered lists 2. foo *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/68][#68]]: Quote block with inline markup #+BEGIN_QUOTE [[https://www.example.com][/this/ *is* _markup_!]] #+END_QUOTE *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/72][#72]]: Support for #+ATTR_HTML #+ATTR_HTML: :alt Go is fine though. :id gopher-image #+ATTR_HTML: :width 300 :style border:2px solid black; [[https://golang.org/doc/gopher/pkg.png]] *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/75][#75]]: Not parsing nested lists correctly - bullet 1 - sub bullet *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/77][#77]]: Recognize =code=--- as code plus dash *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/78][#78]]: Emphasis at beginning of line /italics/ Text /italics/ *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/82][#82]]: Crash on empty headline **** just a space as title... *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/84][#84]]: Paragraphs that are not followed by an empty line are not parsed correctly **** Foo Foo paragraph. **** Bar Bar paragraph *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/86][#86]]: Multiple hyphens not converted to dashes just like #46 - =--= -> -- (en dash) - =---= -> --- (em dash) also, consecutive dashes inside - inline code =--= =---= and verbatim ~--~ ~---~ - src/example/export blocks should not be converted! #+BEGIN_SRC sh --, --- #+END_SRC #+BEGIN_EXAMPLE --, --- #+END_EXAMPLE #+BEGIN_EXPORT html --, --- #+END_EXPORT : --, --- *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/87][#87]]: Markup in footnotes is rendered literally footnotes can contain *markup* - and other elements and stuff [fn:1] [fn:2:that also goes for *inline* footnote /definitions/] *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/92][#92]]: src blocks only render in caps The behaviour of Org mode =neato!

    *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/29][#29:]] Support verse block #+BEGIN_VERSE This *is* verse #+END_VERSE #+BEGIN_CUSTOM or even a *totally* /custom/ kind of block crazy ain't it? #+END_CUSTOM *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/30][#30]]: Support #+SETUPFILE see =./headlines.org= *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/31][#31]]: Support #+INCLUDE Note that only src/example/export block inclusion is supported for now. There's quite a lot more to include (see the [[https://orgmode.org/manual/Include-files.html][org manual for include files]]) but I don't have a use case for this yet and stuff like namespacing footnotes of included files adds quite a bit of complexity. for now files can be included as: - src block #+INCLUDE: "./headlines.org" src org - export block #+INCLUDE: "./paragraphs.html" export html - example block #+INCLUDE: "../../.travis.yml" example yaml *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/33][#33]]: Wrong output when mixing html with Org mode #+HTML:
    | *foo* | foo | | *bar* | bar | #+HTML:
    *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/41][#41]]: Support Table Of Contents *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/46][#46]]: Support for symbols like ndash and mdash - ndash -- - mdash --- - ellipsis ... - acute \Aacute and so on - note that ------ is replaced with 2 mdashes and .... becomes ellipsis+. and so on - that's how org also does it *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/47][#47:]] Consecutive ~code~ wrapped text gets joined either ~this~ or ~that~ foo. either ~this~ or ~that~ foo. *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/50][#50]]: LineBreaks in lists are preserved - this list item has multiple linbreaks - but it's still just one paragraph (i.e. no line breaks are rendered) - foobar 1. same goes for ordered lists 2. foo *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/68][#68]]: Quote block with inline markup #+BEGIN_QUOTE [[https://www.example.com][/this/ *is* _markup_!]] #+END_QUOTE *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/72][#72]]: Support for #+ATTR_HTML #+ATTR_HTML: :alt Go is fine though. :id gopher-image #+ATTR_HTML: :width 300 :style border:2px solid black; [[https://golang.org/doc/gopher/pkg.png]] *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/75][#75]]: Not parsing nested lists correctly - bullet 1 - sub bullet *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/77][#77]]: Recognize =code=--- as code plus dash *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/78][#78]]: Emphasis at beginning of line /italics/ Text /italics/ *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/82][#82]]: Crash on empty headline **** just a space as title... *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/84][#84]]: Paragraphs that are not followed by an empty line are not parsed correctly **** Foo Foo paragraph. **** Bar Bar paragraph *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/86][#86]]: Multiple hyphens not converted to dashes just like #46 - =--= -> -- (en dash) - =---= -> --- (em dash) also, consecutive dashes inside - inline code =--= =---= and verbatim ~--~ ~---~ - src/example/export blocks should not be converted! #+BEGIN_SRC sh --, --- #+END_SRC #+BEGIN_EXAMPLE --, --- #+END_EXAMPLE #+BEGIN_EXPORT html --, --- #+END_EXPORT : --, --- *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/87][#87]]: Markup in footnotes is rendered literally footnotes can contain *markup* - and other elements and stuff [fn:1] [fn:2:that also goes for *inline* footnote /definitions/] *** DONE [[https://github.com/chaseadamsio/goorgeous/issues/92][#92]]: src blocks only render in caps The behaviour of Org mode = DONE [A] #+OPTIONS: toggles supported by go-org   tag1 tag2

    go-org supports multiple export toggles as described in the export settings section of the Org mode manual. By default (most of?) those toggles are enabled. This file starts with #+OPTIONS: toc:nil f:nil e:nil and thus disables the table of contents, footnotes & entities. That means, entities like --- --- (mdash) will be left untouched, footnotes like [fn:1] will not be exported and there won't be a table of contents at the top. As buffer options are merged with the defaults, the above headline will be exported with priority, todo status & tags.

    key description
    f Include footnotes (definitions & links)
    e Include entities
    toc Include table of contents (outline)
    pri Include priority [#A], [#B], [#C] in headline title
    todo Include todo status in headline title
    tags Include tags in headline title
    go-org-1.0.0/org/testdata/options.org000066400000000000000000000026021361136365600175200ustar00rootroot00000000000000#+OPTIONS: toc:nil f:nil e:nil * DONE [#A] =#+OPTIONS:= toggles supported by =go-org= :tag1:tag2: =go-org= supports multiple export toggles as described in the [[https://orgmode.org/manual/Export-settings.html][export settings]] section of the Org mode manual. By default (most of?) those toggles are enabled. This file starts with =#+OPTIONS: toc:nil f:nil e:nil= and thus disables the table of contents, footnotes & entities. That means, entities like =---= --- (mdash) will be left untouched, footnotes like =[fn:1]= [fn:1] will not be exported and there won't be a table of contents at the top. As buffer options are merged with the defaults, the above headline will be exported *with* priority, todo status & tags. | key | description | |------+-----------------------------------------------------------| | f | Include footnotes (definitions & links) | | e | Include entities | | toc | Include table of contents (outline) | |------+-----------------------------------------------------------| | pri | Include priority =[#A]=, =[#B]=, =[#C]= in headline title | | todo | Include todo status in headline title | | tags | Include tags in headline title | [fn:1] This footnote definition won't be printed go-org-1.0.0/org/testdata/options.pretty_org000066400000000000000000000025751361136365600211400ustar00rootroot00000000000000#+OPTIONS: toc:nil f:nil e:nil * DONE [#A] =#+OPTIONS:= toggles supported by =go-org= :tag1:tag2: =go-org= supports multiple export toggles as described in the [[https://orgmode.org/manual/Export-settings.html][export settings]] section of the Org mode manual. By default (most of?) those toggles are enabled. This file starts with =#+OPTIONS: toc:nil f:nil e:nil= and thus disables the table of contents, footnotes & entities. That means, entities like =---= --- (mdash) will be left untouched, footnotes like =[fn:1]= [fn:1] will not be exported and there won't be a table of contents at the top. As buffer options are merged with the defaults, the above headline will be exported *with* priority, todo status & tags. | key | description | |------+-----------------------------------------------------------| | f | Include footnotes (definitions & links) | | e | Include entities | | toc | Include table of contents (outline) | |------+-----------------------------------------------------------| | pri | Include priority =[#A]=, =[#B]=, =[#C]= in headline title | | todo | Include todo status in headline title | | tags | Include tags in headline title | [fn:1] This footnote definition won't be printed go-org-1.0.0/org/testdata/paragraphs.html000066400000000000000000000004751361136365600203400ustar00rootroot00000000000000

    Paragraphs are the default element.

    Empty lines and other elements end paragraphs - but paragraphs can obviously span multiple lines.

    Paragraphs can contain inline markup like emphasis strong and links example.com and stuff.

    go-org-1.0.0/org/testdata/paragraphs.org000066400000000000000000000004061361136365600201550ustar00rootroot00000000000000Paragraphs are the default element. Empty lines and other elements end paragraphs - but paragraphs can obviously span multiple lines. Paragraphs can contain inline markup like /emphasis/ *strong* and links [[https://www.example.com][example.com]] and stuff. go-org-1.0.0/org/testdata/paragraphs.pretty_org000066400000000000000000000004061361136365600215640ustar00rootroot00000000000000Paragraphs are the default element. Empty lines and other elements end paragraphs - but paragraphs can obviously span multiple lines. Paragraphs can contain inline markup like /emphasis/ *strong* and links [[https://www.example.com][example.com]] and stuff. go-org-1.0.0/org/testdata/setup_file_org000066400000000000000000000001021361136365600202360ustar00rootroot00000000000000#+TODO: TODO DONE CUSTOM #+EXCLUDE_TAGS: noexport custom_noexport go-org-1.0.0/org/testdata/tables.html000066400000000000000000000047741361136365600174700ustar00rootroot00000000000000
    a b c
    1 2 3
    table with separator before and after header
    a b c
    1 2 3
    table with separator after header
    Character Org Rendered HTML
    Hyphen a - b a - b
    Ndash a -- b a – b
    Mdash a --- b a — b
    Ellipsis a ... b a … b
    table with unicode characters
    1 2 3
    table without header (but separator before)
    1 2 3
    table without header
    left aligned right aligned center aligned
    42 42 42
    foobar foobar foobar
    table with aligned columns
    long column a long column b long column c
    1 2 3
    table with right aligned columns (because numbers)
    go-org-1.0.0/org/testdata/tables.org000066400000000000000000000021551361136365600173020ustar00rootroot00000000000000#+CAPTION: table with separator before and after header |---+---+---| | a | b | c | |---+---+---| | 1 | 2 | 3 | #+CAPTION: table with separator after header | a | b | c | |---+---+---| | 1 | 2 | 3 | #+CAPTION: table with unicode characters | Character | Org | Rendered HTML | |-----------+-----------+---------------| | Hyphen | =a - b= | a - b | | Ndash | =a -- b= | a – b | | Mdash | =a --- b= | a — b | | Ellipsis | =a ... b= | a … b | #+CAPTION: table without header (but separator before) |---+---+---| | 1 | 2 | 3 | #+CAPTION: table without header | 1 | 2 | 3 | #+CAPTION: table with aligned columns | left aligned | right aligned | center aligned | |--------------+---------------+----------------| | | | | | 42 | 42 | 42 | | foobar | foobar | foobar | #+CAPTION: table with right aligned columns (because numbers) | long column a | long column b | long column c | |---------------+---------------+---------------| | 1 | 2 | 3 | go-org-1.0.0/org/testdata/tables.pretty_org000066400000000000000000000021551361136365600207110ustar00rootroot00000000000000#+CAPTION: table with separator before and after header |---+---+---| | a | b | c | |---+---+---| | 1 | 2 | 3 | #+CAPTION: table with separator after header | a | b | c | |---+---+---| | 1 | 2 | 3 | #+CAPTION: table with unicode characters | Character | Org | Rendered HTML | |-----------+-----------+---------------| | Hyphen | =a - b= | a - b | | Ndash | =a -- b= | a – b | | Mdash | =a --- b= | a — b | | Ellipsis | =a ... b= | a … b | #+CAPTION: table without header (but separator before) |---+---+---| | 1 | 2 | 3 | #+CAPTION: table without header | 1 | 2 | 3 | #+CAPTION: table with aligned columns | left aligned | right aligned | center aligned | |--------------+---------------+----------------| | | | | | 42 | 42 | 42 | | foobar | foobar | foobar | #+CAPTION: table with right aligned columns (because numbers) | long column a | long column b | long column c | |---------------+---------------+---------------| | 1 | 2 | 3 | go-org-1.0.0/org/util.go000066400000000000000000000006261361136365600150130ustar00rootroot00000000000000package org func isSecondBlankLine(d *Document, i int) bool { if i-1 <= 0 { return false } t1, t2 := d.tokens[i-1], d.tokens[i] if t1.kind == "text" && t2.kind == "text" && t1.content == "" && t2.content == "" { return true } return false } func isImageOrVideoLink(n Node) bool { if l, ok := n.(RegularLink); ok && l.Kind() == "video" || l.Kind() == "image" { return true } return false } go-org-1.0.0/org/writer.go000066400000000000000000000050101361136365600153420ustar00rootroot00000000000000package org import "fmt" // Writer is the interface that is used to export a parsed document into a new format. See Document.Write(). type Writer interface { Before(*Document) // Before is called before any nodes are passed to the writer. After(*Document) // After is called after all nodes have been passed to the writer. String() string // String is called at the very end to retrieve the final output. WriterWithExtensions() Writer WriteNodesAsString(...Node) string WriteKeyword(Keyword) WriteInclude(Include) WriteComment(Comment) WriteNodeWithMeta(NodeWithMeta) WriteNodeWithName(NodeWithName) WriteHeadline(Headline) WriteBlock(Block) WriteExample(Example) WriteDrawer(Drawer) WritePropertyDrawer(PropertyDrawer) WriteList(List) WriteListItem(ListItem) WriteDescriptiveListItem(DescriptiveListItem) WriteTable(Table) WriteHorizontalRule(HorizontalRule) WriteParagraph(Paragraph) WriteText(Text) WriteEmphasis(Emphasis) WriteLatexFragment(LatexFragment) WriteStatisticToken(StatisticToken) WriteExplicitLineBreak(ExplicitLineBreak) WriteLineBreak(LineBreak) WriteRegularLink(RegularLink) WriteTimestamp(Timestamp) WriteFootnoteLink(FootnoteLink) WriteFootnoteDefinition(FootnoteDefinition) } func WriteNodes(w Writer, nodes ...Node) { w = w.WriterWithExtensions() for _, n := range nodes { switch n := n.(type) { case Keyword: w.WriteKeyword(n) case Include: w.WriteInclude(n) case Comment: w.WriteComment(n) case NodeWithMeta: w.WriteNodeWithMeta(n) case NodeWithName: w.WriteNodeWithName(n) case Headline: w.WriteHeadline(n) case Block: w.WriteBlock(n) case Example: w.WriteExample(n) case Drawer: w.WriteDrawer(n) case PropertyDrawer: w.WritePropertyDrawer(n) case List: w.WriteList(n) case ListItem: w.WriteListItem(n) case DescriptiveListItem: w.WriteDescriptiveListItem(n) case Table: w.WriteTable(n) case HorizontalRule: w.WriteHorizontalRule(n) case Paragraph: w.WriteParagraph(n) case Text: w.WriteText(n) case Emphasis: w.WriteEmphasis(n) case LatexFragment: w.WriteLatexFragment(n) case StatisticToken: w.WriteStatisticToken(n) case ExplicitLineBreak: w.WriteExplicitLineBreak(n) case LineBreak: w.WriteLineBreak(n) case RegularLink: w.WriteRegularLink(n) case Timestamp: w.WriteTimestamp(n) case FootnoteLink: w.WriteFootnoteLink(n) case FootnoteDefinition: w.WriteFootnoteDefinition(n) default: if n != nil { panic(fmt.Sprintf("bad node %T %#v", n, n)) } } } }