- MakeTable
- AHK forum post
- Reddit post
- Usage
- MakeTable.Prototype.GetMarkdown
- MakeTable.Prototype.GetHtml
- Changelog
- Table of contents generated by Headers2ToC.ahk
An AutoHotkey (AHK) class that takes your csv-style text and converts it to one of the following:
- A Markdown-formatted table.
- An html-formatted table.
- A pretty-aligned plain text table using character count to manage table width (for use with monospace fonts).
https://www.autohotkey.com/boards/viewtopic.php?f=83&t=139518
The examples in this document use the following input:
str := "
(
calldate,src,dst,dcontext,channel
07/14/2025 02:43:44,5555557485,17,play-system-recording,PJSIP/Cox_Trunk-0000d212
07/14/2025 05:58:22,5555557984,s,ivr-6,PJSIP/Cox_Trunk-0000d213
07/14/2025 06:36:41,5555559989,s,ivr-6,PJSIP/Cox_Trunk-0000d214
07/14/2025 06:47:11,5555552202,91017,ext-queues,PJSIP/Cox_Trunk-0000d215
)"Basic usage:
#include <MakeTable>
str := "
(
calldate,src,dst,dcontext,channel
07/14/2025 02:43:44,5555557485,17,play-system-recording,PJSIP/Cox_Trunk-0000d212
07/14/2025 05:58:22,5555557984,s,ivr-6,PJSIP/Cox_Trunk-0000d213
07/14/2025 06:36:41,5555559989,s,ivr-6,PJSIP/Cox_Trunk-0000d214
07/14/2025 06:47:11,5555552202,91017,ext-queues,PJSIP/Cox_Trunk-0000d215
)"
options := {
AddHeaderSeparator: true
, InputColumnSeparator: ','
, LinePrefix: "| "
, LineSuffix: " |"
, OutputColumnSeparator: "|"
}
tbl := MakeTable(str, options)
g := Gui()
; We need a monospaced font for the pretty-aligned text to look pretty
g.SetFont("s11 q5", "Cascadia Mono")
g.Add("Edit", "w1200 r8 -Wrap", tbl.Value)
g.Show()
; write to file
f := FileOpen(A_Temp "\MakeTable-output.md", "w")
f.Write(tbl.Value)
f.Close()- Clone the repository.
git clone https://github.com/Nich-Cebolla/AutoHotkey-MakeTable
- Make a copy of the cloned repository and work with the copy. This is to avoid a situation where
pulling an update breaks our scripts; by using a separate copy we can give ourselves time to review
updates before updating the active copy.
xcopy AutoHotkey-MakeTable AutoHotkey-MakeTable-Active /I /E - Add a file MakeTable.ahk to your lib folder.
In the file is a single statement.
#include C:\users\you\path\to\AutoHotkey-MakeTable-Active\src\MakeTable.ahk
With this setup, we can use #include <MakeTable> from any other script.
The text must be able to be divided into rows and cells using a character or regex pattern. For
example, a common csv without quoted fields is viable as an input string. However, csv with
quoted fields is not viable if the fields contain commas, because StrSplit will split at every
comma. You can use ParseCsv to parse the csv
and then recreate the csv using any character that is wholly absent from the text to separate the
fields, then use that as input for MakeTable.
MakeTable accepts regex patterns to identify the boundaries between each row and each cell, so you
are not limited to only csv.
Define Options.InputColumnSeparator and Options.InputRowSeparator with the patterns. The
default values are listed below.
Pass the options object to MakeTable.Prototype.__New to get a MakeTable object.
The MakeTable object will have the pretty-aligned text set to property "Value". You also have
two additional methods available, MakeTable.Prototype.GetMarkdown and MakeTable.Prototype.GetHtml.
If you use a very large input (e.g. 100k+ lines), MakeTable will finish the job but it might take
a minute or two. Let it run and set a MsgBox to alert you when its finished.
There are various options to customize the output. Here's a few examples using various configurations.
calldate src dst dcontext channel -------------------------------------------------------------------------------------------------------------------- 07/14/2025 02:43:44 5555557485 17 play-system-recording PJSIP/Cox_Trunk-0000d-212-1080-@from-internal -------------------------------------------------------------------------------------------------------------------- 07/14/2025 05:58:22 5555557984 s ivr-6 PJSIP/Cox_Trunk-0000d-213-1080-@from-internal -------------------------------------------------------------------------------------------------------------------- 07/14/2025 06:36:41 5555559989 s ivr-6 PJSIP/Cox_Trunk-0000d-214-1080-@from-internal -------------------------------------------------------------------------------------------------------------------- 07/14/2025 06:47:11 5555552202 91017 ext-queues PJSIP/Cox_Trunk-0000d-215-1080-@from-internal
| calldate | src | dst | dcontext | channel | | --------------------|--------------|---------|--------------------|------------------ | | 07/14/2025 | 5555557485 | 17 | play-system-reco | PJSIP/Cox_Trunk- | | 02:43:44 | | | rding | 0000d-212-1080-@ | | | | | | from-internal | | 07/14/2025 | 5555557984 | s | ivr-6 | PJSIP/Cox_Trunk- | | 05:58:22 | | | | 0000d-213-1080-@ | | | | | | from-internal | | 07/14/2025 | 5555559989 | s | ivr-6 | PJSIP/Cox_Trunk- | | 06:36:41 | | | | 0000d-214-1080-@ | | | | | | from-internal | | 07/14/2025 | 5555552202 | 91017 | ext-queues | PJSIP/Cox_Trunk- | | 06:47:11 | | | | 0000d-215-1080-@ | | | | | | from-internal |
| calldate | src | dst | dcontext | channel | | --------------------|--------------|---------|--------------------|------------------ | | 07/14/2025 | 5555557485 | 17 | play-system-reco | PJSIP/Cox_Trunk- | | 02:43:44 | | | rding | 0000d-212-1080-@ | | | | | | from-internal | | --------------------|--------------|---------|--------------------|------------------ | | 07/14/2025 | 5555557984 | s | ivr-6 | PJSIP/Cox_Trunk- | | 05:58:22 | | | | 0000d-213-1080-@ | | | | | | from-internal | | --------------------|--------------|---------|--------------------|------------------ | | 07/14/2025 | 5555559989 | s | ivr-6 | PJSIP/Cox_Trunk- | | 06:36:41 | | | | 0000d-214-1080-@ | | | | | | from-internal | | --------------------|--------------|---------|--------------------|------------------ | | 07/14/2025 | 5555552202 | 91017 | ext-queues | PJSIP/Cox_Trunk- | | 06:47:11 | | | | 0000d-215-1080-@ | | | | | | from-internal |
- {String} - The input string.
- {Object} - The options object. The options are:
-
{Boolean} [
Options.AddHeaderSeparator = true] - If true, adds a separator between the first row and second row. -
{String} [
Options.ColumnPadding = "`s`s"] - The literal string that is added to the left and right side of every column, EXCEPT the left side of the first column and the right side of the last column. UseOptions.LinePrefixandOptions.LineSuffixto adjust the left side of the first column and the right side of the last column. -
{String} [
Options.InputColumnSeparator = "`t"] - A RegEx pattern that identifies the boundary between columns. -
{String} [
Options.InputRowSeparator = "\R"] - A RegEx pattern that identifies the boundary between rows. -
{String} [
Options.LinePrefix = ""] - The literal string that is added before every line. -
{String} [
Options.LineSuffix = ""] - The literal string that is added after every line. -
{Integer|Integer[]} [
Options.MaxWidths = ""] - If in use,Options.MaxWidthsis an array of integers which define the maximum allowable width per column. If the text in a cell exceeds the maximum,MakeTablebreaks the text into multiple lines. IfOptions.MaxWidthsis an integer, that value is applied as the max width of all columns. -
{String[]} [
Options.OutputColumnPrefix = ""] - Used to specify strings that prefix columns. The difference betweenOptions.OutputColumnPrefixandOptions.OutputColumnSeparatoris thatOptions.OutputColumnSeparatoris applied to all columns, whereasOptions.OutputColumnPrefixis used to prefix every line of a specific column with a specific string. The value of this option, if used, should be an array of strings. The indices of the array are associated with the column index that is to be prefixed by the string at that index. For example, if I have a table that is three columns, and I want to prefix each line of the third column with "; ", then I would setOptions.ColumnPrefix := ["", "", "; "]. I added this option with the intent of using it to comment out portions of text when usingMakeTableto generate pretty-formatted code. -
{Boolean} [
Options.OutputColumnPrefixSkipFirstRow = false] - If true, the first line is not affected byOptions.OutputColumnPrefix. -
{String} [
Options.OutputColumnSeparator = ""] - The literal string that is used to separate cells. -
{Boolean} [
Options.OutputLineBetweenRows = false] - If true, there will be an extra line separating the rows. The line will look just like the header separator seen in the above examples. -
{String} [
Options.OutputRowSeparator = "`n"] - The literal string that is used to separate rows. -
{String} [
Options.TrimCharacters = "`s"] - The value passed to the third parameter ofStrSplitwhen breaking the input text into rows and cells.Options.TrimCharactersRowandOptions.TrimCharactersCellsupercedeOptions.TrimCharacters. For example, if you setOptions.TrimCharacters := "`s"andOptions.TrimCharactersRow := "$", the characters trimmed from the left and right side of each line is "$" and the characters trimmed from the left and right side of each cell is "`s" (space). https://www.autohotkey.com/docs/v2/lib/StrSplit.htm. -
{String} [
Options.TrimCharactersRow = ""] - The value passed to the third parameter ofStrSplitwhen breaking the input text into lines.Options.TrimCharactersRowandOptions.TrimCharactersCellsupercedeOptions.TrimCharacters. For example, if you setOptions.TrimCharacters := "`s"andOptions.TrimCharactersRow := "$", the characters trimmed from the left and right side of each line is "$" and the characters trimmed from the left and right side of each cell is "`s" (space). https://www.autohotkey.com/docs/v2/lib/StrSplit.htm. -
{String} [
Options.TrimCharactersCell = ""] - The value passed to the third parameter ofStrSplitwhen breaking the text lines into individual cells.Options.TrimCharactersRowandOptions.TrimCharactersCellsupercedeOptions.TrimCharacters. For example, if you setOptions.TrimCharacters := "`s"andOptions.TrimCharactersRow := "$", the characters trimmed from the left and right side of each line is "$" and the characters trimmed from the left and right side of each cell is "`s" (space). https://www.autohotkey.com/docs/v2/lib/StrSplit.htm.
The MakeTable object is an array of arrays of arrays of strings.
Each item of the MakeTable array is a MakeTable_Row representing a row in the table.
Each item of the MakeTable_Row arrays is a MakeTable_Cell object representing
a cell in the table. MakeTable_Cell objects are arrays of strings, where each item
represent a line of text for that cell. If you call the object, i.e.
MakeTable_Cell.Prototype.Call, it will return the full text within the cell.
If Options.MaxWidths is not in use, then the length of the MakeTable_Cell array is always 1 (no cells
are broken into multiple lines). If Options.MaxWidths is in use, then the length of the array
is the number of lines that the text was broken into for that cell. The property
"RowLines" is an array of integers representing the number of lines each
row occupies, which is the greatest number of lines into which the text within any cell in that
row was split.
Accessing a string value in the array looks like this:
; Assume `inputStr` and `options` are appropriately defined.
tbl := MakeTable(inputStr, options)
r := 1 ; row index
c := 1 ; column index
k := 1 ; line index
text := tbl[r][c][k]Accessing the text in a cell looks like this:
; Assume `inputStr` and `options` are appropriately defined.
tbl := MakeTable(inputStr, options)
r := 1 ; row index
c := 1 ; column index
cell := tbl[r][c]
text := cell() ; call the cell object to get the entire cell's text contentYou can produce a markdown table that is both pretty-aligned and valid markdown. To do that,
use the following options (in addition to any other options you might want). We can't use
Options.MaxWidths when producing markdown output because the line breaks will disrupt the markdown
syntax. Options.MaxWidths is disabled by default. Use MakeTable.Prototype.GetMarkdown to
include line breaks in your markdown table.
options := {
AddHeaderSeparator: true
, InputColumnSeparator: ',' ; set to whatever character / pattern identifies the boundary between each column
, LinePrefix: "| "
, LineSuffix: " |"
, OutputColumnSeparator: "|"
}
tbl := MakeTable(inputString, options)The above options will yield output like this:
| calldate | src | dst | dcontext | channel |
| ---------------------|--------------|---------|-------------------------|----------------------------------------------- |
| 07/14/2025 02:43:44 | 5555557485 | 17 | play-system-recording | PJSIP/Cox_Trunk-0000d-212-1080-@from-internal |
| 07/14/2025 05:58:22 | 5555557984 | s | ivr-6 | PJSIP/Cox_Trunk-0000d-213-1080-@from-internal |
| 07/14/2025 06:36:41 | 5555559989 | s | ivr-6 | PJSIP/Cox_Trunk-0000d-214-1080-@from-internal |
| 07/14/2025 06:47:11 | 5555552202 | 91017 | ext-queues | PJSIP/Cox_Trunk-0000d-215-1080-@from-internal |Use MakeTable.Prototype.GetMarkdown to produce a markdown table.
-
{String} [
LineSeparator = "`n"] - The string that separates each line in the output string. -
{String} [
InnerLineSeparator = ""] - The value passed to the parameterSeparatorwhich is used to separate each line of text within a cell whenOptions.MaxWidthsis used. For example, if your cells contain very long strings that would look better broken up into multiple lines, you can set theOptions.MaxWidthswith an integer or array of integers. This will break up the text into multiple lines for cells that exceed the maximum. Then, when you callMakeTable.Prototype.GetMarkdown, you can set parameterInnerLineSeparatorwith a string to separate those lines. Use "<br>" to separate the lines with line breaks.
{String} - The markdown table.
MakeTable.Prototype.GetMarkdown has one benefit that is not available directly from the MakeTable
core process - with MakeTable.Prototype.GetMarkdown we can also include <br> tags in-between
long lines of text. We do that by setting the InnerLineSeparator
parameter with "<br>", yielding an output like the below table, which will render correctly and
will include line breaks at the <br> tags.
|calldate|src|dst|dcontext|channel|
|-|-|-|-|-|
|07/14/2025<br>02:43:44|5555557485|17|play-system-reco<br>rding|PJSIP/Cox_Trunk-<br>0000d-212-1080-@<br>from-internal|
|07/14/2025<br>05:58:22|5555557984|s|ivr-6|PJSIP/Cox_Trunk-<br>0000d-213-1080-@<br>from-internal|
|07/14/2025<br>06:36:41|5555559989|s|ivr-6|PJSIP/Cox_Trunk-<br>0000d-214-1080-@<br>from-internal|
|07/14/2025<br>06:47:11|5555552202|91017|ext-queues|PJSIP/Cox_Trunk-<br>0000d-215-1080-@<br>from-internal|Use MakeTable.Prototype.GetHtml to produce an html table.
-
{String} [
Options.IndentChar = "`s"] - The character that is used for indentation. -
{Integer} [
Options.IndentLen = 2] - The number ofOptions.IndentCharthat is used for one level of indentation. -
{Integer} [
Options.InitialIndent = 0] - The initial indentation level of the output string. -
{String} [
Options.InnerLineSeparator = ""] - The value passed to the parametersMakeTable_Cell.Prototype.Call~Separatorwhich is used to separate each line of text within a cell whenMakeTable.Prototype.__New~Options.MaxWidthsis used. For example, if your cells contain very long strings that would look better broken up into multiple lines, you can set the "MaxWidths" property of the options object that you pass toMakeTable.Prototype.__New. This will break up the text into multiple lines for cells that exceed the maximum. Then, when you callMakeTable.Prototype.GetHtml, you can setOptions.InnerLineSeparatorwith a substring to separate those lines. A good option is "<br>". An empty string is also an appropriate option. -
{String} [
Options.LineSeparator = "`n"] - The literal string that separates each line in the output string. -
{String} [
Options.TableAttribute = ""] - Any attributes you want included with the <table> element, e.g. "class="tbl-class" border="1"". Separate each individual attribute with a space character. -
{String} [
Options.TableStyle = ""] - The style attribute value that will be included with the <table> elements. Only include the value that goes between the quotation marks of the style attribute, e.g. "color:red;". -
{String|String[]|String[][]} [
Options.TdAttribute = ""] - If a string, the attribute that will be included with all <td> elements.- If an array of strings, each index in the array corresponds to a row in the table (not including headers) and that string is applied as the attributes for each cell in the row. For example, index 1 is the first row after the headers.
- If an array of arrays of strings, each index of the outer array corresponds with a row in the table (not including headers). For example, index 1 is the first row after the headers. Each index of the inner array corresponds with a cell in that row. For example, index 1 is the first cell in the row. The items of the inner array are strings that wil be applied as the attributes for the corresponding cell.
- The arrays must have the correct number of items for the table content. If there are too few items, an IndexError will occur.
- Separate each individual attribute with a space character.
- For example, these are each valid, assuming the table has 3 data rows and 3 columns:
options := { TdAttribute: "class=`"td-class`"" } options := { TdAttribute: [ "class=`"td-class-1`"", "class=`"td-class-2`"", "class=`"td-class-3`"" ] } options := { TdAttribute: [ [ "class=`"td-class-1-1`"", "class=`"td-class-1-2`"", "class=`"td-class-1-3`"" ] , [ "class=`"td-class-2-1`"", "class=`"td-class-2-2`"", "class=`"td-class-2-3`"" ] , [ "class=`"td-class-3-1`"", "class=`"td-class-3-2`"", "class=`"td-class-3-3`"" ] ]}
-
{String} [
Options.TdStyle = ""] - If a string, the style attribute value that will be included with all <td> elements. Only include the value that goes between the quotation marks of the style attribute, e.g. "color:red;".- If an array of strings, each index in the array corresponds to a row in the table (not including headers) and that string is applied as the attributes for each cell in the row. For example, index 1 is the first row after the headers.
- If an array of arrays of strings, each index of the outer array corresponds with a row in the table (not including headers). For example, index 1 is the first row after the headers. Each index of the inner array corresponds with a cell in that row. For example, index 1 is the first cell in the row. The items of the inner array are strings that wil be used as the style attribute value for the corresponding cell.
- The arrays must have the correct number of items for the table content. If there are too few items, an IndexError will occur.
- For example, these are each valid, assuming the table has 3 data rows and 3 columns:
options := { TdStyle: "color:red;" } options := { TdStyle: [ "color:red;", "color:green;", "color:blue;" ] } options := { TdStyle: [ [ "color:red;", "color:green;", "color:blue;" ] , [ "color:blue;", "color:red;", "color:green;" ] , [ "color:green;", "color:blue;", "color:red;" ] ]}
-
{String|String[]} [
Options.ThAttribute = ""] - If a string, the attribute that will be included with all <th> elements.- If an array of strings, each index in the array corresponds to an individual header.
- The arrays must have the correct number of items for the table content. If there are too few items, an IndexError will occur.
- Separate each individual attribute with a space character.
- For example, these are each valid, assuming the table has a header row with 3 headers.
options := { ThAttribute: "class=`"th-class`"" } options := { ThAttribute: [ "class=`"th-class-1`"", "class=`"th-class-2`"", "class=`"th-class-3`"" ] }
-
{String} [
Options.ThStyle = ""] - If a string, the style attribute value that will be included with all <th> elements. Only include the value that goes between the quotation marks of the style attribute, e.g. "color:red;".- If an array of strings, each index in the array corresponds to a row in the table (not including headers).
- The arrays must have the correct number of items for the table content. If there are too few items, an IndexError will occur.
- For example, these are each valid, assuming the table has a header row with 3 headers.
options := { ThStyle: "color:red;" } options := { ThStyle: [ "color:red;", "color:green;", "color:blue;" ] }
-
{String} [
Options.TrAttribute = ""] - If a string, the attribute that will be included with all <tr> elements.- If an array of strings, each index in the array corresponds to a row in the table (not including headers).
- The arrays must have the correct number of items for the table content. If there are too few items, an IndexError will occur.
- Separate each individual attribute with a space character.
- For example, these are each valid, assuming the table has 3 rows.
options := { TrAttribute: "class=`"tr-class`"" } options := { TrAttribute: [ "class=`"tr-class-1`"", "class=`"tr-class-2`"", "class=`"tr-class-3`"" ] }
-
{String} [
Options.TrStyle = ""] - If a string, the style attribute value that will be included with all <tr> elements. Only include the value that goes between the quotation marks of the style attribute, e.g. "color:red;".- If an array of strings, each index in the array corresponds to a row in the table (not including headers).
- The arrays must have the correct number of items for the table content. If there are too few items, an IndexError will occur.
- For example, these are each valid, assuming the table has 3 rows.
options := { TrStyle: "color:red;" } options := { TrStyle: [ "color:red;", "color:green;", "color:blue;" ] }
<table>
<tr>
<th>calldate</th>
<th>src</th>
<th>dst</th>
<th>dcontext</th>
<th>channel</th>
</tr>
<tr>
<td>07/14/2025 02:43:44</td>
<td>5555557485</td>
<td>17</td>
<td>play-system-recording</td>
<td>PJSIP/Cox_Trunk-0000d-212-1080-@from-internal</td>
</tr>
<tr>
<td>07/14/2025 05:58:22</td>
<td>5555557984</td>
<td>s</td>
<td>ivr-6</td>
<td>PJSIP/Cox_Trunk-0000d-213-1080-@from-internal</td>
</tr>
<tr>
<td>07/14/2025 06:36:41</td>
<td>5555559989</td>
<td>s</td>
<td>ivr-6</td>
<td>PJSIP/Cox_Trunk-0000d-214-1080-@from-internal</td>
</tr>
<tr>
<td>07/14/2025 06:47:11</td>
<td>5555552202</td>
<td>91017</td>
<td>ext-queues</td>
<td>PJSIP/Cox_Trunk-0000d-215-1080-@from-internal</td>
</tr>
</table><table class="table" style="color:red;">
<tr class="tr1" style="color:red;">
<th class="th1" style="color:red;">calldate</th>
<th class="th2" style="color:green;">src</th>
<th class="th3" style="color:blue;">dst</th>
<th class="th4" style="color:pink;">dcontext</th>
<th class="th5" style="color:purple;">channel</th>
</tr>
<tr class="tr2" style="color:green;">
<td class="td2-1" style="color:purple;">07/14/2025 02:43:44</td>
<td class="td2-2" style="color:red;">5555557485</td>
<td class="td2-3" style="color:green;">17</td>
<td class="td2-4" style="color:blue;">play-system-recording</td>
<td class="td2-5" style="color:pink;">PJSIP/Cox_Trunk-0000d-212-1080-@from-internal</td>
</tr>
<tr class="tr3" style="color:blue;">
<td class="td3-1" style="color:pink;">07/14/2025 05:58:22</td>
<td class="td3-2" style="color:purple;">5555557984</td>
<td class="td3-3" style="color:red;">s</td>
<td class="td3-4" style="color:green;">ivr-6</td>
<td class="td3-5" style="color:blue;">PJSIP/Cox_Trunk-0000d-213-1080-@from-internal</td>
</tr>
<tr class="tr4" style="color:pink;">
<td class="td4-1" style="color:blue;">07/14/2025 06:36:41</td>
<td class="td4-2" style="color:pink;">5555559989</td>
<td class="td4-3" style="color:purple;">s</td>
<td class="td4-4" style="color:red;">ivr-6</td>
<td class="td4-5" style="color:green;">PJSIP/Cox_Trunk-0000d-214-1080-@from-internal</td>
</tr>
<tr class="tr5" style="color:purple;">
<td class="td5-1" style="color:green;">07/14/2025 06:47:11</td>
<td class="td5-2" style="color:blue;">5555552202</td>
<td class="td5-3" style="color:pink;">91017</td>
<td class="td5-4" style="color:purple;">ext-queues</td>
<td class="td5-5" style="color:red;">PJSIP/Cox_Trunk-0000d-215-1080-@from-internal</td>
</tr>
</table>- 2025-11-14
- Added property
MakeTableObj.ColumnWidths, which is an array of integers representing the width, in character count, of each column.
- Added property