Last active
April 20, 2026 18:22
-
-
Save JamoCA/4bc3d41106b1da4f8511020985ecd350 to your computer and use it in GitHub Desktop.
markdownTableToQuery UDF - Converts a markdown table string to a ColdFusion query with VARCHAR columns.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <cfscript> | |
| /** | |
| * Converts a markdown table string to a ColdFusion query with VARCHAR columns. | |
| * James Moberg - 2026-04-17 myCFML.com / SunStarMedia.com | |
| * https://gist.github.com/JamoCA/4bc3d41106b1da4f8511020985ecd350 | |
| * @markdown The markdown table string to parse. | |
| * @return A query object with columns derived from the table header. | |
| */ | |
| public query function markdownTableToQuery(required string markdown) hint="Converts a markdown table to a CF query with VARCHAR columns" { | |
| // Local closure: parses a single markdown row into an array of trimmed cells. | |
| // Handles escaped pipes (\|) and strips leading/trailing pipes. | |
| var parseRow = function(required string row) { | |
| var line = trim(arguments.row); | |
| // Strip one leading and one trailing pipe if present | |
| if (left(line, 1) eq "|") { | |
| line = mid(line, 2, len(line) - 1); | |
| } | |
| if (right(line, 1) eq "|") { | |
| line = left(line, len(line) - 1); | |
| } | |
| // Temporarily protect escaped pipes with a safe placeholder token, | |
| // split on the real pipes, then restore the escaped pipes as literal "|". | |
| var placeholder = chr(31); // unit separator | |
| if (find(placeholder, line)) placeholder = chr(28); // file separator | |
| if (find(placeholder, line)) placeholder = chr(29); // group separator | |
| line = replace(line, "\|", placeholder, "all"); | |
| // includeEmptyFields=true so empty cells survive; multiCharDelim=false | |
| var cells = listtoarray(line, "|", true, false); | |
| var out = []; | |
| for (var cell in cells) { | |
| out.append(trim(replace(cell, placeholder, "|", "all"))); | |
| } | |
| return out; | |
| }; | |
| var lines = []; | |
| var rawLines = listtoarray(arguments.markdown, chr(10), false, true); | |
| // Trim each line and drop blanks | |
| for (var rawLine in rawLines) { | |
| var trimmed = trim(replace(rawLine, chr(13), "", "all")); | |
| if (len(trimmed)) { | |
| lines.append(trimmed); | |
| } | |
| } | |
| if (arraylen(lines) lt 2) { | |
| throw(type = "InvalidMarkdownTable", message = "Markdown table must contain at least a header row and a separator row."); | |
| } | |
| // Parse header row into column names | |
| var headerCells = parseRow(lines[ 1 ]); | |
| if (!arraylen(headerCells)) { | |
| throw(type = "InvalidMarkdownTable", message = "Header row contains no columns."); | |
| } | |
| // Validate separator row (line 2): cells made of dashes, optional leading/trailing colons | |
| var separatorCells = parseRow(lines[ 2 ]); | |
| if (arraylen(separatorCells) lt arraylen(headerCells)) { | |
| throw(type = "InvalidMarkdownTable", message = "Separator row has fewer columns than header."); | |
| } | |
| for (var sep in separatorCells) { | |
| if (!refind("^:?-{3,}:?$", sep)) { | |
| throw(type = "InvalidMarkdownTable", message = "Invalid separator row: '#sep#'."); | |
| } | |
| } | |
| // Build column type list (all VARCHAR) | |
| var columnTypes = []; | |
| for (var i = 1; i lte arraylen(headerCells); i++) { | |
| columnTypes.append("VARCHAR"); | |
| } | |
| var result = querynew(arraytolist(headerCells), arraytolist(columnTypes)); | |
| // Populate data rows (everything after the separator) | |
| for (var i = 3; i lte arraylen(lines); i++) { | |
| var rowCells = parseRow(lines[ i ]); | |
| // Normalize cell count to match headers (pad short rows, truncate long ones) | |
| while (arraylen(rowCells) lt arraylen(headerCells)) { | |
| rowCells.append(""); | |
| } | |
| if (arraylen(rowCells) gt arraylen(headerCells)) { | |
| rowCells = rowCells.slice(1, arraylen(headerCells)); | |
| } | |
| queryaddrow(result); | |
| for (var c = 1; c lte arraylen(headerCells); c++) { | |
| querysetcell(result, headerCells[ c ], rowCells[ c ], result.recordcount); | |
| } | |
| } | |
| return result; | |
| } | |
| </cfscript> | |
| <!--- | |
| <cfsavecontent variable="markdown"> | |
| | Name | Expression | Notes | | |
| |---------|----------------------|----------------| | |
| | Alice | a \| b | pipe in middle | | |
| | Bob | \| starts with pipe | leading | | |
| | Carol | ends with pipe \| | trailing | | |
| | Dave | \| | only a pipe | | |
| </cfsavecontent> | |
| <cfset q = markdownTableToQuery(markdown)> | |
| <cfdump var="#q#"> | |
| ---> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment