new tool: args2env
[hband-tools.git] / user-tools / adr2html
blobf03f75e7b82758e0f8d403bbb54d0bbe1b7eb70e
1 #!/usr/bin/env perl
2 # Opera Bookmark address file -to-> HTML converter.
3 # Tested with Opera Hotlist versions:
4 # - 2.0
6 use Data::Dumper;
7 use utf8;
10 sub Parse {
11 my $RootFRef = {
12 'NAME'=>'Bookmarks',
13 'DELETABLE'=>'NO',
14 'EXPANDED'=>'YES',
15 'UNIQUEID'=>'1',
17 my $LastFRef = $RootFRef;
18 my ($FRef, $LastRef, $LastURef);
20 open INFILE, '<', $ARGV[0] || $ENV{'HOME'}."/.opera/bookmarks.adr";
21 while(<INFILE>) {
22 s/\s*$//;
24 if(/^\x23FOLDER/) {
25 $FRef = {'isfolder'=>1};
26 push @{$LastFRef->{'children'}}, $FRef;
27 push @F, $LastFRef;
28 $LastFRef = $FRef;
29 $LastRef = $LastFRef;
31 elsif(/^\x23URL/) {
32 $LastURef = {};
33 push @{$LastFRef->{'children'}}, $LastURef;
34 $LastRef = $LastURef;
36 elsif(/^-/) {
37 $LastFRef = pop @F;
38 if(ref $LastFRef ne 'HASH') {
39 die "Malformed bookmark file.";
42 elsif(/^\s+([^=]+)=(.*)/) {
43 $LastRef->{$1} = $2;
46 # debug # print STDERR Dumper $RootFRef;
47 return $RootFRef;
50 sub htmlentities {
51 $_=$_[0];
52 s/</&lt;/g;
53 s/>/&gt;/g;
54 s/&/&amp;/g;
55 s/[""]/&quot;/g;
56 return $_;
58 sub urldecode {
59 $_=$_[0];
60 s/%([[:xdigit:]]{2})/chr hex $1/eg;
61 return $_;
64 sub PersonalBar {
65 return "<DIV CLASS='toolbar'>" . (
66 join " | ",
67 map { sprintf "<A HREF='#b%s'>%s</A>", $_->{'UNIQUEID'}, $_->{'NAME'} }
68 sort { $a->{'PERSONALBAR_POS'} <=> $b->{'PERSONALBAR_POS'} }
69 @{$_[0]}
70 )."</DIV>\n";
73 sub Node {
74 my $H = $_[0];
75 my $Indent = $_[1] || 0;
76 my ($out, $parent, $ico, $liattr, $url, $spanattr, @liclass);
79 # Populate PersonalBar list
80 if(uc $H->{'ON PERSONALBAR'} eq 'YES') {
81 push @PB, $H;
82 # Anchor to item
83 $out .= "<A NAME='b" . $H->{'UNIQUEID'} . "'></A>";
86 # Item attributes
87 map { $liattr .= sprintf q{ %s="%s"}, $_, $H->{$_} } grep {/^ID|CREATED|VISITED$/} keys %$H;
89 # Is it a parent (folder) ?
90 if(ref $H->{'children'} eq 'ARRAY') {
91 push @liclass, "parent";
92 $spanattr .= " onClick=\"toggle('" . $H->{'UNIQUEID'} . "');\"";
93 $parent = 1;
94 push @liclass, (uc $H->{'EXPANDED'} eq 'YES' ? "expanded" : "collapsed");
95 } else {
96 if($H->{'isfolder'}) {
97 push @liclass, "childless";
101 # Favicon
102 if($H->{'ICONFILE'}) {
103 $ico = $H->{'ICONFILE'};
104 } else {
105 my ($host) = ( $H->{'URL'} =~ m{//([a-z0-9\._-]+)}i );
106 if( open my $idx, '<', $ENV{'HOME'}."/.opera/icons/$host.idx" ) {
107 <$idx>;
108 $ico = <$idx>;
109 $ico =~ s/\s*$//;
110 $ico = urldecode($ico);
111 close $idx;
114 if($ico) {
115 $liattr .= " STYLE=\"list-style: none;\"";
116 $out .= "<IMG CLASS=\"favicon\" SRC=\"$ico\" onError=\"icoNotFound(this, '".$H->{'ID'}."');\" />";
119 $liattr .= " CLASS=\"".join(' ', @liclass)."\"";
120 $out .= "<LI$liattr>";
122 # Hyperlink
123 if(defined $H->{'URL'}) {
124 $url = urldecode($H->{'URL'});
125 if($url =~ /[""]/) {
126 $replaceid++;
127 $out .= "<A HREF=\"#\" ID=\"replace$replaceid\">";
128 $replaces{$replaceid} = $url;
129 } else {
130 $out .= "<A HREF=\"$url\">";
133 $out .= "<SPAN$spanattr>" . htmlentities($H->{'NAME'}) . "</SPAN>";
134 if(defined $H->{'URL'}) {
135 $out .= "</A>";
138 # Description
139 if(defined $H->{'DESCRIPTION'}) {
140 $out .= "<DIV CLASS=\"description\">" . htmlentities($H->{'DESCRIPTION'}) . "</DIV>";
143 # Descendant items...
144 if($parent) {
145 $out .= "<UL ID=\"" . $H->{'UNIQUEID'} . "\">";
146 for my $child ( @{$H->{'children'}} ) {
147 $out .= "\n".' 'x$Indent;
148 $out .= Node( $child, $Indent+4 );
150 $out .= "\n".' 'x$Indent."</UL>";
153 $out .= "</LI>";
154 return $out;
157 # Stylesheet
158 print <<EOF
159 <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8" />
160 <STYLE>
161 div.toolbar {
162 border: solid 1px black;
163 padding: 2px;
165 li {
166 list-style: disc;
168 li.parent > span {
169 font-weight: bold;
170 cursor: pointer;
171 cursor: hand;
173 li.parent.expanded { list-style-image: url('/static/img/icon/folder_opened.gif'); }
174 li.parent.collapsed, li.childless { list-style-image: url('/static/img/icon/folder_closed.gif'); }
175 li.parent.collapsed > ul {
176 display: none;
178 div.description, footer {
179 font-size: 10pt;
180 font-style: italic;
182 img.favicon {
183 width: 16px;
184 height: 16px;
185 position: relative;
186 left: -1.25em;
187 top: 1em;
188 margin-top: -1em;
190 </STYLE>
196 @PB = ();
197 $BMTree = Node( Parse() );
199 # Toolbar
200 print PersonalBar(\@PB);
201 # The Bookmark Tree
202 print "<UL>" . $BMTree . "</UL>";
204 # Footer
205 print "<HR><FOOTER>Made by <A HREF=\"https://github.com/bAndie91/tools/blob/master/user-tools/adr2html\">adr2html</A>.</FOOTER>";
207 # Script - Restore expanded/collapsed state of nodes
208 print "<SCRIPT TYPE='text/javascript'>\n";
209 print <<EOF
210 function toggle(id, yesclass) {
211 var cl = document.getElementById(id).parentNode.classList;
212 if(typeof yesclass == 'undefined') yesclass = cl.contains('expanded') ? 'collapsed' : 'expanded';
213 var noclass = (yesclass == 'expanded') ? 'collapsed' : 'expanded';
214 document.cookie = 'FOLDER'+id+'='+yesclass;
215 cl.remove(noclass);
216 cl.add(yesclass);
218 var as = document.cookie.match(/FOLDER[^=]+=[^;]*/g);
219 for(var ai=0; ai<as.length; ai++) {
220 bs = as[ai].match(/FOLDER([^=]+)=([^;]*)/);
221 toggle(bs[1], bs[2]);
223 function icoNotFound(img, li) {
224 document.getElementById(li).style.listStyle='disc';
225 img.parentNode.removeChild(img);
229 # Script - Fill up HREF attriutes where it couldn't be (due to html escaping)
230 for my $replaceid (keys %replaces) {
231 my $url = $replaces{$replaceid};
232 $url =~ s/['']/\\$&/g;
233 print "document.getElementById('replace$replaceid').setAttribute('HREF', '$url');\n"
235 print "</SCRIPT>";
237 __END__
239 =pod
241 =head1 NAME
243 adr2html - Convert Opera Hostlist 2.0 bookmarks to HTML
245 =cut