예제 #1
0
        public void Import_WithBlogPostHavingBase64EncodedContentWithAttachments_ProperlyRewritesAttachments()
        {
            // arrange
            var          blog = new BlogMLBlog();
            const string originalPostContent = @"<img src=""http://old.example.com/images/my-mug.jpg"" />";
            var          post = new BlogMLPost {
                Content = BlogMLContent.Create(originalPostContent, ContentTypes.Base64)
            };
            var attachment = new BlogMLAttachment {
                Url = "http://old.example.com/images/my-mug.jpg", Embedded = false
            };

            post.Attachments.Add(attachment);
            blog.Posts.Add(post);
            var repository = new Mock <IBlogImportRepository>();

            repository.Setup(r => r.GetAttachmentDirectoryUrl()).Returns("http://new.example.com/images/");
            repository.Setup(r => r.GetAttachmentDirectoryPath()).Returns(@"c:\web\images");
            BlogMLPost publishedPost = null;

            repository.Setup(r => r.CreateBlogPost(blog, post)).Callback <BlogMLBlog, BlogMLPost>((b, p) => publishedPost = p);
            var service = new BlogImportService(repository.Object);

            // act
            service.Import(blog);

            // assert
            Assert.AreEqual(ContentTypes.Base64, publishedPost.Content.ContentType);
            Assert.AreEqual(@"<img src=""http://new.example.com/images/my-mug.jpg"" />", publishedPost.Content.UncodedText);
        }
예제 #2
0
        private void ImportBlogPost(BlogMLBlog blog, BlogMLPost bmlPost)
        {
            if (bmlPost.Attachments.Count > 0)
            {
                //Updates the post content with new attachment urls.
                bmlPost.Content = BlogMLContent.Create(CreateFilesFromAttachments(bmlPost), ContentTypes.Base64);
            }

            string newEntryId = Repository.CreateBlogPost(blog, bmlPost);

            foreach (BlogMLComment bmlComment in bmlPost.Comments)
            {
                try
                {
                    Repository.CreateComment(bmlComment, newEntryId);
                }
                catch (Exception e)
                {
                    LogError(Resources.Import_ErrorWhileImportingComment, e);
                }
            }

            foreach (BlogMLTrackback bmlPingTrack in bmlPost.Trackbacks)
            {
                try
                {
                    Repository.CreateTrackback(bmlPingTrack, newEntryId);
                }
                catch (Exception e)
                {
                    LogError(Resources.Import_ErrorWhileImportingComment, e);
                }
            }
        }
        protected void WriteStartBlogMLPost(BlogPost post)
        {
            WriteStartElement("post");
            WriteNodeAttributes(post.Id.ToString(), post.ActivationDate, post.ModifiedOn, post.Status == PageStatus.Published);
            WriteAttributeString("post-url", post.PageUrl);
            WriteAttributeStringRequired("type", "normal");
            WriteAttributeStringRequired("hasexcerpt", (!string.IsNullOrWhiteSpace(post.Description)).ToString().ToLower());
            WriteAttributeStringRequired("views", "0");
            WriteContent("title", BlogMLContent.Create(post.MetaTitle ?? post.Title, ContentTypes.Text));
            WriteContent("post-name", BlogMLContent.Create(post.Title, ContentTypes.Text));

            if (!string.IsNullOrWhiteSpace(post.Description))
            {
                WriteBlogMLContent("excerpt", BlogMLContent.Create(post.Description, ContentTypes.Text));
            }

            var content = post.PageContents
                          .Where(pc => pc.Content is BlogPostContent && pc.Content.Status == ContentStatus.Published)
                          .Select(pc => pc.Content)
                          .FirstOrDefault();

            if (content != null)
            {
                WriteBlogMLContent("content", BlogMLContent.Create(((BlogPostContent)content).Html, ContentTypes.Text));
            }
        }
예제 #4
0
 protected void WriteStartPost(
     string id,
     string title,
     ContentTypes titleContentType,
     DateTime dateCreated,
     DateTime dateModified,
     bool approved,
     string content,
     ContentTypes postContentType,
     string postUrl,
     UInt32 views,
     bool hasexcerpt,
     string excerpt,
     ContentTypes excerptContentType,
     BlogPostTypes blogpostType,
     string postName
     )
 {
     WriteStartElement("post");
     WriteNodeAttributes(id, dateCreated, dateModified, approved);
     WriteAttributeString("post-url", postUrl);
     WriteAttributeStringRequired("type", blogpostType.ToString().ToLower());
     WriteAttributeStringRequired("hasexcerpt", hasexcerpt.ToString().ToLower());
     WriteAttributeStringRequired("views", views.ToString());
     WriteContent("title", BlogMLContent.Create(title, titleContentType));
     WriteContent("content", BlogMLContent.Create(content, postContentType));
     if (postName != null)
     {
         WriteContent("post-name", BlogMLContent.Create(postName, ContentTypes.Text));
     }
     if (hasexcerpt)
     {
         WriteContent("excerpt", BlogMLContent.Create(excerpt, excerptContentType));
     }
 }
예제 #5
0
        public void Write_WithBlogContainingBase64EncodedPosts_WritesPostsToWriterAsBase64Encoded()
        {
            // arrange
            var stringWriter = new StringWriter();
            var xmlWriter    = new XmlTextWriter(stringWriter)
            {
                Formatting = Formatting.Indented
            };
            var source   = new Mock <IBlogMLSource>();
            var dateTime = DateTime.ParseExact("20090123", "yyyyMMdd", CultureInfo.InvariantCulture);
            var blog     = new BlogMLBlog {
                Title = "Subtext Blog", RootUrl = "http://subtextproject.com/", SubTitle = "A test blog", DateCreated = dateTime
            };

            source.Setup(s => s.GetBlog()).Returns(blog);
            var post = new BlogMLPost {
                Content = BlogMLContent.Create("<p>This is a Test</p>", ContentTypes.Base64)
            };
            var posts = new List <BlogMLPost> {
                post
            };

            blog.Posts.Add(post);
            source.Setup(s => s.GetBlogPosts(false /*embedAttachments*/)).Returns(posts);
            var writer = new BlogMLWriter(source.Object, false /*embedAttachments*/);

            // act
            ((IBlogMLWriter)writer).Write(xmlWriter);

            // assert
            string output = stringWriter.ToString();

            Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes("<p>This is a Test</p>")));
            Assert.Contains(output, @"<content type=""base64""><![CDATA[PHA+VGhpcyBpcyBhIFRlc3Q8L3A+]]></content>");
        }
예제 #6
0
        protected void WriteContent(string elementName, BlogMLContent content)
        {
            WriteStartElement(elementName);
            string contentType = (Enum.GetName(typeof(ContentTypes), content.ContentType) ?? "text").ToLowerInvariant();

            WriteAttributeString("type", contentType);
            Writer.WriteCData(content.Text ?? string.Empty);
            WriteEndElement();
        }
        public void Execute(BlogPostConversionData postConversionData)
        {
            var post = postConversionData.Post;

            var markdown = post.Content.Text;

            markdown = ReverseMarkdownHelper
                       .DecodeAfterConversion(markdown);

            post.Content = BlogMLContent.Create(
                markdown,
                ContentTypes.Text);
        }
        public void Execute(BlogPostConversionData postConversionData)
        {
            var post = postConversionData.Post;

            var mdConverter = new Converter();

            var markdown = mdConverter.Convert(
                post.Content.UncodedText);

            postConversionData.Post.Content = BlogMLContent.Create(
                markdown,
                BlogML.ContentTypes.Text);
        }
예제 #9
0
        public void ConvertBlogPost_WithPostHavingBase64EncodedExcerpt_DecodesContent()
        {
            // arrange
            var post = new BlogMLPost {
                HasExcerpt = true, Excerpt = BlogMLContent.Create("This is a story about a 3 hour voyage", ContentTypes.Base64)
            };
            var mapper = new BlogMLImportMapper();

            // act
            Entry entry = mapper.ConvertBlogPost(post, new BlogMLBlog(), null);

            // assert
            Assert.AreEqual("This is a story about a 3 hour voyage", entry.Description);
        }
예제 #10
0
 private void WritePostComments(BlogMLPost.CommentCollection comments)
 {
     if (comments.Count > 0)
     {
         WriteStartComments();
         foreach (BlogMLComment comment in comments)
         {
             string userName = string.IsNullOrEmpty(comment.UserName) ? "Anonymous" : comment.UserName;
             WriteComment(comment.ID, BlogMLContent.Create(comment.Title, ContentTypes.Text), comment.DateCreated, comment.DateModified,
                          comment.Approved, userName, comment.UserEMail, comment.UserUrl,
                          comment.Content);
         }
         WriteEndElement();
     }
 }
예제 #11
0
 protected void WriteAuthor(
     string id,
     string name,
     string email,
     DateTime dateCreated,
     DateTime dateModified,
     bool approved
     )
 {
     WriteStartElement("author");
     WriteNodeAttributes(id, dateCreated, dateModified, approved);
     WriteAttributeString("email", email);
     WriteContent("title", BlogMLContent.Create(name, ContentTypes.Text));
     WriteEndElement();
 }
        public void Execute(BlogPostConversionData postConversionData)
        {
            var post = postConversionData.Post;

            var postHtml = post.Content.UncodedText;

            var htmlDoc = new HtmlDocument();

            htmlDoc.LoadHtml(postHtml);

            ReplaceBlogImageUrls(htmlDoc);

            post.Content = BlogMLContent.Create(
                htmlDoc.DocumentNode.OuterHtml,
                ContentTypes.Html);
        }
예제 #13
0
 protected void WriteTrackback(
     string id,
     string title,
     ContentTypes titleContentType,
     DateTime dateCreated,
     DateTime dateModified,
     bool approved,
     string url
     )
 {
     WriteStartElement("trackback");
     WriteNodeAttributes(id, dateCreated, dateModified, approved);
     WriteAttributeStringRequired("url", url);
     WriteContent("title", BlogMLContent.Create(title, titleContentType));
     WriteEndElement();
 }
        public void Execute(BlogPostConversionData postConversionData)
        {
            var post = postConversionData.Post;

            var postHtml = post.Content.UncodedText;

            var htmlDoc = new HtmlDocument();

            htmlDoc.LoadHtml(postHtml);

            EncodeSpecialCharactersInHugoShortcodes(htmlDoc);

            post.Content = BlogMLContent.Create(
                htmlDoc.DocumentNode.OuterHtml,
                ContentTypes.Html);
        }
예제 #15
0
        public BlogMLPost ConvertEntry(EntryStatsView entry, bool embedAttachments)
        {
            string postUrl          = null;
            var    entryVirtualPath = Url.EntryUrl(entry);

            if (entryVirtualPath != null)
            {
                postUrl = entryVirtualPath.ToFullyQualifiedUrl(Blog).ToString();
            }
            var post = new BlogMLPost
            {
                Title        = entry.Title,
                PostUrl      = postUrl,
                PostType     = (entry.PostType == PostType.Story) ? BlogPostTypes.Article : BlogPostTypes.Normal,
                Approved     = entry.IsActive,
                Content      = BlogMLContent.Create(entry.Body ?? string.Empty, ContentTypes.Base64),
                HasExcerpt   = entry.HasDescription,
                Excerpt      = BlogMLContent.Create(entry.Description ?? string.Empty, ContentTypes.Base64),
                DateCreated  = Blog.TimeZone.ToUtc(entry.DateCreated),
                DateModified = Blog.TimeZone.ToUtc(entry.IsActive ? entry.DateSyndicated : entry.DateModified),
                Views        = (uint)entry.WebCount
            };

            if (entry.HasEntryName)
            {
                post.PostName = entry.EntryName;
            }

            // When we support multiple authors, this will have to change
            post.Authors.Add(Blog.Id.ToString(CultureInfo.InvariantCulture));
            post.Attachments.AddRange(GetPostAttachments(entry.Body, embedAttachments).ToArray());
            var comments = (from c in entry.Comments where c.FeedbackType == FeedbackType.Comment select ConvertComment(c)).ToList();

            if (comments.Count > 0)
            {
                post.Comments.AddRange(comments);
            }
            var trackbacks = (from c in entry.Comments where c.FeedbackType == FeedbackType.PingTrack select ConvertTrackback(c)).ToList();

            if (trackbacks.Count > 0)
            {
                post.Trackbacks.AddRange(trackbacks);
            }
            return(post);
        }
        public void Execute(BlogPostConversionData postConversionData)
        {
            var post = postConversionData.Post;

            var postHtml = post.Content.UncodedText;

            var htmlDoc = new HtmlDocument();

            htmlDoc.LoadHtml(postHtml);

            FixSpacesInCodeElements(htmlDoc);
            FixSpacesInCodeSpanElements(htmlDoc);
            FixSpacesInInlineElements(htmlDoc);

            post.Content = BlogMLContent.Create(
                htmlDoc.DocumentNode.OuterHtml,
                ContentTypes.Html);
        }
예제 #17
0
 protected void WriteCategory(
     string id,
     string title,
     ContentTypes titleContentType,
     DateTime dateCreated,
     DateTime dateModified,
     bool approved,
     string description,
     string parentRef
     )
 {
     WriteStartElement("category");
     WriteNodeAttributes(id, dateCreated, dateModified, approved);
     WriteAttributeString("description", description);
     WriteAttributeString("parentref", parentRef);
     WriteContent("title", BlogMLContent.Create(title, titleContentType));
     WriteEndElement();
 }
예제 #18
0
 protected void WriteStartBlogMLPost(BlogMLPost post)
 {
     WriteStartElement("post");
     WriteNodeAttributes(post.ID, post.DateCreated, post.DateModified, post.Approved);
     WriteAttributeString("post-url", post.PostUrl);
     WriteAttributeStringRequired("type", "normal");
     WriteAttributeStringRequired("hasexcerpt", post.HasExcerpt.ToString().ToLowerInvariant());
     WriteAttributeStringRequired("views", post.Views.ToString());
     WriteContent("title", BlogMLContent.Create(post.Title, ContentTypes.Text));
     WriteBlogMLContent("content", post.Content);
     if (!string.IsNullOrEmpty(post.PostName))
     {
         WriteContent("post-name", BlogMLContent.Create(post.PostName, ContentTypes.Text));
     }
     if (post.HasExcerpt)
     {
         WriteBlogMLContent("excerpt", post.Excerpt);
     }
 }
예제 #19
0
        protected void WriteStartBlog(
            string title,
            ContentTypes titleContentType,
            string subTitle,
            ContentTypes subTitleContentType,
            string rootUrl,
            DateTime dateCreated)
        {
            Writer.WriteStartElement("blog", BlogMLNamespace);

            WriteAttributeStringRequired("root-url", rootUrl);
            WriteAttributeString("date-created", FormatDateTime(dateCreated));

            // Write the default namespace, identified as xmlns with no prefix
            Writer.WriteAttributeString("xmlns", null, null, BlogMLNamespace);
            Writer.WriteAttributeString("xmlns", "xs", null, XMLSchemaNamespace);

            WriteContent("title", BlogMLContent.Create(title, titleContentType));
            WriteContent("sub-title", BlogMLContent.Create(subTitle, subTitleContentType));
        }
 protected void WriteComment(string id,
                             BlogMLContent title,
                             DateTime dateCreated,
                             DateTime dateModified,
                             bool approved,
                             string userName,
                             string userEmail,
                             string userUrl,
                             BlogMLContent content
                             )
 {
     WriteStartElement("comment");
     WriteNodeAttributes(id, dateCreated, dateModified, approved);
     WriteAttributeStringRequired("user-name", userName ?? "");
     WriteAttributeString("user-url", userUrl ?? "");
     WriteAttributeString("user-email", userEmail ?? "");
     WriteContent("title", title);
     WriteContent("content", content);
     WriteEndElement();
 }
        protected void WriteStartBlog(
            string title,
            ContentTypes titleContentType,
            string subTitle,
            ContentTypes subTitleContentType,
            string rootUrl,
            DateTime dateCreated)
        {
            //WriteStartElement("blog");
            Writer.WriteStartElement("blog", BlogMLNamespace);             // fixes bug in Work Item 2004

            WriteAttributeStringRequired("root-url", rootUrl);
            WriteAttributeString("date-created", FormatDateTime(dateCreated));

            // Write the default namespace, identified as xmlns with no prefix
            Writer.WriteAttributeString("xmlns", null, null, "http://www.blogml.com/2006/09/BlogML");
            Writer.WriteAttributeString("xmlns", "xs", null, "http://www.w3.org/2001/XMLSchema");

            WriteContent("title", BlogMLContent.Create(title, titleContentType));
            WriteContent("sub-title", BlogMLContent.Create(subTitle, subTitleContentType));
        }
예제 #22
0
        public BlogMLComment ConvertComment(FeedbackItem feedbackItem)
        {
            if (feedbackItem == null)
            {
                throw new ArgumentNullException("feedbackItem");
            }
            if (feedbackItem.FeedbackType != FeedbackType.Comment)
            {
                throw new ArgumentException(String.Format(Resources.ArgumentException_CommentTypeMismatch, feedbackItem.FeedbackType, FeedbackType.Comment), "feedbackItem");
            }

            return(new BlogMLComment
            {
                ID = feedbackItem.Id.ToString(CultureInfo.InvariantCulture),
                Title = feedbackItem.Title,
                UserUrl = feedbackItem.SourceUrl != null?feedbackItem.SourceUrl.ToString() : null,
                              UserEMail = feedbackItem.Email,
                              UserName = feedbackItem.Author,
                              Approved = feedbackItem.Approved,
                              Content = BlogMLContent.Create(feedbackItem.Body ?? string.Empty, ContentTypes.Base64),
                              DateCreated = Blog.TimeZone.ToUtc(feedbackItem.DateCreated),
                              DateModified = Blog.TimeZone.ToUtc(feedbackItem.DateModified)
            });
        }
 protected void WriteBlogMLContent(string elementName, BlogMLContent content)
 {
     WriteContent(elementName, content);
 }
예제 #24
0
        /// <summary>
        /// Converts the blog export to BlogML
        /// </summary>
        public void Convert()
        {
            XDocument blogger = XDocument.Load(_bloggerExportPath);

            // extract basic information
            string   blogTitle   = blogger.Element(AtomNamespace + "feed").Element(AtomNamespace + "title").Value;
            DateTime blogUpdated = DateTime.Parse(blogger.Element(AtomNamespace + "feed").Element(AtomNamespace + "updated").Value).ToUniversalTime();
            string   authorName  = blogger.Element(AtomNamespace + "feed").Element(AtomNamespace + "author").Element(AtomNamespace + "name").Value;
            string   authorUri   = blogger.Element(AtomNamespace + "feed").Element(AtomNamespace + "author").Element(AtomNamespace + "uri").Value;
            string   authorEmail = blogger.Element(AtomNamespace + "feed").Element(AtomNamespace + "author").Element(AtomNamespace + "email").Value;

            // assume the updated date and then hunt backwards in time
            DateTime blogCreated = blogUpdated;

            LogMessage(string.Format(CultureInfo.CurrentCulture,
                                     Properties.Resources.Converter_MessageBlogTitle,
                                     blogTitle,
                                     blogUpdated));

            LogMessage(string.Format(CultureInfo.CurrentCulture,
                                     Properties.Resources.Converter_MessageBlogAuthor,
                                     authorName,
                                     authorEmail,
                                     authorUri));

            // parse ATOM entries
            var query = from entry in blogger.Descendants(AtomNamespace + "entry")
                        select new
            {
                Id          = entry.Element(AtomNamespace + "id").Value,
                Published   = DateTime.Parse(entry.Element(AtomNamespace + "published").Value).ToUniversalTime(),
                Updated     = DateTime.Parse(entry.Element(AtomNamespace + "updated").Value).ToUniversalTime(),
                Title       = entry.Element(AtomNamespace + "title").Value,
                Content     = entry.Element(AtomNamespace + "content").Value,
                AuthorName  = entry.Element(AtomNamespace + "author").Element(AtomNamespace + "name").Value,
                AuthorUri   = entry.Element(AtomNamespace + "author").Element(AtomNamespace + "uri") == null ? null : entry.Element(AtomNamespace + "author").Element(AtomNamespace + "uri").Value,
                AuthorEmail = entry.Element(AtomNamespace + "author").Element(AtomNamespace + "email") == null ? null : entry.Element(AtomNamespace + "author").Element(AtomNamespace + "email").Value,
                InReplyTo   = entry.Element(ThrNamespace + "in-reply-to") == null ? null : entry.Element(ThrNamespace + "in-reply-to").Attribute("ref").Value,
                Categories  = (from category in entry.Descendants(AtomNamespace + "category")
                               select new
                {
                    Scheme = category.Attribute("scheme").Value,
                    Term = category.Attribute("term").Value,
                }),
                Links = (from link in entry.Descendants(AtomNamespace + "link")
                         select new
                {
                    Rel = link.Attribute("rel").Value,
                    Type = link.Attribute("type").Value,
                    Href = link.Attribute("href").Value,
                }),
            };

            // separate out the different export categories from the ATOM entries
            Dictionary <string, BlogMLPost> posts      = new Dictionary <string, BlogMLPost>();
            List <CommentWithInReplyTo>     comments   = new List <CommentWithInReplyTo>();
            List <BlogMLAuthor>             authors    = new List <BlogMLAuthor>();
            List <BlogMLCategory>           categories = new List <BlogMLCategory>();
            NameValueCollection             settings   = new NameValueCollection();
            string template = null;

            foreach (var q in query)
            {
                // update the blog created date as we find earlier entires
                if (q.Published < blogCreated)
                {
                    blogCreated = q.Published;
                }

                // create a content holder
                BlogMLContent content = new BlogMLContent();
                content.Text = q.Content;

                // find categories and the type of entry
                List <BlogMLCategoryReference> categoryRefs = new List <BlogMLCategoryReference>();
                string entryKind = null;

                // get the type of entry and any post categories
                foreach (var c in q.Categories)
                {
                    if (c.Scheme == "http://schemas.google.com/g/2005#kind")
                    {
                        entryKind = c.Term;
                    }
                    else if (c.Scheme == "http://www.blogger.com/atom/ns#")
                    {
                        BlogMLCategoryReference categoryRef = new BlogMLCategoryReference();
                        categoryRef.Ref = UpdateCategoriesGetRef(ref categories, c.Term, q.Published);
                        categoryRefs.Add(categoryRef);
                    }
                    else
                    {
                        // we've found a category scheme we don't know about
                        LogMessage(string.Format(CultureInfo.CurrentCulture,
                                                 Properties.Resources.Converter_MessageUnexpectedCategoryScheme,
                                                 c.Scheme));
                    }
                }

                // process entry based on the entry kind
                switch (entryKind)
                {
                case "http://schemas.google.com/blogger/2008/kind#template":
                    template = q.Content;
                    break;

                case "http://schemas.google.com/blogger/2008/kind#settings":
                    LogMessage(string.Format(CultureInfo.CurrentCulture,
                                             Properties.Resources.Converter_ImportingSettings,
                                             q.Id,
                                             q.Content));

                    settings.Add(q.Id, q.Content);
                    break;

                case "http://schemas.google.com/blogger/2008/kind#post":
                    LogMessage(string.Format(CultureInfo.CurrentCulture,
                                             Properties.Resources.Converter_ImportingPost,
                                             q.Title));

                    // get a reference to the author of this entry
                    BlogMLAuthorReference authorReference = new BlogMLAuthorReference();
                    authorReference.Ref = UpdateAuthorsGetRef(ref authors, q.AuthorName, q.AuthorEmail, q.Published);

                    BlogMLPost post = new BlogMLPost();
                    post.Approved = true;
                    post.Authors.Add(authorReference);
                    if (categoryRefs.Count > 0)
                    {
                        post.Categories.AddRange(categoryRefs);
                    }
                    post.Content      = content;
                    post.DateCreated  = q.Published;
                    post.DateModified = q.Updated;
                    post.HasExcerpt   = false;
                    post.ID           = q.Id;
                    post.PostType     = BlogPostTypes.Normal;
                    post.Title        = q.Title;

                    posts.Add(q.Id, post);
                    break;

                case "http://schemas.google.com/blogger/2008/kind#comment":
                    LogMessage(string.Format(CultureInfo.CurrentCulture,
                                             Properties.Resources.Converter_ImportingComment,
                                             q.Title));

                    BlogMLComment comment = new BlogMLComment();
                    comment.Approved     = true;
                    comment.Content      = content;
                    comment.DateCreated  = q.Published;
                    comment.DateModified = q.Updated;
                    comment.ID           = q.Id;
                    comment.Title        = q.Title;
                    comment.UserEMail    = q.AuthorEmail;
                    comment.UserName     = q.AuthorName;
                    comment.UserUrl      = q.AuthorUri;

                    comments.Add(new CommentWithInReplyTo(comment, q.InReplyTo));
                    break;

                default:
                    LogMessage(string.Format(CultureInfo.CurrentCulture,
                                             Properties.Resources.Converter_MessageUnexpectedEntryKind,
                                             entryKind));
                    break;
                }
            }

            // add comments to posts
            foreach (CommentWithInReplyTo comment in comments)
            {
                if (posts.ContainsKey(comment.InReplyTo))
                {
                    BlogMLPost post = posts[comment.InReplyTo];

                    LogMessage(string.Format(CultureInfo.CurrentCulture,
                                             Properties.Resources.Converter_AttachingComment,
                                             comment.Comment.Title,
                                             post.Title));

                    post.Comments.Add(comment.Comment);
                }
                else
                {
                    LogMessage(string.Format(CultureInfo.CurrentCulture,
                                             Properties.Resources.Converter_OrphanedComment,
                                             comment.Comment.Title));
                }
            }

            // build the blog
            LogMessage(Properties.Resources.Converter_BuildingBlogML);

            BlogMLBlog blog = new BlogMLBlog();

            blog.Authors.AddRange(authors);
            blog.Categories.AddRange(categories);
            blog.DateCreated = blogCreated;
            blog.Posts.AddRange(posts.Values);
            blog.Title = blogTitle;

            // add blogger settings as extended properties
            foreach (string name in settings.Keys)
            {
                Pair <string, string> pair = new Pair <string, string>();
                pair.Key   = name;
                pair.Value = settings[name];
                blog.ExtendedProperties.Add(pair);
            }

            // output BlogML
            LogMessage(string.Format(CultureInfo.CurrentCulture,
                                     Properties.Resources.Converter_WritingBlogML,
                                     _blogmlPath));

            XmlWriterSettings writerSettings = new XmlWriterSettings();

            writerSettings.CheckCharacters  = true;
            writerSettings.CloseOutput      = true;
            writerSettings.ConformanceLevel = ConformanceLevel.Document;
            writerSettings.Indent           = true;

            using (XmlWriter writer = XmlWriter.Create(_blogmlPath, writerSettings))
            {
                BlogMLSerializer.Serialize(writer, blog);
            }
        }