Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions lib/components/document/src/BookmarkRangeEnd.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Bookmark } from '../../../classes/src/Bookmarks.ts';
import {
type ComponentAncestor,
Component,
type ComponentAncestor,
} from '../../../classes/src/Component.ts';
import { registerComponent } from '../../../utilities/src/components.ts';
import { create } from '../../../utilities/src/dom.ts';
Expand All @@ -17,12 +17,12 @@ export type BookmarkRangeEndChild = never;
* A type describing the props accepted by {@link BookmarkRangeEnd}.
*/
export type BookmarkRangeEndProps =
| { bookmark: Bookmark; id?: never }
| { bookmark: Bookmark; id?: never; displaced?: 'next' | 'prev' | null }
// Deprecate this way:
| { bookmark?: never; id: number };
| { bookmark?: never; id: number; displaced?: 'next' | 'prev' | null };

/**
* The end of a range associated with a comment.
* The end of a range associated with a bookmark.
*/
export class BookmarkRangeEnd extends Component<
BookmarkRangeEndProps,
Expand All @@ -39,10 +39,14 @@ export class BookmarkRangeEnd extends Component<
public override toNode(_ancestry: ComponentAncestor[]): Node {
return create(
`element ${QNS.w}bookmarkEnd {
attribute ${QNS.w}id { $id }
attribute ${QNS.w}id { $id },
if (exists($displaced)) then attribute ${QNS.w}displacedByCustomXml { $displaced } else ()
}`,
this.props.bookmark || {
id: this.props.id,
{
id: this.props.bookmark
? this.props.bookmark.id
: this.props.id,
displaced: this.props.displaced || null,
}
);
}
Expand All @@ -58,14 +62,19 @@ export class BookmarkRangeEnd extends Component<
* Instantiate this component from the XML in an existing DOCX file.
*/
static override fromNode(node: Node): BookmarkRangeEnd {
return new BookmarkRangeEnd(
evaluateXPathToMap<BookmarkRangeEndProps>(
`map {
"id": ./@${QNS.w}id/number()
}`,
node
)
const props = evaluateXPathToMap<BookmarkRangeEndProps>(
`map {
"id": ./@${QNS.w}id/number(),
"displaced": ./@${QNS.w}displacedByCustomXml/string()
}`,
node
);

if (!props.displaced) {
props.displaced = undefined;
}

return new BookmarkRangeEnd(props);
}
}

Expand Down
25 changes: 19 additions & 6 deletions lib/components/document/src/BookmarkRangeStart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ export type BookmarkRangeStartProps =
bookmark: Bookmark;
id?: never;
name?: never;
displaced?: 'next' | 'prev' | null;
}
// Deprecate this way:
| {
bookmark?: never;
id: number;
name: string;
displaced?: 'next' | 'prev' | null;
};

/**
Expand All @@ -49,11 +51,17 @@ export class BookmarkRangeStart extends Component<
return create(
`element ${QNS.w}bookmarkStart {
attribute ${QNS.w}id { $id },
attribute ${QNS.w}name { $name }
attribute ${QNS.w}name { $name },
if (exists($displaced)) then attribute ${QNS.w}displacedByCustomXml { $displaced } else ()
}`,
this.props.bookmark || {
id: this.props.id,
name: this.props.name,
{
id: this.props.bookmark
? this.props.bookmark.id
: this.props.id,
name: this.props.bookmark
? this.props.bookmark.name
: this.props.name,
displaced: this.props.displaced || null,
}
);
}
Expand All @@ -75,11 +83,16 @@ export class BookmarkRangeStart extends Component<
const props = evaluateXPathToMap<BookmarkRangeStartProps>(
`map {
"id": ./@${QNS.w}id/number(),
"name": ./@${QNS.w}name/string()
}`,
"name": ./@${QNS.w}name/string(),
"displaced": ./@${QNS.w}displacedByCustomXml/string()
}`,
node
);

if (!props.displaced) {
props.displaced = undefined;
}

context.bookmarks?.registerIdentifier(props.id!, props.name);
return new BookmarkRangeStart(props);
}
Expand Down
55 changes: 55 additions & 0 deletions lib/components/document/test/BookmarkRangeEnd.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { expect } from 'std/expect';
import { describe, it } from 'std/testing/bdd';

import { create, serialize } from '../../../utilities/src/dom.ts';
import { NamespaceUri } from '../../../utilities/src/namespaces.ts';
import { BookmarkRangeEnd } from '../src/BookmarkRangeEnd.ts';

describe('BookmarkRangeEnd', () => {
it('parses id from XML', () => {
const node = create(`
<w:bookmarkEnd xmlns:w="${NamespaceUri.w}" w:id="3" />
`);
const component = BookmarkRangeEnd.fromNode(node);
expect(component.props.id).toBe(3);
});

it('serializes back to XML correctly', () => {
const node = create(`
<w:bookmarkEnd xmlns:w="${NamespaceUri.w}" w:id="1" />
`);
const component = BookmarkRangeEnd.fromNode(node);
const output = serialize(component.toNode([]));
expect(output).toContain('id="1"');
});

it('parses the displaced prop from XML', () => {
const node = create(`
<w:bookmarkEnd xmlns:w="${NamespaceUri.w}" w:id="4" w:displacedByCustomXml="prev" />
`);
const component = BookmarkRangeEnd.fromNode(node);
expect(component.props.displaced).toBe('prev');
});

it('serializes the displaced prop to XML', () => {
const component = new BookmarkRangeEnd({
id: 2,
displaced: 'next',
});
const output = serialize(component.toNode([]));
expect(output).toContain('displacedByCustomXml="next"');
});

it('omits the displaced attribute when not set', () => {
const component = new BookmarkRangeEnd({ id: 3 });
const output = serialize(component.toNode([]));
expect(output).not.toContain('displacedByCustomXml');
});

it('matches a w:bookmarkEnd node', () => {
const node = create(
`<w:bookmarkEnd xmlns:w="${NamespaceUri.w}" w:id="1" />`
);
expect(BookmarkRangeEnd.matchesNode(node)).toBe(true);
});
});
33 changes: 33 additions & 0 deletions lib/components/document/test/BookmarkRangeStart.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,37 @@ describe('BookmarkRangeStart', () => {
expect(output).toContain('id="1"');
expect(output).toContain('name="test_bm"');
});

it('parses the displaced prop from XML', () => {
const bookmarks = new Bookmarks();
const context: ComponentContext = {
archive: new Archive(),
relationships: null,
bookmarks,
};
const node = create(`
<w:bookmarkStart xmlns:w="${NamespaceUri.w}" w:id="4" w:name="displaced_bm" w:displacedByCustomXml="next" />
`);
const component = BookmarkRangeStart.fromNode(node, context);
expect(component.props.displaced).toBe('next');
});

it('serializes the displaced prop to XML', () => {
const component = new BookmarkRangeStart({
id: 2,
name: 'displaced_bm',
displaced: 'prev',
});
const output = serialize(component.toNode([]));
expect(output).toContain('displacedByCustomXml="prev"');
});

it('omits the displaced attribute when not set', () => {
const component = new BookmarkRangeStart({
id: 3,
name: 'plain_bm',
});
const output = serialize(component.toNode([]));
expect(output).not.toContain('displacedByCustomXml');
});
});