Как преобразовать img url в строку BASE64 в HTML в одной цепочке методов с помощью LINQ или Rx - PullRequest
1 голос
/ 17 января 2012

Я обнаружил, что могу сгенерировать объект XDocument из html, используя SgmlReader.SL.https://bitbucket.org/neuecc/sgmlreader.sl/

Код подобен этому.

public XDocument Html(TextReader reader)
{
    XDocument xml;
    using (var sgmlReader = new SgmlReader { DocType = "HTML", CaseFolding = CaseFolding.ToLower, InputStream = reader })
    {
        xml = XDocument.Load(sgmlReader);
    }
    return xml;
}

Также мы можем получить атрибуты src тегов img из объекта XDocument.

var ns = xml.Root.Name.Namespace;

var imgQuery = xml.Root.Descendants(ns + "img")
    .Select(e => new
        {
            Link = e.Attribute("src").Value
        });

И мыможет загружать и преобразовывать потоковые данные изображения в строку BASE64.

public static string base64String;

WebClient wc = new WebClient();
wc.OpenReadAsync(new Uri(url));  //image url from src attribute
wc.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted);

void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    using (MemoryStream ms = new MemoryStream())
    {
        while (true)
        {
            byte[] buf = new byte[32768];
            int read = e.Result.Read(buf, 0, buf.Length);

            if (read > 0)
            {
                ms.Write(buf, 0, read);
            }
            else { break; }
        }
        byte[] imageBytes = ms.ToArray();
        base64String = Convert.ToBase64String(imageBytes);
    }
}

Итак, я хотел бы сделать следующее:Я хотел бы сделать следующие шаги в одной цепочке методов, таких как LINQ или Reactive Extensions.

  1. Получить атрибуты src тегов img из объекта XDocument.
  2. Получить данные изображения из URL.
  3. Генерировать строку BASE64 из данных изображения.
  4. Заменить атрибуты src на строку BASE64.

Простейший источник и вывод здесь.

  • До

    <html>
    <head>
    </head>
    <body>
        <img src='http://image.com/image.jpg' />
        <img src='http://image.com/image2.png' />
    </body>
    </html>
    
  • После

    <html>
    <head>
    </head>
    <body>
        <img src='data:image/jpg;base64,iVBORw...' />
                <img src='data:image/png;base64,iSDoske...' />
    </body>
    </html>
    

Кто-нибудь знает решение для этого?

Я хотел бы спросить экспертов.

1 Ответ

3 голосов
/ 19 января 2012

И LINQ, и Rx предназначены для поддержки преобразований, которые приводят к появлению новых объектов, а не тех, которые изменяют существующие объекты, но это все еще выполнимо. Вы уже сделали первый шаг, разбив задачу на части. Следующим шагом будет создание компонуемых функций, которые реализуют эти шаги.

1) У вас, по большей части, уже есть этот, но мы, вероятно, должны сохранить элементы для обновления позже.

public IEnumerable<XElement> GetImages(XDocument document)
{
    var ns = document.Root.Name.Namespace;
    return document.Root.Descendants(ns + "img");
}

2) Кажется, это то, где вы ударились о стену с точки зрения компоновки. Для начала давайте создадим наблюдаемый генератор FromEventAsyncPattern. Уже есть такие для асинхронного шаблона Begin / End и стандартных событий, так что это будет где-то посередине.

public IObservable<TEventArgs> FromEventAsyncPattern<TDelegate, TEventArgs> 
    (Action method, Action<TDelegate> addHandler, Action<TDelegate> removeHandler
    ) where TEventArgs : EventArgs
{
    return Observable.Create<TEventArgs>(
        obs =>
        {
            //subscribe to the handler before starting the method
            var ret = Observable.FromEventPattern<TDelegate, TEventArgs>(addHandler, removeHandler)
                                .Select(ep => ep.EventArgs)
                                .Take(1) //do this so the observable completes
                                .Subscribe(obs);
            method(); //start the async operation
            return ret;
        }
    );
}

Теперь мы можем использовать этот метод, чтобы превратить загрузки в наблюдаемые. Исходя из вашего использования, я думаю, что вы могли бы также использовать DownloadDataAsync вместо WebClient.

public IObservable<byte[]> DownloadAsync(Uri address)
{
    return Observable.Using(
             () => new System.Net.WebClient(),
             wc =>
             {
                return FromEventAsyncPattern<System.Net.DownloadDataCompletedEventHandler,
                                             System.Net.DownloadDataCompletedEventArgs>
                          (() => wc.DownloadDataAsync(address),
                           h => wc.DownloadDataCompleted += h,
                           h => wc.DownloadDataCompleted -= h
                          )
                       .Select(e => e.Result);
                //for robustness, you should probably check the error and cancelled
                //properties instead of assuming it finished like I am here.
             });
}

РЕДАКТИРОВАТЬ: Согласно вашему комментарию, вы, кажется, используете Silverlight, где WebClient не является IDisposable и не имеет метод, который я использовал. Чтобы справиться с этим, попробуйте что-то вроде:

public IObservable<byte[]> DownloadAsync(Uri address)
{
    var wc = new System.Net.WebClient();
    var eap = FromEventAsyncPattern<OpenReadCompletedEventHandler,
                                    OpenReadCompletedEventArgs>(
                 () => wc.OpenReadAsync(address),
                 h => wc.OpenReadCompleted += h,
                 h => wc.OpenReadCompleted -= h);
    return from e in eap
           from b in e.Result.ReadAsync()
           select b;
}

Вам понадобится найти реализацию ReadAsync для чтения потока. Вы сможете найти его довольно легко, и сообщение уже было достаточно длинным, поэтому я его пропустил.

3 & 4) Теперь мы готовы собрать все вместе и обновить элементы. Поскольку шаг 3 очень прост, я просто объединю его с шагом 4.

public IObservable<Unit> ReplaceImageLinks(XDocument document)
{
    return (from element in GetImages(document)
            let address = new Uri(element.Attribute("src").Value)
            select (From data in DownloadAsync(address)
                    Select Convert.ToBase64String(data)
                   ).Do(base64 => element.Attribute("src").Value = base64)
           ).Merge()
            .IgnoreElements()
            .Select(s => Unit.Default); 
            //select doesn't really do anything as IgnoreElements eats all
            //the values, but it is needed to change the type of the observable.
            //Task may be more appropriate here.
}
...