-1

I have a fixed length string message in a that looks like this:

"\0\0\0j\0\0\0\vT3A1111        2999BOSH                          2100021        399APV                           2100022  "

This message is created from me reading a byte[] into a StringBuilder to build string.

Above, string portion "\0\0\0j\0\0\0\v" are supposed to be LENGTH and ID fields, both 4 bytes long. However, I am not sure how to extract these 2 values but I can see that HEX 0j is 106 (1+1+8+9+30+9+9+30+9=106 total in length). I am not sure why the "v" is not "0v" above but I know it is supposed to be HEX value representing message id.

First 2 fields of length 4 are HEX, all other are ASCII.

This is not an EDI message (so cannot use EDI parser library) and unlike EDI messages which has some kind of field identifier, I have only stream of bytes and I know only the length of the fields. The fields are:

4  byte long message length      ("\0\0\0j")
4  byte long message id          ("\0\0\0\v")
1  byte long message type        ("T")
1  byte long message sequence    ("3")
8  byte long car Id              ("A1111   ")  
9  byte long part-1 price        ("     2999")
30 byte long part-1 manufacturer ("BOSH                          ")
9  byte long part#               ("2100021  ")
9  byte long part-2 price        ("      399")
30 byte long part-2 manufacturer ("APV                           ")
9  byte long part#               ("2100022  ")

So, above I have 2 parts made by 2 manufacturers but in real example, it could be more parts than just 2:

Part 1, 29.99, made by Bosh, part# 2100021
Part 2, 3.99, made by APV, part# 2100022

I would like to get all price and manufacturer fields out of this flat file string into a List objects where Part is

class Part
{
   public decimal Price {get; set}
   public string Manufacturer {get; set;}
   public string PartNumber {get; set;}
}

So, my List would contain all parts with their prices and manufacturers.

Since I have lengths of each fields, I know I could loop through this string and get me the Part related data. But, I wonder if there is a more elegant and easier way to do this.

Or even better, is there a open source library allowing me to parse something like this?

I receive this message using this method

private TcpClient clientSocket;
private NetworkStream serverStream;

private async System.Threading.Tasks.Task ReadResponseAsync()
{
    if (serverStream.CanRead)
    {
        byte[] readBuffer = new byte[1024];
        StringBuilder receivedMessage = new StringBuilder();
        int readSoFar = 0;

        do
        {
            readSoFar = await serverStream.ReadAsync(readBuffer, 0, readBuffer.Length);
            receivedMessage.AppendFormat("{0}", Encoding.ASCII.GetString(readBuffer, 0, readSoFar));
        } 
        while (serverStream.DataAvailable);

        string msg = receivedMessage.ToString();
    }
    else
    {
        Log("Error", "Cannot read from NetworkStream");
    }
}

@Enigmativity - I tried posting your answer and running it in LinqPad (never used it, just downloaded and installed it) but I dont see the table-like structure you posted in your answer. How do you get that?

Here is what I get enter image description here

cd491415
  • 823
  • 2
  • 14
  • 33
  • I can't tell from your phrasing if the field lengths are variable and if they are encoded within the message using a string like "44118930930930". If they are, how do you parse this? How do you know the first field is 4 bytes and not 44? – nlawalker Feb 16 '19 at 00:24
  • Assuming that the fields ARE fixed length, I have used the `TextFieldParser` class (inside the `Microsoft.VisualBasic` assembly - but to clarify my code is `c#`) to read fixed width data from files – Brendan Green Feb 16 '19 at 00:26
  • @nlawalker I updated question, see the beginning. Sorry about that. Brendan, I'd like to avoid using VB libraries, I looked at that already – cd491415 Feb 16 '19 at 00:30
  • 1
    What about something like this then? [File Helpers - Read Fixed Width](https://www.filehelpers.net/example/QuickStart/ReadFileFixed/). Not sure what the difference is in referencing `Microsoft.VisualBasic.dll` compared to referencing a Nuget package, but either should get you the same outcome – Brendan Green Feb 16 '19 at 00:44
  • 1
    @cd491415 - Do you have a `string` (which is a sequence of `char`) or do you have a `byte[]` (which is a sequence of `byte`)? They are different. – Enigmativity Feb 16 '19 at 00:47
  • Please include an example sample of your input data in your question. Use `
    ` and `
    ` to make the data display as monospace text.
    – Enigmativity Feb 16 '19 at 00:48
  • @Enigmativity I receive message as byte[] but I append it to StringBuilder, so it is string once I get entire message. I am not sure what will
     tags do, the outcome of using them is same as marking portion with back-apostrophe to mark portion as code which I already did.  Thanks
    – cd491415 Feb 16 '19 at 00:53
  • @cd491415 - Could you please provide multiple lines of unadulterated data? Perhaps as valid C# code instead of `
    ` tags?
    – Enigmativity Feb 16 '19 at 00:56
  • @Enigmativity I thought I did that in the very first code snipet in the question. Just remove the "|" characters separating the fields and that is the message. the "-" mean empty space. I dont have C# code for this, it is stream of data coming to me and it looks exactly like that, chunks of byte[] that I then append to StringBuilder to end up with string. I just need to get all fields that are 9 byte long and their following 30 byte long fields (price, manufacturer). Sorry if I am not of more help on this one – cd491415 Feb 16 '19 at 01:01
  • @cd491415 - You're asking us for help. Please make it easy for us. If you can provide unadulterated (you have adulterated your data) in valid c# code then we can easily check that our answer works thus give you a good answer. – Enigmativity Feb 16 '19 at 01:02
  • @Enigmativity I updated entire question, reworded it entirely and provided more info, including the string message I get after reading from stream (as very first block of code) and the method I use to read response and get the message. Sorry it took a while but I was away for few days. I also installed LINQPAD and posted your code below but I am unable to run it in LinqPad, would you mind providing screenshot how you do it. Much appreciated :) – cd491415 Feb 19 '19 at 17:35
  • You can use one of the Edi parser libraries out there to do the work for you. [Edi.Net](https://github.com/indice-co/EDI.Net) or [EdiFabric](https://www.edifabric.com/) (*disclaimer I am the author of EdiNet*) – cleftheris Jul 12 '19 at 08:25

2 Answers2

1

Perhaps try something like this:

void Main()
{
    var line = "00580011T3A1111        2999Bosh                                399APV                                2399MAG                           ";

    var lengths = new[] { 4, 4, 1, 1, 8, 9, 30, 9, 30, 9, 30 };
    var starts = lengths.Aggregate(new[] { 0 }.ToList(), (a, x) => { a.Add(a.Last() + x); return a; });

    var fields = starts.Zip(lengths, (p, l) => line.Substring(p, l).Trim()).ToArray();

    var message = new
    {
        message_length = int.Parse(fields[0]),
        message_id = int.Parse(fields[1]),
        message_type = fields[2],
        message_sequence = int.Parse(fields[3]),
        car_Id = fields[4],
        parts =
            Enumerable
                .Range(0, 3)
                .Select(x => x * 2 + 5)
                .Select(x => new Part
                {
                    Price = decimal.Parse(fields[x]),
                    Manufacturer = fields[x + 1]
                }).ToArray(),
    };
}

public class Part
{
    public decimal Price { get; set; }
    public string Manufacturer { get; set; }
}

On the sample data that I used (which I had to fix as it appears to be corrupted in your question even when I remove the | and replace the - with spaces), I get this result:

message

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Much appreciated, I will take a look at this. :) – cd491415 Feb 16 '19 at 01:20
  • As I see this as an opportunity for me to learn too, it would be nice if you could add some explanation on what are you doing to get your variables starts, fields, and parts in the code above. Also, I never saw before that a message with its fields and values can created like that in C#. Nice. It would help me understand better and thanks again :) – cd491415 Feb 16 '19 at 01:32
  • 1
    @cd491415 - LINQPad is the tool I used to create the graphical representation of the object. It's a must have tool for any .NET developer, IMHO. – Enigmativity Feb 16 '19 at 01:47
  • @cd491415 - I'm happy to help provide the detail that you're after, but I have asked you to provide better detail in your question by providing unadulterated data in C# format and you haven't done that. It's simple. Just give us several lines like `var messages = new [] { "00580011T3A1111 2999Bosh 399APV 2399MAG ", };`. Simple. – Enigmativity Feb 16 '19 at 01:59
  • I am getting this data as byte[] array I convert to string, in a TCP/IP communication. So, I dont have C# code of that data and how it is generated and sent to me. I only have string representation of that data that I receive and I provided that already above unless I am missing something? – cd491415 Feb 18 '19 at 21:52
  • @cd491415 - Yes, you can capture your actual data from the TCP/IP communication and put it in this question as if you had it as valid C#. Then we can easily answer the question. It helps anyone viewing this question to be able write and test their code. – Enigmativity Feb 18 '19 at 22:35
  • I have updated the question entirely, the very first code line is the message I get as string from the method I provided. Hope that helps and thank you again :) I also installed LinqPAD and tried copying and pasting your solution but I dont get the table look you show in the screenshot if I run LinqPad as "C# Program". – cd491415 Feb 19 '19 at 18:16
0

You say "byte[] into a StringBuilder to build string", so I take it you have a string. Perhaps try using SubString(..), something like:

var length = int.Parse(message.SubString(0,4);
var id = int.Parse(message.SubString(4,4);

etc

Edit: If there are unwanted filler characters try

message.Replace('-', ' ');

Not elegant, but it will work.

Rob Smyth
  • 1,768
  • 11
  • 19
  • Yes, that is what I meant by manual. I was wondering if there is a simple way using some open source lib. Or regex or something... Thanks – cd491415 Feb 16 '19 at 01:02