Words Missing After Placing a Word DOCX in InDesign: How to Check the File
After you place a Word manuscript, a few words are gone. In one reported case on r/indesign, the Word file said "John 1:11-12 said," and the placed text stopped at "John 1:11".
In that thread and in an older Adobe Community thread, invisible bidirectional control characters (U+202A, U+202C, U+202D) were found where the text went missing. These characters mark where left-to-right or right-to-left text starts and ends. The person in the r/indesign thread guessed that the reference had been copied from a website.
To see how these characters relate to the missing words, I built a test .docx with one case per paragraph and placed it in InDesign 2026 (21.5.1) on Windows. The person in the thread had also shared a .docx and a .doc version of the manuscript. I inspected the XML in the shared .docx and the character sequence in the binary .doc file. I did not place those two files.
Text lost inside XML elements in my tests
When I put these control characters directly in the text (in <w:t>), InDesign imported them without losing the surrounding words:
- U+202A to U+202E (embedding and override marks, including the U+202C from the thread)
- U+2066 to U+2069 (isolate marks)
- U+200B to U+200F, U+2060 and U+FEFF (zero-width characters and direction marks)
In my test file, text went missing in these cases:
| Element in the .docx | What it is for | Result in my test |
|---|---|---|
<w:dir> |
Text with its own direction (embedding) | Dropped in the standalone cases |
<w:bdo> |
Text with a forced direction (override) | Dropped in the standalone cases |
<w:customXml> around words |
Custom XML tags applied to text | Dropped |
The words before and after the element stayed. The loss happened with Remove Styles and Formatting both on and off, and also in a footnote.
There were exceptions. A <w:dir> inside a content control kept its text, and so did a <w:customXml> around a whole paragraph. The content control, smart tag, tracked insertion, hyperlink and simple field I tested also kept their text. I could reproduce the loss with these elements, but I did not find out why InDesign skips their contents.
What was in the file from the thread
The shared .docx records Microsoft Office Word 16.0 in its application metadata. In the paragraph with the reference, everything after "John 1:11" up to the end of the paragraph sat inside a single <w:bdo> element: the text "-12 said," and 45 U+202C characters. That matches the missing text shown in the thread's screenshot.
The left-to-right override character (U+202D) that starts such a span did not appear in the .docx text. In the .doc version it was there as a character, right after "John 1:11" and before the hyphen.
So in this file, Word stored the override as a character in the .doc and turned it into a <w:bdo> element in the .docx. The file contents and my test results support this as the reason for the difference reported in the thread. It may also explain why copying the text through Notepad did not help the person in the thread, though I did not test that.
Check the .docx before you place it
A .docx is a zip file, so you can check it without opening InDesign. The PowerShell script below reports <w:dir>, <w:bdo> and <w:customXml> elements in the main text, footnotes, endnotes, headers and footers. It does not check comments. In my tests I placed only main text and footnotes.
Each line of the list is a place to check, and some reported elements kept their text in my tests. Context shows text from the reported location, with the element's text in [[ ]] and paragraph breaks shown as |. It does not reproduce tabs or formatting. Selected invisible characters are omitted from Context; HiddenInside counts those inside the element. For the file from the thread, the list had one line, John 1:11[[-12 said,]], with 45 hidden characters.
If the script reports that it found none of these elements, that only covers this check. Text can go missing for other reasons.
Read this before you make a fixed copy. Without -ListOnly, the script writes <name>_fixed.docx. It removes every listed element, including ones that may import correctly, and with them the direction settings they carry. Run -ListOnly first and confirm with the author that the direction settings were added by accident. Don't run the repair on Arabic, Hebrew or mixed-direction text; I have not tested those documents.
A Note entry or an unsupported-part message prevents the script from writing a fixed copy. Review those cases manually. The script builds the copy in a temporary file, reads it back, and renames it only after that check passes. In my test file, the fixed copy brought back every word, including the footnote.
# FixWordWrappers.ps1
# Reports the outermost <w:dir>, <w:bdo> and <w:customXml> elements in a .docx, in
# word/document.xml, footnotes.xml, endnotes.xml, header*.xml and footer*.xml (not comments).
# Each line identifies a candidate for comparison with the placed text. In my tests
# (InDesign 2026 on Windows), text inside these elements was often dropped on Place, and
# some reported elements retained their text.
# Context shows text from that location with the element's text in [[ ]]; paragraphs inside
# one element are separated by " | ". HiddenInside counts U+200B-200F, U+202A-202E, U+2060,
# U+2066-2069 and U+FEFF inside the element; those characters are left out of Context.
#
# Without -ListOnly, it writes a copy named <name>_fixed.docx with ALL listed elements removed
# and their contents kept in place. This also removes the text direction they set. Do not use
# it on Arabic, Hebrew or mixed-direction text. The original file is not changed.
# It writes nothing if an element is in a structure it does not handle (see Note), or if a
# part uses an XML namespace it does not handle.
param(
[Parameter(Mandatory = $true)][string]$Path,
[switch]$ListOnly
)
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
$src = (Resolve-Path -LiteralPath $Path).Path
$W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
$MC = "http://schemas.openxmlformats.org/markup-compatibility/2006"
$names = @("dir", "bdo", "customXml")
$partPattern = '^word/(document|footnotes|endnotes|header\d*|footer\d*)\.xml$'
$xpath = ($names | ForEach-Object { "//w:$_" }) -join " | "
$hiddenClass = "[" + [char]0x200B + "-" + [char]0x200F + [char]0x202A + "-" + [char]0x202E +
[char]0x2060 + [char]0x2066 + "-" + [char]0x2069 + [char]0xFEFF + "]"
function New-Ns($xml) {
$ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$ns.AddNamespace("w", $W)
$ns.AddNamespace("mc", $MC)
return ,$ns
}
function Read-Part($entry) {
$xml = New-Object System.Xml.XmlDocument
$xml.PreserveWhitespace = $true
$stream = $entry.Open()
try { $xml.Load($stream) } finally { $stream.Dispose() }
return ,$xml
}
function Get-Blocker($n, $ns) {
foreach ($a in $n.Attributes) {
if ($a.Prefix -eq "xmlns" -or $a.Name -eq "xmlns") { return "declares its own namespace" }
if ($a.NamespaceURI -eq $MC) { return "has markup-compatibility attributes" }
if ($a.NamespaceURI -eq "http://www.w3.org/XML/1998/namespace") { return "has xml: attributes (for example xml:space)" }
}
if ($n.SelectSingleNode("ancestor::mc:AlternateContent", $ns)) { return "inside mc:AlternateContent (Context may show both versions)" }
if ($n.SelectSingleNode(".//mc:AlternateContent", $ns)) { return "contains mc:AlternateContent (Context may show both versions)" }
return ""
}
function Get-Context($n, $ns) {
$para = $n.SelectSingleNode("ancestor::w:p[1]", $ns)
if ($para -eq $null) { $para = $n }
$inside = @{}
foreach ($t in $n.SelectNodes(".//w:t", $ns)) { $inside[$t] = $true }
$s = ""; $open = $false; $lastP = $null
foreach ($t in $para.SelectNodes(".//w:t", $ns)) {
$p = $t.SelectSingleNode("ancestor::w:p[1]", $ns)
if ($lastP -ne $null -and -not $p.Equals($lastP)) { $s += " | " }
$lastP = $p
if ($inside.ContainsKey($t) -and -not $open) { $s += "[["; $open = $true }
if (-not $inside.ContainsKey($t) -and $open) { $s += "]]"; $open = $false }
$s += $t.InnerText
}
if ($open) { $s += "]]" }
return [regex]::Replace($s, $hiddenClass, "")
}
function Remove-Wrappers($xml, $ns) {
$nodes = @($xml.SelectNodes($xpath, $ns)) | Sort-Object { -$_.SelectNodes("ancestor::*").Count }
foreach ($n in $nodes) {
while ($n.HasChildNodes) {
$c = $n.FirstChild
if ($c.NamespaceURI -eq $W -and $c.LocalName -eq "customXmlPr") { [void]$n.RemoveChild($c); continue }
[void]$n.ParentNode.InsertBefore($c, $n)
}
[void]$n.ParentNode.RemoveChild($n)
}
}
$found = New-Object System.Collections.ArrayList
$fixed = @{}
$unsupported = @()
$blocked = $false
$zip = [System.IO.Compression.ZipFile]::OpenRead($src)
try {
foreach ($e in $zip.Entries) {
if ($e.FullName -notmatch $partPattern) { continue }
$xml = Read-Part $e
if ($xml.DocumentElement.NamespaceURI -ne $W) { $unsupported += $e.FullName; continue }
$ns = New-Ns $xml
$nodes = @($xml.SelectNodes($xpath, $ns))
if ($nodes.Count -eq 0) { continue }
foreach ($n in $nodes) {
$note = Get-Blocker $n $ns
if ($note) { $blocked = $true }
$isOuter = ($n.SelectSingleNode("ancestor::w:dir | ancestor::w:bdo | ancestor::w:customXml", $ns) -eq $null)
if (-not $isOuter -and -not $note) { continue }
$inner = ($n.SelectNodes(".//w:t", $ns) | ForEach-Object { $_.InnerText }) -join ""
[void]$found.Add([pscustomobject]@{
Part = $e.FullName; Element = "w:" + $n.LocalName
HiddenInside = [regex]::Matches($inner, $hiddenClass).Count
Note = $note; Context = (Get-Context $n $ns) })
}
Remove-Wrappers $xml $ns
$fixed[$e.FullName] = $xml
}
} finally { $zip.Dispose() }
if ($unsupported.Count -gt 0) {
"These parts use an XML namespace this script does not handle (for example Strict Open XML):"
$unsupported | ForEach-Object { " $_" }
"Nothing was checked or changed in them."
}
if ($found.Count -eq 0) {
if ($unsupported.Count -gt 0) { return }
"No w:dir, w:bdo or w:customXml elements found in the checked parts."
"This does not rule out other causes of missing text."
return
}
$found | Format-Table Part, Element, HiddenInside, Note, Context -AutoSize -Wrap | Out-String -Width 220
if ($ListOnly) { return }
if ($blocked -or $unsupported.Count -gt 0) {
"No fixed copy was written: see the Note column, or the parts listed above."
return
}
$dst = Join-Path ([IO.Path]::GetDirectoryName($src)) ([IO.Path]::GetFileNameWithoutExtension($src) + "_fixed.docx")
if (Test-Path -LiteralPath $dst) { throw "$dst already exists. Move or rename it first." }
# A temporary file with a name unique to this run, created exclusively (fails if it exists).
$tmp = $dst + "." + [guid]::NewGuid().ToString("N") + ".tmp"
$created = $false
try {
$out = [System.IO.File]::Open($tmp, [System.IO.FileMode]::CreateNew)
$created = $true
try {
$in = [System.IO.File]::OpenRead($src)
try { $in.CopyTo($out) } finally { $in.Dispose() }
} finally { $out.Dispose() }
$zip = [System.IO.Compression.ZipFile]::Open($tmp, [System.IO.Compression.ZipArchiveMode]::Update)
try {
foreach ($part in $fixed.Keys) {
$zip.GetEntry($part).Delete()
$stream = $zip.CreateEntry($part).Open()
try {
$writer = New-Object System.IO.StreamWriter($stream, (New-Object System.Text.UTF8Encoding $false))
try { $fixed[$part].Save($writer) } finally { $writer.Dispose() }
} finally { $stream.Dispose() }
}
} finally { $zip.Dispose() }
# Re-read the copy: every changed part must parse and contain none of the elements.
$zip = [System.IO.Compression.ZipFile]::OpenRead($tmp)
try {
foreach ($part in $fixed.Keys) {
$xml = Read-Part ($zip.GetEntry($part))
if ($xml.SelectNodes($xpath, (New-Ns $xml)).Count -ne 0) { throw "Check failed in $part." }
}
} finally { $zip.Dispose() }
Move-Item -LiteralPath $tmp -Destination $dst
} catch {
# Only this run's temporary file is removed.
if ($created -and (Test-Path -LiteralPath $tmp)) { Remove-Item -LiteralPath $tmp }
throw
}
"Wrote $dst"
"Place it in a new InDesign document first and compare each listed location with the Word file."
Run it on Windows
- Open Notepad, paste the code and choose File > Save As. Pick a folder, set Save as type to All files, and type
FixWordWrappers.ps1as the file name. If File Explorer shows file name extensions, check that the name doesn't end in.txt. - Open Windows PowerShell from the Start menu. You don't need to run it as administrator.
- To list the elements, paste this as one line, replace the two paths with your own, keep the quotation marks, and press Enter:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\scripts\FixWordWrappers.ps1" -Path "C:\work\manuscript.docx" -ListOnly
- Check the list for Note entries and for a message about unsupported parts. If there are none, write the fixed copy by running the same line without
-ListOnly:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\scripts\FixWordWrappers.ps1" -Path "C:\work\manuscript.docx"
- If the script printed
Wrote ..., placemanuscript_fixed.docxin a new InDesign document first. Compare each listed location, and the footnotes, with the Word file before you use it in your layout.
-ExecutionPolicy Bypass applies only to that run. If your computer is managed by your company, a Group Policy setting can still block scripts; ask whoever manages it.
If you have already placed and formatted the file
Text dropped this way never reached InDesign, so Find/Change can't bring it back. Run the script on the original .docx with -ListOnly and use Context to find each reported passage in InDesign. Compare it with the Word file, restore only text that is actually missing, and check its formatting against the original.
Removing the direction characters
The fixed copy still contains any direction characters that were typed into the text. If you have confirmed they were added by accident, find them with this GREP expression in Find/Change:
[\x{200E}\x{200F}\x{202A}-\x{202E}\x{2066}-\x{2069}]
Leave Change to empty. Use Find Next to inspect each match and Change to remove an unwanted character. Use Change All only if every match in the search scope is unwanted. In my test file, Change All removed all 20 of these characters and kept every word.
U+200B (zero-width space) and U+2060 (word joiner) affect where lines can break. [\x{200B}\x{2060}] found them in my test. Remove them only after confirming they are unwanted, and check the line breaks afterward. I left out U+200C and U+200D, which can be required for text shaping and emoji sequences, and U+00AD (soft hyphen). This GREP search did not match U+FEFF in my test, and I have not verified a way to remove it in InDesign.
These searches remove characters only. They don't bring back text that was never imported.
What to tell the author
Ask the author whether the direction settings at the reported places were intended. If they were not, ask them to retype the affected reference and send a revised file, and check that file with the script before you place it.
Both threads mention saving the manuscript as a Word 97-2003 document (.doc) as a workaround. In the .doc from the thread, the text after "John 1:11" was stored as ordinary characters, with the direction characters among them. I did not place that .doc.
What I tested
I wrote the test .docx by hand, one case per paragraph, and checked the placed text paragraph by paragraph. I also saved it from Word 2007 as .docx and .doc and placed those. I inspected the files shared in the thread without opening them in Word or InDesign, and ran the script on that .docx. I ran the script on small test files with a namespace declaration or an xml:space attribute on the element, mc:AlternateContent, an element around two paragraphs, and a Strict document, and checked that a failure while writing the copy left no files behind.
I did not test macOS, RTF files, copy and paste from Word, or right-to-left documents. The shared manuscript provided one observed instance of a left-to-right override.