@@ -8,10 +8,12 @@ use HTML::Escape qw( escape_html );
88use HTML::Restrict ();
99use URI ();
1010use Digest::MD5 ();
11+ use CommonMark qw( :node :event ) ;
1112
1213our @EXPORT_OK = qw(
1314 filter_html
1415 gravatar_image
16+ render_markdown
1517 split_index
1618) ;
1719
@@ -161,4 +163,66 @@ sub split_index {
161163 return ( $pod_index , $html );
162164}
163165
166+ my @is_leaf ;
167+ $is_leaf [$_ ] = 1
168+ for (
169+ NODE_HTML, NODE_HRULE, NODE_CODE_BLOCK, NODE_TEXT,
170+ NODE_SOFTBREAK, NODE_LINEBREAK, NODE_CODE, NODE_INLINE_HTML,
171+ );
172+
173+ sub render_markdown {
174+ my ($markdown ) = @_ ;
175+
176+ my $doc = CommonMark-> parse_document($markdown );
177+
178+ my ( $html , $header_content , %seen_header );
179+
180+ my $iter = $doc -> iterator;
181+ while ( my ( $ev_type , $node ) = $iter -> next ) {
182+ my $node_type = $node -> get_type;
183+
184+ if ( $node_type == NODE_DOCUMENT ) {
185+ next ;
186+ }
187+
188+ if ( $node_type == NODE_HEADER ) {
189+ if ( $ev_type == EVENT_ENTER ) {
190+ $header_content = ' ' ;
191+ }
192+ if ( $ev_type == EVENT_EXIT ) {
193+ $header_content =~ s { (?:-(\d +))?$} { '-' . (($1 // 1) + 1)} e
194+ while $seen_header {$header_content }++;
195+
196+ my $header_html = $node -> render_html;
197+ $header_html
198+ =~ s / ^<h[0-9]+\b\K / ' id="'.escape_html($header_content ).'"'/ e ;
199+ $html .= $header_html ;
200+
201+ undef $header_content ;
202+ }
203+ }
204+ elsif ($ev_type == EVENT_ENTER
205+ && $node -> parent-> get_type == NODE_DOCUMENT )
206+ {
207+ $html .= $node -> render_html;
208+ }
209+
210+ if ( defined $header_content ) {
211+ if ( $is_leaf [$node_type ] ) {
212+ my $content = lc ( $node -> get_literal );
213+ $content =~ s /\A\s +// ;
214+ $content =~ s /\s +\z // ;
215+ $content =~ s /\s +/ -/ g ;
216+
217+ if ( length $content ) {
218+ $header_content .= ' -' if length $header_content ;
219+ $header_content .= $content ;
220+ }
221+ }
222+ }
223+ }
224+
225+ return $html ;
226+ }
227+
1642281;
0 commit comments