简体   繁体   中英

How to convert ByteString to Text using map to apply a custom Word8 to Char function?

I'm trying to learn Haskell and I thought I would try and replicate the Linux 'xxd' utility. However I am stuck on the right-hand column (which displays ASCII or a blank for non-printing characters).

As a reminder, typical xxd output looks like this:

00000000: 4927 6d20 7472 7969 6e67 2074 6f20 6c65  I'm trying to le
00000010: 6172 6e20 4861 736b 656c 6c2e 2041 7320  arn Haskell. As 
...

I also want to take advantage of the Unicode "Control Pictures" block to display little symbols for the control codes 0..31 rather than dots or placeholders. So I have a helper function that converts a Word8 to a Char and at the same time replaces the control characters with the equivalent from the Control Pictures block.

http://www.unicode-symbol.com/block/Control_Pictures.html

Constraints: Eventually the program will read a file from disk so I am expecting to read in a ByteString or lazy ByteString. Also I want to use Data.Text to hold the output rather than String.

Ideally: I would like to avoid converting the ByteString to something else wholesale eg [Word8] because - ultimately - I need to learn how to work with it, not around it.

My problem is that I can't get map to work for me. Neither B.map nor T.map will work because they expect a function ([a] -> [a]) . Prelude.map looks more promising as it expects ([a] -> [b]) but I can't get it to work with the imported types. So I had a go at defining my own map (just to try and find something that would work which I could then replace with a built-in function when I had a better understanding) but that isn't working either.

The non-functioning code I have is as follows

import qualified Data.ByteString as B
import qualified Data.Text as T
import Data.Word
import Data.Char

{- Make some normally undisplayable bytes into displayable chars -}
displayableChar :: Word8 -> Char
displayableChar w
  | i < 32  = chr (0x2400 + i)  -- 0x2420 control codes
  | i < 33  = chr 0x2423        -- 0x2423 trough for space
  | i < 127 = chr i
  | i < 128 = chr 0x2421        -- 0x2421 del
  | otherwise = ' '
  where i = fromIntegral w


mymap :: (Word8 -> Char) -> B.ByteString -> [Char]
mymap f bstr
  | bstr == B.empty = []
  | otherwise       = f x : map f xs
  where
    x = B.head bstr
    xs = B.tail bstr


test_data = B.pack [1..250]

Advice and suggestions welcome please on what is the 'right' way to apply displayableChar to each byte in the ByeString and get Text out.

You wrote map instead of mymap in your function definition:

mymap :: (Word8 -> Char) -> B.ByteString -> [Char]
mymap f bstr
  | bstr == B.empty = []
  | otherwise       = f x : mymap f xs   -- <- here
  where
    x = B.head bstr
    xs = B.tail bstr

Another way is to use B.unwrap to get a list of Word8 so you can then apply map which works on lists:

mymap :: (Word8 -> Char) -> ByteString -> [Char]
mymap f bs = map f (B.unwrap bs)

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