Special Characters

As its name suggests, psv uses the pipe character (|) to separate columns of data.

So, how can pipes be used in a table's data?

There are two (and a half) solutions to this problem:

Solution #1: Use Escape Sequences

psv understands the following escape sequences.

Escape Sequence Description
\t a tab character (ASCII/Unicode 0x09)
\n a newline character (ASCII/Unicode 0x0a)
\| a tab character (ASCII/Unicode 0x7c)
\\ a tab character (ASCII/Unicode 0x5c)

So if you just need the occasional | in your tables, just put a \ in front of it. Done.

Example:

| Escape Sequence | Description | | --------------- | ---------------------------------------- | | \t | a tab character (ASCII/Unicode 0x09) | | \n | a newline character (ASCII/Unicode 0x0a) | | \| | a tab character (ASCII/Unicode 0x7c) | | \\ | a tab character (ASCII/Unicode 0x5c) |

Solution #2: Use Your Own Encoding

Instead of bending psv to fit you data, you can also bend your data to fit psv.

This is not necessarily a pretty solution, but it is very simple, works suprisingly well and can be self documenting.

The idea is to find any sequence you like to represent otherwise problematic data (e.g. invisible characters) and replace those sequences when you use the data.

Here is an excerpt of one of the unit tests used to verify the ruler parser.

// The following mappings are used here: // I represents a pipe character // {tab} represents a tab character // {nbsp} represents a non-breaking space character testCases := psv.NewTable().FromString(` | input || ruler | | ---------- || --------- | | I{tab}--- || I -I | | I{nbsp}--- || I{nbsp}-I | | ---------- || --------- | | I-^ || I--I | | I-v || I--I | | I-v^ || I--I | | I-^v || I--I | | I^-v || I--I | | I-^IvII^ || I--I | `) // map encoded values back to their intended equivalents unpack := func(in string) string { in = strings.ReplaceAll(in, "I", "|" ) // pipe (not handled by is.TableFromString) in = strings.ReplaceAll(in, "{tab}", "\t" ) // tab in = strings.ReplaceAll(in, "{nbsp}", "\xa0") // non-breaking space return in } for _, tc := range testCases.DataRows() { var ( givenInput = unpack(tc.Field("input")) // givenInput gets actual pipe and tab characters etc. wantRulerString = unpack(tc.Field("ruler")) ) ... }

And, what about Quotes?

psv does not support quoting, as doing so would require that user's always check that they have a matching closing quote for every opening quote. This becomes a real mess when people start using data that contain's apostrophes (i.e., a single, single-quote)

By not automatically quoting / unquoting data, psv allows the natural use of quotes as data without any special treatment.

| Quote | Author | | ------------------------- | ------ | | this is a dog's breakfast | anon |

Solution #2-and-a-half: But You Can Have Quotes When YOU Want Them

Once caveat to the previous section is that you can add quotes to your data, which psv while happily ignore, and then use psv.Unquote() to remove the quotes only where you need to!

psv.Unquote() is a bit special, in that it only removes matching pairs of quotes, and you must specify excactly which quotes you wish to have removed. All other inputs are returned unmodified!

This is very similar to the Use Your Own Encoding solution described above.

Do note, however, that surrounding a pipe character with quotes will not prevent psv from using the pipe as a column separator.

Only \| will allow an actual pipe character to be used directly as data!

For example:

// The following mappings are used here: // - '!' is mapped to '\' for escape sequences // - '{S}' is mapped to ' ' (space) for escape sequences // - quotes are removed explicitly via psv.Unquote() testCases := psv.NewTable().FromString(` | input | quotes || unquoted | notes | | ------- | ------ || -------- | ----------------------------------------- | | | || | empty string | | ' | ' || ' | un-paired single quotes - no change | | ' | " || ' | | | " | " || " | | | !" | " || !" | | | ------- | ------ || -------- | ----------------------------------------- | | '{S}' | ' || {S} | paired quotes surrounding whitespace | | "{S}" | " || {S} | | | ------- | ------ || -------- | ----------------------------------------- | | '' | ' || | paired quotes surrounding embedded quotes | | '"' | ' || " | | | ''"'' | ' || '"' | | | "" | " || | | | "'" | " || ' | | | ""'"" | " || "'" | | | ------- | ------ || -------- | ----------------------------------------- | | "~' | " || "~' | mis-matched pairs of quotes - no change | | '~" | " || '~" | | | '"~'" | " || '"~'" | | | ~"foo"~ | " || ~"foo"~ | embedded quotes - no change | `) decode := func(in string) string { in = psv.Replace(in, "!", "\\" ) in = psv.Replace(in, "{S}", " " ) return in } for _, tc := range testCases.DataRows() { var ( givenInput = decode(tc.field("input")) // remove quotes givenQuotes = tc.field("quotes") // do not remove quotes wantUnquoted = decode(tc.field("unquoted")) // do not remove quotes ) gotOutput := psv.Unquote(givenInput, givenQuotes) // only remove specific quotes ... }