Given the RFC 1035 message diagram from Section 4.1.1 (Header section format) as a string:
http://www.ietf.org/rfc/rfc1035.txt

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                      ID                       |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    QDCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ANCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    NSCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|                    ARCOUNT                    |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Where (every column of the table is 1 bit):

ID is 16 bits
QR = Query (0) or Response (1)
Opcode = Four bits defining kind of query:
  0:    a standard query (QUERY)
  1:    an inverse query (IQUERY)
  2:    a server status request (STATUS)
  3-15: reserved for future use
AA = Authoritative Answer bit
TC = Truncation bit
RD = Recursion Desired bit
RA = Recursion Available bit
Z = Reserved
RCODE = Response code
QC = Question Count
ANC = Answer Count
AUC = Authority Count
ADC = Additional Count

Write a function, member function, class or template that accepts a similar multi-line string as input to define a data structure or something else able to decode or store a header with that specified bit structure.

If your language has macros, introspection, code generation, or powerful enough templates, then accept such string at compile-time to define the header data structure statically.

Such "Header" function or template should accept a table with 8, 16, 32 or 64 columns, and any number of rows. For simplicity the only allowed symbols to define the table are + - | (plus, minus, pipe), and whitespace. Lines of the input string composed just of whitespace should be ignored. Leading and trailing whitespace in the input string should be ignored, as well as before and after each table row. The box for each bit of the diagram takes four chars "+--+". The code should perform a little of validation of the input string, but for brevity a full validation is not required.

Bonus: perform a thoroughly validation of the input string.

function interpret(sequence lines)
    if remainder(length(lines),2)!=1 then
        crash("missing header/footer?")
    end if
    string l1 = lines[1]
    integer w = length(l1)
    integer bits = (w-1)/3  -- sug: check this is 8/16/32/64
    if l1!=join(repeat("+",bits+1),"--") then
        crash("malformed header?")
    end if
    sequence res = {}
    integer offset = 0
    for i=1 to length(lines) do
        string li = lines[i]
        if remainder(i,2) then
            if li!=l1 then
                crash("missing separator (line %d)?",{i})
            end if
        else
            if li[1]!='|' or li[w]!='|' then
                crash("missing separator on line %d",{i})
            end if
            integer k = 1
            while true do
                integer l = find('|',li,k+1)
                string desc = trim(li[k+1..l-1])
                {k,l} = {l,(l-k)/3}
                res = append(res,{desc,l,offset})
                offset += l
                if k=w then exit end if
            end while
        end if
    end for
    res = append(res,{"total",0,offset})
    return res
end function
 
procedure unpack(string data, sequence res)
    if length(data)*8!=res[$][3] then
        crash("wrong length")
    end if
    string bin = ""
    for i=1 to length(data) do
        bin &= sprintf("%08b",data[i])
    end for
    printf(1,"\n\nTest bit string:\n%s\n\nUnpacked:\n",{bin})
    for i=1 to length(res)-1 do
        {string name, integer bits, integer offset} = res[i]
        printf(1,"%7s, %02d bits: %s\n",{name,bits,bin[offset+1..offset+bits]})
    end for
end procedure
 
function trimskip(string diagram)
--
-- split's ",no_empty:=true)" is not quite enough here.
-- Note that if copy/paste slips in any tab characters,
-- it will most likely trigger a length mismatch error.
--
    sequence lines = split(diagram,'\n')
    integer prevlli = 0
    for i=length(lines) to 1 by -1 do
        string li = trim(lines[i])
        integer lli = length(li)
        if lli then
            if prevlli=0 then
                prevlli = lli
            elsif lli!=prevlli then
                crash("mismatching lengths")
            end if
            lines[i] = li
        else
            lines[i..i] = {}
        end if
    end for
    return lines
end function
 
constant diagram = """
 
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      ID                       |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    QDCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ANCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    NSCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ARCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 
"""
 
sequence lines = trimskip(diagram)
sequence res = interpret(lines)
printf(1,"--Name--  Size  Offset\n")
for i=1 to length(res) do
    printf(1," %-7s   %2d  %5d\n",res[i])
end for
 
unpack(x"78477bbf5496e12e1bf169a4",res)
