The update text is markdown formatted, so add a simple markdown parser
[colorhug:deejay1s-colorhug-client.git] / src / ch-markdown.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2008-2011 Richard Hughes <richard@hughsie.com>
4  *
5  * Licensed under the GNU General Public License Version 2
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "config.h"
23
24 #include <string.h>
25 #include <glib.h>
26
27 #include "ch-markdown.h"
28
29 /***********************************************************************
30  *
31  * This is a simple Markdown parser.
32  * It can output to Pango markup. The following limitations are
33  * already known, and properly deliberate:
34  *
35  * - No code section support
36  * - No ordered list support
37  * - No blockquote section support
38  * - No image support
39  * - No links or email support
40  * - No backslash escapes support
41  * - No HTML escaping support
42  * - Auto-escapes certain word patterns, like http://
43  *
44  * It does support the rest of the standard pretty well, although it's not
45  * been run against any conformance tests. The parsing is single pass, with
46  * a simple enumerated intepretor mode and a single line back-memory.
47  *
48  **********************************************************************/
49
50 static void     ch_markdown_finalize    (GObject                *object);
51
52 #define CH_MARKDOWN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CH_TYPE_MARKDOWN, ChMarkdownPrivate))
53
54 #define CH_MARKDOWN_MAX_LINE_LENGTH     1024
55
56 typedef enum {
57         CH_MARKDOWN_MODE_BLANK,
58         CH_MARKDOWN_MODE_RULE,
59         CH_MARKDOWN_MODE_BULLETT,
60         CH_MARKDOWN_MODE_PARA,
61         CH_MARKDOWN_MODE_H1,
62         CH_MARKDOWN_MODE_H2,
63         CH_MARKDOWN_MODE_UNKNOWN
64 } ChMarkdownMode;
65
66 struct ChMarkdownPrivate
67 {
68         ChMarkdownMode           mode;
69         gint                     max_lines;
70         gint                     line_count;
71         gboolean                 smart_quoting;
72         gboolean                 escape;
73         gboolean                 autocode;
74         GString                 *pending;
75         GString                 *processed;
76 };
77
78 G_DEFINE_TYPE (ChMarkdown, ch_markdown, G_TYPE_OBJECT)
79
80 /**
81  * ch_markdown_to_text_line_is_rule:
82  *
83  * Horizontal rules are created by placing three or more hyphens, asterisks,
84  * or underscores on a line by themselves.
85  * You may use spaces between the hyphens or asterisks.
86  **/
87 static gboolean
88 ch_markdown_to_text_line_is_rule (const gchar *line)
89 {
90         guint i;
91         guint len;
92         guint count = 0;
93         gchar *copy = NULL;
94         gboolean ret = FALSE;
95
96         len = strlen (line);
97         if (len == 0 || len > CH_MARKDOWN_MAX_LINE_LENGTH)
98                 goto out;
99
100         /* replace non-rule chars with ~ */
101         copy = g_strdup (line);
102         g_strcanon (copy, "-*_ ", '~');
103         for (i=0; i<len; i++) {
104                 if (copy[i] == '~')
105                         goto out;
106                 if (copy[i] != ' ')
107                         count++;
108         }
109
110         /* if we matched, return true */
111         if (count >= 3)
112                 ret = TRUE;
113 out:
114         g_free (copy);
115         return ret;
116 }
117
118 /**
119  * ch_markdown_to_text_line_is_bullett:
120  **/
121 static gboolean
122 ch_markdown_to_text_line_is_bullett (const gchar *line)
123 {
124         return (g_str_has_prefix (line, "- ") ||
125                 g_str_has_prefix (line, "* ") ||
126                 g_str_has_prefix (line, "+ ") ||
127                 g_str_has_prefix (line, " - ") ||
128                 g_str_has_prefix (line, " * ") ||
129                 g_str_has_prefix (line, " + "));
130 }
131
132 /**
133  * ch_markdown_to_text_line_is_header1:
134  **/
135 static gboolean
136 ch_markdown_to_text_line_is_header1 (const gchar *line)
137 {
138         return g_str_has_prefix (line, "# ");
139 }
140
141 /**
142  * ch_markdown_to_text_line_is_header2:
143  **/
144 static gboolean
145 ch_markdown_to_text_line_is_header2 (const gchar *line)
146 {
147         return g_str_has_prefix (line, "## ");
148 }
149
150 /**
151  * ch_markdown_to_text_line_is_header1_type2:
152  **/
153 static gboolean
154 ch_markdown_to_text_line_is_header1_type2 (const gchar *line)
155 {
156         return g_str_has_prefix (line, "===");
157 }
158
159 /**
160  * ch_markdown_to_text_line_is_header2_type2:
161  **/
162 static gboolean
163 ch_markdown_to_text_line_is_header2_type2 (const gchar *line)
164 {
165         return g_str_has_prefix (line, "---");
166 }
167
168 #if 0
169 /**
170  * ch_markdown_to_text_line_is_code:
171  **/
172 static gboolean
173 ch_markdown_to_text_line_is_code (const gchar *line)
174 {
175         return (g_str_has_prefix (line, "    ") || g_str_has_prefix (line, "\t"));
176 }
177
178 /**
179  * ch_markdown_to_text_line_is_blockquote:
180  **/
181 static gboolean
182 ch_markdown_to_text_line_is_blockquote (const gchar *line)
183 {
184         return (g_str_has_prefix (line, "> "));
185 }
186 #endif
187
188 /**
189  * ch_markdown_to_text_line_is_blank:
190  **/
191 static gboolean
192 ch_markdown_to_text_line_is_blank (const gchar *line)
193 {
194         guint i;
195         guint len;
196         gboolean ret = FALSE;
197
198         /* a line with no characters is blank by definition */
199         len = strlen (line);
200         if (len == 0) {
201                 ret = TRUE;
202                 goto out;
203         }
204
205         /* find if there are only space chars */
206         for (i=0; i<len; i++) {
207                 if (line[i] != ' ' && line[i] != '\t')
208                         goto out;
209         }
210
211         /* if we matched, return true */
212         ret = TRUE;
213 out:
214         return ret;
215 }
216
217 /**
218  * ch_markdown_replace:
219  **/
220 static gchar *
221 ch_markdown_replace (const gchar *haystack,
222                      const gchar *needle,
223                      const gchar *replace)
224 {
225         gchar *new;
226         gchar **split;
227
228         split = g_strsplit (haystack, needle, -1);
229         new = g_strjoinv (replace, split);
230         g_strfreev (split);
231
232         return new;
233 }
234
235 /**
236  * ch_markdown_strstr_spaces:
237  **/
238 static gchar *
239 ch_markdown_strstr_spaces (const gchar *haystack, const gchar *needle)
240 {
241         gchar *found;
242         const gchar *haystack_new = haystack;
243
244 retry:
245         /* don't find if surrounded by spaces */
246         found = strstr (haystack_new, needle);
247         if (found == NULL)
248                 return NULL;
249
250         /* start of the string, always valid */
251         if (found == haystack)
252                 return found;
253
254         /* end of the string, always valid */
255         if (*(found-1) == ' ' && *(found+1) == ' ') {
256                 haystack_new = found+1;
257                 goto retry;
258         }
259         return found;
260 }
261
262 /**
263  * ch_markdown_to_text_line_formatter:
264  **/
265 static gchar *
266 ch_markdown_to_text_line_formatter (const gchar *line,
267                                     const gchar *formatter,
268                                     const gchar *left,
269                                     const gchar *right)
270 {
271         guint len;
272         gchar *str1;
273         gchar *str2;
274         gchar *start = NULL;
275         gchar *middle = NULL;
276         gchar *end = NULL;
277         gchar *copy = NULL;
278         gchar *data = NULL;
279         gchar *temp;
280
281         /* needed to know for shifts */
282         len = strlen (formatter);
283         if (len == 0)
284                 goto out;
285
286         /* find sections */
287         copy = g_strdup (line);
288         str1 = ch_markdown_strstr_spaces (copy, formatter);
289         if (str1 != NULL) {
290                 *str1 = '\0';
291                 str2 = ch_markdown_strstr_spaces (str1+len, formatter);
292                 if (str2 != NULL) {
293                         *str2 = '\0';
294                         middle = str1 + len;
295                         start = copy;
296                         end = str2 + len;
297                 }
298         }
299
300         /* if we found, replace and keep looking for the same string */
301         if (start != NULL && middle != NULL && end != NULL) {
302                 temp = g_strdup_printf ("%s%s%s%s%s", start, left, middle, right, end);
303                 /* recursive */
304                 data = ch_markdown_to_text_line_formatter (temp, formatter, left, right);
305                 g_free (temp);
306         } else {
307                 /* not found, keep return as-is */
308                 data = g_strdup (line);
309         }
310 out:
311         g_free (copy);
312         return data;
313 }
314
315 /**
316  * ch_markdown_to_text_line_format_sections:
317  **/
318 static gchar *
319 ch_markdown_to_text_line_format_sections (ChMarkdown *markdown, const gchar *line)
320 {
321         gchar *data = g_strdup (line);
322         gchar *temp;
323
324         /* bold1 */
325         temp = data;
326         data = ch_markdown_to_text_line_formatter (temp, "**", "<b>", "</b>");
327         g_free (temp);
328
329         /* bold2 */
330         temp = data;
331         data = ch_markdown_to_text_line_formatter (temp, "__", "<b>", "</b>");
332         g_free (temp);
333
334         /* italic1 */
335         temp = data;
336         data = ch_markdown_to_text_line_formatter (temp, "*", "<i>", "</i>");
337         g_free (temp);
338
339         /* italic2 */
340         temp = data;
341         data = ch_markdown_to_text_line_formatter (temp, "_", "<i>", "</i>");
342         g_free (temp);
343
344         /* em-dash */
345         temp = data;
346         data = ch_markdown_replace (temp, " -- ", " — ");
347         g_free (temp);
348
349         /* smart quoting */
350         if (markdown->priv->smart_quoting) {
351                 temp = data;
352                 data = ch_markdown_to_text_line_formatter (temp, "\"", "“", "”");
353                 g_free (temp);
354
355                 temp = data;
356                 data = ch_markdown_to_text_line_formatter (temp, "'", "‘", "’");
357                 g_free (temp);
358         }
359
360         return data;
361 }
362
363 /**
364  * ch_markdown_to_text_line_format:
365  **/
366 static gchar *
367 ch_markdown_to_text_line_format (ChMarkdown *markdown, const gchar *line)
368 {
369         guint i;
370         gchar *text;
371         gboolean mode = FALSE;
372         gchar **codes;
373         GString *string;
374
375         /* optimise the trivial case where we don't have any code tags */
376         text = strstr (line, "`");
377         if (text == NULL) {
378                 text = ch_markdown_to_text_line_format_sections (markdown, line);
379                 goto out;
380         }
381
382         /* we want to parse the code sections without formatting */
383         codes = g_strsplit (line, "`", -1);
384         string = g_string_new ("");
385         for (i=0; codes[i] != NULL; i++) {
386                 if (!mode) {
387                         text = ch_markdown_to_text_line_format_sections (markdown, codes[i]);
388                         g_string_append (string, text);
389                         g_free (text);
390                         mode = TRUE;
391                 } else {
392                         /* just append without formatting */
393                         g_string_append (string, "<tt>");
394                         g_string_append (string, codes[i]);
395                         g_string_append (string, "</tt>");
396                         mode = FALSE;
397                 }
398         }
399         text = g_string_free (string, FALSE);
400 out:
401         return text;
402 }
403
404 /**
405  * ch_markdown_add_pending:
406  **/
407 static gboolean
408 ch_markdown_add_pending (ChMarkdown *markdown, const gchar *line)
409 {
410         gchar *copy;
411
412         /* would put us over the limit */
413         if (markdown->priv->line_count >= markdown->priv->max_lines)
414                 return FALSE;
415
416         copy = g_strdup (line);
417
418         /* strip leading and trailing spaces */
419         g_strstrip (copy);
420
421         /* append */
422         g_string_append_printf (markdown->priv->pending, "%s ", copy);
423
424         g_free (copy);
425         return TRUE;
426 }
427
428 /**
429  * ch_markdown_add_pending_header:
430  **/
431 static gboolean
432 ch_markdown_add_pending_header (ChMarkdown *markdown, const gchar *line)
433 {
434         gchar *copy;
435         gboolean ret;
436
437         /* strip trailing # */
438         copy = g_strdup (line);
439         g_strdelimit (copy, "#", ' ');
440         ret = ch_markdown_add_pending (markdown, copy);
441         g_free (copy);
442         return ret;
443 }
444
445 /**
446  * ch_markdown_count_chars_in_word:
447  **/
448 static guint
449 ch_markdown_count_chars_in_word (const gchar *text, gchar find)
450 {
451         guint i;
452         guint len;
453         guint count = 0;
454
455         /* get length */
456         len = strlen (text);
457         if (len == 0)
458                 goto out;
459
460         /* find matching chars */
461         for (i=0; i<len; i++) {
462                 if (text[i] == find)
463                         count++;
464         }
465 out:
466         return count;
467 }
468
469 /**
470  * ch_markdown_word_is_code:
471  **/
472 static gboolean
473 ch_markdown_word_is_code (const gchar *text)
474 {
475         /* already code */
476         if (g_str_has_prefix (text, "`"))
477                 return FALSE;
478         if (g_str_has_suffix (text, "`"))
479                 return FALSE;
480
481         /* paths */
482         if (g_str_has_prefix (text, "/"))
483                 return TRUE;
484
485         /* bugzillas */
486         if (g_str_has_prefix (text, "#"))
487                 return TRUE;
488
489         /* uri's */
490         if (g_str_has_prefix (text, "http://"))
491                 return TRUE;
492         if (g_str_has_prefix (text, "https://"))
493                 return TRUE;
494         if (g_str_has_prefix (text, "ftp://"))
495                 return TRUE;
496
497         /* patch files */
498         if (g_strrstr (text, ".patch") != NULL)
499                 return TRUE;
500         if (g_strrstr (text, ".diff") != NULL)
501                 return TRUE;
502
503         /* function names */
504         if (g_strrstr (text, "()") != NULL)
505                 return TRUE;
506
507         /* email addresses */
508         if (g_strrstr (text, "@") != NULL)
509                 return TRUE;
510
511         /* compiler defines */
512         if (text[0] != '_' &&
513             ch_markdown_count_chars_in_word (text, '_') > 1)
514                 return TRUE;
515
516         /* nothing special */
517         return FALSE;
518 }
519
520 /**
521  * ch_markdown_word_auto_format_code:
522  **/
523 static gchar *
524 ch_markdown_word_auto_format_code (const gchar *text)
525 {
526         guint i;
527         gchar *temp;
528         gchar **words;
529         gboolean ret = FALSE;
530
531         /* split sentence up with space */
532         words = g_strsplit (text, " ", -1);
533
534         /* search each word */
535         for (i=0; words[i] != NULL; i++) {
536                 if (ch_markdown_word_is_code (words[i])) {
537                         temp = g_strdup_printf ("`%s`", words[i]);
538                         g_free (words[i]);
539                         words[i] = temp;
540                         ret = TRUE;
541                 }
542         }
543
544         /* no replacements, so just return a copy */
545         if (!ret) {
546                 temp = g_strdup (text);
547                 goto out;
548         }
549
550         /* join the array back into a string */
551         temp = g_strjoinv (" ", words);
552 out:
553         g_strfreev (words);
554         return temp;
555 }
556
557 /**
558  * ch_markdown_flush_pending:
559  **/
560 static void
561 ch_markdown_flush_pending (ChMarkdown *markdown)
562 {
563         gchar *copy;
564         gchar *temp;
565
566         /* no data yet */
567         if (markdown->priv->mode == CH_MARKDOWN_MODE_UNKNOWN)
568                 return;
569
570         /* remove trailing spaces */
571         while (g_str_has_suffix (markdown->priv->pending->str, " ")) {
572                 g_string_set_size (markdown->priv->pending,
573                                    markdown->priv->pending->len - 1);
574         }
575
576         /* pango requires escaping */
577         copy = g_strdup (markdown->priv->pending->str);
578         if (!markdown->priv->escape) {
579                 g_strdelimit (copy, "<", '(');
580                 g_strdelimit (copy, ">", ')');
581         }
582
583         /* check words for code */
584         if (markdown->priv->autocode &&
585             (markdown->priv->mode == CH_MARKDOWN_MODE_PARA ||
586              markdown->priv->mode == CH_MARKDOWN_MODE_BULLETT)) {
587                 temp = ch_markdown_word_auto_format_code (copy);
588                 g_free (copy);
589                 copy = temp;
590         }
591
592         /* escape */
593         if (markdown->priv->escape) {
594                 temp = g_markup_escape_text (copy, -1);
595                 g_free (copy);
596                 copy = temp;
597         }
598
599         /* do formatting */
600         temp = ch_markdown_to_text_line_format (markdown, copy);
601         if (markdown->priv->mode == CH_MARKDOWN_MODE_BULLETT) {
602                 g_string_append_printf (markdown->priv->processed, "%s%s%s\n", "• ", temp, "");
603                 markdown->priv->line_count++;
604         } else if (markdown->priv->mode == CH_MARKDOWN_MODE_H1) {
605                 g_string_append_printf (markdown->priv->processed, "%s%s%s\n", "<big>", temp, "</big>");
606         } else if (markdown->priv->mode == CH_MARKDOWN_MODE_H2) {
607                 g_string_append_printf (markdown->priv->processed, "%s%s%s\n", "<b>", temp, "</b>");
608         } else if (markdown->priv->mode == CH_MARKDOWN_MODE_PARA ||
609                    markdown->priv->mode == CH_MARKDOWN_MODE_RULE) {
610                 g_string_append_printf (markdown->priv->processed, "%s\n", temp);
611                 markdown->priv->line_count++;
612         }
613
614         /* clear */
615         g_string_truncate (markdown->priv->pending, 0);
616         g_free (copy);
617         g_free (temp);
618 }
619
620 /**
621  * ch_markdown_to_text_line_process:
622  **/
623 static gboolean
624 ch_markdown_to_text_line_process (ChMarkdown *markdown, const gchar *line)
625 {
626         gboolean ret;
627
628         /* blank */
629         ret = ch_markdown_to_text_line_is_blank (line);
630         if (ret) {
631                 ch_markdown_flush_pending (markdown);
632                 /* a new line after a list is the end of list, not a gap */
633                 if (markdown->priv->mode != CH_MARKDOWN_MODE_BULLETT)
634                         ret = ch_markdown_add_pending (markdown, "\n");
635                 markdown->priv->mode = CH_MARKDOWN_MODE_BLANK;
636                 goto out;
637         }
638
639         /* header1_type2 */
640         ret = ch_markdown_to_text_line_is_header1_type2 (line);
641         if (ret) {
642                 if (markdown->priv->mode == CH_MARKDOWN_MODE_PARA)
643                         markdown->priv->mode = CH_MARKDOWN_MODE_H1;
644                 goto out;
645         }
646
647         /* header2_type2 */
648         ret = ch_markdown_to_text_line_is_header2_type2 (line);
649         if (ret) {
650                 if (markdown->priv->mode == CH_MARKDOWN_MODE_PARA)
651                         markdown->priv->mode = CH_MARKDOWN_MODE_H2;
652                 goto out;
653         }
654
655         /* rule */
656         ret = ch_markdown_to_text_line_is_rule (line);
657         if (ret) {
658                 ch_markdown_flush_pending (markdown);
659                 markdown->priv->mode = CH_MARKDOWN_MODE_RULE;
660                 ret = ch_markdown_add_pending (markdown, "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯\n");
661                 goto out;
662         }
663
664         /* bullett */
665         ret = ch_markdown_to_text_line_is_bullett (line);
666         if (ret) {
667                 ch_markdown_flush_pending (markdown);
668                 markdown->priv->mode = CH_MARKDOWN_MODE_BULLETT;
669                 ret = ch_markdown_add_pending (markdown, &line[2]);
670                 goto out;
671         }
672
673         /* header1 */
674         ret = ch_markdown_to_text_line_is_header1 (line);
675         if (ret) {
676                 ch_markdown_flush_pending (markdown);
677                 markdown->priv->mode = CH_MARKDOWN_MODE_H1;
678                 ret = ch_markdown_add_pending_header (markdown, &line[2]);
679                 goto out;
680         }
681
682         /* header2 */
683         ret = ch_markdown_to_text_line_is_header2 (line);
684         if (ret) {
685                 ch_markdown_flush_pending (markdown);
686                 markdown->priv->mode = CH_MARKDOWN_MODE_H2;
687                 ret = ch_markdown_add_pending_header (markdown, &line[3]);
688                 goto out;
689         }
690
691         /* paragraph */
692         if (markdown->priv->mode == CH_MARKDOWN_MODE_BLANK || markdown->priv->mode == CH_MARKDOWN_MODE_UNKNOWN) {
693                 ch_markdown_flush_pending (markdown);
694                 markdown->priv->mode = CH_MARKDOWN_MODE_PARA;
695         }
696
697         /* add to pending */
698         ret = ch_markdown_add_pending (markdown, line);
699 out:
700         /* if we failed to add, we don't know the mode */
701         if (!ret)
702                 markdown->priv->mode = CH_MARKDOWN_MODE_UNKNOWN;
703         return ret;
704 }
705
706 /**
707  * ch_markdown_parse:
708  **/
709 gchar *
710 ch_markdown_parse (ChMarkdown *markdown, const gchar *text)
711 {
712         gchar **lines;
713         guint i;
714         guint len;
715         gchar *temp;
716         gboolean ret;
717
718         g_return_val_if_fail (CH_IS_MARKDOWN (markdown), NULL);
719
720         /* process */
721         markdown->priv->mode = CH_MARKDOWN_MODE_UNKNOWN;
722         markdown->priv->line_count = 0;
723         g_string_truncate (markdown->priv->pending, 0);
724         g_string_truncate (markdown->priv->processed, 0);
725         lines = g_strsplit (text, "\n", -1);
726         len = g_strv_length (lines);
727
728         /* process each line */
729         for (i=0; i<len; i++) {
730                 ret = ch_markdown_to_text_line_process (markdown, lines[i]);
731                 if (!ret)
732                         break;
733         }
734         g_strfreev (lines);
735         ch_markdown_flush_pending (markdown);
736
737         /* remove trailing \n */
738         while (g_str_has_suffix (markdown->priv->processed->str, "\n"))
739                 g_string_set_size (markdown->priv->processed, markdown->priv->processed->len - 1);
740
741         /* get a copy */
742         temp = g_strdup (markdown->priv->processed->str);
743         g_string_truncate (markdown->priv->pending, 0);
744         g_string_truncate (markdown->priv->processed, 0);
745         return temp;
746 }
747
748 /**
749  * ch_markdown_class_init:
750  **/
751 static void
752 ch_markdown_class_init (ChMarkdownClass *klass)
753 {
754         GObjectClass *object_class = G_OBJECT_CLASS (klass);
755         object_class->finalize = ch_markdown_finalize;
756         g_type_class_add_private (klass, sizeof (ChMarkdownPrivate));
757 }
758
759 /**
760  * ch_markdown_init:
761  **/
762 static void
763 ch_markdown_init (ChMarkdown *markdown)
764 {
765         markdown->priv = CH_MARKDOWN_GET_PRIVATE (markdown);
766         markdown->priv->mode = CH_MARKDOWN_MODE_UNKNOWN;
767         markdown->priv->pending = g_string_new ("");
768         markdown->priv->processed = g_string_new ("");
769         markdown->priv->max_lines = -1;
770         markdown->priv->smart_quoting = FALSE;
771         markdown->priv->escape = FALSE;
772         markdown->priv->autocode = FALSE;
773 }
774
775 /**
776  * ch_markdown_finalize:
777  **/
778 static void
779 ch_markdown_finalize (GObject *object)
780 {
781         ChMarkdown *markdown;
782
783         g_return_if_fail (CH_IS_MARKDOWN (object));
784
785         markdown = CH_MARKDOWN (object);
786
787         g_return_if_fail (markdown->priv != NULL);
788         g_string_free (markdown->priv->pending, TRUE);
789         g_string_free (markdown->priv->processed, TRUE);
790
791         G_OBJECT_CLASS (ch_markdown_parent_class)->finalize (object);
792 }
793
794 /**
795  * ch_markdown_new:
796  **/
797 ChMarkdown *
798 ch_markdown_new (void)
799 {
800         ChMarkdown *markdown;
801         markdown = g_object_new (CH_TYPE_MARKDOWN, NULL);
802         return CH_MARKDOWN (markdown);
803 }