Пример #1
0
        public static void ProcessFileWithReverseMarkdown(string path)
        {
            Console.WriteLine("Processed file '{0}'.", path);

            // with config
            string unknownTagsConverter = "pass_through";
            bool   githubFlavored       = true;
            var    config = new ReverseMarkdown.Config(unknownTagsConverter, githubFlavored);

            var    converter = new ReverseMarkdown.Converter(config);
            string markdown  = converter.Convert(File.ReadAllText(path));

            // Replace html <u> and </u> tag with empty space
            // markdown = markdown.Replace("<u>", "").Replace("</u>", "");

            string directoryPath = path.Substring(0, path.LastIndexOf('\\'));
            string htmlFileName  = path.Substring(path.LastIndexOf('\\') + 1);
            string mdFileName    = path.Substring(path.LastIndexOf('\\') + 1).Replace(".html", ".md");

            // Before writing markdown to the md file some small modifications
            // Replace '$' with '\$'
            markdown = markdown.Replace("$", "\\$");

            // Replace '<' html tag with '&lt;'
            // Replace '>' html tag with '&gt;'
            //markdown = markdown.Replace("<", "&lt;");
            //markdown = markdown.Replace(">", "&gt;");

            TextWriter txt = new StreamWriter(Path.Combine(directoryPath, mdFileName));

            txt.Write(markdown);
            txt.Close();
        }
Пример #2
0
        public ModStore()
        {
            config.parentForm = this;
            InitializeComponent();
            this.Icon           = Resources.icon;
            PoweredByLinkS.Text = "Powered By HakchiResources.com";

            var welcomeURL = new Uri("https://hakchiresources.com/modstorewelcome/?mode=welcome");

            if (Shared.isWindows)
            {
                var browser = new WebBrowser()
                {
                    ScriptErrorsSuppressed = true, AllowWebBrowserDrop = false, Dock = DockStyle.Fill, Url = welcomeURL
                };
                tabPage0.Controls.Add(browser);
            }
            else
            {
                var welcomeControl = new TextReadmeControl()
                {
                    Dock = DockStyle.Fill
                };
                using (var webClient = new System.Net.WebClient())
                {
                    ReverseMarkdown.Converter converter = new ReverseMarkdown.Converter();

                    var welcomeText = Shared.ReverseMarkdown(webClient.DownloadString(welcomeURL));

                    welcomeControl.setReadme(null, welcomeText);
                }

                tabPage0.Controls.Add(welcomeControl);
            }
        }
Пример #3
0
        public static string ReverseMarkdown(string html)
        {
            var converter = new ReverseMarkdown.Converter();
            var text      = converter.Convert(html);

            return(Regex.Replace(text.Replace("\r", ""), @"[\n]{2,}", "\n\n").Replace("\n", "\r\n").Trim());
        }
Пример #4
0
        public void ReverseMarkdown_FromClipboard()
        {
            var html = ClipboardHelper.GetHtmlFromClipboard();

            if (string.IsNullOrEmpty(html))
            {
                Console.WriteLine("You need to have some HTML on the clipboard for this test to do something useful.");

                return;
            }

            var config = new ReverseMarkdown.Config
            {
                GithubFlavored = true,
                UnknownTags    =
                    ReverseMarkdown.Config.UnknownTagsOption
                    .PassThrough,        // Include the unknown tag completely in the result (default as well)
                SmartHrefHandling = true // remove markdown output for links where appropriate
            };

            var    converter = new ReverseMarkdown.Converter(config);
            string markdown  = converter.Convert(html);

            Console.WriteLine(html);

            Console.WriteLine("\n---\n");

            Console.WriteLine(markdown);
        }
        public string ConvertDescription(string xhtml)
        {
            var converter = new ReverseMarkdown.Converter();

            string result = converter.Convert(xhtml);

            return(result);
        }
Пример #6
0
        public static String ToMarkdown(this string html)
        {
            var config = new ReverseMarkdown.Config()
            {
                UnknownTags = ReverseMarkdown.Config.UnknownTagsOption.Bypass
            };
            var converter = new ReverseMarkdown.Converter(config);

            return(converter.Convert(html).Replace("<br>", "\r\n"));
        }
Пример #7
0
        /// <summary>
        /// Converts an HTML string to Markdown.
        /// </summary>
        /// <remarks>
        /// This routine relies on a browser window
        /// and an HTML file that handles the actual
        /// parsing: Editor\HtmlToMarkdown.htm
        /// </remarks>
        /// <param name="html"></param>
        /// <returns></returns>
        public static string HtmlToMarkdown(string html, bool noErrorUI = false)
        {
            if (string.IsNullOrEmpty(html))
            {
                return("");
            }
#if false
            var config = new ReverseMarkdown.Config {
                GithubFlavored    = true,
                UnknownTags       = ReverseMarkdown.Config.UnknownTagsOption.PassThrough, // Include the unknown tag completely in the result (default as well)
                SmartHrefHandling = true                                                  // remove markdown output for links where appropriate
            };
            var    converter = new ReverseMarkdown.Converter(config);
            string markdown  = converter.Convert(html);

//            if (!string.IsNullOrEmpty(markdown))
//                markdown = markdown.Replace("\n\n", "\r\n").Replace("\r\n\r\n", "\r\n");
            return(markdown ?? html);
#else
            // Old code that uses JavaScript in a WebBrowser Control
            string markdown = null;
            string htmlFile = Path.Combine(App.InitialStartDirectory, "Editor\\htmltomarkdown.htm");

            var form = new BrowserDialog();
            form.ShowInTaskbar = false;
            form.Width         = 1;
            form.Height        = 1;
            form.Left          = -10000;
            form.Show();

            bool exists = File.Exists(htmlFile);
            form.NavigateAndWaitForCompletion(htmlFile);

            WindowUtilities.DoEvents();

            try
            {
                dynamic doc = form.Browser.Document;
                markdown = doc.ParentWindow.htmltomarkdown(html);
            }
            catch (Exception ex)
            {
                mmApp.Log("Failed to convert Html to Markdown", ex);
                if (!noErrorUI)
                {
                    MessageBox.Show("Unable to convert Html to Markdown. Returning Html.", "Html to Markdown Conversion Failed.", MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }

            form.Close();
            form = null;

            return(markdown ?? html);
#endif
        }
Пример #8
0
        async Task ImportPost(PostItem post)
        {
            await ImportImages(post);
            await ImportFiles(post);

            var converter = new ReverseMarkdown.Converter();

            post.Content = converter.Convert(post.Content);

            post.Cover = AppSettings.Cover;

            await _db.BlogPosts.SaveItem(post);
        }
        async Task <bool> ImportPost(Post post)
        {
            await ImportImages(post);
            await ImportFiles(post);

            var converter = new ReverseMarkdown.Converter();

            post.Description = converter.Convert(post.Description);
            post.Content     = converter.Convert(post.Content);

            await _dbContext.Posts.AddAsync(post);

            return(await _dbContext.SaveChangesAsync() > 0);
        }
Пример #10
0
        /// <summary>
        /// Converts an HTML string to Markdown.
        /// </summary>
        /// <remarks>
        /// This routine relies on a browser window
        /// and an HTML file that handles the actual
        /// parsing: Editor\HtmlToMarkdown.htm
        /// </remarks>
        /// <param name="html"></param>
        /// <returns></returns>
        public static string HtmlToMarkdown(string html)
        {
            if (string.IsNullOrEmpty(html))
            {
                return("");
            }
#if false
            var    config    = new ReverseMarkdown.Config(githubFlavored: true);
            var    converter = new ReverseMarkdown.Converter(config);
            string markdown  = converter.Convert(html);
            return(markdown ?? html);
#else
            // Old code that uses JavaScript in a WebBrowser Control
            string markdown = null;
            string htmlFile = Path.Combine(Environment.CurrentDirectory, "Editor\\htmltomarkdown.htm");

            var form = new BrowserDialog();
            form.ShowInTaskbar = false;
            form.Width         = 1;
            form.Height        = 1;
            form.Left          = -10000;
            form.Show();

            bool exists = File.Exists(htmlFile);
            form.Navigate(htmlFile);

            WindowUtilities.DoEvents();

            for (int i = 0; i < 200; i++)
            {
                dynamic doc = form.Browser.Document;

                if (!form.IsLoaded)
                {
                    Task.Delay(10);
                    WindowUtilities.DoEvents();
                }
                else
                {
                    markdown = doc.ParentWindow.htmltomarkdown(html);
                    break;
                }
            }

            form.Close();
            form = null;

            return(markdown ?? html);
#endif
        }
Пример #11
0
        async Task ImportPost(PostItem post)
        {
            await ImportImages(post);
            await ImportFiles(post);

            var converter = new ReverseMarkdown.Converter();

            post.Content = converter.Convert(post.Content);

            var blog = await _db.CustomFields.GetBlogSettings();

            post.Cover = blog.Cover;

            await _db.BlogPosts.SaveItem(post);
        }
Пример #12
0
        /// <summary>
        /// Converts an HTML string to Markdown.
        /// </summary>
        /// <remarks>
        /// This routine relies on a browser window
        /// and an HTML file that handles the actual
        /// parsing: Editor\HtmlToMarkdown.htm
        /// </remarks>
        /// <param name="html"></param>
        /// <returns></returns>
        public static string HtmlToMarkdown(string html, bool noErrorUI = false)
        {
            if (string.IsNullOrEmpty(html))
            {
                return("");
            }
#if false
            var    config    = new ReverseMarkdown.Config(githubFlavored: true);
            var    converter = new ReverseMarkdown.Converter(config);
            string markdown  = converter.Convert(html);
            return(markdown ?? html);
#else
            // Old code that uses JavaScript in a WebBrowser Control
            string markdown = null;
            string htmlFile = Path.Combine(Environment.CurrentDirectory, "Editor\\htmltomarkdown.htm");

            var form = new BrowserDialog();
            form.ShowInTaskbar = false;
            form.Width         = 1;
            form.Height        = 1;
            form.Left          = -10000;
            form.Show();

            bool exists = File.Exists(htmlFile);
            form.NavigateAndWaitForCompletion(htmlFile);

            WindowUtilities.DoEvents();

            try
            {
                dynamic doc = form.Browser.Document;
                markdown = doc.ParentWindow.htmltomarkdown(html);
            }
            catch (Exception ex)
            {
                mmApp.Log("Failed to convert Html to Markdown", ex);
                if (!noErrorUI)
                {
                    MessageBox.Show("Unable to convert Html to Markdown. Returning Html.", "Html to Markdown Conversion Failed.", MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }

            form.Close();
            form = null;

            return(markdown ?? html);
#endif
        }
Пример #13
0
        /// <summary>
        /// Converts an HTML string to Markdown.
        /// </summary>
        /// <remarks>
        /// This routine relies on a browser window
        /// and an HTML file that handles the actual
        /// parsing: Editor\HtmlToMarkdown.htm
        /// </remarks>
        /// <param name="html"></param>
        /// <returns></returns>
        public static string HtmlToMarkdown(string html, bool noErrorUI = false)
        {
            if (string.IsNullOrEmpty(html))
            {
                return("");
            }

            var config = new ReverseMarkdown.Config
            {
                GithubFlavored    = true,
                UnknownTags       = ReverseMarkdown.Config.UnknownTagsOption.PassThrough, // Include the unknown tag completely in the result (default as well)
                SmartHrefHandling = true                                                  // remove markdown output for links where appropriate
            };
            var    converter = new ReverseMarkdown.Converter(config);
            string markdown  = converter.Convert(html);

            return(markdown ?? html);
        }
Пример #14
0
        public void HtmlToMarkdownReverseMarkdownFromClipboardTest()
        {
            var html =
                @"<span style=""color: rgb(36, 41, 46); font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;"">Markdown Monster is an easy to use and extensible<span> </span></span><strong style=""box-sizing: border-box; font-weight: 600; color: rgb(36, 41, 46); font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"">Markdown Editor</strong><span style=""color: rgb(36, 41, 46); font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;"">,<span> </span></span><strong style=""box-sizing: border-box; font-weight: 600; color: rgb(36, 41, 46); font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"">Viewer</strong><span style=""color: rgb(36, 41, 46); font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;""><span> </span>and<span> </span></span><strong style=""box-sizing: border-box; font-weight: 600; color: rgb(36, 41, 46); font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;"">Weblog Publisher</strong><span style=""color: rgb(36, 41, 46); font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;""><span> </span>for Windows. Our goal is to provide the best Markdown specific editor for Windows and make it as easy as possible to create Markdown documents. We provide a core editor and previewer, and a number of non-intrusive helpers to help embed content like images, links, tables, code and more into your documents with minimal effort.</span>";

            var config = new ReverseMarkdown.Config
            {
                GithubFlavored = true,
                UnknownTags    =
                    ReverseMarkdown.Config.UnknownTagsOption
                    .PassThrough,        // Include the unknown tag completely in the result (default as well)
                SmartHrefHandling = true // remove markdown output for links where appropriate
            };
            var    converter = new ReverseMarkdown.Converter(config);
            string markdown  = converter.Convert(html);

            Console.WriteLine(markdown);

            Assert.IsTrue(markdown.Contains("and **Weblog Publisher** for Windows"));
        }
        public async Task ProcessLinks(List <string> listOfLinks)
        {
            this.pageHTML = new HtmlDocument();

            // Configure how to handle the HTML and how to parse it.
            for (var i = 1; i <= listOfLinks.Count; i += 2)
            {
                var link = listOfLinks[i - 1];
                var date = listOfLinks[i];

                var markDownConverter = new ReverseMarkdown.Converter(MdConfig);
                var response          = await client.GetAsync(link);

                var pageContents = await response.Content.ReadAsStringAsync();

                pageHTML.LoadHtml(pageContents);


                // Use XPath to find the div with an Id='Content'
                //var blogPostContent = pageHTML.DocumentNode.SelectSingleNode("(//div[contains(@id,'content' and not (@class='featured-blogs'))]").OuterHtml;

                var blogPostContent = pageHTML.DocumentNode.SelectSingleNode("(//*[@id='content' and not(@class='slick-track')])").InnerHtml;
                var blogTitle       = pageHTML.DocumentNode.SelectSingleNode("/html/head/title").InnerHtml;
                var metaDescription = pageHTML.DocumentNode.SelectSingleNode("/html/head/meta[9]").GetAttributeValue("content", "");



                string blogPostMarkdown = markDownConverter.Convert(blogPostContent);


                //Trim Url to get filename
                string filename = this.GetFilename(link);


                // Create Markdown file, pass the markdown, filename and id
                this.CreateMarkDownFile(blogPostMarkdown, blogTitle, metaDescription, filename, date);

                // Get any images that are in the blog
                this.GetAllImagesFromBlog(blogPostContent, folderDirectory);
            }
        }
        public async Task <bool> ImportPost(Post post)
        {
            try
            {
                await ImportImages(post);
                await ImportFiles(post);

                var converter = new ReverseMarkdown.Converter();

                post.Description = GetDescription(converter.Convert(post.Description));
                post.Content     = converter.Convert(post.Content);
                post.Selected    = false;

                await _dbContext.Posts.AddAsync(post);

                if (await _dbContext.SaveChangesAsync() == 0)
                {
                    Serilog.Log.Error($"Error saving post {post.Title}");
                    return(false);
                }

                Post savedPost = await _dbContext.Posts.SingleAsync(p => p.Slug == post.Slug);

                if (savedPost == null)
                {
                    Serilog.Log.Error($"Error finding saved post - {post.Title}");
                    return(false);
                }

                savedPost.Blog = await _dbContext.Blogs.FirstOrDefaultAsync();

                return(await _dbContext.SaveChangesAsync() > 0);
            }
            catch (Exception ex)
            {
                Serilog.Log.Error($"Error importing post {post.Title}: {ex.Message}");
                return(false);
            }
        }
Пример #17
0
        public MdConverter(List <string> popupResources)
        {
            _popupResources = popupResources;

            string unknownTagsConverter = "pass_through";
            bool   githubFlavored       = true;
            var    config = new ReverseMarkdown.Config(unknownTagsConverter, githubFlavored);

            _mdConverter = new ReverseMarkdown.Converter(config);
            _mdConverter.Unregister("span");
            _mdConverter.Register("span", new SpanEx(_mdConverter));

            _mdConverter.Unregister("p");
            _mdConverter.Register("p", new PEx(_mdConverter));

            _mdConverter.Unregister("table");
            _mdConverter.Register("table", new TableEx(_mdConverter));

            _mdConverter.Unregister("img");
            _mdConverter.Register("img", new ImgEx(_mdConverter));

            _mdConverter.Unregister("a");
            _mdConverter.Register("a", new AEx(_mdConverter));


            _mdConverter.Register("object", new ObjectEx(_mdConverter));

            _mdConverter.Unregister("td");
            _mdConverter.Unregister("th");
            _mdConverter.Register("td", new TdEx(_mdConverter));
            _mdConverter.Register("th", new TdEx(_mdConverter));


            if (HtmlNode.ElementsFlags.ContainsKey("li"))
            {
                HtmlNode.ElementsFlags.Remove("li");
            }
        }
Пример #18
0
        public void ReverseMarkdown_CodeSnippetFromBrowserHTML()
        {
            var html = ClipboardHelper.GetHtmlFromClipboard();


            var config = new ReverseMarkdown.Config
            {
                GithubFlavored = true,
                UnknownTags    =
                    ReverseMarkdown.Config.UnknownTagsOption
                    .PassThrough,        // Include the unknown tag completely in the result (default as well)
                SmartHrefHandling = true // remove markdown output for links where appropriate
            };

            var    converter = new ReverseMarkdown.Converter(config);
            string markdown  = converter.Convert(html);

            Console.WriteLine(html);

            Console.WriteLine("\n---\n");

            Console.WriteLine(markdown);
        }
Пример #19
0
        private static async Task Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                         .WriteTo.Console()
                         .WriteTo.File("logs\\log.txt", rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Error)
                         .CreateLogger();

            var client = new WordPressClient("https://www.codewrecks.com/blog/wp-json/");

            var mongoUrl             = new MongoUrl("mongodb://*****:*****@localhost/codewrecks?authSource=admin");
            var mongoClient          = new MongoClient(mongoUrl);
            var db                   = mongoClient.GetDatabase(mongoUrl.DatabaseName);
            var postsCollection      = db.GetCollection <Post>("posts");
            var tagsCollection       = db.GetCollection <WordPressPCL.Models.Tag>("tags");
            var categoriesCollection = db.GetCollection <Category>("categories");

            // Posts
            bool shouldLoadPosts = true;
            var  postCount       = postsCollection.AsQueryable().Count();

            if (postCount > 0)
            {
                Console.Write("Do you want to Re-Load all post into mongodb?");
                var answer = Console.ReadKey();
                if (!answer.Equals('y'))
                {
                    shouldLoadPosts = false;
                }
            }

            if (shouldLoadPosts)
            {
                Console.WriteLine("reading everything from blog");
                var posts = await client.Posts.GetAll(false, true);

                var tags = await client.Tags.GetAll();

                var categories = await client.Categories.GetAll();

                db.DropCollection("posts");
                db.DropCollection("tags");
                db.DropCollection("categories");

                postsCollection.InsertMany(posts);
                tagsCollection.InsertMany(tags);
                categoriesCollection.InsertMany(categories);
            }
            var orderedPosts = postsCollection.AsQueryable()
                               .OrderByDescending(p => p.Date)
                               .ToList();

            var allCategories = categoriesCollection.AsQueryable()
                                .ToDictionary(c => c.Id);

            var allTags = tagsCollection.AsQueryable()
                          .ToDictionary(t => t.Id);

            var allTagOnPosts = orderedPosts
                                .SelectMany(p => p.Tags)
                                .GroupBy(p => p)
                                .Select(g => (allTags[g.Key].Name, Count: g.Count()))
                                .OrderBy(t => t.Item2)
                                .ToList();

            var allMeaningfulTags = allTagOnPosts
                                    .Where(t => t.Count > 1)
                                    .Select(t => t.Name);

            HashSet <string> allowedTags = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            foreach (var allowedTag in allMeaningfulTags)
            {
                allowedTags.Add(allowedTag);
            }

            var converter = new ReverseMarkdown.Converter();

            Int32         index         = 0;
            StringBuilder redirectRules = new StringBuilder();

            foreach (var post in orderedPosts)
            {
                //if (post.Id != 2965)
                //{
                //    continue;
                //}

                index++;
                try
                {
                    var doc = new HtmlDocument();
                    doc.LoadHtml(PreParsePostContent(post));

                    StringBuilder sb = new StringBuilder(post.Content.Rendered.Length);

                    var postCategories = string.Join(",", post
                                                     .Categories
                                                     .Select(c => Sanitize($"\"{allCategories[c].Name}\""))
                                                     .Distinct());

                    if (string.IsNullOrEmpty(postCategories))
                    {
                        postCategories = "General";
                    }

                    var allTagEntries = post
                                        .Tags
                                        .Where(t => allowedTags.Contains(allTags[t].Name))
                                        .Select(t => Sanitize($"\"{allTags[t].Name}\""))
                                        //this will create tags for year and month to find post byt date to verify conversions
                                        //.Union(new[] { "\"Converted\"", $"\"{post.Date.Year}/{post.Date.Month}\"" })
                                        .Where(n => !"uncategorized".Equals(n))
                                        .Distinct()
                                        .ToList();

                    var postTags = string.Join(",", allTagEntries);
                    if (String.IsNullOrEmpty(postTags))
                    {
                        postTags = postCategories;
                    }

                    sb.AppendFormat(Header,
                                    Sanitize(post.Title.Rendered),
                                    "",
                                    $"{post.Date.Year}-{post.Date.Month.ToString("00")}-{post.Date.Day.ToString("00")}T{post.Date.Hour.ToString("00")}:00:37+02:00",
                                    postTags,
                                    postCategories);
                    sb.Append(ConvertToMarkDown(converter, doc));

                    var    newSlug  = $@"old\{post.Date.Year}\{post.Date.Month.ToString("00")}\{post.Slug}";
                    string fileName = $@"{baseOutputDir}\{newSlug}.md";
                    EnsureDirectory(fileName);
                    File.WriteAllText(
                        fileName,
                        sb.ToString(),
                        Encoding.UTF8);
                    Console.WriteLine("Converted post " + post.Id);
                    redirectRules.AppendFormat(@"
<rule name=""Reroute{2}"" stopProcessing=""true"">
    <match url = ""{0}"" />
    <action type = ""Redirect"" url = ""post/general/{1}"" redirectType = ""Temporary"" />
</rule>
", post.Link.Substring("https://www.codewrecks.com/".Length).TrimEnd('/', '\\'), newSlug.Replace('\\', '/').TrimEnd('/', '\\'), index);
                }
                catch (Exception ex)
                {
                    Log.Error(ex, "Error convertig post {postId}", post.Id);
                }
                if (index % 10 == 0)
                {
                    Console.Write("Press enter to continue with the next block. Done {0}", index);
                    //Console.ReadLine();
                }
            }

            File.WriteAllText($"{baseOutputDir}\\old\\web.config.redirect", redirectRules.ToString(), Encoding.ASCII);
            Console.Write("Press enter to finish, converted {0} posts", index);
            Console.ReadLine();
        }
Пример #20
0
        private static string ConvertToMarkDown(ReverseMarkdown.Converter converter, HtmlDocument doc)
        {
            FixOldCodeFormatterPlugin(doc);
            var preNodes = doc.DocumentNode.SelectNodes("//pre");

            if (preNodes != null)
            {
                foreach (var node in preNodes.ToList())
                {
                    if (String.IsNullOrWhiteSpace(node.InnerText))
                    {
                        continue;
                    }

                    var    text = node.OuterHtml;
                    string lang = GetLanguageByRegex(text);

                    if (lang == null)
                    {
                        lang = TryGetLanguageFromContent(node, ref text);
                    }

                    if (lang == null)
                    {
                        Log.Error("Unable to find language in pre class {text}", node.OuterHtml);
                        lang = "csharp"; //most probable lang for a snippet of code.
                        File.AppendAllText(@"x:\temp\errors.txt", node.InnerText + "\n\n\n\n");
                    }

                    if (lang == "plain")
                    {
                    }

                    var sb = new StringBuilder();
                    sb.AppendLine("<pre>{{< highlight " + lang + " \"linenos=table,linenostart=1\" >}}");

                    text = RemoveDoubleCrLf(text);
                    sb.Append(text);
                    sb.AppendLine("{{< / highlight >}}</pre>");

                    node.ParentNode.ReplaceChild(HtmlNode.CreateNode(sb.ToString()), node);
                }
            }

            var rawConversion = converter.Convert(doc.DocumentNode.InnerHtml);

            rawConversion = rawConversion.Trim('\n', '\r');
            for (int i = 0; i < 10; i++)
            {
                rawConversion = rawConversion.Replace("\r\n\r\n\r\n", "\r\n\r\n");
            }
            rawConversion = rawConversion.Replace("** ", "**");

            var lines = rawConversion.Split("\r\n");

            var final = new StringBuilder();

            foreach (var line in lines)
            {
                var parsedLine = line;
                if (parsedLine.StartsWith("    "))
                {
                    parsedLine = parsedLine.Substring(4);
                }
                final.AppendLine(parsedLine);
            }

            var result = final.ToString();

            //ok now regexes to fix Figure xxx markdown
            result = Regex.Replace(result, @"\*\*\s*(?<inner>[^\*]+?)\s*\*\*\s*", " **${inner}** ");
            result = Regex.Replace(result, @"\*\*Figure (?<id>\d*):\s*\*\*\s*", "***Figure ${id}***: ");
            result = result.Replace(" .", ".");
            result = result.Replace("[ ", "[");
            result = result.Replace(" ]", "]");
            return(result);
        }
Пример #21
0
        public async Task SubRSS(CommandContext ctx)
        {
            await ctx.RespondAsync("Started");

            var channels = await _service.GetGuildRssUrlsAsync(ctx.Guild);

            foreach (var channel in channels)
            {
                try
                {
                    await channel.SendMessageAsync($"Subscribed to {channel.Topic} at {DateTimeOffset.Now}");
                }
                catch
                {
                    Console.Error.WriteLine("Exception messsage");
                }
            }

            async void Callback(object?s)
            {
                foreach (var channel in channels)
                {
                    try
                    {
                        Uri uri_parsed = new(channel.Topic);
                        var path       = $"log_rss/{ctx.Guild.Id}_log_{uri_parsed.Host}.txt";
                        IEnumerable <SyndicationItem>?feed;
                        if (File.Exists(path))
                        {
                            var date = await File.ReadAllTextAsync(path);

                            feed = await _service.GetRssByUriLogAsync(uri_parsed.ToString(), DateTimeOffset.Parse(date),
                                                                      path);
                        }
                        else
                        {
                            feed = await _service.GetRssByUriLogAsync(uri_parsed.ToString(),
                                                                      DateTimeOffset.Now.Subtract(TimeSpan.FromDays(3)), path);
                        }

                        if (feed is null)
                        {
                            continue;
                        }

                        var homepage = $"https://{uri_parsed.Host}";

                        string favicon = await _service.FetchFaviconAsync(homepage) switch
                        {
                            { } fav when Uri.IsWellFormedUriString(fav, UriKind.Absolute) => fav,
                            _ => ctx.Client.CurrentUser.GetAvatarUrl(ImageFormat.Auto),
                        };
                        var author = new DiscordEmbedBuilder.EmbedAuthor()
                        {
                            IconUrl = favicon,
                            Url     = homepage,
                            Name    = uri_parsed.Host.Substring(0, Math.Min(uri_parsed.Host.Length, 200))
                        };
                        foreach (var item in feed)
                        {
                            DiscordEmbedBuilder embed = new()
                            {
                                Title       = item.Title?.Text.Substring(0, Math.Min(item.Title.Text.Length, 200)),
                                Url         = item.Links?.FirstOrDefault()?.Uri.ToString() ?? homepage,
                                Description = new ReverseMarkdown.Converter().Convert(
                                    item.Summary?.Text.Substring(0, Math.Min(item.Summary.Text.Length, 1000)) ??
                                    String.Empty),
                                Footer = new DiscordEmbedBuilder.EmbedFooter()
                                {
                                    Text = "RSS by Tomori"
                                },
                                Timestamp = DateTimeOffset.Now,
                                Author    = author,
                                Color     = Optional.FromValue <DiscordColor>(_random_color.Next(16777216)),
                            };
                            await channel.SendMessageAsync(embed : embed.Build());
                        }
                    }
                    catch (Exception e)
                    {
                        DiscordEmbedBuilder embed = new();
                        var built = embed.WithColor(new DiscordColor(255, 0, 0))
                                    .WithAuthor(channel.Name, "https://image.prntscr.com/image/1tlt8aj7RY_ywP-OPPivyg.png")
                                    .WithFooter("RSS by Tomori")
                                    .WithTitle(e.Message)
                                    .WithDescription(e.StackTrace)
                                    .Build();
                        await ctx.RespondAsync(built);
                    }
                }
            }

            _timers.Add(new Timer(Callback, null, TimeSpan.Zero, TimeSpan.FromMinutes(15)));
        }
Пример #22
0
 public void NestedList_test_with_ReverseMarkdown()
 {
     var    converter = new ReverseMarkdown.Converter();
     string result    = converter.Convert(html);
 }
Пример #23
0
        string ConvertHtmlToMarkDown(string text)
        {
            var htmlText = text.Replace("<fullname>", "<h1>").Replace("</fullname>", "</h1>");
            htmlText = htmlText.Replace("<note>", "<i>").Replace("</note>", "</i>");
            var converter = new ReverseMarkdown.Converter(new ReverseMarkdown.Config(unknownTagsConverter: "raise", githubFlavored: true));
            try
            {
                var markdownText = converter.Convert(htmlText);
                return markdownText;
            }
            catch (Exception e)
            {
                Console.WriteLine("html text = {0}", text);
                Console.WriteLine(e.StackTrace);
                throw e;
            }

        }
Пример #24
0
        public static string ConvertToMarkDown(string data, bool allowiframe = false)
        {
            if (string.IsNullOrEmpty(data))
            {
                return(string.Empty);
            }

            Regex VimeoVideoRegex   = new Regex(@".*vimeo\.com\/video\/?([0-9]+)");
            Regex YoutubeVideoRegex = new Regex(@".*youtu(?:\.be|be\.com)\/embed\/?(.*)");
            Regex HyperlinkRegex    = new Regex("http(s)?://([\\w+?\\.\\w+])+([a-zA-Z0-9\\~\\!\\@\\#\\$\\%\\^\\&amp;\\*\\(\\)_\\-\\=\\+\\\\\\/\\?\\.\\:\\;\\'\\,]*)?");
            var   document          = new HtmlDocument();

            document.LoadHtml(data);
            if (document.DocumentNode.Descendants().All(n => n.NodeType == HtmlNodeType.Text))
            {
                string[] stringSeparators  = new string[] { "\r\n" };
                string[] lines             = data.Split(stringSeparators, StringSplitOptions.None);
                bool     lastStartsWithTab = false;
                lines = lines.ToList().Select(l => {
                    if (l.StartsWith("\t") || l.StartsWith("    "))
                    {
                        lastStartsWithTab = true;
                        l = l.Replace("\t", "- ").Replace("    ", "- ");
                    }
                    else if (lastStartsWithTab)
                    {
                        l = "\r\n" + l;
                        lastStartsWithTab = false;
                    }
                    else
                    {
                        l = "\r\n" + l;
                        lastStartsWithTab = false;
                    }
                    return(l);
                }).ToArray();
                return(string.Join(" \r\n", lines));
                //return data.Replace("\r\n\t", "\r\n- ").Replace("\r\n"," \r\n");
                //return data;
            }
            else
            {
                document.DocumentNode.Descendants()
                .Where(n => n.Name == "script" || n.Name == "style")
                .ToList()
                .ForEach(n => n.Remove());

                var acceptableTags    = new String[] { "strong", "em", "u", "b", "h1", "h2", "h3", "h4", "h5", "p", "i", "ul", "ol", "li", "hr", "table", "tr", "th", "td", "br" };
                var acceptableTagslst = acceptableTags.ToList();
                if (allowiframe)
                {
                    acceptableTagslst.Add("iframe");
                }

                var nodes = new Queue <HtmlNode>(document.DocumentNode.SelectNodes("./*|./text()"));
                while (nodes.Count > 0)
                {
                    var node       = nodes.Dequeue();
                    var parentNode = node.ParentNode;

                    if (!acceptableTagslst.ToArray().Contains(node.Name) && node.Name != "#text")
                    {
                        var childNodes = node.SelectNodes("./*|./text()");

                        if (childNodes != null)
                        {
                            foreach (var child in childNodes)
                            {
                                nodes.Enqueue(child);
                                parentNode.InsertBefore(child, node);
                            }
                        }

                        parentNode.RemoveChild(node);
                    }
                    if (node.Name == "iframe" && allowiframe)
                    {
                        var isrc = node.Attributes.Where(at => at.Name == "src").FirstOrDefault();
                        if (isrc != null)
                        {
                            var             newchild         = document.CreateElement("p");
                            var             iframeSrc        = isrc.Value;
                            MatchCollection HyperLinkmatches = HyperlinkRegex.Matches(iframeSrc);
                            foreach (Match match in HyperLinkmatches)
                            {
                                Match youtubeMatch = YoutubeVideoRegex.Match(match.Value);
                                Match vimeoMatch   = VimeoVideoRegex.Match(match.Value);
                                if (youtubeMatch.Success)
                                {
                                    var id = youtubeMatch.Groups[1].Value;
                                    iframeSrc = "https://www.youtube.com/watch?v=" + id;
                                }
                                else if (vimeoMatch.Success)
                                {
                                    var id = vimeoMatch.Groups[1].Value;
                                    iframeSrc = "https://vimeo.com/" + id;
                                }
                                else
                                {
                                    iframeSrc = match.Value;
                                }
                            }
                            newchild.InnerHtml = "![Video](" + iframeSrc + ")";
                            //nodes.Enqueue(child);
                            parentNode.InsertBefore(newchild, node);
                            parentNode.RemoveChild(node);
                        }
                    }
                }

                var converter = new ReverseMarkdown.Converter();
                return(converter.Convert(document.DocumentNode.InnerHtml).Trim());
            }
        }
Пример #25
0
 string ConvertHtmlToMarkDown(string text)
 {
     var htmlText = text.Replace("<fullname>", "<h1>").Replace("</fullname>", "</h1>");
     htmlText = htmlText.Replace("<note>", "<i>").Replace("</note>", "</i>");
     var converter = new ReverseMarkdown.Converter(new ReverseMarkdown.Config(unknownTagsConverter: "raise", githubFlavored: true));
     try
     {
         var markdownText = converter.Convert(htmlText);
         return markdownText;
     }
     catch (Exception e)
     {
         Console.WriteLine("exception while parsing markdown for the service {0}", Configuration.ServiceModel.ServiceFullName);
         Console.WriteLine("html text = {0}", text);
         throw e;
     }
 }
Пример #26
0
        public static void DumpSingle(XmlEntity.Note note, string dir, MarkdownConfig config = null)
        {
            if (config == null)
            {
                config = new MarkdownConfig();
            }

            Directory.CreateDirectory(Path.Combine(dir, config.AttachmentPath));

            var doc = new HtmlAgilityPack.HtmlDocument();

            doc.LoadHtml(note.Content);
            var html      = doc.DocumentNode.SelectSingleNode("/en-note").InnerHtml;
            var converter = new ReverseMarkdown.Converter(config);

            converter.Register("en-media", new MarkdownConverters.EnMedia(note));
            converter.Register("en-todo", new MarkdownConverters.EnTodo());
            var sb = new StringBuilder();

            if (config.InsertTitle)
            {
                sb.AppendLine($"# {note.Title}");
                sb.AppendLine();
            }
            sb.Append(System.Web.HttpUtility.HtmlDecode(converter.Convert(html)));

            if (note.Resource != null)
            {
                foreach (var item in note.Resource)
                {
                    sb.AppendLine();
                    if (config.InlineImage && item.Mime.ToLower().StartsWith("image/"))
                    {
                        sb.AppendLine($"[{item.Data.Hash}]: data:{item.Mime};base64,{item.Data.Base64}");
                    }
                    else
                    {
                        var filename = item.FileName;
                        if (File.Exists(Path.Combine(dir, config.AttachmentPath, filename)))
                        {
                            var ext  = Path.GetExtension(filename);
                            var name = Path.GetFileNameWithoutExtension(filename);
                            var n    = 1;
                            while (File.Exists(Path.Combine(dir, config.AttachmentPath, $"{name}_{n}{ext}")))
                            {
                                ++n;
                            }
                            filename = $"{name}_{n}{ext}";
                        }
                        sb.AppendLine($"[{item.Data.Hash}]: {Path.Combine(config.AttachmentPath, filename)} ({item.FileName})");
                        File.WriteAllBytes(Path.Combine(dir, config.AttachmentPath, filename), item.Data.Content);
                    }
                }
            }

            var result = Regex.Replace(sb.ToString(), "(?:" + Environment.NewLine + "){3,}", $"{Environment.NewLine}{Environment.NewLine}");

            result = result.Trim();

            string mdfilename = config.UUIDFilename ? Guid.NewGuid().ToString() : Utility.SafeFileName(note.Title);

            File.WriteAllText(Path.Combine(dir, Utility.SafeFileName($"{mdfilename}.md")), result);
        }
		private string HtmlToMarkdown(string html)
		{
			var config = new ReverseMarkdown.Config("pass_through", true);
			var converter = new ReverseMarkdown.Converter(config);

			return converter.Convert(html);
		}
Пример #28
0
        static void Main(string[] args)
        {
            var sanitizer = new HtmlSanitizer();
            var html      = @"<script>alert('xss')</script><div onload=""alert('xss')"""
                            + @"style=""background-color: test"">Test<img src=""test.gif"""
                            + @"style=""background-image: url(javascript:alert('xss')); margin: 10px""></div>";
            var memStream = new MemoryStream(Encoding.UTF8.GetBytes(html));

            Stopwatch st;

            for (var j = 0; j < 6; j++)
            {
                st = Stopwatch.StartNew();
                for (var i = 0; i < 5000; i++)
                {
                    var sanitized = sanitizer.Sanitize(html, "http://www.example.com");
                }
                Console.WriteLine("HtmlSanitizer {0} ms", st.ElapsedMilliseconds);

                st = Stopwatch.StartNew();
                for (var i = 0; i < 5000; i++)
                {
                    var sanitized = Html.Sanitize(html);
                }
                Console.WriteLine("BracketPipe {0} ms", st.ElapsedMilliseconds);

                st = Stopwatch.StartNew();
                for (var i = 0; i < 5000; i++)
                {
                    memStream.Position = 0;
                    var sanitized = Html.Sanitize(memStream);
                }
                Console.WriteLine("BracketPipe {0} ms", st.ElapsedMilliseconds);
            }


            const string htmlInput = @"<!DOCTYPE html>
<html>
    <head>
        <meta charset=""utf-8"" />
        <title>The test document</title>
        <link href=""favicon.ico"" rel=""shortcut icon"" type=""image/x-icon"" />
        <meta name=""viewport"" content=""width=device-width"" />
        <link rel=""stylesheet"" type=""text/css"" href=""/Content/Site.css"" />
    </head>
    <body>
        <p>Lorem ipsum dolor sit amet...</p>

        <script src=""http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js""></script>
        <script>
            (window.jquery) || document.write('<script src=""/Scripts/jquery-1.9.1.min.js""><\/script>');
        </script>
    </body>
</html>";

            memStream = new MemoryStream(Encoding.UTF8.GetBytes(htmlInput));

            var htmlMinifier = new HtmlMinifier();

            for (var j = 0; j < 5; j++)
            {
                st = Stopwatch.StartNew();
                for (var i = 0; i < 5000; i++)
                {
                    var result = htmlMinifier.Minify(htmlInput);
                }
                Console.WriteLine("WebMarkupMin {0} ms", st.ElapsedMilliseconds);

                st = Stopwatch.StartNew();
                for (var i = 0; i < 5000; i++)
                {
                    var result = Html.Minify(htmlInput);
                }
                Console.WriteLine("BracketPipe {0} ms", st.ElapsedMilliseconds);

                st = Stopwatch.StartNew();
                for (var i = 0; i < 5000; i++)
                {
                    memStream.Position = 0;
                    var result = Html.Minify(memStream);
                }
                Console.WriteLine("BracketPipe {0} ms", st.ElapsedMilliseconds);
            }

            var          mdConverter  = new Html2Markdown.Converter();
            var          revConverter = new ReverseMarkdown.Converter();
            const string mdHtml       = "<p>This is the second part of a two part series about building real-time web applications with server-sent events.</p>\r\n\r\n<ul>\r\n<li><a href=\"http://bayn.es/real-time-web-applications-with-server-sent-events-pt-1/\">Building Web Apps with Server-Sent Events - Part 1</a></li>\r\n</ul>\r\n\r\n<h2 id=\"reconnecting\">Reconnecting</h2>\r\n\r\n<p>In this post we are going to look at handling reconnection if the browser loses contact with the server. Thankfully the native JavaScript functionality for SSEs (the <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/EventSource\">EventSource</a>) handles this natively. You just need to make sure that your server-side implementation supports the mechanism.</p>\r\n\r\n<p>When the server reconnects your SSE end point it will send a special HTTP header <code>Last-Event-Id</code> in the reconnection request. In the previous part of this blog series we looked at just sending events with the <code>data</code> component. Which looked something like this:-</p>\r\n\r\n<pre><code>data: The payload we are sending\\n\\n\r\n</code></pre>\r\n\r\n<p>Now while this is enough to make the events make it to your client-side implementation. We need more information to handle reconnection. To do this we need to add an event id to the output.</p>\r\n\r\n<p>E.g.</p>\r\n\r\n<pre><code>id: 1439887379635\\n\r\ndata: The payload we are sending\\n\\n\r\n</code></pre>\r\n\r\n<p>The important thing to understand here is that each event needs a unique identifier, so that the client can communicate back to the server (using the <code>Last-Event-Id</code> header) which was the last event it received on reconnection.</p>\r\n\r\n<h2 id=\"persistence\">Persistence</h2>\r\n\r\n<p>In our previous example we used <a href=\"http://redis.io/topics/pubsub\">Redis Pub/Sub</a> to inform <a href=\"https://nodejs.org/\">Node.js</a> that it needs to push a new SSE to the client. Redis Pub/Sub is a topic communication which means it will be delivered to all <em>connected clients</em>, and then it will be removed from the topic. So there is no persistence for when clients reconnect. To implement this we need to add a persistence layer and so in this demo I have chosen to use <a href=\"https://www.mongodb.org/\">MongoDB</a>.</p>\r\n\r\n<p>Essentially we will be pushing events into both Redis and MongoDB. Redis will still be our method of initiating an SSE getting sent to the browser, but we will also be be storing that event into MongoDB so we can query it on a reconnection to get anything we've missed.</p>\r\n\r\n<h2 id=\"thecode\">The Code</h2>\r\n\r\n<p>OK so let us look at how we can actually implement this.</p>\r\n\r\n<h3 id=\"updateserverevent\">Update ServerEvent</h3>\r\n\r\n<p>We need to update the ServerEvent object to support having an <code>id</code> for an event.</p>\r\n\r\n<pre><code>function ServerEvent(name) {\r\n    this.name = name || \"\";\r\n    this.data = \"\";\r\n};\r\n\r\nServerEvent.prototype.addData = function(data) {\r\n    var lines = data.split(/\\n/);\r\n\r\n    for (var i = 0; i &lt; lines.length; i++) {\r\n        var element = lines[i];\r\n        this.data += \"data:\" + element + \"\\n\";\r\n    }\r\n}\r\n\r\nServerEvent.prototype.payload = function() {\r\n    var payload = \"\";\r\n    if (this.name != \"\") {\r\n        payload += \"id: \" + this.name + \"\\n\";\r\n    }\r\n\r\n    payload += this.data;\r\n    return payload + \"\\n\";\r\n}\r\n</code></pre>\r\n\r\n<p>This is pretty straightforward string manipulation and won't impress anyone, but it is foundation for what will follow.</p>\r\n\r\n<h3 id=\"storeeventsinmongodb\">Store Events in MongoDB</h3>\r\n\r\n<p>We need to update the <code>post.js</code> code to also store new events in MongoDB.</p>\r\n\r\n<pre><code>app.put(\"/api/post-update\", function(req, res) {\r\n    var json = req.body;\r\n    json.timestamp = Date.now();\r\n\r\n    eventStorage.save(json).then(function(doc) {\r\n        dataChannel.publish(JSON.stringify(json));\r\n    }, errorHandling);\r\n\r\n    res.status(204).end();\r\n});\r\n</code></pre>\r\n\r\n<p>The <code>event-storage</code> module looks as follows:</p>\r\n\r\n<pre><code>var Q = require(\"q\"),\r\n    config = require(\"./config\"),\r\n    mongo = require(\"mongojs\"),\r\n    db = mongo(config.mongoDatabase),\r\n    collection = db.collection(config.mongoScoresCollection);\r\n\r\nmodule.exports.save = function(data) {\r\n    var deferred = Q.defer();\r\n    collection.save(data, function(err, doc){\r\n        if(err) {\r\n            deferred.reject(err);\r\n        }\r\n        else {\r\n            deferred.resolve(doc);\r\n        }\r\n    });\r\n\r\n    return deferred.promise;\r\n};\r\n</code></pre>\r\n\r\n<p>Here we are just using basic MongoDB commands to save a new event into the collection. Yep that is it, we are now additionally persisting the events so they can be retrieved later.</p>\r\n\r\n<h3 id=\"retrievingeventsonreconnection\">Retrieving Events on Reconnection</h3>\r\n\r\n<p>When an <code>EventSource</code> reconnects after a disconnection it passes a special header <code>Last-Event-Id</code>. So we need to look for that and return the events that got broadcast while the client was disconnected.</p>\r\n\r\n<pre><code>app.get(\"/api/updates\", function(req, res){\r\n    initialiseSSE(req, res);\r\n\r\n    if (typeof(req.headers[\"last-event-id\"]) != \"undefined\") {\r\n        replaySSEs(req, res);\r\n    }\r\n});\r\n\r\nfunction replaySSEs(req, res) {\r\n    var lastId = req.headers[\"last-event-id\"];\r\n\r\n    eventStorage.findEventsSince(lastId).then(function(docs) {\r\n        for (var index = 0; index &lt; docs.length; index++) {\r\n            var doc = docs[index];\r\n            var messageEvent = new ServerEvent(doc.timestamp);\r\n            messageEvent.addData(doc.update);\r\n            outputSSE(req, res, messageEvent.payload());\r\n        }\r\n    }, errorHandling);\r\n};\r\n</code></pre>\r\n\r\n<p>What we are doing here is querying MongoDB for the events that were missed. We then iterate over them and output them to the browser.</p>\r\n\r\n<p>The code for querying MongoDB is as follows:</p>\r\n\r\n<pre><code>module.exports.findEventsSince = function(lastEventId) {\r\n    var deferred = Q.defer();\r\n\r\n    collection.find({\r\n        timestamp: {$gt: Number(lastEventId)}\r\n    })\r\n    .sort({timestamp: 1}, function(err, docs) {\r\n        if (err) {\r\n            deferred.reject(err);\r\n        }\r\n        else {\r\n            deferred.resolve(docs);\r\n        }\r\n    });\r\n\r\n    return deferred.promise;\r\n};\r\n</code></pre>\r\n\r\n<h2 id=\"testing\">Testing</h2>\r\n\r\n<p>To test this you will need to run both apps at the same time.</p>\r\n\r\n<pre><code>node app.js\r\n</code></pre>\r\n\r\n<p>and </p>\r\n\r\n<pre><code>node post.js\r\n</code></pre>\r\n\r\n<p>Once they are running open two browser windows <a href=\"http://localhost:8181/\">http://localhost:8181/</a> and <a href=\"http://localhost:8082/api/post-update\">http://localhost:8082/api/post-update</a></p>\r\n\r\n<p>Now you can post updates as before. If you stop <code>app.js</code> but continue posting events, when you restart <code>app.js</code> within 10 seconds the <code>EventSource</code> will reconnect. This will deliver all missed events.</p>\r\n\r\n<h2 id=\"conclusion\">Conclusion</h2>\r\n\r\n<p>This very simple code gives you a very elegant and powerful push architecture to create real-time apps.</p>\r\n\r\n<h3 id=\"improvements\">Improvements</h3>\r\n\r\n<p>A possible improvement would be to render the events from MongoDB server-side when the page is first output. Then we would get updates client-side as they are pushed to the browser.</p>\r\n\r\n<h3 id=\"download\">Download</h3>\r\n\r\n<p>If you want to play with this application you can fork or browse it on <a href=\"https://github.com/baynezy/RealtimeDemo/tree/part-2\">GitHub</a>.</p>";

            for (var j = 0; j < 5; j++)
            {
                st = Stopwatch.StartNew();
                for (var i = 0; i < 1000; i++)
                {
                    var result = mdConverter.Convert(mdHtml);
                }
                Console.WriteLine("Html2Markdown {0} ms", st.ElapsedMilliseconds);

                st = Stopwatch.StartNew();
                for (var i = 0; i < 1000; i++)
                {
                    var result = revConverter.Convert(mdHtml);
                }
                Console.WriteLine("ReverseMarkdown {0} ms", st.ElapsedMilliseconds);

                st = Stopwatch.StartNew();
                for (var i = 0; i < 1000; i++)
                {
                    var result = Html.ToMarkdown(mdHtml);
                }
                Console.WriteLine("BracketPipe {0} ms", st.ElapsedMilliseconds);
            }


            Console.ReadLine();
        }