пятница, 30 августа 2013 г.

Преобразование html в обычный текст при помощи элемента управления WebBrowser. C#, WinForms.

[Convert html to plain text using the WebBrowser control. C#, WinForms]

Для преобразования html в обычный текст без форматирования (также известный среди разработчиков как plain text), можно использовать много различных способов, от “ручного” парсинга html-тегов, до использования специальных библиотек для работы с html (самая известная среди них, пожалуй, “Html Agility Pack”). В интернете можно найти очень много примеров, одним из самых интересных способов, является на мой взгляд, применение xslt, где шаблон как фильтр исключает из результата ненужные теги.

Мои исходные данные были в бинарном формате (массив байтов: byte[]), причем данные были представлены в самых различных кодировках. Понятно, что основной проблемой становилось преобразование байтов в строку с учетом правильной кодировки. Вычитывать для этого charcterset “вручную” показалось мне слишко хлопотоным, поэтому я решил использовать элемент управления WebBrowser. Этот контрол базируется на установленном в системе Internet Explorer и справляется с черной работой за нас довольно таки неплохо.

Итак, все что нужно, это загрузить данные в элемент управления и получить значение свойства WebBrowser.Document.Body.InnerText.

Но и здесь, в таком простом сценарии, не обошлось без сюрпризов, с которыми мне пришлось некоторое время повозиться. Дело в том, что после установки источника данных, нельзя сразу обращаться к свойство Document.Body.InnerText, т.к. данные могут быть еще не загруженными, прежде нужно дождаться события DocumentCompleted. В следующем примере учитывается эта особенность. Обратите внимание на вызовы “Application.DoEvents()”, без них вызов события происходить не будет, программа зависает.

Также, хочу отметить, что при обработке значительной коллекции необходимо следить за своевременным освобождением ресурсов (использование Dispose или как здесь через using). Если этого не делать, память утекает, и программа падает. Так было у меня.


private String HtmlToPlainText(byte[] htmlBytes) {
    using (MemoryStream memStream = new MemoryStream(htmlBytes)) {
        memStream.Position = 0;

        using (WebBrowser webBrowser = new WebBrowser()) {
            webBrowser.ScriptErrorsSuppressed = true;

            bool loaded = false;
            webBrowser.DocumentCompleted += (x, y) => {
                loaded = true;
            };

            webBrowser.DocumentStream = memStream;

            // wait DocumentCompleted
            int waitCount = 0;
            Application.DoEvents(); // !!!
            while (loaded == false) {
                Thread.Sleep(200);
                Application.DoEvents(); // !!!
                waitCount++;
                if (waitCount * 50 >= 10000 /*ca. 10 sec*/ )
                    throw new Exception("timeout");
            }

            return webBrowser.Document.Body.InnerText;
        }
    }
}

воскресенье, 25 августа 2013 г.

Linq. Изменение порядка сортировки OrderBy без IComparer.

[Linq. Change the sort order by using OrderBy without IComparer]

Метод-расширение OrderBy предоставляет собой отличное и эффективное средство для упорядочивания коллекций, например, по строковому ключу. Можно использовать версию метода без компаратора, тогда используется его стандартная общая реализация. Все же, для строк рекомендуется использовать специальный строковой компаратор StringComparer, с помощью которого можно учитывать регистр, язык и региональные параметры. Но даже его в некоторых, казалось бы, элементарных случаях может не хватать. Например тогда, когда среди ключей есть нулевые значения, эти элементы занимают самые первые позиции. А мне нужно было их откинуть назад. Что делать? Казалось бы, пришло время браться за реализацию своего компаратора! Но, оказывается, можно обойтись без нее, нужно лишь правильно выбирать ключ, т.е. "подменять" его нулевые значения значением "последнего ряда". У меня это "xxxxxxxxxx".

Итак, для наглядности на примере:

using System;
using System.Collections.Generic;
using System.Linq;

namespace BlogApp
{
    class Program
    {
        static void Main(string[] args) {

            List<MyData> myDataList = new List<MyData>();
            myDataList.Add(new MyData() { Val1 = "D", Val2 = "1" });
            myDataList.Add(new MyData() { Val1 = "B", Val2 = "2" });
            myDataList.Add(new MyData() { Val1 = "A", Val2 = "3" });
            myDataList.Add(new MyData() { Val1 = "D", Val2 = "4" });
            myDataList.Add(new MyData() { Val1 = null, Val2 = "5" });
            myDataList.Add(new MyData() { Val1 = null, Val2 = "6" });
            myDataList.Add(new MyData() { Val1 = "C", Val2 = "7" });
            myDataList.Add(new MyData() { Val1 = "B", Val2 = "8" });

            var res1 = myDataList.OrderBy(x => x.Val1);
            var res2 = myDataList.OrderBy(x => x.Val1 == null ? "xxxxxxxxxx" : x.Val1); // !!!!!!!

            Console.WriteLine("Result 1:");
            Console.WriteLine(String.Join(Environment.NewLine, res1.Select(x => String.Format("{0}:{1}", x.Val2, x.Val1))));
            Console.WriteLine();
            Console.WriteLine("Result 2:");
            Console.WriteLine(String.Join(Environment.NewLine, res2.Select(x => String.Format("{0}:{1}", x.Val2, x.Val1))));
            Console.ReadLine();
        }
    }

    public class MyData
    {
        public String Val1 { get; set; }
        public String Val2 { get; set; }
    }
}

Результат: