[valadoc] gir-importer: add support for markdown



commit 30c774c06fa73105ee30032a4f967dce95b55f1f
Author: Florian Brosch <flo brosch gmail com>
Date:   Wed Aug 20 00:36:16 2014 +0200

    gir-importer: add support for markdown

 src/libvaladoc/Makefile.am                         |    3 +
 src/libvaladoc/api/girsourcecomment.vala           |    6 +-
 src/libvaladoc/content/blockcontent.vala           |    5 +-
 .../documentation/documentationparser.vala         |   51 ++-
 src/libvaladoc/documentation/girmetadata.vala      |    7 +
 .../documentation/gtkdoccommentparser.vala         |  149 +----
 .../documentation/gtkdocmarkdownparser.vala        |  816 ++++++++++++++++++++
 .../documentation/gtkdocmarkdownscanner.vala       |  761 ++++++++++++++++++
 src/libvaladoc/documentation/importerhelper.vala   |  156 ++++
 .../importer/girdocumentationimporter.vala         |   66 ++-
 src/libvaladoc/parser/token.vala                   |   10 +-
 src/libvaladoc/parser/tokentype.vala               |   68 ++
 src/libvaladoc/taglets/tagletdeprecated.vala       |    7 +-
 src/libvaladoc/taglets/tagletlink.vala             |   62 ++-
 src/libvaladoc/taglets/tagletparam.vala            |    6 +-
 src/libvaladoc/taglets/tagletreturn.vala           |    6 +-
 src/libvaladoc/taglets/tagletthrows.vala           |    7 +-
 17 files changed, 1996 insertions(+), 190 deletions(-)
---
diff --git a/src/libvaladoc/Makefile.am b/src/libvaladoc/Makefile.am
index 82caa07..84cebd8 100644
--- a/src/libvaladoc/Makefile.am
+++ b/src/libvaladoc/Makefile.am
@@ -52,6 +52,9 @@ libvaladoc_la_VALASOURCES = \
        documentation/wikiscanner.vala \
        documentation/gtkdoccommentparser.vala \
        documentation/gtkdoccommentscanner.vala \
+       documentation/gtkdocmarkdownparser.vala \
+       documentation/gtkdocmarkdownscanner.vala \
+       documentation/importerhelper.vala \
        documentation/girmetadata.vala \
        importer/documentationimporter.vala \
        importer/valadocdocumentationimporter.vala \
diff --git a/src/libvaladoc/api/girsourcecomment.vala b/src/libvaladoc/api/girsourcecomment.vala
index 77bfb5f..c935ba3 100644
--- a/src/libvaladoc/api/girsourcecomment.vala
+++ b/src/libvaladoc/api/girsourcecomment.vala
@@ -31,7 +31,11 @@ public class Valadoc.Api.GirSourceComment : SourceComment {
        private Map<string, SourceComment> parameters = new HashMap<string, SourceComment> ();
 
        public string? instance_param_name { set; get; }
-       public SourceComment return_comment { set; get; }
+       public SourceComment? return_comment { set; get; }
+       public SourceComment? deprecated_comment { set; get; }
+       public SourceComment? version_comment { get; set; }
+       public SourceComment? stability_comment { get; set; }
+
 
        public MapIterator<string, SourceComment> parameter_iterator () {
                return parameters.map_iterator ();
diff --git a/src/libvaladoc/content/blockcontent.vala b/src/libvaladoc/content/blockcontent.vala
index 0411b61..47e99a2 100644
--- a/src/libvaladoc/content/blockcontent.vala
+++ b/src/libvaladoc/content/blockcontent.vala
@@ -28,10 +28,13 @@ public abstract class Valadoc.Content.BlockContent : ContentElement {
 
        private Gee.List<Block> _content;
 
-       internal BlockContent () {
+       construct {
                _content = new ArrayList<Block> ();
        }
 
+       internal BlockContent () {
+       }
+
        public override void configure (Settings settings, ResourceLocator locator) {
        }
 
diff --git a/src/libvaladoc/documentation/documentationparser.vala 
b/src/libvaladoc/documentation/documentationparser.vala
index 9e0b3bb..a4a1201 100644
--- a/src/libvaladoc/documentation/documentationparser.vala
+++ b/src/libvaladoc/documentation/documentationparser.vala
@@ -27,6 +27,7 @@ using Gee;
 
 
 public class Valadoc.DocumentationParser : Object, ResourceLocator {
+       private HashMap<Api.SourceFile, GirMetaData> metadata = new HashMap<Api.SourceFile, GirMetaData> ();
 
        public DocumentationParser (Settings settings, ErrorReporter reporter,
                                                                Api.Tree tree, ModuleLoader modules)
@@ -47,11 +48,13 @@ public class Valadoc.DocumentationParser : Object, ResourceLocator {
                _comment_scanner.set_parser (_comment_parser);
 
                gtkdoc_parser = new Gtkdoc.Parser (settings, reporter, tree, modules);
+               gtkdoc_markdown_parser = new Gtkdoc.MarkdownParser (settings, reporter, tree, modules);
 
                init_valadoc_rules ();
        }
 
        private Gtkdoc.Parser gtkdoc_parser;
+       private Gtkdoc.MarkdownParser gtkdoc_markdown_parser;
 
        private Settings _settings;
        private ErrorReporter _reporter;
@@ -69,8 +72,16 @@ public class Valadoc.DocumentationParser : Object, ResourceLocator {
 
        public Comment? parse (Api.Node element, Api.SourceComment comment) {
                if (comment is Api.GirSourceComment) {
-                       Comment doc_comment = gtkdoc_parser.parse (element, (Api.GirSourceComment) comment);
-                       return doc_comment;
+                       Api.GirSourceComment gir_comment = (Api.GirSourceComment) comment;
+                       GirMetaData metadata = get_metadata_for_comment (gir_comment);
+
+                       if (metadata.is_docbook) {
+                               Comment doc_comment = gtkdoc_parser.parse (element, gir_comment, metadata);
+                               return doc_comment;
+                       } else {
+                               Comment doc_comment = gtkdoc_markdown_parser.parse (element, gir_comment, 
metadata);
+                               return doc_comment;
+                       }
                } else {
                        return parse_comment_str (element, comment.content, comment.file.get_name (),
                                                                          comment.first_line, 
comment.first_column);
@@ -125,6 +136,17 @@ public class Valadoc.DocumentationParser : Object, ResourceLocator {
                return (Page) pop ();
        }
 
+       private GirMetaData get_metadata_for_comment (Api.GirSourceComment gir_comment) {
+               GirMetaData metadata = metadata.get (gir_comment.file);
+               if (metadata != null) {
+                       return metadata;
+               }
+
+               metadata = new GirMetaData (gir_comment.file.relative_path, _settings.metadata_directories, 
_reporter);
+               this.metadata.set (gir_comment.file, metadata);
+               return metadata;
+       }
+
        public string resolve (string path) {
                return path;
        }
@@ -146,6 +168,7 @@ public class Valadoc.DocumentationParser : Object, ResourceLocator {
                return node;
        }
 
+       private Rule multiline_block_run;
        private Rule multiline_run;
        private int current_level = 0;
        private int[] levels = new int[0];
@@ -769,16 +792,36 @@ public class Valadoc.DocumentationParser : Object, ResourceLocator {
                        })
                        .set_name ("Description");
 
+               multiline_block_run =
+                       Rule.seq ({
+                               multiline_run
+                       })
+                       .set_start (() => { push (_factory.create_paragraph ()); })
+                       .set_reduce (() => {
+                               Paragraph p = (Paragraph) pop ();
+                               ((BlockContent) peek ()).content.add (p);
+                       })
+                       .set_name ("BlockMultilineRun");
+
                Rule taglet =
                        Rule.seq ({
                                TokenType.AROBASE,
                                TokenType.any_word ().action ((token) => {
-                                       var taglet = _factory.create_taglet (token.to_string ());
+
+                                       string tag_name = token.to_string ();
+                                       var taglet = _factory.create_taglet (tag_name);
                                        if (!(taglet is Block)) {
                                                _parser.error (token, "Invalid taglet in this context");
                                        }
                                        push (taglet);
-                                       Rule? taglet_rule = taglet.get_parser_rule (multiline_run);
+
+                                       Rule? taglet_rule;
+                                       if (taglet is BlockContent) {
+                                               taglet_rule = taglet.get_parser_rule (multiline_block_run);   
                                  
+                                       } else {
+                                               taglet_rule = taglet.get_parser_rule (multiline_run);
+                                       }
+
                                        if (taglet_rule != null) {
                                                _parser.push_rule (Rule.seq ({ TokenType.SPACE, taglet_rule 
}));
                                        }
diff --git a/src/libvaladoc/documentation/girmetadata.vala b/src/libvaladoc/documentation/girmetadata.vala
index 53c1e9e..41e2be8 100644
--- a/src/libvaladoc/documentation/girmetadata.vala
+++ b/src/libvaladoc/documentation/girmetadata.vala
@@ -27,6 +27,9 @@ public class Valadoc.GirMetaData : Object {
        private string? metadata_path = null;
        private string? resource_dir = null;
 
+       public bool is_docbook { private set; get; default = false; }
+
+
        /**
         * Used to manipulate paths to resources inside gir-files
         */
@@ -82,6 +85,10 @@ public class Valadoc.GirMetaData : Object {
                                this.resource_dir = key_file.get_string ("General", "resources");
                                break;
 
+                       case "is_docbook":
+                               this.is_docbook = key_file.get_boolean ("General", "is_docbook");
+                               break;
+
                        default:
                                reporter.simple_warning ("%s: warning: Unknown key 'General.%s'", 
metadata_path, key);
                                break;
diff --git a/src/libvaladoc/documentation/gtkdoccommentparser.vala 
b/src/libvaladoc/documentation/gtkdoccommentparser.vala
index 5e0ce5b..f102934 100644
--- a/src/libvaladoc/documentation/gtkdoccommentparser.vala
+++ b/src/libvaladoc/documentation/gtkdoccommentparser.vala
@@ -48,20 +48,8 @@ public class Valadoc.Gtkdoc.Parser : Object, ResourceLocator {
        private Regex? is_numeric_regex = null;
        private Regex? normalize_regex = null;
 
-       private HashMap<Api.SourceFile, GirMetaData> metadata = new HashMap<Api.SourceFile, GirMetaData> ();
        private GirMetaData? current_metadata = null;
 
-       private GirMetaData get_metadata_for_comment (Api.GirSourceComment gir_comment) {
-               GirMetaData metadata = metadata.get (gir_comment.file);
-               if (metadata != null) {
-                       return metadata;
-               }
-
-               metadata = new GirMetaData (gir_comment.file.relative_path, settings.metadata_directories, 
reporter);
-               this.metadata.set (gir_comment.file, metadata);
-               return metadata;
-       }
-
        private inline string fix_resource_path (string path) {
                return this.current_metadata.get_resource_path (path);
        }
@@ -88,131 +76,6 @@ public class Valadoc.Gtkdoc.Parser : Object, ResourceLocator {
                return is_numeric_regex.match (str);
        }
 
-       private inline Text? split_text (Text text) {
-               int offset = 0;
-               while ((offset = text.content.index_of_char ('.', offset)) >= 0) {
-                       if (offset >= 2) {
-                               // ignore "e.g."
-                               unowned string cmp4 = ((string) (((char*) text.content) + offset - 2));
-                               if (cmp4.has_prefix (" e.g.") || cmp4.has_prefix ("(e.g.")) {
-                                       offset = offset + 3;
-                                       continue;
-                               }
-
-                               // ignore "i.e."
-                               if (cmp4.has_prefix (" i.e.") || cmp4.has_prefix ("(i.e.")) {
-                                       offset = offset + 3;
-                                       continue;
-                               }
-                       }
-
-                       unowned string cmp0 = ((string) (((char*) text.content) + offset));
-
-                       // ignore ... (varargs)
-                       if (cmp0.has_prefix ("...")) {
-                               offset = offset + 3;
-                               continue;
-                       }
-
-                       // ignore numbers
-                       if (cmp0.get (1).isdigit ()) {
-                               offset = offset + 2;
-                               continue;
-                       }
-
-
-                       Text sec = factory.create_text (text.content.substring (offset+1, -1));
-                       text.content = text.content.substring (0, offset+1);
-                       return sec;
-               }
-
-               return null;
-       }
-
-       private inline Run? split_run (Run run) {
-               Run? sec = null;
-
-               Iterator<Inline> iter = run.content.iterator ();
-               for (bool has_next = iter.next (); has_next; has_next = iter.next ()) {
-                       Inline item = iter.get ();
-                       if (sec == null) {
-                               Inline? tmp = split_inline (item);
-                               if (tmp != null) {
-                                       sec = factory.create_run (run.style);
-                                       sec.content.add (tmp);
-                               }
-                       } else {
-                               sec.content.add (item);
-                               iter.remove ();
-                       }
-               }
-
-               return sec;
-       }
-
-       private inline Inline? split_inline (Inline item) {
-               if (item is Text) {
-                       return split_text ((Text) item);
-               } else if (item is Run) {
-                       return split_run ((Run) item);
-               }
-
-               return null;
-       }
-
-       private inline Paragraph? split_paragraph (Paragraph p) {
-               Paragraph? sec = null;
-
-               Iterator<Inline> iter = p.content.iterator ();
-               for (bool has_next = iter.next (); has_next; has_next = iter.next ()) {
-                       Inline item = iter.get ();
-                       if (sec == null) {
-                               Inline? tmp = split_inline (item);
-                               if (tmp != null) {
-                                       sec = factory.create_paragraph ();
-                                       sec.horizontal_align = p.horizontal_align;
-                                       sec.vertical_align = p.vertical_align;
-                                       sec.style = p.style;
-                                       sec.content.add (tmp);
-                               }
-                       } else {
-                               sec.content.add (item);
-                               iter.remove ();
-                       }
-               }
-
-               return sec;
-       }
-
-       private void extract_short_desc (Comment comment) {
-               if (comment.content.size == 0) {
-                       return ;
-               }
-
-               Paragraph? first_paragraph = comment.content[0] as Paragraph;
-               if (first_paragraph == null) {
-                       // add empty paragraph to avoid non-text as short descriptions
-                       comment.content.insert (1, factory.create_paragraph ());
-                       return ;
-               }
-
-
-               // avoid fancy stuff in short descriptions:
-               first_paragraph.horizontal_align = null;
-               first_paragraph.vertical_align = null;
-               first_paragraph.style = null;
-
-
-               Paragraph? second_paragraph = split_paragraph (first_paragraph);
-               if (second_paragraph == null) {
-                       return ;
-               }
-
-               if (second_paragraph.is_empty () == false) {
-                       comment.content.insert (1, second_paragraph);
-               }
-       }
-
        private void report_unexpected_token (Token got, string expected) {
                if (!this.show_warnings) {
                        return ;
@@ -252,9 +115,9 @@ public class Valadoc.Gtkdoc.Parser : Object, ResourceLocator {
 
        private Api.Node? element;
 
-       public Comment? parse (Api.Node element, Api.GirSourceComment gir_comment) {
-               this.current_metadata = get_metadata_for_comment (gir_comment);
+       public Comment? parse (Api.Node element, Api.GirSourceComment gir_comment, GirMetaData gir_metadata) {
                this.instance_param_name = gir_comment.instance_param_name;
+               this.current_metadata = gir_metadata;
                this.element = element;
 
                Comment? comment = this.parse_main_content (gir_comment);
@@ -321,9 +184,11 @@ public class Valadoc.Gtkdoc.Parser : Object, ResourceLocator {
                        return null;
                }
 
-               InlineContent? taglet = factory.create_taglet (taglet_name) as InlineContent;
+               BlockContent? taglet = factory.create_taglet (taglet_name) as BlockContent;
                assert (taglet != null);
-               taglet.content.add (ic);
+               Paragraph paragraph = factory.create_paragraph ();
+               paragraph.content.add (ic);
+               taglet.content.add (paragraph);
                return taglet as Taglet;
        }
 
@@ -357,7 +222,7 @@ public class Valadoc.Gtkdoc.Parser : Object, ResourceLocator {
                        return null;
                }
 
-               extract_short_desc (comment);
+               ImporterHelper.extract_short_desc (comment, factory);
 
                return comment;
        }
diff --git a/src/libvaladoc/documentation/gtkdocmarkdownparser.vala 
b/src/libvaladoc/documentation/gtkdocmarkdownparser.vala
new file mode 100644
index 0000000..841949d
--- /dev/null
+++ b/src/libvaladoc/documentation/gtkdocmarkdownparser.vala
@@ -0,0 +1,816 @@
+/* gtkdocmarkdownparser.vala
+ *
+ * Copyright (C) 2014 Florian Brosch
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+ *
+ * Author:
+ *  Florian Brosch <flo brosch gmail com>
+ */
+
+
+using Valadoc.Content;
+using Valadoc;
+using Gee;
+
+
+
+public class Valadoc.Gtkdoc.MarkdownParser : Object, ResourceLocator {
+       private Valadoc.Parser parser;
+       private Content.ContentFactory _factory;
+
+       private Settings _settings;
+       private ErrorReporter _reporter;
+       private Api.Tree _tree;
+
+       private ArrayList<Object> _stack = new ArrayList<Object> ();
+
+       private Valadoc.Token preserved_token = null;
+       private Regex regex_source_lang;
+
+       private GirMetaData metadata;
+       private Api.GirSourceComment gir_comment;
+       private Api.Node element;
+
+
+       public MarkdownParser (Settings settings, ErrorReporter reporter, Api.Tree? tree, ModuleLoader 
_modules) {
+               MarkdownScanner scanner = new MarkdownScanner (settings);
+               parser = new Valadoc.Parser (settings, scanner, reporter);
+               scanner.set_parser (parser);
+
+
+               _factory = new Content.ContentFactory (settings, this, _modules);
+               _settings = settings;
+               _reporter = reporter;
+               _tree = tree;
+
+               init_rules ();
+
+               try {
+                       regex_source_lang = new Regex ("^<!--[ \t]+language=\"([A-Za-z]*)\"[ \t]+-->");
+               } catch (Error e) {
+                       assert_not_reached ();
+               }
+       }
+
+       public void init_rules () {
+               Valadoc.TokenType word = Valadoc.TokenType.any_word ().action (add_text);
+
+               StubRule content = new StubRule ();
+               content.set_name ("Content");
+
+               StubRule run = new StubRule ();
+               run.set_name ("Run");
+
+
+               Rule param = Rule.seq ({
+                       Valadoc.TokenType.MARKDOWN_PARAMETER.action ((token) => {
+                               Run _run = null;
+
+                               if (token.value == gir_comment.instance_param_name) {                         
  
+                                       _run = _factory.create_run (Run.Style.LANG_KEYWORD);
+                                       _run.content.add (_factory.create_text ("this"));
+                               } else if (is_error_parameter (token.value)) {
+                                       _run = _factory.create_run (Run.Style.LANG_KEYWORD);
+                                       _run.content.add (_factory.create_text ("throws"));
+                               } else {
+                                       _run = _factory.create_run (Run.Style.MONOSPACED);
+                                       _run.content.add (_factory.create_text (token.value));
+                               }
+
+                               push (_run);
+                       })
+               })
+               .set_name ("Parameter");
+
+
+               Rule constant = Rule.seq ({
+                       Valadoc.TokenType.MARKDOWN_CONSTANT.action ((token) => {
+                               if (is_literal (token.value)) {
+                                       var _run = _factory.create_run (Run.Style.LANG_LITERAL);
+                                       _run.content.add (_factory.create_text (token.value.down ()));
+                                       push (_run);
+                               } else {
+                                       add_symbol_link ("c::" + token.value, true);
+                               }
+                       })
+               })
+               .set_name ("Constant");
+
+
+               Rule gmember = Rule.seq ({
+                       Valadoc.TokenType.MARKDOWN_LOCAL_GMEMBER.action ((token) => {
+                               Api.Item gtype = element;
+                               while (gtype is Api.Class == false && gtype is Api.Interface == false && 
gtype != null) {
+                                       gtype = gtype.parent;
+                               }
+
+                               string parent_cname;
+                               if (gtype is Api.Class) {
+                                       parent_cname = ((Api.Class) gtype).get_cname ();
+                               } else if (gtype is Api.Interface) {
+                                       parent_cname = ((Api.Interface) gtype).get_cname ();
+                               } else {
+                                       parent_cname = "";
+                               }
+
+                               add_symbol_link ("c::" + parent_cname + token.value, true);
+                       })
+               })
+               .set_name ("GLocalMember");
+
+
+               Rule symbol = Rule.seq ({
+                       Valadoc.TokenType.MARKDOWN_SYMBOL.action ((token) => {
+                               add_symbol_link ("c::" + token.value, true);
+                       })
+               })
+               .set_name ("Symbol");
+
+
+               Rule function = Rule.seq ({
+                       Valadoc.TokenType.MARKDOWN_FUNCTION.action ((token) => {
+                               add_symbol_link ("c::" + token.value, false);
+                       })
+               })
+               .set_name ("Function");
+
+
+               Rule link_short = Rule.seq ({
+                       Valadoc.TokenType.MARKDOWN_LESS_THAN,
+                       Rule.option ({
+                               Rule.one_of ({
+                                       Valadoc.TokenType.MARKDOWN_MAIL.action (preserve_token),
+                                       Valadoc.TokenType.MARKDOWN_LINK.action (preserve_token)
+                               }),
+                               Rule.option ({
+                                       Valadoc.TokenType.MARKDOWN_GREATER_THAN,
+                               })
+                               .set_reduce (() => {
+                                       Link url = _factory.create_link ();
+                                       url.url = pop_preserved_link ();
+                                       push (url);
+                               })
+                               .set_skip (() => {
+                                       add_content_string ("<");
+                                       add_value (preserved_token);
+                                       preserved_token = null;
+                               })
+                       })
+                       .set_skip (() => {
+                               add_content_string ("<");
+                       })
+               })
+               .set_name ("Link");
+
+
+               Rule link = Rule.seq ({
+                       Valadoc.TokenType.MARKDOWN_OPEN_BRACKET,
+                       Rule.option ({
+                               Rule.option ({
+                                       run
+                               }),
+                               Valadoc.TokenType.MARKDOWN_CLOSE_BRACKET,
+                               Rule.option ({
+                                       Rule.one_of ({
+                                               // External link:
+                                               Rule.seq ({
+                                                       Valadoc.TokenType.MARKDOWN_OPEN_PARENS,
+                                                       Rule.option ({
+                                                               Rule.one_of ({
+                                                                       
Valadoc.TokenType.MARKDOWN_LINK.action (preserve_token),
+                                                                       
Valadoc.TokenType.MARKDOWN_MAIL.action (preserve_token)
+                                                               }),
+                                                               Rule.option ({
+                                                                       
Valadoc.TokenType.MARKDOWN_CLOSE_PARENS
+                                                               })
+                                                               .set_reduce (() => {
+                                                                       Link url = _factory.create_link ();
+                                                                       url.url = pop_preserved_link ();
+
+                                                                       Run label = (Run) peek ();
+                                                                       url.content.add_all (label.content);
+                                                                       label.content.clear ();
+                                                                       label.content.add (url);
+                                                               })
+                                                               .set_skip (() => {
+                                                                       Run _run = (Run) peek ();
+                                                                       _run.content.insert (0, 
_factory.create_text ("["));
+                                                                       _run.content.add 
(_factory.create_text ("](" + pop_preserved_link ()));
+                                                               })
+                                                       })
+                                                       .set_skip (() => {
+                                                               Run _run = (Run) peek ();
+                                                               _run.content.insert (0, _factory.create_text 
("["));
+                                                               _run.content.add (_factory.create_text 
("]("));
+                                                       })
+                                               }),
+                                               // Internal link:
+                                               Rule.seq ({
+                                                       Valadoc.TokenType.MARKDOWN_OPEN_BRACKET,
+                                                       Rule.option ({
+                                                               Valadoc.TokenType.any_word ().action 
(preserve_token),
+                                                               Rule.option ({
+                                                                       
Valadoc.TokenType.MARKDOWN_CLOSE_BRACKET
+                                                               })
+                                                               .set_reduce (() => {
+                                                                       Link url = _factory.create_link ();
+                                                                       url.url = pop_preserved_link ();
+
+                                                                       Run label = (Run) peek ();
+                                                                       url.content.add_all (label.content);
+                                                                       label.content.clear ();
+                                                                       label.content.add (url);
+                                                               })
+                                                               .set_skip (() => {
+                                                                       Run _run = (Run) peek ();
+                                                                       _run.content.insert (0, 
_factory.create_text ("["));
+
+                                                                       _run.content.add 
(_factory.create_text ("][" + pop_preserved_link ()));
+                                                               })
+                                                       })
+                                                       .set_skip (() => {
+                                                               Run _run = (Run) peek ();
+                                                               _run.content.insert (0, _factory.create_text 
("["));
+                                                               _run.content.add (_factory.create_text 
("]["));
+                                                       })
+                                               })
+                                       })
+                               })
+                               .set_skip (() => {
+                                       Run _run = (Run) peek ();
+                                       _run.content.insert (0, _factory.create_text ("["));
+                                       _run.content.add (_factory.create_text ("]"));
+                               })
+                       })
+                       .set_skip (() => {
+                               Run _run = (Run) peek ();
+                               _run.content.insert (0, _factory.create_text ("["));
+                       })
+               })
+               .set_start (() => { push (_factory.create_run (Run.Style.NONE)); })
+               .set_name ("Link");
+
+
+               Rule image = Rule.seq ({
+                       Valadoc.TokenType.MARKDOWN_EXCLAMATION_MARK,
+                       Rule.option ({
+                               Valadoc.TokenType.MARKDOWN_OPEN_BRACKET,
+                               Rule.option ({
+                                       run
+                               }),
+                               Valadoc.TokenType.MARKDOWN_CLOSE_BRACKET,
+                               Rule.option ({
+                                       Valadoc.TokenType.MARKDOWN_OPEN_BRACKET,
+                                       Rule.option ({
+                                               Rule.one_of ({
+                                                       Valadoc.TokenType.any_word ().action (preserve_token),
+                                                       Valadoc.TokenType.MARKDOWN_LINK.action 
(preserve_token),
+                                                       Valadoc.TokenType.MARKDOWN_MAIL.action 
(preserve_token)
+                                               }),
+                                               Rule.option ({
+                                                       Valadoc.TokenType.MARKDOWN_CLOSE_BRACKET,
+                                               })
+                                               .set_reduce (() => {
+                                                       Run label = (Run) peek ();
+
+                                                       string label_str;
+                                                       try {
+                                                               label_str = run_to_string (label, "<image>");
+                                                       } catch (Error e) {
+                                                               parser.warning (preserved_token, e.message);
+                                                               label_str = null;
+                                                       }
+
+                                                       Embedded embedded = _factory.create_embedded ();
+                                                       embedded.url = fix_resource_path (pop_preserved_path 
());
+                                                       embedded.caption = label_str;
+
+                                                       label.content.clear ();
+                                                       label.content.add (embedded);
+                                               })
+                                               .set_skip (() => {
+                                                       Run _run = (Run) peek ();
+                                                       _run.content.insert (0, _factory.create_text ("!["));
+                                                       _run.content.add (_factory.create_text ("][" + 
pop_preserved_link ()));
+                                               })
+                                       })
+                                       .set_skip (() => {
+                                               Run _run = (Run) peek ();
+                                               _run.content.insert (0, _factory.create_text ("!["));
+                                               _run.content.add (_factory.create_text ("]["));
+                                       })
+                               })
+                               .set_skip (() => {
+                                       Run _run = (Run) peek ();
+                                       _run.content.insert (0, _factory.create_text ("!["));
+                                       _run.content.add (_factory.create_text ("]"));
+                               })
+                       })
+                       .set_skip (() => {
+                               Run _run = (Run) peek ();
+                               _run.content.insert (0, _factory.create_text ("!"));
+                       })
+               })
+               .set_start (() => { push (_factory.create_run (Run.Style.NONE)); })
+               .set_name ("Image");
+
+
+               Rule source = Rule.seq ({
+                       Valadoc.TokenType.MARKDOWN_SOURCE.action ((token) => {
+                               SourceCode code = _factory.create_source_code ();
+                               MatchInfo info;
+
+                               unowned string source = token.value;
+                               if (regex_source_lang.match (source, 0, out info)) {
+                                       string lang_name = info.fetch (1).down ();
+                                       SourceCode.Language? lang = SourceCode.Language.from_string 
(lang_name);
+                                       code.language = lang;
+
+                                       if (lang == null) {
+                                               parser.warning (token, "Unknown language `%s' in source code 
block |[<!-- language=\"\"".printf (lang.to_string ()));
+                                       }
+
+                                       source = source.offset (source.index_of_char ('>') + 1);
+                               }
+
+                               code.code = source;
+                               push (code);
+                       })
+               })
+               .set_name ("Source");
+
+
+               Rule text = Rule.many ({
+                       Rule.one_of ({
+                               word,
+                               Valadoc.TokenType.MARKDOWN_SPACE.action (add_text),
+                               Valadoc.TokenType.MARKDOWN_MAIL.action (add_value),
+                               Valadoc.TokenType.MARKDOWN_LINK.action (add_value),
+                               Valadoc.TokenType.MARKDOWN_OPEN_PARENS.action (add_text),
+                               Valadoc.TokenType.MARKDOWN_CLOSE_PARENS.action (add_text),
+                               Valadoc.TokenType.MARKDOWN_CLOSE_BRACKET.action (add_text),
+                               Valadoc.TokenType.MARKDOWN_GREATER_THAN.action (add_text)
+                       })
+               })
+               .set_start (() => { push (_factory.create_text ()); })
+               .set_name ("Text");
+
+
+               run.set_rule (
+                       Rule.many ({
+                               Rule.one_of ({
+                                       text,
+                                       link,
+                                       link_short,
+                                       image,
+                                       function,
+                                       constant,
+                                       param,
+                                       symbol,
+                                       gmember,
+                                       source
+                               })
+                               .set_reduce (() => {
+                                       var head = (Inline) pop ();
+                                       ((InlineContent) peek ()).content.add (head);
+                               })
+                       })
+               );
+
+
+               Rule unordered_list = Rule.seq ({
+                       Rule.seq ({
+                               Valadoc.TokenType.MARKDOWN_UNORDERED_LIST_ITEM_START,
+                               content,
+                               Valadoc.TokenType.MARKDOWN_UNORDERED_LIST_ITEM_END
+                       })
+                       .set_start (() => { push (_factory.create_list_item ()); })
+                       .set_reduce (() => {
+                               var head = (ListItem) pop ();
+                               ((Content.List) peek ()).items.add (head);
+                       })
+               })
+               .set_start (() => {
+                       var siblings = ((BlockContent) peek ()).content;
+                       if (siblings.size > 0 && siblings.last () is Content.List) {
+                               push (siblings.last ());                                
+                       } else {
+                               Content.List list = _factory.create_list ();
+                               list.bullet = Content.List.Bullet.UNORDERED;
+                               siblings.add (list);
+                               push (list);
+                       }
+               })
+               .set_reduce (() => {
+                       (Content.List) pop ();
+               })
+               .set_name ("UnorderedList");
+
+
+               Rule ordered_list = Rule.seq ({
+                       Rule.seq ({
+                               Valadoc.TokenType.MARKDOWN_ORDERED_LIST_ITEM_START,
+                               content,
+                               Valadoc.TokenType.MARKDOWN_ORDERED_LIST_ITEM_END
+                       })
+                       .set_start (() => { push (_factory.create_list_item ()); })
+                       .set_reduce (() => {
+                               var head = (ListItem) pop ();
+                               ((Content.List) peek ()).items.add (head);
+                       })
+               })
+               .set_start (() => {
+                       var siblings = ((BlockContent) peek ()).content;
+                       if (siblings.size > 0 && siblings.last () is Content.List) {
+                               push (siblings.last ());                                
+                       } else {
+                               Content.List list = _factory.create_list ();
+                               list.bullet = Content.List.Bullet.ORDERED_NUMBER;
+                               siblings.add (list);
+                               push (list);
+                       }
+               })
+               .set_reduce (() => {
+                       (Content.List) pop ();
+               })
+               .set_name ("OrderedList");
+
+
+               Rule paragraph = Rule.seq ({
+                       Valadoc.TokenType.MARKDOWN_PARAGRAPH,
+                       Rule.option ({
+                               Valadoc.TokenType.MARKDOWN_SPACE
+                       }),
+                       Rule.option ({
+                               run
+                       })
+               })
+               .set_start (() => { push (_factory.create_paragraph ()); })
+               .set_reduce (() => {
+                       var head = (Paragraph) pop ();
+                       ((BlockContent) peek ()).content.add (head);
+               })
+               .set_name ("Paragraph");
+
+
+               Rule block = Rule.seq ({
+                       Valadoc.TokenType.MARKDOWN_BLOCK_START,
+                       content,
+                       Valadoc.TokenType.MARKDOWN_BLOCK_END
+               })
+               .set_start (() => { push (_factory.create_note ()); })
+               .set_reduce (() => {
+                       var head = (Note) pop ();
+                       ((BlockContent) peek ()).content.add (head);
+               })
+               .set_name ("Block");
+
+
+               Rule headline = Rule.seq ({
+                       Rule.one_of ({
+                               Valadoc.TokenType.MARKDOWN_HEADLINE_1.action ((token) => {
+                                       Headline h = (Headline) peek ();
+                                       h.level = 1;
+                               }),
+                               Valadoc.TokenType.MARKDOWN_HEADLINE_2.action ((token) => {
+                                       Headline h = (Headline) peek ();
+                                       h.level = 2;
+                               })
+                       }),
+                       run,
+                       Rule.option ({
+                               Valadoc.TokenType.MARKDOWN_HEADLINE_HASH.action (() => {
+                                       // TODO
+                               })
+                       }),
+                       Valadoc.TokenType.MARKDOWN_HEADLINE_END
+               })
+               .set_start (() => { push (_factory.create_headline ()); })
+               .set_reduce (() => {
+                       Headline h = (Headline) pop ();
+                       ((BlockContent) peek ()).content.add (h);                       
+               })
+               .set_name ("Headline");
+
+               content.set_rule (
+                       Rule.many ({
+                               Rule.one_of ({
+                                       paragraph,
+                                       unordered_list,
+                                       ordered_list,
+                                       headline,
+                                       block
+                               })
+                       })
+               );
+
+
+               Rule comment = Rule.seq ({
+                       content,
+                       Valadoc.TokenType.MARKDOWN_EOC
+               })
+               .set_start (() => { push (_factory.create_comment ()); })
+               .set_name ("Comment");
+
+               parser.set_root_rule (comment);
+       }
+
+       private Comment? _parse (Api.SourceComment comment) {
+               try {
+                       _stack.clear ();
+                       parser.parse (comment.content, comment.file.get_name (), comment.first_line, 
comment.first_column);
+                       return (Comment) pop ();
+               } catch (ParserError e) {
+                       return null;
+               }
+       }
+
+       private Taglet? _parse_block_taglet (Api.SourceComment comment, string taglet_name) {
+               Comment? cmnt = _parse (comment);
+               if (cmnt == null) {
+                       return null;
+               }
+
+               Taglet? taglet = _factory.create_taglet (taglet_name);
+               BlockContent block = (BlockContent) taglet;
+               assert (taglet != null && block != null);
+
+               block.content.add_all (cmnt.content);
+
+               return taglet;
+       }
+
+       private Note? _parse_note (Api.SourceComment comment) {
+               Comment? cmnt = _parse (comment);
+               if (cmnt == null) {
+                       return null;
+               }
+
+               Note note = _factory.create_note ();
+               note.content.add_all (cmnt.content);
+
+               return note;
+       }
+
+       private void add_taglet (ref Comment? comment, Taglet? taglet) {
+               if (taglet == null) {
+                       return ;
+               }
+
+               if (comment == null) {
+                       comment = _factory.create_comment ();
+               }
+
+               comment.taglets.add (taglet);
+       }
+
+       private void add_note (ref Comment? comment, Note? note) {
+               if (note == null) {
+                       return ;
+               }
+
+               if (comment == null) {
+                       comment = _factory.create_comment ();
+               }
+
+               if (comment.content.size == 0) {
+                       comment.content.add (_factory.create_paragraph ());
+               }
+
+               comment.content.insert (1, note);
+       }
+
+       public Comment? parse (Api.Node element, Api.GirSourceComment gir_comment, GirMetaData metadata, 
string? this_name = null) {
+               this.metadata = metadata;
+               this.gir_comment = gir_comment;
+               this.element = element;
+
+               bool has_instance = false;
+               if (element is Api.Method) {
+                       has_instance = !((Api.Method) element).is_static;
+               }
+
+
+               // main:
+               Comment? cmnt = _parse (gir_comment);
+               if (cmnt != null) {
+                       ImporterHelper.extract_short_desc (cmnt, _factory);
+               }
+
+
+               // deprecated:
+               if (gir_comment.deprecated_comment != null) {
+                       Note? note = _parse_note (gir_comment.deprecated_comment);
+                       add_note (ref cmnt, note);
+               }
+
+
+               // version:
+               if (gir_comment.version_comment != null) {
+                       Note? note = _parse_note (gir_comment.version_comment);
+                       add_note (ref cmnt, note);
+               }
+
+
+               // stability:
+               if (gir_comment.stability_comment != null) {
+                       Note? note = _parse_note (gir_comment.stability_comment);
+                       add_note (ref cmnt, note);
+               }
+
+
+               // return:
+               if (gir_comment.return_comment != null) {
+                       Taglet? taglet = _parse_block_taglet (gir_comment.return_comment, "return");
+                       add_taglet (ref cmnt, taglet);
+               }
+
+
+               // parameters:
+               MapIterator<string, Api.SourceComment> iter = gir_comment.parameter_iterator ();
+               for (bool has_next = iter.next (); has_next; has_next = iter.next ()) {
+                       Taglets.Param? taglet = _parse_block_taglet (iter.get_value (), "param") as 
Taglets.Param;
+                       string param_name = iter.get_key ();
+
+                       if (taglet != null && !(has_instance && param_name == 
gir_comment.instance_param_name)) {
+                               taglet.parameter_name = param_name;
+                               add_taglet (ref cmnt, taglet);
+                       }
+               }
+
+
+               cmnt.check (_tree, element, gir_comment.file.relative_path, _reporter, _settings);
+
+               this.metadata = null;
+               this.gir_comment = null;
+               this.element = element;
+
+               return cmnt;
+       }
+
+
+       private void add_text (Valadoc.Token token) {
+               add_content_string (token.to_string ());
+       }
+
+       private void add_value (Valadoc.Token token) {
+               assert (token.value != null);
+
+               add_content_string (token.value);
+       }
+
+       private void add_content_string (string str) {
+               var text = peek () as Text;
+               if (text == null) {
+                       push (text = _factory.create_text ());
+               }
+
+               text.content += str;
+       }
+
+       private void add_symbol_link (string symbol, bool accept_plural) {
+               var taglet = new Taglets.Link ();
+               taglet.c_accept_plural = accept_plural;
+               taglet.symbol_name = symbol;
+
+               var run = _factory.create_run (Run.Style.NONE);
+               run.content.add (taglet);
+
+               push (run);
+       }
+
+       private void preserve_token (Valadoc.Token token) {
+               assert (preserved_token == null);
+               preserved_token = token;
+       }
+
+       private string pop_preserved_link () {
+               assert (preserved_token != null);
+
+               Valadoc.Token _link_token = (owned) preserved_token;
+
+               // email:
+               if (_link_token.token_type == Valadoc.TokenType.MARKDOWN_MAIL) {
+                       return "mailto:"; + _link_token.value;
+               }
+
+               // http or https link:
+               if (_link_token.value != null) {
+                       return _link_token.value;
+               }
+
+               // GTKDOC-ID:
+               return _link_token.word;
+       }
+
+       private string pop_preserved_path () {
+               assert (preserved_token != null);
+
+               Valadoc.Token _path_token = (owned) preserved_token;
+               return _path_token.word ?? _path_token.value;
+       }
+
+       private inline string run_to_string (Run run, string rule_name) throws Error {
+               StringBuilder builder = new StringBuilder ();
+               inline_to_string (run, rule_name, builder);
+               return (owned) builder.str;
+       }
+
+       private void inline_to_string (Inline element, string rule_name, StringBuilder? builder) throws Error 
{
+               if (element is Run) {
+                       Run run = (Run) element;
+
+                       foreach (Inline item in run.content) {
+                               inline_to_string (item, rule_name, builder);
+                       }
+               } else if (element is Text) {
+                       Text text = (Text) element;
+                       builder.append (text.content);
+               } else if (element is Embedded) {
+                       throw new ContentToStringError.UNEXPECTED_ELEMENT ("Unexpected tag: <image> in `%s'", 
rule_name);
+               } else if (element is Link) {
+                       throw new ContentToStringError.UNEXPECTED_ELEMENT ("Unexpected tag: <link> in `%s'", 
rule_name);
+               } else if (element is SourceCode) {
+                       throw new ContentToStringError.UNEXPECTED_ELEMENT ("Unexpected tag: `|[' in `%s'", 
rule_name);
+               } else {
+                       throw new ContentToStringError.UNEXPECTED_ELEMENT ("Unexpected tag in `%s''", 
rule_name);
+               }
+       }
+
+       private bool is_literal (string str) {
+               if (str == "TRUE") {
+                       return true;
+               }
+
+               if (str == "FALSE") {
+                       return true;
+               }
+
+               if (str == "NULL") {
+                       return true;
+               }
+
+               if (str[0].isdigit ()) {
+                       return true;
+               }
+
+               return true;
+       }
+
+       private bool is_error_parameter (string name) {
+               if (element == null || name != "error") {
+                       return false;
+               }
+
+               if (!(element is Api.Method || element is Api.Delegate)) {
+                       return false;
+               }
+
+               return element.get_children_by_types ({
+                       Api.NodeType.ERROR_DOMAIN,
+                       Api.NodeType.CLASS}).size > 0;
+       }
+
+
+       public string resolve (string path) {
+               return path;
+       }
+
+       private void push (Object element) {
+               _stack.add (element);
+       }
+
+       private Object peek (int offset = -1) {
+               assert (_stack.size >= - offset);
+               return _stack.get (_stack.size + offset);
+       }
+
+       private Object pop () {
+               Object node = peek ();
+               _stack.remove_at (_stack.size - 1);
+               return node;
+       }
+
+       private inline string fix_resource_path (string path) {
+               return metadata.get_resource_path (path);
+       }
+}
+
+
+private errordomain Valadoc.Gtkdoc.ContentToStringError {
+       UNEXPECTED_ELEMENT
+}
+
diff --git a/src/libvaladoc/documentation/gtkdocmarkdownscanner.vala 
b/src/libvaladoc/documentation/gtkdocmarkdownscanner.vala
new file mode 100644
index 0000000..1908957
--- /dev/null
+++ b/src/libvaladoc/documentation/gtkdocmarkdownscanner.vala
@@ -0,0 +1,761 @@
+/* gtkdocmarkdownscanner.vala
+ *
+ * Copyright (C) 2014 Florian Brosch
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+ *
+ * Author:
+ *  Florian Brosch <flo brosch gmail com>
+ */
+
+
+using Valadoc.Content;
+using Valadoc;
+using Gee;
+
+
+
+public class Valadoc.Gtkdoc.MarkdownScanner : GLib.Object, Valadoc.Scanner {
+       private enum State {
+               NORMAL,
+               UNORDERED_LIST,
+               ORDERED_LIST,
+               BLOCK
+       }
+
+       private Settings _settings;
+       private Valadoc.Parser parser;
+
+       private unowned string _content;
+       private int _skip;
+
+       private StringBuilder _current_string = new StringBuilder ();
+       private unowned string _index;
+       private bool contains_at;
+
+       private int _line;
+       private int _column;
+       private int _last_line;
+       private int _last_column;
+       private bool _stop;
+
+       private string? headline_end;
+
+       private Regex regex_mail;
+
+       private LinkedList<State> states = new LinkedList<State> ();
+
+       private inline void push_state (State state) {
+               states.offer_head (state);
+       }
+
+       private inline State pop_state () {
+               return states.poll_head ();
+       }
+
+       private inline State peek_state () {
+               return states.peek_head ();
+       }
+
+
+       public MarkdownScanner (Settings settings) {
+               _settings = settings;
+
+               try {
+                       regex_mail = new Regex ("^[A-Za-z0-9 _-]+ [A-Za-z0-9 _-]+$");
+               } catch (Error e) {
+                       assert_not_reached ();
+               }
+       }
+
+       public void set_parser (Valadoc.Parser parser) {
+               this.parser = parser;
+       }
+
+       public void reset () {
+               _stop = false;
+               _last_line = 0;
+               _last_column = 0;
+               _line = 0;
+               _column = 0;
+               _skip = 0;
+               _current_string.erase (0, -1);
+               contains_at = false;
+               
+               states.clear ();
+               push_state (State.NORMAL);
+       }
+
+       public void scan (string content) throws ParserError {
+               _content = content;
+               _index = _content;
+
+
+               // Accept block taglets:
+               if (handle_newline (_index, true)) {
+                       _index = _index.next_char ();
+               } else {
+                       // Empty string
+                       emit_token (Valadoc.TokenType.MARKDOWN_PARAGRAPH);
+               }
+
+
+               while (!_stop && _index.get_char () != 0) {
+                       unichar c = _index.get_char ();
+                       accept (c);
+                       
+                       _index = _index.next_char ();
+               }
+
+
+               // Close open blocks:
+               while (peek_state () != State.NORMAL) {
+                       if (peek_state () == State.BLOCK) {
+                               emit_token (Valadoc.TokenType.MARKDOWN_BLOCK_END);                      
+                               pop_state ();
+                       } else {
+                               close_block ();
+                       }
+               }
+
+
+               emit_token (Valadoc.TokenType.MARKDOWN_EOC);
+       }
+
+       private void accept (unichar c) throws ParserError {
+               _column++;
+               if (_skip > 0) {
+                       _skip--;
+                       return ;
+               }
+
+               // In headline:
+               string? hash = null;
+               
+               if (headline_end != null && is_headline_end (ref _index, headline_end, out hash)) {
+                       if (hash != null) {
+                               emit_token (Valadoc.TokenType.MARKDOWN_HEADLINE_HASH, hash);
+                       }
+                       emit_token (Valadoc.TokenType.MARKDOWN_HEADLINE_END);
+                       headline_end = null;
+
+                       handle_newline (_index, true);
+
+                       return ;
+               }
+
+               switch (c) {
+               case '\\':
+                       switch (get_next_char ()) {
+                       case '(':
+                               if (get_next_char (2) == ')') {
+                                       _current_string.append ("()");
+                                       _skip += 2;
+                                       break;
+                               }
+
+                               _current_string.append ("\\(");
+                               _skip++;
+                               break;
+
+                       case '<':
+                               append_char ('<');
+                               _skip++;
+                               break;
+
+                       case '>':
+                               append_char ('>');
+                               _skip++;
+                               break;
+
+                       case '@':
+                               append_char ('@');
+                               _skip++;
+                               break;
+
+                       case '%':
+                               append_char ('%');
+                               _skip++;
+                               break;
+
+                       case '#':
+                               append_char ('#');
+                               _skip++;
+                               break;
+
+                       default:
+                               append_char ('\\');
+                               break;
+                       }
+
+                       break;
+
+               case ':':
+                       unichar next_char = get_next_char ();
+                       unichar next2_char = get_next_char (2);
+
+                       // :id or ::id
+                       if ((_current_string.len == 0 || !_current_string.str[_current_string.len - 
1].isalpha ())
+                               && (next_char.isalpha () || (next_char == ':' && next2_char.isalpha ()))) {
+
+                               unowned string _iter;
+                               if (next_char == ':') {
+                                       _iter = _index.offset (2);
+                                       _skip++;
+                               } else {
+                                       _iter = _index.offset (1);
+                               }
+
+                               while (_iter[0].isalnum () || _iter[0] == '_' || (_iter[0] == '-' && 
_iter[1].isalnum ())) {
+                                       _iter = _iter.offset (1);
+                                       _skip++;
+                               }
+
+                               emit_token (Valadoc.TokenType.MARKDOWN_LOCAL_GMEMBER, _index.substring (0, 
_skip + 1));
+                               break;
+                       }
+
+                       append_char (c);
+                       break;
+
+               case '%':
+                       // " %foo", "-%foo" but not " %", "a%foo", ...
+                       if ((_current_string.len == 0 || !_current_string.str[_current_string.len - 
1].isalpha ()) && get_next_char ().isalpha ()) {
+                               unowned string _iter = _index.offset (1);
+
+                               while (_iter[0].isalnum () || _iter[0] == '_') {
+                                       _iter = _iter.offset (1);
+                                       _skip++;
+                               }
+
+                               emit_token (Valadoc.TokenType.MARKDOWN_CONSTANT, _index.substring (1, _skip));
+                               break;
+                       }
+                       // %numeric:
+                       if ((_current_string.len == 0 || !_current_string.str[_current_string.len - 
1].isalpha ()) && get_next_char ().isdigit ()) {
+                               unowned string _iter = _index.offset (1);
+
+                               while (_iter[0].isdigit ()) {
+                                       _iter = _iter.offset (1);
+                                       _skip++;
+                               }
+
+                               // Integers:
+                               if (_iter[0].tolower () == 'u' && _iter[0].tolower () == 'l') {
+                                       _iter = _iter.offset (1);
+                                       _skip += 2;
+
+                                       emit_token (Valadoc.TokenType.MARKDOWN_CONSTANT, _index.substring (1, 
_skip));
+                                       break;
+                               } else if (_iter[0].tolower () == 'u' || _iter[0].tolower () == 'l') {
+                                       _iter = _iter.offset (1);
+                                       _skip++;
+
+                                       emit_token (Valadoc.TokenType.MARKDOWN_CONSTANT, _index.substring (1, 
_skip));
+                                       break;
+                               }
+
+
+                               // Float, double:
+                               if (_iter[0] == '.' && _iter[1].isdigit ()) {
+                                       _iter = _iter.offset (2);
+                                       _skip += 2;
+                               }
+
+                               while (_iter[0].isdigit ()) {
+                                       _iter = _iter.offset (1);
+                                       _skip++;
+                               }
+       
+                               if (_iter[0].tolower () == 'f' || _iter[0].tolower () == 'l') {
+                                       _iter = _iter.offset (1);
+                                       _skip++;
+                               }
+
+                               emit_token (Valadoc.TokenType.MARKDOWN_CONSTANT, _index.substring (1, _skip));
+                               break;
+                       }
+
+                       append_char (c);
+                       break;
+
+               case '#':
+                       // " #foo", "-#foo" but not " #"", "a#""foo", ...
+                       if ((_current_string.len == 0 || !_current_string.str[_current_string.len - 
1].isalpha ()) && get_next_char ().isalpha ()) {
+                               unowned string _iter = _index.offset (1);
+
+                               while (_iter[0].isalnum () || _iter[0] == '_') {
+                                       _iter = _iter.offset (1);
+                                       _skip++;
+                               }
+
+                               // signals, fields, properties
+                               bool is_field = false;
+                               if (((_iter[0] == ':' || _iter[0] == '.') && _iter[1].isalpha ())
+                                       || (_iter.has_prefix ("::") && _iter[2].isalpha ())) {
+
+                                       is_field = (_iter[0] == '.');
+                                       _iter = _iter.offset (2);
+                                       _skip += 2;
+
+                                       while (_iter[0].isalnum () || _iter[0] == '_' || (!is_field && 
_iter[0] == '-')) {
+                                               _iter = _iter.offset (1);
+                                               _skip++;
+                                       }
+                               }
+
+                               if (is_field && _iter.has_prefix ("()")) {
+                                       _skip += 2;
+
+                                       emit_token (Valadoc.TokenType.MARKDOWN_SYMBOL, _index.substring (1, 
_skip - 2));
+                               } else {
+                                       emit_token (Valadoc.TokenType.MARKDOWN_SYMBOL, _index.substring (1, 
_skip));
+                               }
+
+                               break;
+                       }
+
+                       append_char (c);
+                       break;
+
+               case '@':
+                       // " @foo", "- foo" but not " @", "a foo", ...
+                       if ((_current_string.len == 0 || !_current_string.str[_current_string.len - 
1].isalpha ())) {
+                               if (get_next_char ().isalpha ()) {
+                                       unowned string _iter = _index.offset (1);
+
+                                       while (_iter[0].isalnum () || _iter[0] == '_') {
+                                               _iter = _iter.offset (1);
+                                               _skip++;
+                                       }
+
+                                       emit_token (Valadoc.TokenType.MARKDOWN_PARAMETER, _index.substring 
(1, _skip));
+                                       break;
+                               } else if (_index.has_prefix ("@...")) {
+                                       _skip += 3;
+                                       emit_token (Valadoc.TokenType.MARKDOWN_PARAMETER, "...");
+                                       break;
+                               }
+                       }
+
+                       append_char (c);
+                       contains_at = true;
+                       break;
+
+               case '(':
+                       if (get_next_char () == ')' && is_id ()) {
+                               string id = _current_string.str;
+                               _current_string.erase (0, -1);
+                               contains_at = false;
+
+                               emit_token (Valadoc.TokenType.MARKDOWN_FUNCTION, id);
+                               _skip++;
+                               break;
+                       }
+
+                       emit_token (Valadoc.TokenType.MARKDOWN_OPEN_PARENS);
+                       break;
+
+               case ')':
+                       emit_token (Valadoc.TokenType.MARKDOWN_CLOSE_PARENS);
+                       break;
+
+               case '[':
+                       unowned string iter = _index;
+                       int count = 1;
+                       while (iter[0] != '\n' && iter[0] != '\0' && count > 0) {
+                               iter = iter.offset (1);
+                               switch (iter[0]) {
+                               case '[':
+                                       count++;
+                                       break;
+
+                               case ']':
+                                       count--;
+                                       break;
+                               }
+                       }
+
+                       if (iter[0] == ']') {
+                               emit_token (Valadoc.TokenType.MARKDOWN_OPEN_BRACKET);
+                       } else {
+                               append_char ('[');
+                       }
+                       break;
+
+               case ']':
+                       emit_token (Valadoc.TokenType.MARKDOWN_CLOSE_BRACKET);
+                       break;
+
+               case '<':
+                       emit_token (Valadoc.TokenType.MARKDOWN_LESS_THAN);
+                       break;
+
+               case '>':
+                       emit_token (Valadoc.TokenType.MARKDOWN_GREATER_THAN);
+                       break;
+
+               case '!':
+                       emit_token (Valadoc.TokenType.MARKDOWN_EXCLAMATION_MARK);
+                       break;
+
+               case '|':
+                       if (get_next_char () == '[') {
+                               unowned string _iter = _index.offset (2);
+                               int end = _iter.index_of ("]|");
+                               if (end < 0) {
+                                       append_char ('|');
+                               } else {
+                                       emit_token (Valadoc.TokenType.MARKDOWN_SOURCE, _index.substring (2, 
end));
+                                       _skip = end + 3;
+                               }
+
+                               break;
+                       }
+
+                       append_char (c);
+                       break;
+
+               case '\t':
+               case ' ':
+                       unowned string _iter = _index.offset (1);
+                       _skip += skip_spaces (ref _iter);
+
+                       if (_iter[0] != '\n' && _iter[0] != '\0') {
+                               emit_token (Valadoc.TokenType.MARKDOWN_SPACE);
+                       }
+                       break;
+
+               case '\r':
+                       // Ignore
+                       break;
+
+               case '\n':
+                       unowned string _iter = _index.offset (1);
+
+                       _line++;
+                       _column = 0;
+                       _last_column = 0;
+                       handle_newline (_iter, false);
+                       break;
+
+               default:
+                       append_char (c);
+                       break;
+               }
+       }
+
+       private bool handle_newline (string _iter, bool is_paragraph) throws ParserError {
+               int leading_spaces;
+
+               leading_spaces = skip_spaces (ref _iter);
+
+               if (_iter[0] == '\0') {
+                       return false;
+               }
+
+               // Do not emit paragraphs twice:
+               if (is_paragraph) {
+                       while (_iter[0] == '\n') {
+                               _line++;
+                               _iter = _iter.offset (1);
+                               leading_spaces = skip_spaces (ref _iter);
+                       }
+               }
+
+               bool in_block = states.contains (State.BLOCK);
+               if (_iter[0] == '>') {
+                       if (!in_block) {
+                               close_block ();
+
+                               if (is_paragraph) {
+                                       _column += (int) ((char*) _iter - (char*) _index);
+                                       _index = _iter.offset (1);
+                                       emit_token (Valadoc.TokenType.MARKDOWN_BLOCK_START);
+                                       push_state (State.BLOCK);
+                               }
+                       }
+
+                       if (in_block || is_paragraph) {
+                               _column++;
+                               _index = _iter;
+
+                               _iter = _iter.offset (1);
+                               skip_spaces (ref _iter);
+                       }
+               } else if (in_block && is_paragraph) {
+                       _column += (int) ((char*) _iter - (char*) _index);
+                       _index = _iter;
+
+                       close_block ();
+
+                       emit_token (Valadoc.TokenType.MARKDOWN_BLOCK_END);
+                       pop_state ();
+               }
+
+
+               int list_token_len = 0;
+               bool is_unsorted_list = _iter[0] == '-' && _iter[1].isspace ();
+               bool is_sorted_list = is_ordered_list (_iter, out list_token_len);
+               if ((is_unsorted_list || is_sorted_list)  && (is_paragraph || states.contains 
(State.UNORDERED_LIST) || states.contains (State.ORDERED_LIST))) {
+                       Valadoc.TokenType start_token = Valadoc.TokenType.MARKDOWN_ORDERED_LIST_ITEM_START;
+                       State new_state = State.ORDERED_LIST;
+
+                       if (is_unsorted_list) {
+                               start_token = Valadoc.TokenType.MARKDOWN_UNORDERED_LIST_ITEM_START;
+                               new_state = State.UNORDERED_LIST;
+                               list_token_len = 2;
+                       }
+
+
+                       _iter = _iter.offset (list_token_len);
+                       close_block ();
+
+                       skip_spaces (ref _iter);
+
+                       _column += (int) ((char*) _iter - (char*) _index);
+                       _index = _iter.offset (-1);
+                       emit_token (start_token);
+                       push_state (new_state);
+
+                       emit_token (Valadoc.TokenType.MARKDOWN_PARAGRAPH);
+                       return true;
+               }
+
+               if ((_iter[0] == '#' && _iter[1].isspace ()) || (_iter[0] == '#' && _iter[1] == '#' && 
_iter[2].isspace ()) && is_paragraph) {
+                       close_block ();
+
+                       if (_iter[1] != '#') {
+                               _iter = _iter.offset (1);
+                               emit_token (Valadoc.TokenType.MARKDOWN_HEADLINE_1);
+                               headline_end = "#";
+                       } else {
+                               emit_token (Valadoc.TokenType.MARKDOWN_HEADLINE_2);
+                               _iter = _iter.offset (2);       
+                               headline_end = "##";
+                       }
+
+                       _column += (int) ((char*) _iter - (char*) _index);
+                       _index = _iter.offset (-1);
+
+                       return true;
+               }
+
+               if (is_paragraph) {
+                       if (leading_spaces == 0) {
+                               close_block ();
+                       }
+
+                       _column += (int) ((char*) _iter - (char*) _index);
+                       _index = _iter.offset (-1);
+                       emit_token (Valadoc.TokenType.MARKDOWN_PARAGRAPH);
+               } else if (_iter[0] == '\n') {
+                       _line++;
+                       _column = 0;
+                       _last_column = 0;
+
+                       handle_newline (_iter.offset (1), true);
+               } else {
+                       emit_token (Valadoc.TokenType.MARKDOWN_SPACE);
+               }
+
+               return true;
+       }
+
+       private bool is_headline_end (ref unowned string iter, string separator, out string? hash) {
+               unowned string _iter = iter;
+               hash = null;
+
+
+               if (_iter[0] == '\n' || _iter[0] == '\0') {
+                       _line++;
+                       return true;
+               }
+               if (!_iter.has_prefix (separator)) {
+                       return false;
+               }
+
+               _iter = _iter.offset (separator.length);
+
+
+               skip_spaces (ref _iter);
+               if (_iter[0] == '\n' || _iter[0] == '\0') {
+                       iter = _iter;
+                       _line++;
+                       return true;
+               } else if (!_iter.has_prefix ("{#")) {
+                       return false;
+               }
+               _iter = _iter.offset (2);
+
+               unowned string id_start = _iter;
+               int hash_len = 0;
+               
+               while (_iter[0] != '}' && _iter[0] != '\n' && _iter[0] != '\0') {
+                       _iter = _iter.offset (1);
+                       hash_len++;
+               }
+
+               if (_iter[0] != '}') {
+                       return false;
+               }
+
+               _iter = _iter.offset (1);
+
+               skip_spaces (ref _iter);
+
+               if (_iter[0] == '\n' || _iter[0] == '\0') {
+                       hash = id_start.substring (0, hash_len);
+                       iter = _iter;
+                       _line++;
+                       return true;
+               }
+
+               return false;
+       }
+
+       private bool is_ordered_list (string iter, out int numeric_prefix_count) {
+               numeric_prefix_count = 0;
+               while (iter[0] >= '0' && iter[0] <= '9') {
+                       numeric_prefix_count++;
+                       iter = iter.offset (1);
+               }
+
+               if (numeric_prefix_count > 0 && iter[0] == '.' && iter[1].isspace ()) {
+                       numeric_prefix_count++;
+                       return true;
+               }
+
+               return false;
+       }
+
+       private inline int skip_spaces (ref unowned string _iter) {
+               int count = 0;
+               while (_iter[0] == ' ' || _iter[0] == '\t' || _iter[0] == '\r') {
+                       _iter = _iter.offset (1);
+                       count++;
+               }
+
+               return count;
+       }
+
+       private bool close_block () throws ParserError {
+               if (states.peek () == State.UNORDERED_LIST) {
+                       emit_token (Valadoc.TokenType.MARKDOWN_UNORDERED_LIST_ITEM_END);
+                       pop_state ();
+                       return true;
+               } else if (states.peek () == State.ORDERED_LIST) {
+                       emit_token (Valadoc.TokenType.MARKDOWN_ORDERED_LIST_ITEM_END);
+                       pop_state ();
+                       return true;
+               }
+
+               return false;
+       }
+
+       public void end () throws ParserError {
+               emit_token (Valadoc.TokenType.EOF);
+       }
+
+       public void stop () {
+               _stop = true;
+       }
+
+       public string get_line_content () {
+               StringBuilder builder = new StringBuilder ();
+               weak string line_start = _index;
+               unichar c;
+
+               while ((char*) line_start > (char*) _content && line_start.prev_char ().get_char () != '\n') {
+                       line_start = line_start.prev_char ();
+               }
+
+               while ((c = line_start.get_char ()) != '\n' && c != '\0') {
+                       if (c == '\t') {
+                               builder.append_c (' ');
+                       } else {
+                               builder.append_unichar (c);
+                       }
+                       line_start = line_start.next_char ();
+               }
+
+               return builder.str;
+       }
+
+       private void emit_token (Valadoc.TokenType type, string? value = null) throws ParserError {
+               emit_current_word ();
+
+               parser.accept_token (new Valadoc.Token.from_type (type, get_begin (), get_end (_skip), 
value));
+       }
+
+       private void emit_current_word () throws ParserError {
+               if (_current_string.len > 0) {
+                       if (is_mail ()) {
+                               parser.accept_token (new Valadoc.Token.from_type 
(Valadoc.TokenType.MARKDOWN_MAIL, get_begin (), get_end (_skip), _current_string.str));
+                       } else if (_current_string.str.has_prefix ("http://";) || 
_current_string.str.has_prefix ("https://";)) {
+                               // TODO: (https?:[\/]{2}[^\s]+?)
+                               parser.accept_token (new Valadoc.Token.from_type 
(Valadoc.TokenType.MARKDOWN_LINK, get_begin (), get_end (_skip), _current_string.str));
+                       } else {
+                               parser.accept_token (new Valadoc.Token.from_word (_current_string.str, 
get_begin (), get_end (-1)));
+                       }
+
+                       _current_string.erase (0, -1);
+                       contains_at = false;
+               }
+       }
+
+       private SourceLocation get_begin () {
+               return SourceLocation (_last_line, get_line_start_column () + _last_column);
+       }
+
+       private SourceLocation get_end (int offset = 0) {
+               return SourceLocation (_line, get_line_start_column () + _column + offset);
+       }
+
+       public int get_line_start_column () {
+               return 0;
+       }
+
+       private void append_char (unichar c) {
+               _current_string.append_unichar (c);
+       }
+
+       private unichar get_next_char (int offset = 1) {
+               return _index.get_char (_index.index_of_nth_char (offset));
+       }
+
+       private inline bool is_mail () {
+               return contains_at && regex_mail.match (_current_string.str);
+       }
+
+       private bool is_id () {
+               if (_current_string.len == 0) {
+                       return false;
+               }
+
+               if (_current_string.str[0].isalpha () == false && _current_string.str[0] != '_') {
+                       return false;
+               }
+
+               for (int i = 1; i < _current_string.len ; i++) {
+                       if (_current_string.str[i].isalnum () == false && _current_string.str[i] != '_') {
+                               return false;
+                       }
+               }
+
+               return true;
+       }
+}
+
diff --git a/src/libvaladoc/documentation/importerhelper.vala 
b/src/libvaladoc/documentation/importerhelper.vala
new file mode 100644
index 0000000..36ed06d
--- /dev/null
+++ b/src/libvaladoc/documentation/importerhelper.vala
@@ -0,0 +1,156 @@
+/* importhelper.vala
+ *
+ * Copyright (C) 2014 Florian Brosch
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
+ *
+ * Author:
+ *  Florian Brosch <flo brosch gmail com>
+ */
+
+
+using Valadoc.Content;
+using Gee;
+
+
+namespace Valadoc.ImporterHelper {
+
+
+       internal void extract_short_desc (Comment comment, ContentFactory factory) {
+               if (comment.content.size == 0) {
+                       return ;
+               }
+
+               Paragraph? first_paragraph = comment.content[0] as Paragraph;
+               if (first_paragraph == null) {
+                       // add empty paragraph to avoid non-text as short descriptions
+                       comment.content.insert (1, factory.create_paragraph ());
+                       return ;
+               }
+
+
+               // avoid fancy stuff in short descriptions:
+               first_paragraph.horizontal_align = null;
+               first_paragraph.vertical_align = null;
+               first_paragraph.style = null;
+
+
+               Paragraph? second_paragraph = split_paragraph (first_paragraph, factory);
+               if (second_paragraph == null) {
+                       return ;
+               }
+
+               if (second_paragraph.is_empty () == false) {
+                       comment.content.insert (1, second_paragraph);
+               }
+       }
+
+       private inline Text? split_text (Text text, ContentFactory factory) {
+               int offset = 0;
+               while ((offset = text.content.index_of_char ('.', offset)) >= 0) {
+                       if (offset >= 2) {
+                               // ignore "e.g."
+                               unowned string cmp4 = ((string) (((char*) text.content) + offset - 2));
+                               if (cmp4.has_prefix (" e.g.") || cmp4.has_prefix ("(e.g.")) {
+                                       offset = offset + 3;
+                                       continue;
+                               }
+
+                               // ignore "i.e."
+                               if (cmp4.has_prefix (" i.e.") || cmp4.has_prefix ("(i.e.")) {
+                                       offset = offset + 3;
+                                       continue;
+                               }
+                       }
+
+                       unowned string cmp0 = ((string) (((char*) text.content) + offset));
+
+                       // ignore ... (varargs)
+                       if (cmp0.has_prefix ("...")) {
+                               offset = offset + 3;
+                               continue;
+                       }
+
+                       // ignore numbers
+                       if (cmp0.get (1).isdigit ()) {
+                               offset = offset + 2;
+                               continue;
+                       }
+
+
+                       Text sec = factory.create_text (text.content.substring (offset+1, -1));
+                       text.content = text.content.substring (0, offset+1);
+                       return sec;
+               }
+
+               return null;
+       }
+
+       private inline Run? split_run (Run run, ContentFactory factory) {
+               Run? sec = null;
+
+               Iterator<Inline> iter = run.content.iterator ();
+               for (bool has_next = iter.next (); has_next; has_next = iter.next ()) {
+                       Inline item = iter.get ();
+                       if (sec == null) {
+                               Inline? tmp = split_inline (item, factory);
+                               if (tmp != null) {
+                                       sec = factory.create_run (run.style);
+                                       sec.content.add (tmp);
+                               }
+                       } else {
+                               sec.content.add (item);
+                               iter.remove ();
+                       }
+               }
+
+               return sec;
+       }
+
+       private inline Inline? split_inline (Inline item, ContentFactory factory) {
+               if (item is Text) {
+                       return split_text ((Text) item, factory);
+               } else if (item is Run) {
+                       return split_run ((Run) item, factory);
+               }
+
+               return null;
+       }
+
+       private inline Paragraph? split_paragraph (Paragraph p, ContentFactory factory) {
+               Paragraph? sec = null;
+
+               Iterator<Inline> iter = p.content.iterator ();
+               for (bool has_next = iter.next (); has_next; has_next = iter.next ()) {
+                       Inline item = iter.get ();
+                       if (sec == null) {
+                               Inline? tmp = split_inline (item, factory);
+                               if (tmp != null) {
+                                       sec = factory.create_paragraph ();
+                                       sec.horizontal_align = p.horizontal_align;
+                                       sec.vertical_align = p.vertical_align;
+                                       sec.style = p.style;
+                                       sec.content.add (tmp);
+                               }
+                       } else {
+                               sec.content.add (item);
+                               iter.remove ();
+                       }
+               }
+
+               return sec;
+       }
+
+}
diff --git a/src/libvaladoc/importer/girdocumentationimporter.vala 
b/src/libvaladoc/importer/girdocumentationimporter.vala
index 1988b96..45bbf09 100644
--- a/src/libvaladoc/importer/girdocumentationimporter.vala
+++ b/src/libvaladoc/importer/girdocumentationimporter.vala
@@ -317,31 +317,67 @@ public class Valadoc.Importer.GirDocumentationImporter : DocumentationImporter {
        }
 
        private Api.GirSourceComment? parse_symbol_doc () {
-               if (reader.name != "doc") {
-                       return null;
+               Api.GirSourceComment? comment = null;
+
+               if (reader.name == "doc") {
+                       start_element ("doc");
+                       next ();
+
+
+                       if (current_token == MarkupTokenType.TEXT) {
+                               comment = new Api.GirSourceComment (reader.content, file, begin.line,
+                                                                                                       
begin.column, end.line, end.column);
+                               next ();
+                       }
+
+                       end_element ("doc");
                }
 
-               start_element ("doc");
-               next ();
+               while (true) {
+                       if (reader.name == "doc-deprecated") {
+                               Api.SourceComment? doc_deprecated = parse_doc ("doc-deprecated");
+                               if (doc_deprecated != null) {
+                                       if (comment == null) {
+                                               comment = new Api.GirSourceComment ("", file, begin.line, 
end.line,
+                                                                                                             
          begin.line, end.line);
+                                       }
 
-               Api.GirSourceComment? comment = null;
+                                       comment.deprecated_comment = doc_deprecated;
+                               }
+                       } else if (reader.name == "doc-version") {
+                               Api.SourceComment? doc_version = parse_doc ("doc-version");
+                               if (doc_version != null) {
+                                       if (comment == null) {
+                                               comment = new Api.GirSourceComment ("", file, begin.line, 
end.line,
+                                                                                                             
          begin.line, end.line);
+                                       }
 
-               if (current_token == MarkupTokenType.TEXT) {
-                       comment = new Api.GirSourceComment (reader.content, file, begin.line,
-                                                                                               begin.column, 
end.line, end.column);
-                       next ();
+                                       comment.version_comment = doc_version;
+                               }
+                       } else if (reader.name == "doc-stability") {
+                               Api.SourceComment? doc_stability = parse_doc ("doc-stability");
+                               if (doc_stability != null) {
+                                       if (comment == null) {
+                                               comment = new Api.GirSourceComment ("", file, begin.line, 
end.line,
+                                                                                                             
          begin.line, end.line);
+                                       }
+
+                                       comment.stability_comment = doc_stability;
+                               }
+                       } else {
+                               break;
+                       }
                }
 
-               end_element ("doc");
                return comment;
        }
 
-       private Api.SourceComment? parse_doc () {
-               if (reader.name != "doc") {
+       private Api.SourceComment? parse_doc (string element_name = "doc") {
+               if (reader.name != element_name) {
                        return null;
                }
 
-               start_element ("doc");
+               start_element (element_name);
                next ();
 
                Api.SourceComment? comment = null;
@@ -352,7 +388,7 @@ public class Valadoc.Importer.GirDocumentationImporter : DocumentationImporter {
                        next ();
                }
 
-               end_element ("doc");
+               end_element (element_name);
                return comment;
        }
 
diff --git a/src/libvaladoc/parser/token.vala b/src/libvaladoc/parser/token.vala
index ced9ab3..6cbff5e 100644
--- a/src/libvaladoc/parser/token.vala
+++ b/src/libvaladoc/parser/token.vala
@@ -24,10 +24,11 @@ using Gee;
 
 public class Valadoc.Token : Object {
 
-       public Token.from_type (TokenType type, SourceLocation begin, SourceLocation end) {
+       public Token.from_type (TokenType type, SourceLocation begin, SourceLocation end, string? val = null) 
{
                _type = type;
                _begin = begin;
                _end = end;
+               _value = val;
        }
 
        public Token.from_word (string word, SourceLocation begin, SourceLocation end) {
@@ -40,6 +41,7 @@ public class Valadoc.Token : Object {
        private string? _word = null;
        private SourceLocation _begin;
        private SourceLocation _end;
+       private string? _value;
 
        public bool is_word {
                get {
@@ -69,6 +71,12 @@ public class Valadoc.Token : Object {
                }
        }
 
+       public string? value {
+               get {
+                       return _value;
+               }
+       }
+
        public TokenType? token_type {
                get {
                        return _type;
diff --git a/src/libvaladoc/parser/tokentype.vala b/src/libvaladoc/parser/tokentype.vala
index ea61955..3cad35e 100644
--- a/src/libvaladoc/parser/tokentype.vala
+++ b/src/libvaladoc/parser/tokentype.vala
@@ -69,6 +69,39 @@ public class Valadoc.TokenType : Object {
        public static TokenType VALADOC_TAB;
        public static TokenType VALADOC_EOL;
 
+
+       // markdown:
+       public static TokenType MARKDOWN_PARAGRAPH;
+       public static TokenType MARKDOWN_ANY_WORD;
+       public static TokenType MARKDOWN_SPACE;
+       public static TokenType MARKDOWN_SOURCE;
+       public static TokenType MARKDOWN_PARAMETER;
+       public static TokenType MARKDOWN_CONSTANT;
+       public static TokenType MARKDOWN_SYMBOL;
+       public static TokenType MARKDOWN_LOCAL_GMEMBER;
+       public static TokenType MARKDOWN_FUNCTION;
+       public static TokenType MARKDOWN_MAIL;
+       public static TokenType MARKDOWN_LINK;
+       public static TokenType MARKDOWN_GREATER_THAN;
+       public static TokenType MARKDOWN_LESS_THAN;
+       public static TokenType MARKDOWN_OPEN_BRACKET;
+       public static TokenType MARKDOWN_CLOSE_BRACKET;
+       public static TokenType MARKDOWN_OPEN_PARENS;
+       public static TokenType MARKDOWN_CLOSE_PARENS;
+       public static TokenType MARKDOWN_EXCLAMATION_MARK;
+       public static TokenType MARKDOWN_HEADLINE_1;
+       public static TokenType MARKDOWN_HEADLINE_2;
+       public static TokenType MARKDOWN_HEADLINE_HASH;
+       public static TokenType MARKDOWN_HEADLINE_END;
+       public static TokenType MARKDOWN_UNORDERED_LIST_ITEM_START;
+       public static TokenType MARKDOWN_UNORDERED_LIST_ITEM_END;
+       public static TokenType MARKDOWN_ORDERED_LIST_ITEM_START;
+       public static TokenType MARKDOWN_ORDERED_LIST_ITEM_END;
+       public static TokenType MARKDOWN_BLOCK_START;
+       public static TokenType MARKDOWN_BLOCK_END;
+       public static TokenType MARKDOWN_EOC;
+
+
        private static bool initialized = false;
 
        internal static void init_token_types () {
@@ -119,6 +152,41 @@ public class Valadoc.TokenType : Object {
                        VALADOC_EOL = EOL;
 
                        initialized = true;
+
+
+                       // Markdown: (importer)
+                       MARKDOWN_PARAGRAPH = new TokenType.basic ("<paragraph>");
+                       MARKDOWN_BLOCK_START = new TokenType.basic ("<block>");
+                       MARKDOWN_BLOCK_END = new TokenType.basic ("</block>");
+                       MARKDOWN_UNORDERED_LIST_ITEM_START = new TokenType.basic ("<unordered-list>");
+                       MARKDOWN_UNORDERED_LIST_ITEM_END = new TokenType.basic ("</unordered-list>");
+                       MARKDOWN_ORDERED_LIST_ITEM_START = new TokenType.basic ("<ordered-list>");
+                       MARKDOWN_ORDERED_LIST_ITEM_END  = new TokenType.basic ("</ordered-list>");
+
+                       MARKDOWN_HEADLINE_1 = new TokenType.basic ("<headline-1>");
+                       MARKDOWN_HEADLINE_2 = new TokenType.basic ("<headline-2>");
+                       MARKDOWN_HEADLINE_HASH = new TokenType.basic ("<hash>");
+                       MARKDOWN_HEADLINE_END = new TokenType.basic ("</headline>");
+                       MARKDOWN_SOURCE = new TokenType.basic ("<source>");
+                       MARKDOWN_PARAMETER = new TokenType.basic ("<parameter>");
+                       MARKDOWN_CONSTANT = new TokenType.basic ("<constant>");
+                       MARKDOWN_FUNCTION = new TokenType.basic ("<function>");
+                       MARKDOWN_SYMBOL = new TokenType.basic ("<symbol>");
+                       MARKDOWN_LOCAL_GMEMBER = new TokenType.basic ("<local-gmember>");
+                       MARKDOWN_MAIL = new TokenType.basic ("<mail>");
+                       MARKDOWN_LINK = new TokenType.basic ("<link>");
+
+                       MARKDOWN_OPEN_BRACKET = new TokenType.basic ("[");
+                       MARKDOWN_CLOSE_BRACKET = new TokenType.basic ("]");
+                       MARKDOWN_OPEN_PARENS = new TokenType.basic ("(");
+                       MARKDOWN_CLOSE_PARENS = new TokenType.basic (")");
+                       MARKDOWN_EXCLAMATION_MARK = new TokenType.basic ("!");
+                       MARKDOWN_GREATER_THAN = GREATER_THAN;
+                       MARKDOWN_LESS_THAN = LESS_THAN;
+
+                       MARKDOWN_ANY_WORD = ANY_WORD;
+                       MARKDOWN_SPACE = SPACE;
+                       MARKDOWN_EOC = EOL;
                }
        }
 
diff --git a/src/libvaladoc/taglets/tagletdeprecated.vala b/src/libvaladoc/taglets/tagletdeprecated.vala
index 16e71af..c10a3c6 100644
--- a/src/libvaladoc/taglets/tagletdeprecated.vala
+++ b/src/libvaladoc/taglets/tagletdeprecated.vala
@@ -24,7 +24,8 @@
 using Gee;
 using Valadoc.Content;
 
-public class Valadoc.Taglets.Deprecated : InlineContent, Taglet, Block {
+
+public class Valadoc.Taglets.Deprecated : BlockContent, Taglet, Block {
        public Rule? get_parser_rule (Rule run_rule) {
                return run_rule;
        }
@@ -49,8 +50,8 @@ public class Valadoc.Taglets.Deprecated : InlineContent, Taglet, Block {
                Deprecated deprecated = new Deprecated ();
                deprecated.parent = new_parent;
 
-               foreach (Inline element in content) {
-                       Inline copy = element.copy (deprecated) as Inline;
+               foreach (Block element in content) {
+                       Block copy = element.copy (deprecated) as Block;
                        deprecated.content.add (copy);
                }
 
diff --git a/src/libvaladoc/taglets/tagletlink.vala b/src/libvaladoc/taglets/tagletlink.vala
index 89ab8c3..72ed12a 100644
--- a/src/libvaladoc/taglets/tagletlink.vala
+++ b/src/libvaladoc/taglets/tagletlink.vala
@@ -28,6 +28,19 @@ using Valadoc.Content;
 public class Valadoc.Taglets.Link : InlineTaglet {
        public string symbol_name { internal set; get; }
 
+       /**
+        * Accept leading 's', e.g. #Widgets
+        */
+       public bool c_accept_plural { internal set; get; }
+
+       /**
+        * True if symbol_name could only be resolved after removing 's'
+        *
+        * E.g. true or #Widgets, false for #Widget
+        */
+       public bool c_is_plural { private set; get; }
+
+
        private enum SymbolContext {
                NORMAL,
                FINISH,
@@ -54,9 +67,18 @@ public class Valadoc.Taglets.Link : InlineTaglet {
 
        public override void check (Api.Tree api_root, Api.Node container, string file_path,
                                                                ErrorReporter reporter, Settings settings) {
+
                if (symbol_name.has_prefix ("c::")) {
                        _symbol_name = _symbol_name.substring (3);
+                       string? singular_symbol_name = (c_accept_plural && _symbol_name.has_suffix ("s"))
+                               ? symbol_name.substring (0, _symbol_name.length - 1)
+                               : null;
+
                        _symbol = api_root.search_symbol_cstr (container, symbol_name);
+                       if (_symbol == null && singular_symbol_name != null) {
+                               _symbol = api_root.search_symbol_cstr (container, singular_symbol_name);
+                               c_is_plural = true;
+                       }
                        _context = SymbolContext.NORMAL;
 
                        if (_symbol == null && _symbol_name.has_suffix ("_finish")) {
@@ -77,6 +99,10 @@ public class Valadoc.Taglets.Link : InlineTaglet {
 
                        if (_symbol == null) {
                                _symbol = api_root.search_symbol_type_cstr (symbol_name);
+                               if (_symbol == null && singular_symbol_name != null) {
+                                       _symbol = api_root.search_symbol_type_cstr (singular_symbol_name);
+                                       c_is_plural = true;
+                               }
                                if (_symbol != null) {
                                        _context = SymbolContext.TYPE;
                                }
@@ -84,10 +110,6 @@ public class Valadoc.Taglets.Link : InlineTaglet {
 
                        if (_symbol != null) {
                                symbol_name = _symbol.name;
-
-                               if (_context == SymbolContext.FINISH) {
-                                       symbol_name = symbol_name + ".end";
-                               }
                        }
                } else {
                        _symbol = api_root.search_symbol_str (container, symbol_name);
@@ -107,27 +129,39 @@ public class Valadoc.Taglets.Link : InlineTaglet {
                link.symbol = _symbol;
                link.label = symbol_name;
 
-               // TODO: move typeof () to gtkdoc-importer
+               Content.Inline content;
                switch (_context) {
                case SymbolContext.FINISH:
-                       // covered by symbol_name
-                       return link;
+                       link.label += ".end";
+                       content = link;
+                       break;
 
                case SymbolContext.TYPE:
-                       Content.Run content = new Content.Run (Run.Style.MONOSPACED);
+                       Run run = new Content.Run (Run.Style.MONOSPACED);
+                       content = run;
 
                        Content.Run keyword = new Content.Run (Run.Style.LANG_KEYWORD);
                        keyword.content.add (new Content.Text ("typeof"));
-                       content.content.add (keyword);
+                       run.content.add (keyword);
 
-                       content.content.add (new Content.Text (" ("));
-                       content.content.add (link);
-                       content.content.add (new Content.Text (")"));
-                       return content;
+                       run.content.add (new Content.Text (" ("));
+                       run.content.add (link);
+                       run.content.add (new Content.Text (")"));
+                       break;
 
                default:
-                       return link;
+                       content = link;
+                       break;
                }
+
+               if (c_is_plural == true) {
+                       Run run = new Content.Run (Run.Style.NONE);
+                       run.content.add (content);
+                       run.content.add (new Content.Text ("s"));
+                       return run;
+               }
+
+               return content;
        }
 
        public override bool is_empty () {
diff --git a/src/libvaladoc/taglets/tagletparam.vala b/src/libvaladoc/taglets/tagletparam.vala
index b2a42be..94c602a 100644
--- a/src/libvaladoc/taglets/tagletparam.vala
+++ b/src/libvaladoc/taglets/tagletparam.vala
@@ -25,7 +25,7 @@ using Valadoc.Content;
 using Gee;
 
 
-public class Valadoc.Taglets.Param : InlineContent, Taglet, Block {
+public class Valadoc.Taglets.Param : BlockContent, Taglet, Block {
        public string parameter_name { internal set; get; }
 
        public weak Api.Symbol? parameter { private set; get; }
@@ -141,8 +141,8 @@ public class Valadoc.Taglets.Param : InlineContent, Taglet, Block {
                param.parameter = parameter;
                param.position = position;
 
-               foreach (Inline element in content) {
-                       Inline copy = element.copy (param) as Inline;
+               foreach (Block element in content) {
+                       Block copy = element.copy (param) as Block;
                        param.content.add (copy);
                }
 
diff --git a/src/libvaladoc/taglets/tagletreturn.vala b/src/libvaladoc/taglets/tagletreturn.vala
index 71b5836..50d3adb 100644
--- a/src/libvaladoc/taglets/tagletreturn.vala
+++ b/src/libvaladoc/taglets/tagletreturn.vala
@@ -25,7 +25,7 @@ using Gee;
 using Valadoc.Content;
 
 
-public class Valadoc.Taglets.Return : InlineContent, Taglet, Block {
+public class Valadoc.Taglets.Return : BlockContent, Taglet, Block {
        public Rule? get_parser_rule (Rule run_rule) {
                return run_rule;
        }
@@ -69,8 +69,8 @@ public class Valadoc.Taglets.Return : InlineContent, Taglet, Block {
                Return ret = new Return ();
                ret.parent = new_parent;
 
-               foreach (Inline element in content) {
-                       Inline copy = element.copy (ret) as Inline;
+               foreach (Block element in content) {
+                       Block copy = element.copy (ret) as Block;
                        ret.content.add (copy);
                }
 
diff --git a/src/libvaladoc/taglets/tagletthrows.vala b/src/libvaladoc/taglets/tagletthrows.vala
index daacd73..cfbb098 100644
--- a/src/libvaladoc/taglets/tagletthrows.vala
+++ b/src/libvaladoc/taglets/tagletthrows.vala
@@ -24,7 +24,8 @@
 using Gee;
 using Valadoc.Content;
 
-public class Valadoc.Taglets.Throws : InlineContent, Taglet, Block {
+
+public class Valadoc.Taglets.Throws : BlockContent, Taglet, Block {
        // TODO: rename
        public string error_domain_name { private set; get; }
 
@@ -113,8 +114,8 @@ public class Valadoc.Taglets.Throws : InlineContent, Taglet, Block {
                tr.error_domain_name = error_domain_name;
                tr.error_domain = error_domain;
 
-               foreach (Inline element in content) {
-                       Inline copy = element.copy (tr) as Inline;
+               foreach (Block element in content) {
+                       Block copy = element.copy (tr) as Block;
                        tr.content.add (copy);
                }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]