简体   繁体   中英

How to assign IObservable<Unit> return value to a XDocument instance

I'm a Reactive Extension beginner. Gideon Engelberth gave me excellent answer about Reactive Extension in my question.

How to convert img url to BASE64 string in HTML on one method chain by using LINQ or Rx

Now I have second question that How to assign IObservable return value to a XDocument instance.

Gideon gave me bellow sample.

    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);
    }

I'd like to do like this. Bud It seems to be hard...

public void Convert(XDocument input, out XDocument output)
{
    output = ReplaceImageLinks(input);
}

So what you have here is only half useful. It seems the Gideon has answered your question but, perhaps didnt know your extra requirements. So I assume that you want take an XDoc, convert all of the Image src attributes from a path to the BASE64 conent that the path represents. You then want to return that new XDoc object once all the processing is done. Correct?

If that is the case, then you could do this.

public IObservable<XDocument> ReplaceImageLinks(XDocument document)
{
    return Observable.Create<XDocument>(o=>
    {
        try{
            var images = document.Descendants("Image");
            Parallel.ForEach(images, SwapUriForContent);
            o.OnNext(document);
            o.OnCompleted();
        }
        catch(Exception ex)
        {
            o.OnError(ex);
        }
        return Disposable.Empty;    //Should really be a handle to Parallel.ForEach cancellation token.
    });
}

private static void SwapUriForContent(XElement imageElement)
{
    var address = new Uri(imageElement.Attribute("src").Value, UriKind.RelativeOrAbsolute);
    imageElement.Attribute("src").Value = Download(address);
}

public static string Download(Uri input)
{
    //TODO Replace with real implemenation
    return input.ToString().ToUpper();
}

Which I just tested with

string str =
    @"<?xml version=""1.0""?>
    <!-- comment at the root level -->
    <Root>
        <Child>Content</Child>
        <Image src=""image.jpg""/>
        <Child>
            <Image src=""image2.jpg""/>
        </Child>
    </Root>";
XDocument doc = XDocument.Parse(str);
ReplaceImageLinks2(doc).Dump(); 

However, there are things wrong with this. 1st, we are mutating state. This is not a great start when dealing with concurrency. We really should be returning a new XDoc that has the correct values. 2nd this seem much more of a TPL problem than an Rx one. Rx is best for dealing with streaming of incoming data, what you have here is a need to perform parallel processing. I think a far better option would be to just task.

public Task<XDocument> ReplaceImageLinks3(XDocument source)
{
    return Task.Factory.StartNew(()=>
    {
        var copy = new XDocument(source);
        var images = copy.Descendants("Image");
        Parallel.ForEach(images, SwapUriForContent);
        return copy;
    });
}

See the the section of my book When to Use Rx in IntroToRx.com

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM