Nothing threw an error: one missing attribute in a DOCX, and two converters lost the text
The usual worry about DOCX to HTML is that the library will mishandle a merged cell. On this sample it never did. The thing that actually broke came out of the document: one attribute missing from the source file, and a
The usual worry about DOCX to HTML is that the library will mishandle a merged cell. On this sample it never did. The thing that actually broke came out of the document: one attribute missing from the source file, and a cell of text was gone from the converted HTML, with nothing printed and nothing thrown on either side of the split.
The scope is small and worth stating up front. One self-made sample document, 43,790 bytes, carrying three tables: one with horizontal merges, one with vertical merges, and a mixed one at five columns by four rows. All of it on a single Apple-silicon Mac in a desktop Chromium, one pass per tool. Four ways of converting went in: Mammoth 1.13.0, pandoc 3.13, docx-preview 0.4.1, and a browser-side DOCX to HTML converter, ImgIng https://imging.ai/ , used straight off the page with no account. Forty lines of hand-written reconstruction rode along as a control, so one member of the set had behaviour known line by line. None of this generalises to a real business document full of text boxes, equations and floating shapes.
The restart attribute is the only anchor a vertical merge has
Two facts about the format decide everything that follows. How many columns a table has comes from the w:gridCol children of w:tblGrid, not from how many w:tc a given row happens to contain. And a horizontal merge is a plain number, <w:gridSpan w:val="2"/>, which copies straight into colspan="2".
A vertical merge has no such number. There is no rowspan field in OOXML at all. The first cell of the group carries <w:vMerge w:val="restart"/>, and every continuation row carries a bare <w:vMerge/> with no w:val and an empty paragraph inside. Those continuation cells still exist in the XML and still occupy their grid positions. So any rowspan you see in converted HTML was counted by the converter, and the count has exactly one anchor: the restart. Take the anchor away and every tool in the set is guessing, in its own direction.
Two quiet ways to be wrong
The variant was the same document with one edit: the first cell of the vertical-merge group written as a bare <w:vMerge/> instead of restart, nothing else touched, 43,789 bytes. That table is three columns by four rows, and its merged cell holds a label.
Mammoth 1.13.0 and pandoc 3.13 came out identical on it. The header cell in the row above became the merge start and got rowspan="4", the four rows came out with 3, 2, 2 and 2 cells, and the label that used to live in the cell whose restart had been removed is not anywhere in the output HTML. docx-preview 0.4.1 and the other browser-side converter went the other way: no merge at all, four rows of three cells each, the label kept, the two cells under it empty. The control script landed with the second group.
One group loses text, the other loses the merge. Neither reports anything, and both outputs render as a plausible table in a browser.
This starts in the file, so a better library will not save you
The attribute is missing from the document, which is where I would put the blame if I had to put it somewhere. On the well-formed version of the same sample, all four produced correct colspan and rowspan values across all three tables, so the folklore that open-source converters cannot handle merged cells did not reproduce here at all. Swapping libraries buys nothing against a malformed file. The job is noticing, and for one person that has to be cheap.
The cheap thing turned out to be text coverage: whatever strings the document itself carries have to be findable in the HTML afterwards. List the w:t contents out of the unzipped word/document.xml, then grep the result for each string. That comparison is where the missing label turned up, and it was the only place it turned up; read on its own, each output looked like an ordinary table with ordinary span numbers. One pass over a file, no service in the loop, no quota to watch, and it does not care which way a converter went wrong.
I would not call any of that coverage. Exactly one malformed shape went through, the missing restart. Nested tables were never tried, nor a grid whose declared column count disagrees with its rows, nor a continuation cell that actually holds text β and my guess is that at least one of those splits the same set of tools in some new way. That guess is a guess. The text comparison is the one check I would keep in my own build today, and I would run it before anyone opens the page, because the page is the one thing that looks fine either way.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.