| lib/Text/Hatena.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 19 | 21 | 90.4 |
| branch | n/a | ||
| condition | n/a | ||
| subroutine | 7 | 7 | 100.0 |
| pod | n/a | ||
| total | 26 | 28 | 92.8 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package Text::Hatena; | ||||||
| 2 | 3 | 3 | 536909 | use strict; | |||
| 3 | 8 | ||||||
| 3 | 103 | ||||||
| 3 | 3 | 3 | 17 | use warnings; | |||
| 3 | 5 | ||||||
| 3 | 79 | ||||||
| 4 | 3 | 3 | 15 | use Carp; | |||
| 3 | 10 | ||||||
| 3 | 1034 | ||||||
| 5 | 3 | 3 | 15 | use base qw(Class::Data::Inheritable); | |||
| 3 | 5 | ||||||
| 3 | 1599 | ||||||
| 6 | 2 | 2 | 2005 | use vars qw($VERSION); | |||
| 2 | 4 | ||||||
| 2 | 88 | ||||||
| 7 | 2 | 2 | 3417 | use Parse::RecDescent; | |||
| 2 | 377143 | ||||||
| 2 | 17 | ||||||
| 8 | 2 | 2 | 1680 | use Text::Hatena::AutoLink; | |||
| 0 | |||||||
| 0 | |||||||
| 9 | |||||||
| 10 | $VERSION = '0.20'; | ||||||
| 11 | |||||||
| 12 | my ($parser, $syntax); | ||||||
| 13 | |||||||
| 14 | __PACKAGE__->mk_classdata('syntax'); | ||||||
| 15 | |||||||
| 16 | #$::RD_HINT = 1; | ||||||
| 17 | #$::RD_TRACE = 1; | ||||||
| 18 | #$::RD_WARN = undef; | ||||||
| 19 | $Parse::RecDescent::skip = ''; | ||||||
| 20 | $syntax = q( | ||||||
| 21 | body : section(s) | ||||||
| 22 | section : h3(?) block(s?) | ||||||
| 23 | # Block Elements | ||||||
| 24 | block : h5 | ||||||
| 25 | | h4 | ||||||
| 26 | | blockquote | ||||||
| 27 | | dl | ||||||
| 28 | | list | ||||||
| 29 | | super_pre | ||||||
| 30 | | pre | ||||||
| 31 | | table | ||||||
| 32 | | cdata | ||||||
| 33 | | p | ||||||
| 34 | h3 : "\n*" inline(s) | ||||||
| 35 | h4 : "\n**" inline(s) | ||||||
| 36 | h5 : "\n***" inline(s) | ||||||
| 37 | blockquote : "\n>" http(?) ">" block(s) "\n<<" ..."\n" | ||||||
| 38 | dl : dl_item(s) | ||||||
| 39 | dl_item : "\n:" inline[term => ':'](s) ':' inline(s) | ||||||
| 40 | list : list_item[level => $arg{level} || 1](s) | ||||||
| 41 | list_item : "\n" /[+-]{$arg{level}}/ inline(s) list[level => $arg{level} + 1](?) | ||||||
| 42 | super_pre : /\n>\|(\w*)\|/o text_line(s) "\n||<" ..."\n" | ||||||
| 43 | text_line : ...!"\n||<\n" "\n" /[^\n]*/o | ||||||
| 44 | pre : "\n>|" pre_line(s) "\n|<" ..."\n" | ||||||
| 45 | pre_line : ...!"\n|<" "\n" inline(s?) | ||||||
| 46 | table : table_row(s) | ||||||
| 47 | table_row : "\n|" td(s /\|/) '|' | ||||||
| 48 | td : /\*?/o inline[term => '\|'](s) | ||||||
| 49 | cdata : "\n><" /.+?(?=><\n)/so "><" ..."\n" | ||||||
| 50 | p : ...!p_terminal "\n" inline(s?) | ||||||
| 51 | p_terminal : h3 | "\n<<\n" | ||||||
| 52 | # Inline Elements | ||||||
| 53 | inline : /[^\n$arg{term}]+/ | ||||||
| 54 | http : /https?:\/\/[A-Za-z0-9~\/._\?\&=\-%#\+:\;,\@\']+(?::title=[^\]]+)?/ | ||||||
| 55 | ); | ||||||
| 56 | |||||||
| 57 | sub parse { | ||||||
| 58 | my $class = shift; | ||||||
| 59 | my $text = shift or return; | ||||||
| 60 | $text =~ s/\r//g; | ||||||
| 61 | $text = "\n" . $text unless $text =~ /^\n/; | ||||||
| 62 | $text .= "\n" unless $text =~ /\n$/; | ||||||
| 63 | my $node = shift || 'body'; | ||||||
| 64 | my $html = $class->parser->$node($text); | ||||||
| 65 | # warn $html; | ||||||
| 66 | return $html; | ||||||
| 67 | } | ||||||
| 68 | |||||||
| 69 | sub parser { | ||||||
| 70 | my $class = shift; | ||||||
| 71 | unless (defined $parser) { | ||||||
| 72 | $::RD_AUTOACTION = q|my $method = shift @item;| . | ||||||
| 73 | $class . q|->$method({items => \@item});|; | ||||||
| 74 | $parser = Parse::RecDescent->new($syntax); | ||||||
| 75 | if ($class->syntax) { | ||||||
| 76 | $parser->Replace($class->syntax); | ||||||
| 77 | } | ||||||
| 78 | } | ||||||
| 79 | return $parser; | ||||||
| 80 | } | ||||||
| 81 | |||||||
| 82 | sub expand { | ||||||
| 83 | my $class = shift; | ||||||
| 84 | my $array = shift or return; | ||||||
| 85 | ref($array) eq 'ARRAY' or return; | ||||||
| 86 | my $ret = ''; | ||||||
| 87 | while (my $item = shift @$array) { | ||||||
| 88 | if (ref($item) eq 'ARRAY') { | ||||||
| 89 | my $c = $class->expand($item); | ||||||
| 90 | $ret .= $c if $c; | ||||||
| 91 | } else { | ||||||
| 92 | $ret .= $item if $item; | ||||||
| 93 | } | ||||||
| 94 | } | ||||||
| 95 | return $ret; | ||||||
| 96 | } | ||||||
| 97 | |||||||
| 98 | # Nodes | ||||||
| 99 | # Block Nodes | ||||||
| 100 | sub abstract { | ||||||
| 101 | my $class = shift; | ||||||
| 102 | my $items = shift->{items}; | ||||||
| 103 | return $class->expand($items); | ||||||
| 104 | } | ||||||
| 105 | |||||||
| 106 | *body = \&abstract; | ||||||
| 107 | *block = \&abstract; | ||||||
| 108 | *line = \&abstract; | ||||||
| 109 | |||||||
| 110 | sub section { | ||||||
| 111 | my $class = shift; | ||||||
| 112 | my $items = shift->{items}; | ||||||
| 113 | my $body = $class->expand($items) || ''; | ||||||
| 114 | $body =~ s/\n\n$/\n/; | ||||||
| 115 | return $body ? qq| \n| . $body . qq| \n| : ''; |
||||||
| 116 | } | ||||||
| 117 | |||||||
| 118 | sub h3 { | ||||||
| 119 | my $class = shift; | ||||||
| 120 | my $items = shift->{items}; | ||||||
| 121 | my $title = $class->expand($items->[1]); | ||||||
| 122 | return if $title =~ /^\*/; | ||||||
| 123 | return "$title\n"; |
||||||
| 124 | } | ||||||
| 125 | |||||||
| 126 | sub h4 { | ||||||
| 127 | my $class = shift; | ||||||
| 128 | my $items = shift->{items}; | ||||||
| 129 | my $title = $class->expand($items->[1]); | ||||||
| 130 | return if $title =~ /^\*/; | ||||||
| 131 | return "$title\n"; |
||||||
| 132 | } | ||||||
| 133 | |||||||
| 134 | sub h5 { | ||||||
| 135 | my $class = shift; | ||||||
| 136 | my $items = shift->{items}; | ||||||
| 137 | my $title = $class->expand($items->[1]); | ||||||
| 138 | return "$title\n"; |
||||||
| 139 | } | ||||||
| 140 | |||||||
| 141 | sub blockquote { | ||||||
| 142 | my $class = shift; | ||||||
| 143 | my $items = shift->{items}; | ||||||
| 144 | my $body = $class->expand($items->[3]); | ||||||
| 145 | my $http = $items->[1]->[0]; | ||||||
| 146 | my $ret = ''; | ||||||
| 147 | if ($http) { | ||||||
| 148 | $ret = qq|\n|; |
||||||
| 149 | } else { | ||||||
| 150 | $ret = "\n"; |
||||||
| 151 | } | ||||||
| 152 | $ret .= $body; | ||||||
| 153 | if ($http) { | ||||||
| 154 | $ret .= qq|$http->{title}\n|; | ||||||
| 155 | } | ||||||
| 156 | $ret .= "\n"; | ||||||
| 157 | return $ret; | ||||||
| 158 | } | ||||||
| 159 | |||||||
| 160 | sub bq_block { | ||||||
| 161 | my $class = shift; | ||||||
| 162 | my $items = shift->{items}; | ||||||
| 163 | return $class->expand($items->[0]); | ||||||
| 164 | } | ||||||
| 165 | |||||||
| 166 | sub dl { | ||||||
| 167 | my $class = shift; | ||||||
| 168 | my $items = shift->{items}; | ||||||
| 169 | my $list = $class->expand($items->[0]); | ||||||
| 170 | return "
|
||||||
| 171 | } | ||||||
| 172 | |||||||
| 173 | sub dl_item { | ||||||
| 174 | my $class = shift; | ||||||
| 175 | my $items = shift->{items}; | ||||||
| 176 | my $dt = $class->expand($items->[1]); | ||||||
| 177 | my $dd = $class->expand($items->[3]); | ||||||
| 178 | return " |
||||||
| 179 | } | ||||||
| 180 | |||||||
| 181 | sub dt { | ||||||
| 182 | my $class = shift; | ||||||
| 183 | my $items = shift->{items}; | ||||||
| 184 | my $dt = $class->expand($items->[1]); | ||||||
| 185 | return " |
||||||
| 186 | } | ||||||
| 187 | |||||||
| 188 | sub list { | ||||||
| 189 | my $class = shift; | ||||||
| 190 | my $items = shift->{items}; | ||||||
| 191 | my ($list,$tag); | ||||||
| 192 | for my $li (@{$items->[0]}) { | ||||||
| 193 | $tag ||= $li =~ /^\-/ ? 'ul' : 'ol'; | ||||||
| 194 | $li =~ s/^[+-]+//; | ||||||
| 195 | $list .= $li; | ||||||
| 196 | } | ||||||
| 197 | return "<$tag>\n$list$tag>\n"; | ||||||
| 198 | } | ||||||
| 199 | |||||||
| 200 | sub list_item { | ||||||
| 201 | my $class = shift; | ||||||
| 202 | my $items = shift->{items}; | ||||||
| 203 | my $li = $class->expand($items->[2]); | ||||||
| 204 | my $sl = $class->expand($items->[3]) || ''; | ||||||
| 205 | $sl = "\n" . $sl if $sl; | ||||||
| 206 | return $items->[1] . " |
||||||
| 207 | } | ||||||
| 208 | |||||||
| 209 | sub super_pre { | ||||||
| 210 | my $class = shift; | ||||||
| 211 | my $items = shift->{items}; | ||||||
| 212 | my $filter = $1 || ''; # todo | ||||||
| 213 | my $texts = $class->expand($items->[1]); | ||||||
| 214 | return "\n$texts\n"; |
||||||
| 215 | } | ||||||
| 216 | |||||||
| 217 | sub pre { | ||||||
| 218 | my $class = shift; | ||||||
| 219 | my $items = shift->{items}; | ||||||
| 220 | my $lines = $class->expand($items->[1]); | ||||||
| 221 | return "\n$lines\n"; |
||||||
| 222 | } | ||||||
| 223 | |||||||
| 224 | sub pre_line { | ||||||
| 225 | my $class = shift; | ||||||
| 226 | my $items = shift->{items}; | ||||||
| 227 | my $inlines = $class->expand($items->[2]); | ||||||
| 228 | return "$inlines\n"; | ||||||
| 229 | } | ||||||
| 230 | |||||||
| 231 | sub table { | ||||||
| 232 | my $class = shift; | ||||||
| 233 | my $items = shift->{items}; | ||||||
| 234 | my $trs = $class->expand($items->[0]); | ||||||
| 235 | return " |
||||||
| 236 | } | ||||||
| 237 | |||||||
| 238 | sub table_row { # we can't use tr! | ||||||
| 239 | my $class = shift; | ||||||
| 240 | my $items = shift->{items}; | ||||||
| 241 | my $tds = $class->expand($items->[1]); | ||||||
| 242 | return " | ||||||
| 243 | } | ||||||
| 244 | |||||||
| 245 | sub td { | ||||||
| 246 | my $class = shift; | ||||||
| 247 | my $items = shift->{items}; | ||||||
| 248 | my $tag = $items->[0] ? 'th' : 'td'; | ||||||
| 249 | my $inlines = $class->expand($items->[1]); | ||||||
| 250 | return "<$tag>$inlines$tag>\n"; | ||||||
| 251 | } | ||||||
| 252 | |||||||
| 253 | sub cdata { | ||||||
| 254 | my $class = shift; | ||||||
| 255 | my $items = shift->{items}; | ||||||
| 256 | my $data = $items->[1]; | ||||||
| 257 | return "<$data>\n"; | ||||||
| 258 | } | ||||||
| 259 | |||||||
| 260 | sub p { | ||||||
| 261 | my $class = shift; | ||||||
| 262 | my $items = shift->{items}; | ||||||
| 263 | my $inlines = $class->expand($items->[2]); | ||||||
| 264 | return $inlines ? " $inlines \n" : "\n"; |
||||||
| 265 | } | ||||||
| 266 | |||||||
| 267 | sub text_line { | ||||||
| 268 | my $class = shift; | ||||||
| 269 | my $text = shift->{items}->[2]; | ||||||
| 270 | return "$text\n"; | ||||||
| 271 | } | ||||||
| 272 | |||||||
| 273 | # Inline Nodes | ||||||
| 274 | sub inline { | ||||||
| 275 | my $class = shift; | ||||||
| 276 | my $items = shift->{items}; | ||||||
| 277 | my $item = $items->[0] or return; | ||||||
| 278 | $item = Text::Hatena::AutoLink->parse($item); | ||||||
| 279 | return $item; | ||||||
| 280 | } | ||||||
| 281 | |||||||
| 282 | sub http { | ||||||
| 283 | my $class = shift; | ||||||
| 284 | my $items = shift->{items}; | ||||||
| 285 | my $item = $items->[0] or return; | ||||||
| 286 | $item =~ s/:title=([^\]]+)$//; | ||||||
| 287 | my $title = $1 || $item; | ||||||
| 288 | return { | ||||||
| 289 | cite => $item, | ||||||
| 290 | title => $title, | ||||||
| 291 | } | ||||||
| 292 | } | ||||||
| 293 | |||||||
| 294 | 1; | ||||||
| 295 | |||||||
| 296 | __END__ |