Convert HTML to Well-Formatted Microsoft Word Document

Recently I wanted to convert my LINQ via C# tutorial into a Word document (.doc). The tasks are:

  1. Download the content of index page of the entire tutorial.
  2. Interpret the index page and get the title/URI of each chapter and its sections.
  3. Download the content of each chapter/section.
  4. Merge all contents as one well formatted document, with:
    • title
    • table of contents
    • header
    • footer (page number)
    • etc.

There might be several possible solutions, e.g.:

  • Node.js: It is easy to use JavaScript to process downloaded HTML DOM.
  • C#: it is easier to use C# to implement the conversion to Word document.

After searching around, I found CsQuery library, which is available from Nuget:

Install-Package CsQuery

It is a jQuery-like library for DOM process via C#. So The decision is to go with C#.

Download index page HTML and all contents via CsQuery

The first steps are to download everything from this blog:

  1. Download HTML string from index page: http://weblogs.asp.net/dixin/linq-via-csharp, which is easy by just calling WebClient.DownloadString.
  2. In the downloaded HTML string, get the title of the tutorial from the <title> tag of the downloaded HTML string: indexPage["title"].Text()
  3. Get the article content of the index page (get rid of HTML page header, footer, sidebar, article comments …): indexPage["article.blog-post"]
  4. In the page content, the title of each chapter, which is so easy with jQuery-style API: indexPage["article.blog-post"].Children("ol").Children("li")
    1. Get the title of each section.
    2. Get the URI of each section from the HTML hyperlink.
      1. Download HTML string from each section.
      2. Get the article content of the section page (get rid of HTML page header, footer, sidebar, article comments …)
      3. In the contents, downgrade the <h1>, <h2>, <h3>, … tags: replace <h7> to <h9>, <h6> to <h8>, … <h2> to <h4>, <h1> to <h3>. This is a must, because later when merge all contents, chapter title will be <h1> and section title will be <h2>. The headings inside each section must downgrade 2 levels. Again, fortunately, this is very easy with jQuery-style API.
      4. Remove unnecessary hyperlinks.
    3. Merge all section’s HTML.
  5. Merge all chapters’ HTML.

Here is the crawler code:

private static Html DownloadHtml(string indexUrl = @"http://weblogs.asp.net/dixin/linq-via-csharp")
{
    using (WebClient webClient = new WebClient() { Encoding = Encoding.UTF8 })
    {
        Console.WriteLine($"Downloading {indexUrl}.");
        CQ indexPage = webClient.DownloadString(indexUrl);

        CQ article = indexPage["article.blog-post"];
        IEnumerable<IGrouping<string, Tuple<string, string>>> chapters = article
            .Children("ol")
            .Children("li")
            .Select(chapter => chapter.Cq())
            .Select(chapter =>
            {
                Tuple<string, string>[] sections = chapter.Find("h2")
                    .Select(section => section.Cq().Find("a:last"))
                    .Select(section =>
                    {
                        string sectionUrl = section.Attr<string>("href");
                        Console.WriteLine($"Downloading {sectionUrl}.");
                        CQ sectionPage = webClient.DownloadString(sectionUrl);
                                
                        CQ sectionArticle = sectionPage["article.blog-post"];
                        sectionArticle.Children("header").Remove();
                        Enumerable
                            .Range(1, 7)
                            .Reverse()
                            .ForEach(i => sectionArticle
                                .Find($"h{i}").Contents().Unwrap()
                                .Wrap($"<h{i + 2}/>")
                                .Parent()
                                .Find("a").Contents().Unwrap());
                        sectionArticle.Find("pre span").Css("background", string.Empty);
                        sectionArticle.Find("p")
                            .Select(paragraph => paragraph.Cq())
                            .ForEach(paragraph =>
                            {
                                string paragrapgText = paragraph.Text().Trim();
                                if ((paragraph.Children().Length == 0 && string.IsNullOrWhiteSpace(paragrapgText))
                                    || paragrapgText.StartsWith("[LinQ via C#", StringComparison.OrdinalIgnoreCase))
                                {
                                    paragraph.Remove();
                                }
                            });
                        return Tuple.Create(section.Text().Trim(), sectionArticle.Html());
                    })
                    .ToArray();
                return new Grouping<string, Tuple<string, string>>(
                    chapter.Find("h1").Text().Trim(),
                    sections);
            })
            .ToArray();

        return new Html(
            indexPage["title"].Text().Replace("Dixin's Blog -", string.Empty).Trim(),
            chapters);
    }
}

WebClient.ncoding has to be specified as UTF8, otherwise the downloaded HTML will be messy. Also above Grouping class is under Microsoft.FSharp.Linq.RuntimeHelpers namespace. This is the only IGrouping<TKey, TElement> implementation that can be found in .NET libraries.

Represent entire tutorial as one single piece of HTML via T4 template

Above code constructs and returns a Html object, representing all chapters and all sections of  the tutorial. The Html type is actually a T4 template (Text Template Transformation Toolkit) for the entire tutorial:

<#@ template language="C#" debug="true" visibility="internal" linePragmas="false" #>
<#@ import namespace="System.Linq" #>
<html>
    <head>
        <title><#= this.Title #></title>
        <style type="text/css">
            table {
                border-collapse: collapse;
            }

            table, th, td {
                border: 1px solid black;
            }
        </style>
    </head>
    <body>
<# 
foreach (IGrouping<string, Tuple<string, string>> chapter in this.Chapters)
{
#>
        <h1><br /><#= chapter.Key #></h1>
<#
    foreach (Tuple<string, string> section in chapter)
    {
#>
        <h2><#= section.Item1 #></h2>
        <#= section.Item2 #>
<#
    }
}
#>
    </body>
</html>

As fore mentioned. <h1> represents each chapter title, and <h2> represents each section title. A little CSS is used to unify all tables with 1 pixel solid border.  This Html.tt file will automatically generate a Html.cs file, containing above Html type.

The generated Html class is a partial class, so that some custom code can be appended to make is more intuitive:

internal partial class Html
{
    internal Html(string title, IEnumerable<IGrouping<string, Tuple<string, string>>> chapters)
    {
        this.Title = title;
        this.Chapters = chapters;
    }

    internal string Title { get; }

    internal IEnumerable<IGrouping<string, Tuple<string, string>>> Chapters { get; }
}

Straightforward. To get the HTML string, just need to call Html.TransformText method, which is defined in the generated Html.cs.

Convert HTML to Word document via VSTO

As fore mentioned, one possible way is to using Microsoft’s Open XML SDK. It is extremely easy with a third party helper HtmlToOpenXml, which is also available from Nuget:

Install-Package HtmlToOpenXml.dll

Here is the code:

private static byte[] HtmlToWord(string html, string fileName)
{
    using (MemoryStream memoryStream = new MemoryStream())
    using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(
        memoryStream, WordprocessingDocumentType.Document))
    {
        MainDocumentPart mainPart = wordDocument.MainDocumentPart;
        if (mainPart == null)
        {
            mainPart = wordDocument.AddMainDocumentPart();
            new Document(new Body()).Save(mainPart);
        }

        HtmlConverter converter = new HtmlConverter(mainPart);
        converter.ImageProcessing = ImageProcessing.AutomaticDownload;
        Body body = mainPart.Document.Body;

        IList<OpenXmlCompositeElement> paragraphs = converter.Parse(html);
        body.Append(paragraphs);

        mainPart.Document.Save();
        return memoryStream.ToArray();
    }
}

Unfortunately, the result document’s format is totally messed up. There is no other mature library for this (Microsoft’s Power Tools for Open XML provides APIs to convert Word document’s Open XML into HTML, but there is no API to convert HTML to Open XML), so the other way, VSTO, will be the solution.

Microsoft word is a powerful application. It can directly open HTML document, and save it as Word document. So the task becomes:

  1. Save above Html object as a HTML document.
  2. Use Word application to open the saved HTML document.
  3. Format the document.
  4. Save the document as word document.
private static void ConvertDocument(
    string inputFile, WdOpenFormat inputFormat,
    string outputFile, WdSaveFormat outputFormat,
    Action<Document> format = null,
    bool isWordVisible = false)
{
    Application word = null;
    try
    {
        word = new Application { Visible = isWordVisible };

        Console.WriteLine($"Opening {inputFile} as {inputFormat}.");
        word.Documents.Open(inputFile, Format: inputFormat);
        Document document = word.Documents[inputFile];

        format?.Invoke(document);

        Console.WriteLine($"Saving {outputFile} as {outputFormat}");
        document.SaveAs2(outputFile, outputFormat);
    }
    finally
    {
        word?.Documents?.Close();
        word?.Quit();
    }
}

Format word document via VSTO

The task has the following steps (in order):

  1. Download all referenced pictures (<img> tags in HTML), and save them along with the Word document, so that the document can be viewed offline.
  2. Apply a specified template (.dot) to the Word document. This is the easiest way to format document’s
    • title
    • table of contents
    • header
    • footer (page number)
    • etc.
  3. Insert a detailed table of contents to the Word document, which shows all headings of the tutorial.
  4. Insert a abstract table of contents to the Word document, which only shows chapter titles (“Heading 1” fields in Word, or <h1> tags in HTM).
  5. Insert a title to the Word document (“Title” field in word, or <title> tag in HTML)
  6. Insert author next to the title.
  7. Insert page numbers to the Word document footer.
  8. Insert chapter (fields with “Heading 1”) to Word document header via FieldStyleRef.

And the code:

private static void FormatDocument(Document document, Html html, string template, string author = "Dixin Yan")
{
    document.InlineShapes
            .OfType<InlineShape>()
            .Where(shape => shape.Type == WdInlineShapeType.wdInlineShapeLinkedPicture)
            .ForEach(picture =>
            {
                Console.WriteLine($"Downloading {picture.LinkFormat.SourceFullName}");
                picture.LinkFormat.SavePictureWithDocument = true;
            });

    Console.WriteLine($"Applying template {template}");
    document.set_AttachedTemplate(template);
    document.UpdateStyles();

    Range range = document.Range(document.Content.Start, document.Content.Start);

    document.TablesOfContents.Add(range);

    TableOfContents table = document.TablesOfContents.Add(range, LowerHeadingLevel: 1);

    Console.WriteLine($"Adding title {html.Title}");
    Paragraph titleParagraph = document.Paragraphs.Add(range);
    titleParagraph.Range.Text = $"{html.Title}{Environment.NewLine}";
    range.set_Style("Title");

    Console.WriteLine($"Adding author {author}");
    range = document.Range(table.Range.Start, table.Range.Start);
    Paragraph authorParagraph = document.Paragraphs.Add(range);
    authorParagraph.Range.Text = $"{author}{Environment.NewLine}";
    range.set_Style("Author");

    range = document.Range(table.Range.End, table.Range.End);
    range.InsertBreak(WdBreakType.wdPageBreak);

    document.Sections.OfType<Section>().ForEach(section =>
    {
        range = section.Headers[WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
        range.Fields.Add(range, WdFieldType.wdFieldStyleRef, @"""Heading 1""", true);

        section.Footers[WdHeaderFooterIndex.wdHeaderFooterPrimary].PageNumbers.Add(
            WdPageNumberAlignment.wdAlignPageNumberCenter);
    });
}

The VSTO programming is not intuitive, and APIs are lack of examples. It was quite time consuming to insert the FieldStyleRef - the style name is not “Heading 1”, but “"Heading 1"”, the double quote around the style ref name is required.

Save as Word document via VSTO

The is the method to save as Word document (.doc)

private static void SaveDocument(Html html, string outputDocument)
{
    string tempHtmlFile = Path.ChangeExtension(Path.GetTempFileName(), "htm");
    string htmlContent = html.TransformText();
    Console.WriteLine($"Saving HTML as {tempHtmlFile}, {htmlContent.Length}.");
    File.WriteAllText(tempHtmlFile, htmlContent);

    string template = Path.Combine(PathHelper.ExecutingDirectory(), "Book.dot");
    ConvertDocument(
        tempHtmlFile, WdOpenFormat.wdOpenFormatWebPages,
        outputDocument, WdSaveFormat.wdFormatDocument,
        document => FormatDocument(document, html, template));
}

And this is how to call it:

private static void Main(string[] arguments)
{
    string outputDirectory = arguments.Any() && !string.IsNullOrWhiteSpace(arguments.First())
        ? arguments.First()
        : (PathHelper.TryGetOneDrive(out outputDirectory)
            ? Path.Combine(outputDirectory, @"Share\Book")
            : Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory));

    Html html = DownloadHtml();
    SaveDocument(html, Path.Combine(outputDirectory, $"{html.Title}.doc"));
}

By default the document is saved to my local OneDrive directory, so that readers and always get the latest version of tutorial from there. If OneDrive does not exist, it is saved to local desktop.

Share document via OneDrive

To get the OneDrive local path:

  1. First lookup the registry: HKEY_CURRENT_USER\Software\Microsoft\OneDrive
  2. If not found, then lookup a .ini file in %LocalApplicationData%\Microsoft\OneDrive\Settings\Personal

The last line of the .ini file contains the local OneDrive path, e.g.:

library = 1 4 A3BD24426A36B9EE!129 1388966861 "SkyDrive" Me personal "D:\SkyDrive"

And here is the implementation of above TryGetOneDriveRoot method:

public static bool TryGetOneDriveRoot(out string oneDrive)
{
    oneDrive = Registry.GetValue(
        @"HKEY_CURRENT_USER\Software\Microsoft\OneDrive", "UserFolder", null) as string;
    if (!string.IsNullOrWhiteSpace(oneDrive))
    {
        return true;
    }

    string settingsDirectory = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        @"Microsoft\OneDrive\Settings\Personal");
    if (!Directory.Exists(settingsDirectory))
    {
        return false;
    }

    try
    {
        string datFile = Directory.EnumerateFiles(settingsDirectory, "*.dat").FirstOrDefault();
        string iniFile = Path.ChangeExtension(datFile, "ini");
        oneDrive = File.ReadLines(iniFile)
            .Last(line => !string.IsNullOrWhiteSpace(line))
            .Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
            .Last()
            .Trim('"');
        return !string.IsNullOrWhiteSpace(oneDrive);
    }
    catch (Exception exception) when (exception.IsNotCritical())
    {
        return false;
    }
}

After saving the file to the right location, it is automatically uploaded to OneDrive:

image

Conclusion

It is not straightforward to perform the entire job. Many technologies have to be involved:

  • CsQuery is used for HTML DOM traversal and manipulation
  • T4 template is used for HTML merging and formatting.
  • VSTO is used to open, format, and save/convert HTML file to Microsoft Word document.
  • OneDrive is used to share the latest build of the document.

The is the final look of the project (Book.csproj):

image

And below is the converted Word document (no manual editing at all):

  • First page: title, author, abstract table of contents
    image
  • Detailed table of contents:
    image
  • Beginning of a chapter:
    image

Currently, the entire tutorial has 558 pages. Hope it helps.

76 Comments

  • awesome

  • Great! If you could list all namespaces in program.cs, it would be great.
    Because I got some errors:
    'Document' is an ambiguous reference between 'Microsoft.Office.Interop.Word.Document' and 'DocumentFormat.OpenXml.Wordprocessing.Document'.

    Here are the namespaces in my code:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Text;
    using CsQuery;
    using CsQuery.ExtensionMethods;
    using Microsoft.FSharp.Linq.RuntimeHelpers;
    using System.IO;
    using DocumentFormat.OpenXml.Packaging;
    using DocumentFormat.OpenXml.Wordprocessing;
    using Microsoft.Office.Interop.Word;

  • awesome Great!

  • @Hui The project is uploaded here https://github.com/Dixin/CodeSnippets/tree/master/Dixin.Office.HtmlToWord

  • Getting error in Invariant function. Can you please help me

  • @Babu

    That is C# 6 syntax. If you are using older C#, please replace
    Invariant($"Output directory {outputDirectory}.")
    With
    String.Format("Output directory {0}.", outputDirectory)

    Please let me know if there is any other problems.

    Thanks.
    Dixin

  • Ok, this is amazing. Haven't tried it yet but I'm already thankful for the clean explanation and easy-to-follow organization. I have spent a few hours trying to figure out a good solution for this problem and this is by far the best article I have read on the issue. Many, many thanks for your effort to make this understandable.

  • the content flow of this article is really good. first list out requirements, then design logics, last with detailed code. very well organized.

  • Nice article but this won't work if it's a server application like ASP.NET/MVC since you are using office interop to manipulate and save as document. For your case it's ok since it's a console application.

  • Thankyou so much. This is exactly what I am searching for.

  • Great!
    I Used it in my Website.
    thank you.

  • that's great , but the source code is missing on GIT,
    this url does not work : https://github.com/Dixin/CodeSnippets/tree/master/Dixin.Office.HtmlToWord
    can you please share a working link.

  • https://github.com/Dixin/CodeSnippets/tree/master/Blog/Dixin.Office.HtmlToWord

  • پکیج های روف تاپ عموما روی سقف ساختمان به سادگی و بدون برهم زدن نمای ساختمان نصب می شود. این دستگاه ها معمولا برای فضا ها و ظرفیت های بالا مورد استفاده قرار می گیرند. از این دستگاه ها معمولا برای کارخانه ها ،سوله ها ،ورزشگا ها، مساجد ،فروشگا ها و سینماها استفاده می شود. این محصول را میتوانید از شرکت تهویه نوین ایرانیان تهیه نمایید.
    https://tahvienovin.com/product/roof-top

  • در دستگاه های آبسردکن <a href="https://www.jolgeh.ir/%d8%b4%db%8c%d8%b1-%d8%a2%d8%a8%d8%b3%d8%b1%d8%af%da%a9%d9%86/">شیر آبسردکن</a> بالاترین میزان مصرف را دارد که به همین دلیل تعمیر آن امر بسیار مهمی است و هنگام خرید شیر آبسردکن باید دقت کنید که حتما شیر خریداری شده مشابه شیر قبلی باشد. آبسردکن ها دارای مدل های مختلفی هستند و شیر های مختلفی دارند که در دونوع پلاستیکی و تولید می شوند.

  • ما به عنوان دبستان غیر دولتی پیشرو برای اولین بار در ایران با ارائه طرح کیف در مدرسه توانستیم گام به گام با آموزش نوین دنیا پیش رفته و کیفیت آموزش را ارتقا بخشیم و توانایی کودکانمان را در تمامی مهارت های زندگی مانند ایجاد تفکر واگرا و همگرا ، قدرت حل مسئله ، مسئولیت پذیری ،عزت نفس و توجه و تمرکز در آنان ایجاد نموده و در آموزش کامپیوتر و زبان انگلیسی که از مهارت های بسیار لازم فردای کودکانمان است همواره پیشگام بوده ایم.

  • https://ma-study.blogspot.com/

  • مشاوره مهندسی و فروش آنلاین داکت اسپلیتهای معمولی و اینورتر

  • perfect

  • https://shofazhiran.com/product/%d8%a8%d9%88%d8%b3%d8%aa%d8%b1-%d9%be%d9%85%d9%be-%d8%a2%d8%aa%d8%b4-%d9%86%d8%b4%d8%a7%d9%86%db%8c/

  • It's a truly clear point of view.
    https://www.aliasharma.in/aerocity-escorts.html
    https://www.thebangaloreescorts.in/escorts/rajaji-nagar.html
    http://www.thegoaescort.com/dudhsagar-waterfall-call-girls.html
    http://www.rituwalia.in/locations/aerocity-escorts.html
    https://www.dwarkacallgirls.com/aerocity-escorts-service/
    https://www.mallikakhan.in/aerocity-escorts.html
    https://www.tanishababy.in/aerocity-escorts.html
    http://www.tanvirai.in/aerocity-escorts.html
    http://www.sweetdelhiescorts.com/mahipalpur-escorts.html
    https://www.escortsouthdelhi.com/escorts-in-mahipalpur/
    http://www.russianescortsindelhi.in/mahipalpur-escorts.html
    http://www.delhitescorts.com/mahipalpur-call-girls.html
    http://www.delhi37.in/russian-mahipalpur-escort-girls/
    http://www.royalvvip.in/dwarka-escorts/
    https://www.royalangels.in/escorts-service-in-dwarka.html
    http://www.desidelhicallgirls.in/call-girl-delhi-kamini.html
    http://www.escortsindelhie.in/hot-dwarka-escorts/
    http://www.delhiescortsgirls.co.in/dwarka-call-girls.html
    http://www.delhincrescortgirl.com/connaught-place-call-girls.html
    https://www.callgirlsservicemahipalpur.com/connaughtplace.php
    http://www.escortservicesindelhi.in/vip-connaught-place-call-girls.html
    https://www.escortdelhi.net/connaught-place-escorts.html
    https://www.modelescortsindelhi.com/Connaught-Place-call-girls.html
    http://www.priyadelhiescorts.in/janakpuri-escorts-services.html
    http://www.a1delhiescort.in/call-girls/janakpuri.html
    http://www.surbhisinha.in/janakpuri-escort-girls.html
    http://www.hifiescortsdelhi.com/escorts-in-janakpuri/
    https://www.delhi-escorts.in/independent-call-girls-janakpuri.html
    http://www.rituwalia.in
    Watchfully update us as unequivocally as key for extra.

  • After reading a few of your blog posts, I came to the conclusion that this website offered advice. maintain the configuration place.
    https://www.pinkjaipurescorts.com/independent-escorts-jaipur.html
    https://www.pinkjaipurescorts.com/russian-escorts-jaipur.html
    https://www.pinkjaipurescorts.com/hotel-escorts-jaipur.html
    https://www.pinkjaipurescorts.com/ajmer-escorts.html
    https://www.pinkjaipurescorts.com/alwar-escorts.html
    https://www.pinkjaipurescorts.com/jodhpur-escorts.html

  • I read the essay. I think you worked really hard on this article. I value what you do.
    https://www.callgiral.in/
    https://www.agraescort.co.in/
    https://www.babesofcochin.in/
    https://www.callgirlsservicejaipur.com/

  • I enjoy reading your works because they make me smarter and more confident. I'll be watching for your next article in the hopes of learning more from you in the future. Welcome aboard.
    https://www.elitepassion.club/call-girls/patna
    https://www.elitepassion.club/call-girls/raipur
    https://www.elitepassion.club/call-girls/dehradun
    https://www.elitepassion.club/call-girls/mumbai
    https://www.elitepassion.club/call-girls/bangalore
    https://www.elitepassion.club/call-girls/chandigarh
    https://www.elitepassion.club/call-girls/gurgaon
    https://www.elitepassion.club/call-girls/chennai
    https://www.elitepassion.club/call-girls/hyderabad

  • very useful, thanks mate!

  • Thank you for every other informative site. Where else may I be getting that type of information written in such a perfect method? I have an undertaking that I am simply now running on, and I have been on the glance out for such information

  • The fundamental place of union of our Chennai escort association affiliation is redirection and fulfillment of clients. Despite what your necessities are, our call young ladies in Chennai set forth a genuine endeavor to satisfy your sales.

  • I really like your article. You wrote it very well. I hope every time I want to read more of it. I really like your article.

  • If your love life lacks fun and excitement or you miss your girlfriend in Anjuna Beach, the best way to say goodbye to any worries or emotional pain is to book Anjuna Beach Escorts from Ojolit. Check and drop a call on your choicest Independent Anjuna Beach Escorts for booking.

  • ❤️ <a href="https://escortchandigarhagency.in/zirakpur-call-girl/">zirakpur call girl</a>

    Hi, my name is Liza. I was born in zirakpur. I recently turned 22 years of age. My figer is 36-28-38. when I go outside everyone is looking me. I am a college student working part-time as a call girl with zirakpur Escort Service. I am seeking a guy who can stay with me at night and evening. If you are single and want to have some fun tonight, call or WhatsApp me.❤️.

  • nice and informational blog. this article is very excellent and awesome. I am your regular visitor and I always like and share your articles to my friends and your website is awesome',.


  • A mohali escort service agency is a company that provides escorts, girls, for clients, usually for sexual services with free home delivery..❤️..

  • Hi, my name is Avani. I was born in chandigarh. I recently turned 21 years of age. My figer is 36-28-38. when I go outside everyone is looking me. I am a college student working part-time as a call girl with chandigarh Escort Service. I am seeking a guy who can stay with me at night and evening. If you are single and want to have some fun tonight, call or WhatsApp me.

  • Everyone really likes the content of your articles. and i am one of them that would like you to write for us to read again and again.

  • This was a really great contest and hopefully I can attend the next one.

  • The Bhopal defense group This evening, explore your true feelings of love.Hi there, friends! Our aim is to present our products as artistic works that make people happy. Since a smile is a person's best ornament, we truly are placing a smile on your joyful faces. I think that structure is the single most significant element that genuinely adds to the beauty of life.

  • We're very grateful for every gorgeous blog post that you've published. Are there any other issues your blog posts must be able to solve? To comprehend the particulars? A technique to assist writers in improving their writing?

  • The site was developed to make users feel comfortable and to provide them with more information. I want to express my appreciation for the newspaper-based site.

  • This is a wonderful site. I'm astonished that I've added this website to my Bookmarks. We're grateful! In addition, I've shared your site with my family and friends.

  • Also, I've recommended the site to my friends, who I trust. The site is fantastic. I recommend inviting others to join the site in the same manner.

  • It is possible to access the site while you're reading blog posts while you're reading. The site is unique and well-organized. I'm sure that the site will be amazing.

  • We appreciate every piece of information. Are there other ways to get the information? I've come up with many new ideas.

  • This style could form the basis of a collection that's very popular. This is a style that I'd like to learn more about. It's distinctive and different from other styles.

  • The article was extremely informative. I've already recommended it to my friends and family. The website is amazing, and I'll recommend it to my family and acquaintances.

  • Keep sharing great content. This Blog is among my top sites. I find it interesting. Furthermore, I have read a lot of interesting articles.

  • This website is full of amazing writing. Thank you for your discussion. "Be completely committed to keeping track of every single step you make.

  • I am a graduate in this field, which is extremely instructive and helpful. It's also extremely. It's beautiful and also stunning.

  • We're grateful for the amazing writing you've written. Should you not be, how could you improve your communication skills to convey this information?

  • This website is perfect for anyone looking for info. It's loaded with helpful information. The task you're working on could be completed in a short time.

  • You must not stop writing these articles. I want to read it every day. The more I read, the happier I am.

  • The layout and design and the information that you provide are fantastic. This is exactly what I'm trying for. The effort is well worth the reward. I am truly grateful to you.

  • This is a stunning website. It's informative and contains important information. We appreciate your information. It's a blog I love reading, and I am grateful to you.

  • This is an excellent article. This is a great reference. We are grateful for your giving us these amazing ideas. The article is excellent.

  • Your writing skills and the layout that you've designed for your website are stunning. Amazing, exactly what I was seeking.

  • Also, I've recommended the site to my friends, who I trust. The site is fantastic. I recommend inviting others to join the site in the same manner.

  • We appreciate every piece of information. Are there other ways to get the information? I've come up with many new ideas.

  • This website is full of amazing writing. Thank you for your discussion. "Be completely committed to keeping track of every single step you make.

  • This website is perfect for anyone looking for info. It's loaded with helpful information. The task you're working on could be completed in a short time.

  • I found your site to be extremely helpful and informative. Thank you for the tremendous effort you've put into it.

  • The website is amazing. I would highly recommend it to my friends and family. It's original. I'm excited to share the article's details with my readers.

  • You may have found a fascinating profession that's brimming with details. Amazingly, you complete your mission. One more task was to be completed, which was the most important thing.

  • Your letter was fantastic, and I've suggested your website to colleagues and acquaintances. It's an amazing site, and I'll recommend it to my colleagues.

  • A lot of people will be thankful for the efforts you've contributed to helping others. Are there websites that can assist you in improving your writing abilities?

  • It's an exciting concept. Thank you for your careful study. The gorgeous setting is worth the cost. It's simple to go back to work.

  • This is a fantastic infographic on this kind of site. We appreciate your sharing of this information. Create a habit of being dedicated to your work.

  • Thank you for the blog posts that you've created. It's great. Where else can we offer an abundance of information as well as facts in such an elegant manner?

  • It's the same topic , but I was quite surprised to see the opinions I didn't think of. My blog also has articles on these topics, so I look forward to your visit. <a href="https://google.ad/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">casinosite</a>

  • Why couldn't I have the same or similar opinions as you? T^T I hope you also visit my blog and give us a good opinion. <a href="https://toolbarqueries.google.ws/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">baccaratsite</a>

  • Your article has answered the question I was wondering about! I would like to write a thesis on this subject, but I would like you to give your opinion once :D <a href="https://toolbarqueries.google.vu/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">slotsite</a>

  • I came to this site with the introduction of a friend around me and I was very impressed when I found your writing. I'll come back often after bookmarking! <a href="https://toolbarqueries.google.vg/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">baccaratcommunity</a>

  • You may remark on the request design of the blog. You should visit it's psyche boggling. Your blog review would swell up your guests. I was to an extraordinary degree satisfied to discover this site.I anticipated that would thank you for this incredible read!!

  • Positive site, where did u come up with the information on this posting? I'm pleased I discovered it though, ill be checking back soon to find out what additional posts you include.

  • Our Nainital call girls are here to meet you right in your hotel room. We provide ac rooms for gentlemen so they can have a comfortable and cozy experience. As the temperature of AC goes down, The level of excitement and pleasure goes up.

  • We have different types of girls in our Escorts Service Noida. You like any type of girl, whether it is nepali, kashmiri and russian. We keep all types of girls available 24/7 to fully satisfy the customer so that if you want a call girl then Book sexy call girls from our Noida Escorts Service. Our professional and Vip call girl services in Noida. Call 9899869190 now!!

  • Outstanding post once again. I am looking forward to more updates.

  • How many times will I read your article? It opened my eyes to the world. There is always new knowledge inserted.

Add a Comment

As it will appear on the website

Not displayed

Your website