For me this kind of problem would be just past the use-a-real-parsing-tool threshold. Here's a quick working example with Attoparsec:
import Control.Applicative
import Data.Attoparsec (maybeResult)
import Data.Attoparsec.Char8
import qualified Data.Attoparsec.Char8 as A (takeWhile)
import qualified Data.ByteString.Char8 as B
import Data.Maybe (fromMaybe)
data Entry = Entry String [String] [[String]] deriving (Show)
entry = Entry <$> table <*> cols <*> many1 vals
items = sepBy1 (A.takeWhile $ notInClass " \n") $ char ' '
table = string (B.pack "TABLE ") *> many1 (notChar '\n') <* endOfLine
cols = string (B.pack "COLUMNS ") *> (map B.unpack <$> items) <* endOfLine
vals = string (B.pack "VALUES ") *> (map B.unpack <$> items) <* endOfLine
parseEntries :: B.ByteString -> Maybe [Entry]
parseEntries = maybeResult . flip feed B.empty . parse (sepBy1 entry skipSpace)
And a bit of machinery:
pretty :: Entry -> String
pretty (Entry t cs vs)
= unwords $ ["TABLE", t, "COLUMNS"]
++ cs ++ concatMap ("VALUES" :) vs
layout :: B.ByteString -> Maybe String
layout = (unlines . map pretty <$>) . parseEntries
testLayout :: FilePath -> IO ()
testLayout f = putStr . fromMaybe [] =<< layout <$> B.readFile f
And given this input:
TABLE test
COLUMNS a b c
VALUES 1 2 3
VALUES 4 5 6
TABLE another
COLUMNS x y z q
VALUES 7 8 9 10
VALUES 1 2 3 4
We get the following:
*Main> testLayout "test.dat"
TABLE test COLUMNS a b c VALUES 1 2 3 VALUES 4 5 6
TABLE another COLUMNS x y z q VALUES 7 8 9 10 VALUES 1 2 3 4
Which seems to be what you want?