diff --git a/src/lib/import/CLAUDE.md b/src/lib/import/CLAUDE.md index 1819536..c84f9ee 100644 --- a/src/lib/import/CLAUDE.md +++ b/src/lib/import/CLAUDE.md @@ -37,7 +37,7 @@ The **Open .odt** button (`App.svelte`) parses an uploaded file with `importOdt( - **A cell's number format** is on its *style* in ODF (`style:data-style-name` → a `number:number-style`/`percentage-style`/`currency-style`/`date-style`, read for its family, decimals and grouping) and on the field in DOCX (the `\#` picture, or `\@` for a date, which is not part of the formula and is cut out of it). Only a formula cell keeps one, and the file's own currency symbol and date order give way to the document language's. - **Vertical text in a box** is the style's writing mode in ODF (a frame's graphic properties — where LibreOffice writes it — or a drawing shape's paragraph properties; both top-to-bottom modes) and `w:bodyPr vert` in DOCX (`vert`/`eaVert`/`mongolianVert`/`wordArtVert`; `vert270` reads bottom-to-top, which the browser cannot lay out, so it is not one). VML says the same as `layout-flow:vertical` on the textbox. - **Ruby**: `text:ruby` and `w:ruby` are one atom here (`ruby.ts`), so only the two texts are read — the base out of `text:ruby-base` / `w:rubyBase`, the reading out of `text:ruby-text` / `w:rt`. A file's own alignment and position are dropped; both products' defaults are what the exports write. -- **Bibliography**: a `text:bibliography-mark`'s every `text:` attribute but the identifier and the type is a source field, whatever the file names it, so nothing is lost on a format we have no UI for. Word's `CITATION` names a tag only — the record comes from `customXml/item*.xml` (each is probed for a `b:Sources` root), and a tag no source declares still cites with the field's cached text. `BIBLIOGRAPHY` and `text:bibliography` both become the index node. +- **Bibliography**: a `text:bibliography-mark`'s every `text:` attribute but the identifier and the type is a source field, whatever the file names it, so nothing is lost on a format we have no UI for. Word's `CITATION` names a tag only — the record comes from `customXml/item*.xml` (each is probed for a `b:Sources` root), and a tag no source declares still cites with the field's cached text. `BIBLIOGRAPHY` and `text:bibliography` both become the index node — and Word caches that field's rows as a **`w:tbl`** inside the field, so every block between the field's `separate` and its `end` is skipped, not only the paragraphs; imported, that table printed the whole list a second time (measured: two pages). `b:Sources StyleName` picks the citation style (`citationStyleFromDocx`). - **Tracked changes**: ODF points at a registry, so `odfRevisions(body)` reads `` first and the body walk resolves each `text:change-start`/`-end` and `text:change` against it (the open insertions live in `Ctx` — a range may close in a later paragraph). A `text:change` is a deletion whose text is *only* in the registry: it is emitted there and then, marked. Word needs no registry — `w:ins`/`w:del` wrap their runs, whose text sits in `w:delText` rather than `w:t` for a deletion. Whether the document goes on **recording** is read too (`recordChanges`): the registry's `text:track-changes`, which ODF defaults to true where the element carries none, and Word's `w:trackRevisions` on/off in settings.xml. - **Footnotes and endnotes** round-trip: `text:note` (ODF) and `word/footnotes.xml`/`endnotes.xml` (DOCX) become a `noteRef` anchor plus a `note` in the document-final `noteSection`, and the numbering configuration becomes `NoteSettings`. See `docs/architecture/notes.md`. - **Graceful degradation** (collected as `warnings`, surfaced once): comments dropped, hyperlinks flattened to text, nested tables flattened to paragraphs, non-`Pictures` drawings dropped, unreadable pictures skipped (each with a warning). Page size is normalized to A4. Floating/wrapped images are imported as left/right/top-bottom wrap (free x/y positions collapse to the nearest side; see `image.ts`). diff --git a/src/lib/import/docx.ts b/src/lib/import/docx.ts index b6adc42..5c0d3c3 100644 --- a/src/lib/import/docx.ts +++ b/src/lib/import/docx.ts @@ -573,6 +573,10 @@ function convertBlocks(children: Element[], ctx: Ctx, kind: BlockKind, boldByDef out.push(...blocks); breakPending = trailingBreak; } + } else if (tocState.tocDepth >= 0) { + // Still inside an open TOC/INDEX/BIBLIOGRAPHY field: whatever carries its cached + // result is skipped, the paragraphs above and a bibliography's table alike. + continue; } else if (el.localName === 'tbl') { breakPending = false; // a break before a table can't be modeled; drop it flush(); diff --git a/src/lib/utils/citationStyle.ts b/src/lib/utils/citationStyle.ts index 11c4710..ececbc6 100644 --- a/src/lib/utils/citationStyle.ts +++ b/src/lib/utils/citationStyle.ts @@ -22,7 +22,7 @@ export const DOCX_STYLE_NAME: Record = { const FROM_DOCX_STYLE: Record = { APA: 'apa', MLA: 'mla', Chicago: 'chicago', ChicagoAuthorDate: 'chicago', - ISO690Numerical: 'numbered', IEEE2006: 'numbered', + ISO690Numerical: 'numbered', IEEE: 'numbered', IEEE2006: 'numbered', }; export function citationStyleFromDocx(name: string): CitationStyle | null { diff --git a/tests/unit/docx-bibliography-field.test.ts b/tests/unit/docx-bibliography-field.test.ts new file mode 100644 index 0000000..ad8d0f1 --- /dev/null +++ b/tests/unit/docx-bibliography-field.test.ts @@ -0,0 +1,62 @@ +// Word writes a BIBLIOGRAPHY field's cached rows as a borderless w:tbl inside the field. +// The index node is regenerated, so that table is the field's result and not content — +// imported it would print the whole list a second time. +import { describe, it, expect } from 'vitest'; +import { zipSync, strToU8 } from 'fflate'; +import { importDocx } from '../../src/lib/import/docx'; + +const W = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"'; +const B = 'http://schemas.openxmlformats.org/officeDocument/2006/bibliography'; + +const CT = ` + + +`; + +const RELS = ` + +`; + +const sources = (styleName: string) => ` +Kaf25BookDer Process1925 +KafkaFranz +`; + +// The shape Word writes: a Bibliographies content control holding the heading, then the +// field, whose cached result between `separate` and `end` is a table. +const DOCUMENT = ` + + Literaturverzeichnis + + BIBLIOGRAPHY + [1] F. Kafka, Der Process, 1925. + + + +`; + +const build = (styleName: string) => zipSync({ + '[Content_Types].xml': strToU8(CT), + '_rels/.rels': strToU8(RELS), + 'word/document.xml': strToU8(DOCUMENT), + 'customXml/item1.xml': strToU8(sources(styleName)), +}); + +const types = (doc: { content?: { type: string }[] }): string[] => (doc.content ?? []).map((b) => b.type); + +describe('a BIBLIOGRAPHY field', () => { + it('becomes one index node, its cached table dropped', () => { + const doc = importDocx(build('IEEE')).content as unknown as { content?: { type: string }[] }; + expect(types(doc)).toEqual(['paragraph', 'tableOfContents']); + }); + + it('takes the numbered style IEEE names', () => { + const doc = importDocx(build('IEEE')).content as unknown as { content?: { type: string; attrs?: Record }[] }; + expect(doc.content![1].attrs).toMatchObject({ index: 'bibliography', citationStyle: 'numbered' }); + }); + + it('falls back to cite-by-key where the style is one we have not got', () => { + const doc = importDocx(build('Gost-Name')).content as unknown as { content?: { attrs?: Record }[] }; + expect(doc.content![1].attrs).toMatchObject({ citationStyle: 'key' }); + }); +});