<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jack Liu</title>
    <description>The latest articles on DEV Community by Jack Liu (@cser700).</description>
    <link>https://dev.to/cser700</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4030392%2F8f1b3c9f-7a5f-4374-a752-66a7a81bfb6e.png</url>
      <title>DEV Community: Jack Liu</title>
      <link>https://dev.to/cser700</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cser700"/>
    <language>en</language>
    <item>
      <title>Converting Excel Tables to Markdown Without Uploading the Workbook</title>
      <dc:creator>Jack Liu</dc:creator>
      <pubDate>Sat, 18 Jul 2026 10:01:27 +0000</pubDate>
      <link>https://dev.to/cser700/converting-excel-tables-to-markdown-without-uploading-the-workbook-4ej2</link>
      <guid>https://dev.to/cser700/converting-excel-tables-to-markdown-without-uploading-the-workbook-4ej2</guid>
      <description>&lt;p&gt;Moving a table from Excel into a README looks like a copy-and-paste task. It usually stops being simple as soon as the workbook contains more than one sheet, blank border rows, merged cells, dates, identifiers with leading zeros, or text containing a pipe character.&lt;/p&gt;

&lt;p&gt;I wanted a workflow that produces reviewable Markdown without sending a workbook to a conversion server. This post explains the conversion rules that mattered most and a practical checklist for checking the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ordinary copy and paste breaks
&lt;/h2&gt;

&lt;p&gt;A Markdown table needs a rectangular grid, a header row, and a separator row. A spreadsheet is much less strict. It can contain title blocks above the data, notes below it, formatting-only cells, hidden or helper sheets, merged ranges, formulas, and cells whose displayed value differs from the stored value.&lt;/p&gt;

&lt;p&gt;The fragile approach is to copy every visible cell and join each row with &lt;code&gt;|&lt;/code&gt;. It fails in several predictable ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a literal &lt;code&gt;|&lt;/code&gt; inside a cell creates an extra Markdown column;&lt;/li&gt;
&lt;li&gt;a line break splits one record across multiple Markdown lines;&lt;/li&gt;
&lt;li&gt;backslashes can change how later characters are interpreted;&lt;/li&gt;
&lt;li&gt;empty leading or trailing rows make the output noisy;&lt;/li&gt;
&lt;li&gt;numeric IDs such as &lt;code&gt;00124&lt;/code&gt; may be treated as numbers and lose information;&lt;/li&gt;
&lt;li&gt;converting every worksheet produces irrelevant cover or notes tables;&lt;/li&gt;
&lt;li&gt;styling, charts, macros, and workbook interactions have no Markdown equivalent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A safer browser-local pipeline
&lt;/h2&gt;

&lt;p&gt;The conversion can run entirely in the browser. At a high level, the pipeline is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the selected &lt;code&gt;.xlsx&lt;/code&gt;, &lt;code&gt;.xls&lt;/code&gt;, or &lt;code&gt;.csv&lt;/code&gt; file as an &lt;code&gt;ArrayBuffer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Parse the workbook locally and build a matrix for each worksheet.&lt;/li&gt;
&lt;li&gt;Let the user choose the relevant sheet or sheets.&lt;/li&gt;
&lt;li&gt;Trim empty rows and columns from the outer edges, while preserving empty cells inside the table.&lt;/li&gt;
&lt;li&gt;Decide whether the first row is a header and choose column alignment.&lt;/li&gt;
&lt;li&gt;Escape Markdown-sensitive cell content.&lt;/li&gt;
&lt;li&gt;Render the table, preview it, and copy or download the result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The escaping step is small but essential:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;escapeMarkdownCell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\r\n?&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps a pipe inside the cell instead of turning it into a column boundary, preserves backslashes, and represents an in-cell line break without breaking the table row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sheet selection matters
&lt;/h2&gt;

&lt;p&gt;Workbooks often contain a cover sheet, instructions, a data dictionary, and one or more actual data sheets. Automatically dumping all of them into one document creates confusing output.&lt;/p&gt;

&lt;p&gt;A better default is to recommend a visible, data-rich worksheet while still showing the full sheet list. When several sheets are selected, each table can be preceded by a level-two heading derived from the sheet name. The user remains in control, and the resulting Markdown has a predictable structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve displayed values, not spreadsheet behavior
&lt;/h2&gt;

&lt;p&gt;Markdown can represent text and table structure, but it cannot preserve the workbook as an interactive program. For documentation, the useful result is normally the value a reader sees in the cell. Formula logic, charts, macros, colors, borders, comments, and conditional formatting should not be presented as if they survived the conversion.&lt;/p&gt;

&lt;p&gt;There are also cases where conservative text handling is important. Product codes, postal codes, and IDs with leading zeros should stay text. Dates should be checked after conversion because locale-specific display formats can be ambiguous. Merged cells need an explicit policy; expanding the anchor value across the merged range is often more understandable than silently leaving holes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A five-minute verification checklist
&lt;/h2&gt;

&lt;p&gt;Before pasting the generated table into documentation, I check:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Row and column counts:&lt;/strong&gt; Does the Markdown contain the same logical records as the selected range?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headers:&lt;/strong&gt; Is the first row really a header, or should generated names such as &lt;code&gt;Column 1&lt;/code&gt; be used?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escaped characters:&lt;/strong&gt; Test at least one pipe, backslash, and multiline cell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identifiers and dates:&lt;/strong&gt; Confirm leading zeros and displayed date values were not changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unsupported workbook features:&lt;/strong&gt; Make sure no reader could mistake the Markdown for a full-fidelity Excel export.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a small table, reading the Markdown is enough. For a large table, I also compare a few records from the start, middle, and end of the selected sheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool I use
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://exceltomd.com/excel-to-markdown" rel="noopener noreferrer"&gt;Excel to Markdown&lt;/a&gt; around this browser-local workflow. The public converter accepts XLSX, XLS, and CSV files, lets you select worksheets and alignment, and provides copy and &lt;code&gt;.md&lt;/code&gt; download output without requiring an account. Workbook content stays in the browser rather than being uploaded by the converter.&lt;/p&gt;

&lt;p&gt;Disclosure: I built and operate Excel to Markdown. The implementation details above are shared because the edge cases apply to any spreadsheet-to-Markdown pipeline, whether you use this tool or write your own.&lt;/p&gt;

</description>
      <category>markdown</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Why I Prefer Browser-Local Image Resizing for Small Files</title>
      <dc:creator>Jack Liu</dc:creator>
      <pubDate>Wed, 15 Jul 2026 12:21:54 +0000</pubDate>
      <link>https://dev.to/cser700/why-i-prefer-browser-local-image-resizing-for-small-files-1178</link>
      <guid>https://dev.to/cser700/why-i-prefer-browser-local-image-resizing-for-small-files-1178</guid>
      <description>&lt;p&gt;When a form asks for an image under 100KB, the obvious reaction is to search for an online compressor and upload the file. That works, but it also adds an unnecessary privacy decision: does this image need to leave the device at all?&lt;/p&gt;

&lt;h2&gt;
  
  
  A simpler workflow
&lt;/h2&gt;

&lt;p&gt;For ID photos, screenshots, receipts, and other personal images, I prefer tools that do the work locally in the browser. The browser reads the file, resizes or recompresses it, and gives the result back without sending the original to a remote server.&lt;/p&gt;

&lt;p&gt;My practical process is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with the original JPG, PNG, or WebP.&lt;/li&gt;
&lt;li&gt;Set the required maximum size rather than guessing a quality percentage.&lt;/li&gt;
&lt;li&gt;Keep the aspect ratio unless the destination specifies exact dimensions.&lt;/li&gt;
&lt;li&gt;Preview the result at normal size, especially around text and faces.&lt;/li&gt;
&lt;li&gt;Save the new file under a different name so the original remains untouched.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why target size matters
&lt;/h2&gt;

&lt;p&gt;A generic “compress” button may produce a smaller file, but not necessarily one that meets a strict upload limit. A target-size workflow is more useful because it can adjust dimensions and quality together. For many document portals, a visually clean 80–95KB result is safer than a 99.9KB result that may fail after metadata is added.&lt;/p&gt;

&lt;p&gt;PNG is excellent for flat graphics and screenshots, while JPG is often better for photos. WebP can be efficient, but some older upload forms still accept only JPG or PNG. The destination's rules should decide the output format.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool I use
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://resizeimage.site/" rel="noopener noreferrer"&gt;Resize Image&lt;/a&gt; around this browser-local approach. It is useful when I need a quick image under a specific size and do not want the original uploaded as part of the resizing process. The link is included for context and disclosure: I am the maker.&lt;/p&gt;

&lt;p&gt;Local processing does not remove every privacy concern—you should still review the downloaded result and the site where you eventually upload it—but it reduces one unnecessary transfer.&lt;/p&gt;

&lt;p&gt;The larger lesson is simple: for lightweight image work, the browser is already capable enough. A small, focused local tool can be faster, more predictable, and easier to trust than sending every file through a remote processing queue.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>privacy</category>
    </item>
  </channel>
</rss>
