views:

456

answers:

2

What's the easiest way to filter a stream/reader line-by-line in c# (somewhat like putting sed in the middle of a pipeline). I want to feed an iCalendar file to DDay.iCal but DDay.iCal dies on "VERSION:5.1.1" because it wants a number or number SEMICOLON number (where number is digits (DOT digits)? so the last "." is unexpected).

What I want to do is filter the VERSION: line to something harmless like "VERSION:5.1" so the parser doesn't die.

Update: OK, here's a sample:

BEGIN:VCALENDAR
PRODID:-//SunONE/Calendar Hosting Server//EN
METHOD:PUBLISH
VERSION:5.1.1
X-NSCP-CALPROPS-LAST-MODIFIED:20011208T005613Z
X-NSCP-CALPROPS-CREATED:20010913T223336Z
X-NSCP-CALPROPS-READ:999
X-NSCP-CALPROPS-WRITE:999

Now, the DDay.iCal parser doesn't like "VERSION:5.1.1", so I want to replace that with something harmless like "VERSION:5.1".

The parser interface takes a reader or a stream.

Anyway, I tried to use the code here and it works (reimplementing TextReader on top of a filtered ReadLine).

+3  A: 

The easiest way might be to wrap the stream as an IEnumerable and filter with LINQ:

static void Main(string[] args)
{
    System.IO.StreamReader sr = // ...
    var filtered = Enumerable.Where(
        StreamReaderToSeq(sr), input => { int temp; return int.TryParse(x, out temp); });
}

static IEnumerable<string> StreamReaderToSeq(System.IO.StreamReader sr)
{
    while(!sr.EndOfStream)
    {
        yield return sr.ReadLine();
    }
}

The sequence above filters only integers, but its easy enough to write a better filter to handle all of the inputs you want.

Juliet
+2  A: 

System.IO.Stream uses the decorator pattern so it makes it pretty easy to create your own which wraps an underlying stream. This allows streams such as CryptoStream and GZipStream to wrap any other Stream instance and effectively "override" its Read/Write methods without deriving from the class you want to extend. Very flexible and popular design pattern described in the Gang of Four book.

Now I'm not sure if the API you're working with requires a Stream or StreamReader. There is a significant distinction between the two. A StreamReader works at the text level and operations on characters/lines. A Stream works at the binary level and operates on bytes. In other words, the StreamReader is expected to be able to decode the bytes into text so that the consumer doesn't need to be concerned about the encoding. Use a Stream when encoding doesn't matter (such as when compressing or encyrpting) and use a StreamReader when working with text data.

From what it sounds like, a StreamReader would make more sense here. If the API can accept a StreamReader, just derive your own from TextReader and override its ReadLine method so that the first call returns the line of text you need appended and subsequent calls just function as normal.

The other option is to just use a StringWriter/StringReader and stuff it all into an in-memory string buffer, manipulate it, then pass it.

Josh Einstein