diff --git a/.gitignore b/.gitignore
index ddaef248..316981ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,6 @@ coverage/
examples/
lib/
node_modules/
-test/example
+example.spec.ts
TODO.md
diff --git a/CHANGELOG.md b/CHANGELOG.md
index accfdf85..748a7b3d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Changed
- **meta:** Use `Date` instead of `number` when dealing with dates
- **chore:** Update dev dependencies, place test code next to production code, mention contributors in package.json
+- **refactor:** Split API and serialization logic, closes [#60](https://github.com/connium/simple-odf/issues/60)
## [0.6.0] (2018-10-12)
### Added
diff --git a/README.md b/README.md
index 454b7664..1d84b785 100644
--- a/README.md
+++ b/README.md
@@ -22,14 +22,15 @@ Create your first document.
const simpleOdf = require("simple-odf");
const document = new simpleOdf.TextDocument();
+const body = document.getBody();
-const image = document.addParagraph().addImage("/home/homer/myself.png");
+const image = body.addParagraph().addImage("/home/homer/myself.png");
image.getStyle().setAnchorType(simpleOdf.AnchorType.AsChar);
image.getStyle().setSize(29.4, 36.5);
-document.addHeading("Welcome to simple-odf");
+body.addHeading("Welcome to simple-odf");
-const p1 = document.addParagraph("The quick, brown fox jumps over a lazy dog.");
+const p1 = body.addParagraph("The quick, brown fox jumps over a lazy dog.");
p1.addText("\nThe five boxing wizards jump quickly.\n\n");
p1.addHyperlink("Visit me", "http://example.org/");
const style1 = new simpleOdf.ParagraphStyle();
@@ -45,15 +46,15 @@ style1.setKeepTogether();
p1.setStyle(style1);
// font usage
document.declareFont("Open Sans", "Open Sans", simpleOdf.FontPitch.Variable);
-const p2 = document.addParagraph("It always seems impossible until it's done.");
+const p2 = body.addParagraph("It always seems impossible until it's done.");
const style2 = new simpleOdf.ParagraphStyle();
style1.setFontName("Open Sans");
-document.addHeading("Credits", 2);
+body.addHeading("Credits", 2);
-document.addParagraph("This was quite easy. Do you want to know why?");
+body.addParagraph("This was quite easy. Do you want to know why?");
-const list = document.addList();
+const list = body.addList();
list.addItem("one-liner setup");
list.addItem("just write like you would do in a full-blown editor");
diff --git a/docs/API.md b/docs/API.md
index dcb6d0af..50805390 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -3,11 +3,35 @@
- Image
This class represents an image in a paragraph.
+It is used to embed image data in BASE64 encoding.
- Meta
This class represents the metadata of a document.
It is used to set descriptive information about the document.
+- TextBody
+This class represents the content of a text document.
+
+- TextDocument
+This class represents a text document in OpenDocument format.
+
+- Heading ⇐
Paragraph
+This class represents a heading in a document.
+It is used to structure a document into multiple sections.
+A chapter or section begins with a heading and extends to the next heading at the same or higher level.
+
+- Hyperlink
+This class represents a hyperlink in a paragraph.
+
+- List
+This class represents a list and may contain any number list items.
+
+- ListItem
+This class represents an item in a list.
+
+- Paragraph
+This class represents a paragraph.
+
- Color
This class represents a Color.
@@ -25,25 +49,6 @@
Tab stops are used to align text in a paragraph.
To become effective they must be set to the style of the respective paragraph.
-- Heading
-This class represents a heading.
-
-- Hyperlink
-This class represents a hyperlink in a paragraph.
-
-- List
-This class represents a list.
-It can contain multiple list items.
-
-- ListItem
-This class represents an item in a list.
-
-- Paragraph
-This class represents a paragraph.
-
-- TextDocument
-This class represents an empty ODF text document.
-
@@ -51,13 +56,15 @@ It can contain multiple list items.
## Image
This class represents an image in a paragraph.
+It is used to embed image data in BASE64 encoding.
+
**Since**: 0.3.0
* [Image](#Image)
* [`new Image(path)`](#new_Image_new)
- * [`.setStyle(style)`](#Image+setStyle)
+ * [`.getPath()`](#Image+getPath) ⇒ string
+ * [`.setStyle(style)`](#Image+setStyle) ⇒ [Image
](#Image)
* [`.getStyle()`](#Image+getStyle) ⇒ IImageStyle
- * [`.toXml()`](#Image+toXml)
* * *
@@ -71,18 +78,51 @@ Creates an image
- path string
Path to the image file that should be embedded
+**Example**
+```js
+document.getBody()
+ .addParagraph()
+ .addImage("/home/homer/myself.png")
+ .getStyle()
+ .setSize(42, 23);
+```
+
+* * *
+
+
+
+### `image.getPath()` ⇒ string
+The `getPath()` method returns the path to the image file that should be embedded.
+
+**Return value**
+string
- The path to the image file
+
+**Example**
+```js
+const image = new Image("/home/homer/myself.png");
+image.getPath(); // '/home/homer/myself.png'
+```
+**Since**: 0.7.0
* * *
-### `image.setStyle(style)`
+### `image.setStyle(style)` ⇒ [Image
](#Image)
Sets the new style of this image.
#### Parameters
- style IImageStyle
The new style
+**Return value**
+[Image
](#Image) - The `Image` object
+
+**Example**
+```js
+const image = new Image("/home/homer/myself.png");
+image.setStyle(new ImageStyle());
+```
**Since**: 0.5.0
* * *
@@ -95,16 +135,17 @@ Returns the style of this image.
**Return value**
IImageStyle
- The style of the image
+**Example**
+```js
+const image = new Image("/home/homer/myself.png");
+image.getStyle(); // default style
+image.setStyle(new ImageStyle());
+image.getStyle(); // previously set style
+```
**Since**: 0.5.0
* * *
-
-
-### `image.toXml()`
-
-* * *
-
## Meta
@@ -118,9 +159,9 @@ It is used to set descriptive information about the document.
* [`new Meta()`](#new_Meta_new)
* [`.setCreator(creator)`](#Meta+setCreator) ⇒ [Meta
](#Meta)
* [`.getCreator()`](#Meta+getCreator) ⇒ string
\| undefined
- * [`.getCreationDate()`](#Meta+getCreationDate) ⇒ number
+ * [`.getCreationDate()`](#Meta+getCreationDate) ⇒ Date
* [`.setDate(date)`](#Meta+setDate) ⇒ [Meta
](#Meta)
- * [`.getDate()`](#Meta+getDate) ⇒ number
\| undefined
+ * [`.getDate()`](#Meta+getDate) ⇒ Date
\| undefined
* [`.setDescription(description)`](#Meta+setDescription) ⇒ [Meta
](#Meta)
* [`.getDescription()`](#Meta+getDescription) ⇒ string
\| undefined
* [`.getEditingCycles()`](#Meta+getEditingCycles) ⇒ number
@@ -134,14 +175,13 @@ It is used to set descriptive information about the document.
* [`.setLanguage(language)`](#Meta+setLanguage) ⇒ [Meta
](#Meta)
* [`.getLanguage()`](#Meta+getLanguage) ⇒ string
\| undefined
* [`.setPrintDate(printDate)`](#Meta+setPrintDate) ⇒ [Meta
](#Meta)
- * [`.getPrintDate()`](#Meta+getPrintDate) ⇒ number
\| undefined
+ * [`.getPrintDate()`](#Meta+getPrintDate) ⇒ Date
\| undefined
* [`.setPrintedBy(printedBy)`](#Meta+setPrintedBy) ⇒ [Meta
](#Meta)
* [`.getPrintedBy()`](#Meta+getPrintedBy) ⇒ string
\| undefined
* [`.setSubject(subject)`](#Meta+setSubject) ⇒ [Meta
](#Meta)
* [`.getSubject()`](#Meta+getSubject) ⇒ string
\| undefined
* [`.setTitle(title)`](#Meta+setTitle) ⇒ [Meta
](#Meta)
* [`.getTitle()`](#Meta+getTitle) ⇒ string
\| undefined
- * [`.toXml(document, root)`](#Meta+toXml)
* * *
@@ -213,13 +253,13 @@ meta.getCreator(); // 'Lisa Simpson'
-### `meta.getCreationDate()` ⇒ number
+### `meta.getCreationDate()` ⇒ Date
The `getCreationDate()` method returns the UTC timestamp specifying the date and time when a document was created.
The creation date is initialized with the UTC timestamp of the moment the `Meta` instance was created.
**Return value**
-number
- The UTC timestamp specifying the date and time when a document was created
+Date
- A `Date` instance specifying the date and time when a document was created
**Example**
```js
@@ -236,9 +276,9 @@ meta.getCreationDate(); // 1585742400000
The `setDate()` method sets the date and time when the document was last modified.
#### Parameters
-- date number
| undefined
-The UTC timestamp specifying the date and time when the document was last modified
- or `undefined` to unset the date
+- date Date
| undefined
+A `Date` instance specifying the date and time when the document was last modified
+ or `undefined` to unset the date
**Return value**
[Meta
](#Meta) - The `Meta` object
@@ -246,7 +286,7 @@ The UTC timestamp specifying the date and time when the document was last modifi
**Example**
```js
const meta = new Meta();
-meta.setDate(Date.now()); // 2020-07-23 13:37:00
+meta.setDate(new Date()); // 2020-07-23 13:37:00
```
**Since**: 0.6.0
@@ -254,18 +294,18 @@ meta.setDate(Date.now()); // 2020-07-23 13:37:00
-### `meta.getDate()` ⇒ number
\| undefined
+### `meta.getDate()` ⇒ Date
\| undefined
The `getDate()` method returns the date and time when the document was last modified.
**Return value**
-number
\| undefined
- The UTC timestamp specifying the date and time when the document was last modified
- or `undefined` if the date is not set
+Date
\| undefined
- A `Date` instance specifying the date and time when the document was last modified
+ or `undefined` if the date is not set
**Example**
```js
const meta = new Meta();
meta.getDate(); // undefined
-meta.setDate(Date.now()); // 2020-07-23 13:37:00
+meta.setDate(new Date()); // 2020-07-23 13:37:00
meta.getDate(); // 1595511420000
```
**Since**: 0.6.0
@@ -528,9 +568,9 @@ meta.getLanguage(); // 'en-US'
The `setPrintDate()` method sets the date and time when the document was last printed.
#### Parameters
-- printDate number
| undefined
-The UTC timestamp specifying the date and time when the document was last
- printed or `undefined` to unset the print date
+- printDate Date
| undefined
+A `Date` instance specifying the date and time when the document was last
+ printed or `undefined` to unset the print date
**Return value**
[Meta
](#Meta) - The `Meta` object
@@ -538,7 +578,7 @@ The UTC timestamp specifying the date and time when the document was last
**Example**
```js
const meta = new Meta();
-meta.setPrintDate(Date.now()); // 2020-07-23 13:37:00
+meta.setPrintDate(new Date()); // 2020-07-23 13:37:00
```
**Since**: 0.6.0
@@ -546,18 +586,18 @@ meta.setPrintDate(Date.now()); // 2020-07-23 13:37:00
-### `meta.getPrintDate()` ⇒ number
\| undefined
+### `meta.getPrintDate()` ⇒ Date
\| undefined
The `getPrintDate()` method returns the date and time when the document was last printed.
**Return value**
-number
\| undefined
- The UTC timestamp specifying the date and time when the document was last printed
- or `undefined` if the print date is not set
+Date
\| undefined
- A `Date` instance specifying the date and time when the document was last printed
+ or `undefined` if the print date is not set
**Example**
```js
const meta = new Meta();
meta.getPrintDate(); // undefined
-meta.setPrintDate(Date.now()); // 2020-07-23 13:37:00
+meta.setPrintDate(new Date()); // 2020-07-23 13:37:00
meta.getPrintDate(); // 1595511420000
```
**Since**: 0.6.0
@@ -689,1077 +729,1393 @@ meta.getTitle(); // 'Memoirs of Homer Simpson'
* * *
-
-
-### `meta.toXml(document, root)`
-Transforms the text style into Open Document Format.
-
-#### Parameters
-- document Document
-The XML document
-- root Element
-The root node in the DOM
-
-**Since**: 0.6.0
-
-* * *
-
-
+
-## Color
-This class represents a Color.
+## TextBody
+This class represents the content of a text document.
-**Since**: 0.4.0
+**Since**: 0.7.0
-* [Color](#Color)
- * [`new Color(red, green, blue)`](#new_Color_new)
- * _instance_
- * [`.toHex()`](#Color+toHex) ⇒ string
- * _static_
- * [`.fromHex(value)`](#Color.fromHex) ⇒ [Color
](#Color) \| undefined
- * [`.fromRgb(red, green, blue)`](#Color.fromRgb) ⇒ [Color
](#Color) \| undefined
+* [TextBody](#TextBody)
+ * [`.addHeading([text], [level])`](#TextBody+addHeading) ⇒ [Heading
](#Heading)
+ * [`.addList()`](#TextBody+addList) ⇒ [List
](#List)
+ * [`.addParagraph([text])`](#TextBody+addParagraph) ⇒ [Paragraph
](#Paragraph)
* * *
-
+
-### `new Color(red, green, blue)`
-Creates a new color with the specified channel values.
+### `textBody.addHeading([text], [level])` ⇒ [Heading
](#Heading)
+Adds a heading at the end of the document.
+If a text is given, this will be set as text content of the heading.
#### Parameters
-- red number
-The red channel of the color
-- green number
-The green channel of the color
-- blue number
-The blue channel of the color
-
-
-* * *
-
-
-
-### `color.toHex()` ⇒ string
-The toHex() method returns a string representing the color as hex string.
+- [text] string
+The text content of the heading
+- [level] number
= 1
+The heading level; defaults to 1 if omitted
**Return value**
-string
- A hex string representing the color
+[Heading
](#Heading) - The newly added heading
-**Since**: 0.4.0
+**Since**: 0.7.0
* * *
-
-
-### `Color.fromHex(value)` ⇒ [Color
](#Color) \| undefined
-The `Color.fromHex()` method parses a string argument and returns a color.
-The string is expected to be in `#rrggbb` or `rrggbb` format.
+
-#### Parameters
-- value string
-The value you want to parse
+### `textBody.addList()` ⇒ [List
](#List)
+Adds an empty list at the end of the document.
**Return value**
-[Color
](#Color) \| undefined
- A color parsed from the given value.
-If the value cannot be converted to a color, `undefined` is returned.
+[List
](#List) - The newly added list
-**Since**: 0.4.0
+**Since**: 0.7.0
* * *
-
+
-### `Color.fromRgb(red, green, blue)` ⇒ [Color
](#Color) \| undefined
-The `Color.fromRgb()` method returns a color with the channel arguments.
+### `textBody.addParagraph([text])` ⇒ [Paragraph
](#Paragraph)
+Adds a paragraph at the end of the document.
+If a text is given, this will be set as text content of the paragraph.
#### Parameters
-- red number
-The red channel of the color with a range of 0...255
-- green number
-The green channel of the color with a range of 0...255
-- blue number
-The blue channel of the color with a range of 0...255
+- [text] string
+The text content of the paragraph
**Return value**
-[Color
](#Color) \| undefined
- A color parsed from the given value.
-If any channel is outside the allowable range, `undefined` is returned.
+[Paragraph
](#Paragraph) - The newly added paragraph
-**Since**: 0.4.0
+**Since**: 0.7.0
* * *
-
+
-## ImageStyle
-This class represents the style of an image
+## TextDocument
+This class represents a text document in OpenDocument format.
-**Since**: 0.5.0
+**Since**: 0.1.0
-* [ImageStyle](#ImageStyle)
- * [`new ImageStyle()`](#new_ImageStyle_new)
- * [`.setAnchorType()`](#ImageStyle+setAnchorType)
- * [`.getAnchorType()`](#ImageStyle+getAnchorType)
- * [`.setHeight(height)`](#ImageStyle+setHeight)
- * [`.getHeight()`](#ImageStyle+getHeight) ⇒ number
\| undefined
- * [`.setWidth(width)`](#ImageStyle+setWidth)
- * [`.getWidth()`](#ImageStyle+getWidth) ⇒ number
\| undefined
- * [`.setSize(width, height)`](#ImageStyle+setSize)
- * [`.toXml()`](#ImageStyle+toXml)
+* [TextDocument](#TextDocument)
+ * [`.getBody()`](#TextDocument+getBody) ⇒ [TextBody
](#TextBody)
+ * [`.declareFont(name, fontFamily, fontPitch)`](#TextDocument+declareFont) ⇒ FontFace
+ * [`.getFonts()`](#TextDocument+getFonts) ⇒ Array.<FontFace>
+ * [`.getMeta()`](#TextDocument+getMeta) ⇒ [Meta
](#Meta)
+ * [`.saveFlat(filePath)`](#TextDocument+saveFlat) ⇒ Promise.<void>
+ * ~~[`.toString()`](#TextDocument+toString) ⇒ string
~~
* * *
-
+
-### `new ImageStyle()`
-Constructor.
+### `textDocument.getBody()` ⇒ [TextBody
](#TextBody)
+The `getBody()` method returns the content of the document.
+**Return value**
+[TextBody
](#TextBody) - A `TextBody` object that holds the content of the document
+
+**Example**
+```js
+new TextDocument()
+ .getBody()
+ .addHeading('My first document');
+```
+**Since**: 0.7.0
* * *
-
+
-### `imageStyle.setAnchorType()`
+### `textDocument.declareFont(name, fontFamily, fontPitch)` ⇒ FontFace
+The `declareFont` method creates a font face to be used in the document.
-* * *
+**Note: There is no check whether the font exists.
+In order to be displayed properly, the font must be present on the target system.**
-
+#### Parameters
+- name string
+The name of the font; this name must be set to a [ParagraphStyle](#ParagraphStyle)
+- fontFamily string
+The name of the font family
+- fontPitch FontPitch
+The pitch of the font
-### `imageStyle.getAnchorType()`
+**Return value**
+FontFace
- The declared `FontFace` object
+
+**Example**
+```js
+new TextDocument()
+ .declareFont("FreeSans", "FreeSans", FontPitch.Variable);
+```
+**Since**: 0.4.0
* * *
-
+
-### `imageStyle.setHeight(height)`
-Sets the target height of the image.
+### `textDocument.getFonts()` ⇒ Array.<FontFace>
+The `getFonts()` method returns all font face declarations for the document.
-#### Parameters
-- height number
-The target height of the image in millimeter
+**Return value**
+Array.<FontFace>
- A copy of the list of font face declarations for the document
-**Since**: 0.5.0
+**Example**
+```js
+const document = new TextDocument();
+document.declareFont("FreeSans", "FreeSans", FontPitch.Variable);
+document.getFonts();
+```
+**Since**: 0.7.0
* * *
-
+
-### `imageStyle.getHeight()` ⇒ number
\| undefined
-Returns the target height of the image or `undefined` if no height was set.
+### `textDocument.getMeta()` ⇒ [Meta
](#Meta)
+The `getMeta()` method returns the metadata of the document.
**Return value**
-number
\| undefined
- The target height of the image in millimeter or `undefined` if no height was set
+[Meta
](#Meta) - An object holding the metadata of the document
-**Since**: 0.5.0
+**See**: [Meta](#Meta)
+**Example**
+```js
+new TextDocument.getMeta()
+ .setCreator('Homer Simpson');
+```
+**Since**: 0.6.0
* * *
-
+
-### `imageStyle.setWidth(width)`
-Sets the target width of the image.
+### `textDocument.saveFlat(filePath)` ⇒ Promise.<void>
+The `saveFlat()` method converts the document into an XML string and stores it in flat open document xml format.
#### Parameters
-- width number
-The target width of the image in millimeter
+- filePath string
+The file path to write to
-**Since**: 0.5.0
+**Example**
+```js
+new TextDocument()
+ .saveFlat("/home/homer/document.fodt");
+```
+**Since**: 0.1.0
* * *
-
+
-### `imageStyle.getWidth()` ⇒ number
\| undefined
-Returns the target width of the image or `undefined` if no width was set.
+### ~~`textDocument.toString()` ⇒ string
~~
+***Deprecated***
+
+Returns the string representation of this document in flat open document xml format.
**Return value**
-number
\| undefined
- The target width of the image in millimeter or `undefined` if no width was set
+string
- The string representation of this document
-**Since**: 0.5.0
+**Since**: 0.1.0
* * *
-
-
-### `imageStyle.setSize(width, height)`
-Sets the target size of the image.
+
-#### Parameters
-- width number
-The target width of the image in millimeter
-- height number
-The target height of the image in millimeter
+## Heading ⇐ [Paragraph
](#Paragraph)
+This class represents a heading in a document.
-**Since**: 0.5.0
+It is used to structure a document into multiple sections.
+A chapter or section begins with a heading and extends to the next heading at the same or higher level.
-* * *
+**Extends**: [Paragraph
](#Paragraph)
+**Since**: 0.1.0
-
+* [Heading](#Heading) ⇐ [Paragraph
](#Paragraph)
+ * [`new Heading([text], [level])`](#new_Heading_new)
+ * [`.setLevel(level)`](#Heading+setLevel) ⇒ [Heading
](#Heading)
+ * [`.getLevel()`](#Heading+getLevel) ⇒ number
+ * [`.addText(text)`](#Paragraph+addText) ⇒ [Paragraph
](#Paragraph)
+ * [`.getText()`](#Paragraph+getText) ⇒ string
+ * [`.setText(text)`](#Paragraph+setText) ⇒ [Paragraph
](#Paragraph)
+ * [`.addHyperlink(text, uri)`](#Paragraph+addHyperlink) ⇒ [Hyperlink
](#Hyperlink)
+ * [`.addImage(path)`](#Paragraph+addImage) ⇒ [Image
](#Image)
+ * [`.setStyle(style)`](#Paragraph+setStyle) ⇒ [Paragraph
](#Paragraph)
+ * [`.getStyle()`](#Paragraph+getStyle) ⇒ IParagraphStyle
\| undefined
-### `imageStyle.toXml()`
* * *
-
+
-## ParagraphStyle
-This class represents the style of a paragraph
+### `new Heading([text], [level])`
+Creates a `Heading` instance that represents a heading in a document.
-**Since**: 0.4.0
-
-* [ParagraphStyle](#ParagraphStyle)
- * [`new ParagraphStyle()`](#new_ParagraphStyle_new)
- * [`.setColor()`](#ParagraphStyle+setColor)
- * [`.getColor()`](#ParagraphStyle+getColor)
- * [`.setFontName()`](#ParagraphStyle+setFontName)
- * [`.getFontName()`](#ParagraphStyle+getFontName)
- * [`.setFontSize()`](#ParagraphStyle+setFontSize)
- * [`.getFontSize()`](#ParagraphStyle+getFontSize)
- * [`.setTextTransformation()`](#ParagraphStyle+setTextTransformation)
- * [`.getTextTransformation()`](#ParagraphStyle+getTextTransformation)
- * [`.setTypeface()`](#ParagraphStyle+setTypeface)
- * [`.getTypeface()`](#ParagraphStyle+getTypeface)
- * [`.setHorizontalAlignment()`](#ParagraphStyle+setHorizontalAlignment)
- * [`.getHorizontalAlignment()`](#ParagraphStyle+getHorizontalAlignment)
- * [`.setPageBreakBefore()`](#ParagraphStyle+setPageBreakBefore)
- * [`.setKeepTogether()`](#ParagraphStyle+setKeepTogether)
- * [`.getTabStops()`](#ParagraphStyle+getTabStops)
- * [`.clearTabStops()`](#ParagraphStyle+clearTabStops)
- * [`.toXml()`](#ParagraphStyle+toXml)
+#### Parameters
+- [text] string
= "''"
+The text content of the heading; defaults to an empty string if omitted
+- [level] number
= 1
+The level of the heading, starting with `1`; defaults to `1` if omitted
+**Example**
+```js
+document.getBody().addHeading("First Headline", 1);
+```
+**Example**
+```js
+document.getBody().addHeading()
+ .setText("Second Headline")
+ .setLevel(2);
+```
* * *
-
+
-### `new ParagraphStyle()`
-Constructor.
+### `heading.setLevel(level)` ⇒ [Heading
](#Heading)
+The `setLevel()` method sets the level of the heading, starting with `1`.
+If an illegal value is provided, then the heading is assumed to be at level `1`.
+#### Parameters
+- level number
+The level of the heading, starting with `1`
+
+**Return value**
+[Heading
](#Heading) - The `Heading` object
+
+**Since**: 0.1.0
* * *
-
+
-### `paragraphStyle.setColor()`
+### `heading.getLevel()` ⇒ number
+The `getLevel()` method returns the level of the heading.
+
+**Return value**
+number
- The level of the heading
+
+**Since**: 0.1.0
* * *
-
+
-### `paragraphStyle.getColor()`
+### `heading.addText(text)` ⇒ [Paragraph
](#Paragraph)
+Appends the specified text to the end of the paragraph.
+
+#### Parameters
+- text string
+The additional text content
+
+**Return value**
+[Paragraph
](#Paragraph) - The `Paragraph` object
+
+**Example**
+```js
+new Paragraph("Some text") // Some text
+ .addText("\nEven more text"); // Some text\nEven more text
+```
+**Since**: 0.1.0
* * *
-
+
-### `paragraphStyle.setFontName()`
+### `heading.getText()` ⇒ string
+Returns the text content of the paragraph.
+Note: This will only return the text; other elements and markup will be omitted.
+
+**Return value**
+string
- The text content of the paragraph
+
+**Example**
+```js
+const paragraph = new Paragraph("Some text, ");
+paragraph.addHyperlink("some linked text");
+paragraph.addText(", even more text");
+paragraph.getText(); // Some text, some linked text, even more text
+```
+**Since**: 0.1.0
* * *
-
+
-### `paragraphStyle.getFontName()`
+### `heading.setText(text)` ⇒ [Paragraph
](#Paragraph)
+Sets the text content of the paragraph.
+Note: This will replace any existing content of the paragraph.
+
+#### Parameters
+- text string
+The new text content
+
+**Return value**
+[Paragraph
](#Paragraph) - The `Paragraph` object
+
+**Example**
+```js
+new Paragraph("Some text") // Some text
+ .setText("Some other text"); // Some other text
+```
+**Since**: 0.1.0
* * *
-
+
-### `paragraphStyle.setFontSize()`
+### `heading.addHyperlink(text, uri)` ⇒ [Hyperlink
](#Hyperlink)
+Appends the specified text as hyperlink to the end of the paragraph.
+
+#### Parameters
+- text string
+The text content of the hyperlink
+- uri string
+The target URI of the hyperlink
+
+**Return value**
+[Hyperlink
](#Hyperlink) - The added `Hyperlink` object
+
+**Example**
+```js
+new Paragraph("Some text, ") // Some text,
+ .addHyperlink("some linked text"); // Some text, some linked text
+```
+**Since**: 0.3.0
* * *
-
+
-### `paragraphStyle.getFontSize()`
+### `heading.addImage(path)` ⇒ [Image
](#Image)
+Appends the image of the denoted path to the end of the paragraph.
+The current paragraph will be set as anchor for the image.
+
+#### Parameters
+- path string
+The path to the image file
+
+**Return value**
+[Image
](#Image) - The added `Image` object
+
+**Example**
+```js
+new Paragraph("Some text")
+ .addImage("/home/homer/myself.png");
+```
+**Since**: 0.3.0
* * *
-
+
-### `paragraphStyle.setTextTransformation()`
+### `heading.setStyle(style)` ⇒ [Paragraph
](#Paragraph)
+Sets the new style of the paragraph.
+To reset the style, `undefined` must be given.
+
+#### Parameters
+- style IParagraphStyle
| undefined
+The new style or `undefined` to reset the style
+
+**Return value**
+[Paragraph
](#Paragraph) - The `Paragraph` object
+
+**Example**
+```js
+new Paragraph("Some text")
+ .setStyle(new ParagraphStyle());
+```
+**Since**: 0.3.0
* * *
-
+
-### `paragraphStyle.getTextTransformation()`
+### `heading.getStyle()` ⇒ IParagraphStyle
\| undefined
+Returns the style of the paragraph.
+
+**Return value**
+IParagraphStyle
\| undefined
- The style of the paragraph or `undefined` if no style was set
+
+**Example**
+```js
+const paragraph = new Paragraph("Some text");
+paragraph.getStyle(); // undefined
+paragraph.setStyle(new ParagraphStyle());
+paragraph.getStyle(); // previously set style
+```
+**Since**: 0.3.0
* * *
-
+
+
+## Hyperlink
+This class represents a hyperlink in a paragraph.
+
+**Since**: 0.3.0
+
+* [Hyperlink](#Hyperlink)
+ * [`new Hyperlink(text, uri)`](#new_Hyperlink_new)
+ * [`.setURI(uri)`](#Hyperlink+setURI) ⇒ [Hyperlink
](#Hyperlink)
+ * [`.getURI()`](#Hyperlink+getURI) ⇒ string
-### `paragraphStyle.setTypeface()`
* * *
-
+
-### `paragraphStyle.getTypeface()`
+### `new Hyperlink(text, uri)`
+Creates a hyperlink
+
+#### Parameters
+- text string
+The text content of the hyperlink
+- uri string
+The target URI of the hyperlink
+
+**Example**
+```js
+document.getBody()
+ .addParagraph('This is a ')
+ .addHyperlink('link', 'https://example.com/');
+```
* * *
-
+
-### `paragraphStyle.setHorizontalAlignment()`
+### `hyperlink.setURI(uri)` ⇒ [Hyperlink
](#Hyperlink)
+The `setURI()` method sets the target URI for this hyperlink.
+If an illegal value is provided, the value will be ignored.
+
+#### Parameters
+- uri string
+The target URI of this hyperlink
+
+**Return value**
+[Hyperlink
](#Hyperlink) - The `Hyperlink` object
+
+**Example**
+```js
+const hyperlink = new Hyperlink('My website', 'https://example.com/');
+hyperlink.setURI('https://github.com'); // https://github.com
+hyperlink.setURI(''); // https://github.com
+```
+**Since**: 0.3.0
* * *
-
+
-### `paragraphStyle.getHorizontalAlignment()`
+### `hyperlink.getURI()` ⇒ string
+The `getURI()` method returns the target URI of this hyperlink.
+
+**Return value**
+string
- The target URI of this hyperlink
+
+**Example**
+```js
+const hyperlink = new Hyperlink('My website', 'https://example.com/');
+hyperlink.getURI(); // https://example.com
+hyperlink.setURI('https://github.com');
+hyperlink.getURI(); // https://github.com
+```
+**Since**: 0.3.0
* * *
-
+
+
+## List
+This class represents a list and may contain any number list items.
+
+**Since**: 0.2.0
+
+* [List](#List)
+ * [`new List()`](#new_List_new)
+ * [`.addItem([item])`](#List+addItem) ⇒ [ListItem
](#ListItem)
+ * [`.insertItem(position, item)`](#List+insertItem) ⇒ [ListItem
](#ListItem)
+ * [`.getItem(position)`](#List+getItem) ⇒ [ListItem
](#ListItem) \| undefined
+ * [`.getItems()`](#List+getItems) ⇒ [Array.<ListItem>
](#ListItem)
+ * [`.removeItemAt(position)`](#List+removeItemAt) ⇒ [ListItem
](#ListItem) \| undefined
+ * [`.clear()`](#List+clear) ⇒ [List
](#List)
+ * [`.size()`](#List+size) ⇒ number
-### `paragraphStyle.setPageBreakBefore()`
* * *
-
+
-### `paragraphStyle.setKeepTogether()`
+### `new List()`
+Creates a `List` instance that represents a list.
+
+**Example**
+```js
+const list = document.getBody().addList();
+list.addItem("First item");
+list.addItem("Second item");
+list.insertItem(1, "After first item")
+list.removeItemAt(2);
+```
* * *
-
+
-### `paragraphStyle.getTabStops()`
+### `list.addItem([item])` ⇒ [ListItem
](#ListItem)
+The `addItem()` method adds a new list item with the specified text or adds the specified item to the list.
+
+#### Parameters
+- [item] string
| [ListItem
](#ListItem)
+The text content of the new item or the item to add
+
+**Return value**
+[ListItem
](#ListItem) - The added `ListItem` object
+
+**Example**
+```js
+const list = new List();
+list.addItem("First item");
+list.addItem(new ListItem("Second item"));
+```
+**Since**: 0.2.0
* * *
-
+
-### `paragraphStyle.clearTabStops()`
+### `list.insertItem(position, item)` ⇒ [ListItem
](#ListItem)
+The `insertItem` method inserts a new list item with the specified text
+or inserts the specified item at the specified position.
+The item is inserted before the item at the specified position.
+
+If the position is greater than the current number items, the new item is appended at the end of the list.
+If the position is negative, the new item is inserted as first element.
+
+#### Parameters
+- position number
+The index at which to insert the list item (starting from 0).
+- item string
| [ListItem
](#ListItem)
+The text content of the new item or the item to insert
+
+**Return value**
+[ListItem
](#ListItem) - The inserted `ListItem` object
+
+**Example**
+```js
+const list = new List();
+list.addItem("First item"); // "First item"
+list.addItem("Second item"); // "First item", "Second item"
+list.insertItem(1, "After first item"); // "First item", "After first item", "Second item"
+```
+**Since**: 0.2.0
* * *
-
+
-### `paragraphStyle.toXml()`
+### `list.getItem(position)` ⇒ [ListItem
](#ListItem) \| undefined
+The `getItem()` method returns the item at the specified position in the list.
+If an invalid position is given, undefined is returned.
+
+#### Parameters
+- position number
+The index of the requested list item (starting from 0).
+
+**Return value**
+[ListItem
](#ListItem) \| undefined
- The `ListItem` object at the specified position
+or `undefined` if there is no list item at the specified position
+
+**Example**
+```js
+const list = new List();
+list.addItem("First item");
+list.addItem("Second item");
+list.getItem(1); // "Second item"
+list.getItem(2); // undefined
+```
+**Since**: 0.2.0
* * *
-
+
-## StyleHelper
-Utility class for dealing with styles.
+### `list.getItems()` ⇒ [Array.<ListItem>
](#ListItem)
+The `getItems()` method returns all list items.
-**Since**: 0.4.0
+**Return value**
+[Array.<ListItem>
](#ListItem) - A copy of the list of `ListItem` objects
+
+**Example**
+```js
+const list = new List();
+list.getItems(); // []
+list.addItem("First item");
+list.addItem("Second item");
+list.getItems(); // ["First item", "Second item"]
+```
+**Since**: 0.2.0
* * *
-
+
-### `StyleHelper.getAutomaticStylesElement(document)` ⇒ Element
-Returns the `automatic-styles` element of the document.
-If there is no such element yet, it will be created.
+### `list.removeItemAt(position)` ⇒ [ListItem
](#ListItem) \| undefined
+The `removeItemAt()` method removes the list item from the specified position.
#### Parameters
-- document Document
-The XML document
+- position number
+The index of the list item to remove (starting from 0).
**Return value**
-Element
- The documents `automatic-styles` element
+[ListItem
](#ListItem) \| undefined
- The removed `ListItem` object
+or undefined if there is no list item at the specified position
-**Since**: 0.4.0
+**Example**
+```js
+const list = new List();
+list.addItem("First item");
+list.addItem("Second item");
+list.removeItemAt(0); // "First item"
+list.getItems(); // ["Second item"]
+list.removeItemAt(2); // undefined
+```
+**Since**: 0.2.0
* * *
-
+
-## TabStop
-This class represents a tab stop.
+### `list.clear()` ⇒ [List
](#List)
+The `clear()` method removes all items from the list.
-Tab stops are used to align text in a paragraph.
-To become effective they must be set to the style of the respective paragraph.
+**Return value**
+[List
](#List) - The `List` object
-**Since**: 0.3.0
+**Example**
+```js
+const list = new List();
+list.addItem("First item"); // "First item"
+list.addItem("Second item"); // "First item", "Second item"
+list.clear(); // -
+```
+**Since**: 0.2.0
-* [TabStop](#TabStop)
- * [`new TabStop([position], [type])`](#new_TabStop_new)
- * [`.setPosition(position)`](#TabStop+setPosition)
- * [`.getPosition()`](#TabStop+getPosition) ⇒ number
- * [`.setType(type)`](#TabStop+setType)
- * [`.getType()`](#TabStop+getType) ⇒ TabStopType
- * [`.toXml(document, parent)`](#TabStop+toXml)
+* * *
+
+
+### `list.size()` ⇒ number
+The `size()` method returns the number of items in the list.
+
+**Return value**
+number
- The number of items in this list
+
+**Example**
+```js
+const list = new List();
+list.size(); // 0
+list.addItem("First item");
+list.addItem("Second item");
+list.size(); // 2
+```
+**Since**: 0.2.0
* * *
-
+
-### `new TabStop([position], [type])`
-Creates a tab stop to be set to the style of a paragraph.
+## ListItem
+This class represents an item in a list.
+
+**Since**: 0.2.0
+
+* * *
+
+
+
+### `new ListItem([text])`
+Creates a `ListItem` instance that represents an item in a list.
#### Parameters
-- [position] number
-The position of the tab stop in centimeters relative to the left margin.
-If a negative value is given, the `position` will be set to `0`.
-- [type] TabStopType
-The type of the tab stop. Defaults to `TabStopType.Left`.
+- [text] string
= "\"\""
+The text content of the list item; defaults to an empty string if omitted
**Example**
```js
-// creates a right aligned tab stop with a distance of 4 cm from the left margin
-const tabStop4 = new TabStop(4, TabStopType.Right);
-paragraph.getStyle().addTabStop(tabStop4);
+const list = document.getBody()
+ .addList()
+ .addItem("First item");
```
* * *
-
+
-### `tabStop.setPosition(position)`
-Sets the position of this tab stop.
+## Paragraph
+This class represents a paragraph.
-#### Parameters
-- position number
-The position of the tab stop in centimeters relative to the left margin.
-If a negative value is given, the `position` will be set to `0`.
+**Since**: 0.1.0
+
+* [Paragraph](#Paragraph)
+ * [`new Paragraph([text])`](#new_Paragraph_new)
+ * [`.addText(text)`](#Paragraph+addText) ⇒ [Paragraph
](#Paragraph)
+ * [`.getText()`](#Paragraph+getText) ⇒ string
+ * [`.setText(text)`](#Paragraph+setText) ⇒ [Paragraph
](#Paragraph)
+ * [`.addHyperlink(text, uri)`](#Paragraph+addHyperlink) ⇒ [Hyperlink
](#Hyperlink)
+ * [`.addImage(path)`](#Paragraph+addImage) ⇒ [Image
](#Image)
+ * [`.setStyle(style)`](#Paragraph+setStyle) ⇒ [Paragraph
](#Paragraph)
+ * [`.getStyle()`](#Paragraph+getStyle) ⇒ IParagraphStyle
\| undefined
-**Since**: 0.3.0
* * *
-
+
-### `tabStop.getPosition()` ⇒ number
-Returns the position of this tab stop.
+### `new Paragraph([text])`
+Creates a `Paragraph` instance.
-**Return value**
-number
- The position of this tab stop in centimeters
+#### Parameters
+- [text] string
+The text content of the paragraph; defaults to an empty string if omitted
-**Since**: 0.3.0
+**Example**
+```js
+document.getBody().addParagraph("Some text")
+ .addText("\nEven more text")
+ .addImage("/home/homer/myself.png");
+```
* * *
-
+
-### `tabStop.setType(type)`
-Sets the type of this tab stop.
+### `paragraph.addText(text)` ⇒ [Paragraph
](#Paragraph)
+Appends the specified text to the end of the paragraph.
#### Parameters
-- type TabStopType
-The type of the tab stop
+- text string
+The additional text content
-**Since**: 0.3.0
+**Return value**
+[Paragraph
](#Paragraph) - The `Paragraph` object
+
+**Example**
+```js
+new Paragraph("Some text") // Some text
+ .addText("\nEven more text"); // Some text\nEven more text
+```
+**Since**: 0.1.0
* * *
-
+
-### `tabStop.getType()` ⇒ TabStopType
-Returns the type of this tab stop.
+### `paragraph.getText()` ⇒ string
+Returns the text content of the paragraph.
+Note: This will only return the text; other elements and markup will be omitted.
**Return value**
-TabStopType
- The type of this tab stop
+string
- The text content of the paragraph
-**Since**: 0.3.0
+**Example**
+```js
+const paragraph = new Paragraph("Some text, ");
+paragraph.addHyperlink("some linked text");
+paragraph.addText(", even more text");
+paragraph.getText(); // Some text, some linked text, even more text
+```
+**Since**: 0.1.0
* * *
-
+
-### `tabStop.toXml(document, parent)`
-Transforms the tab stop into Open Document Format.
+### `paragraph.setText(text)` ⇒ [Paragraph
](#Paragraph)
+Sets the text content of the paragraph.
+Note: This will replace any existing content of the paragraph.
#### Parameters
-- document Document
-The XML document
-- parent Element
-The parent node in the DOM (`style:tab-stops`)
+- text string
+The new text content
-**Since**: 0.3.0
+**Return value**
+[Paragraph
](#Paragraph) - The `Paragraph` object
+
+**Example**
+```js
+new Paragraph("Some text") // Some text
+ .setText("Some other text"); // Some other text
+```
+**Since**: 0.1.0
* * *
-
+
-## Heading
-This class represents a heading.
+### `paragraph.addHyperlink(text, uri)` ⇒ [Hyperlink
](#Hyperlink)
+Appends the specified text as hyperlink to the end of the paragraph.
-**Since**: 0.1.0
+#### Parameters
+- text string
+The text content of the hyperlink
+- uri string
+The target URI of the hyperlink
-* [Heading](#Heading)
- * [`new Heading([text], [level])`](#new_Heading_new)
- * [`.setLevel(level)`](#Heading+setLevel)
- * [`.getLevel()`](#Heading+getLevel) ⇒ number
- * [`.createElement()`](#Heading+createElement)
+**Return value**
+[Hyperlink
](#Hyperlink) - The added `Hyperlink` object
+**Example**
+```js
+new Paragraph("Some text, ") // Some text,
+ .addHyperlink("some linked text"); // Some text, some linked text
+```
+**Since**: 0.3.0
* * *
-
+
-### `new Heading([text], [level])`
-Creates a heading
+### `paragraph.addImage(path)` ⇒ [Image
](#Image)
+Appends the image of the denoted path to the end of the paragraph.
+The current paragraph will be set as anchor for the image.
#### Parameters
-- [text] string
-The text content of the heading
-- [level] number
-The heading level; defaults to 1 if omitted
+- path string
+The path to the image file
+
+**Return value**
+[Image
](#Image) - The added `Image` object
+**Example**
+```js
+new Paragraph("Some text")
+ .addImage("/home/homer/myself.png");
+```
+**Since**: 0.3.0
* * *
-
+
-### `heading.setLevel(level)`
-Sets the level of this heading.
+### `paragraph.setStyle(style)` ⇒ [Paragraph
](#Paragraph)
+Sets the new style of the paragraph.
+To reset the style, `undefined` must be given.
#### Parameters
-- level number
-The heading level
+- style IParagraphStyle
| undefined
+The new style or `undefined` to reset the style
-**Since**: 0.1.0
+**Return value**
+[Paragraph
](#Paragraph) - The `Paragraph` object
+
+**Example**
+```js
+new Paragraph("Some text")
+ .setStyle(new ParagraphStyle());
+```
+**Since**: 0.3.0
* * *
-
+
-### `heading.getLevel()` ⇒ number
-Returns the level of this heading.
+### `paragraph.getStyle()` ⇒ IParagraphStyle
\| undefined
+Returns the style of the paragraph.
**Return value**
-number
- The heading level
-
-**Since**: 0.1.0
-
-* * *
-
-
+IParagraphStyle
\| undefined
- The style of the paragraph or `undefined` if no style was set
-### `heading.createElement()`
+**Example**
+```js
+const paragraph = new Paragraph("Some text");
+paragraph.getStyle(); // undefined
+paragraph.setStyle(new ParagraphStyle());
+paragraph.getStyle(); // previously set style
+```
+**Since**: 0.3.0
* * *
-
+
-## Hyperlink
-This class represents a hyperlink in a paragraph.
+## Color
+This class represents a Color.
-**Since**: 0.3.0
+**Since**: 0.4.0
-* [Hyperlink](#Hyperlink)
- * [`new Hyperlink(text, uri)`](#new_Hyperlink_new)
- * [`.setURI(uri)`](#Hyperlink+setURI)
- * [`.getURI()`](#Hyperlink+getURI) ⇒ string
- * [`.toXml()`](#Hyperlink+toXml)
+* [Color](#Color)
+ * [`new Color(red, green, blue)`](#new_Color_new)
+ * _instance_
+ * [`.toHex()`](#Color+toHex) ⇒ string
+ * _static_
+ * [`.fromHex(value)`](#Color.fromHex) ⇒ [Color
](#Color) \| never
+ * [`.fromRgb(red, green, blue)`](#Color.fromRgb) ⇒ [Color
](#Color) \| never
* * *
-
+
-### `new Hyperlink(text, uri)`
-Creates a hyperlink
+### `new Color(red, green, blue)`
+Creates a new color with the specified channel values.
#### Parameters
-- text string
-The text content of the hyperlink
-- uri string
-The target URI of the hyperlink
+- red number
+The red channel of the color
+- green number
+The green channel of the color
+- blue number
+The blue channel of the color
* * *
-
+
-### `hyperlink.setURI(uri)`
-Sets the target URI for this hyperlink.
+### `color.toHex()` ⇒ string
+The toHex() method returns a string representing the color as hex string.
-#### Parameters
-- uri string
-The new target URI
+**Return value**
+string
- A hex string representing the color
-**Since**: 0.3.0
+**Since**: 0.4.0
* * *
-
+
-### `hyperlink.getURI()` ⇒ string
-Returns the target URI of this hyperlink.
+### `Color.fromHex(value)` ⇒ [Color
](#Color) \| never
+The `Color.fromHex()` method parses a string argument and returns a color.
+The string is expected to be in `#rrggbb` or `rrggbb` format.
-**Return value**
-string
- The target URI
+#### Parameters
+- value string
+The value you want to parse
-**Since**: 0.3.0
+**Return value**
+[Color
](#Color) \| never
- A color parsed from the given value
-* * *
+**Throws**:
-
+- Error
If the value cannot be converted to a color
-### `hyperlink.toXml()`
+**Since**: 0.4.0
* * *
-
+
-## List
-This class represents a list.
-It can contain multiple list items.
+### `Color.fromRgb(red, green, blue)` ⇒ [Color
](#Color) \| never
+The `Color.fromRgb()` method returns a color with the channel arguments.
-**Since**: 0.2.0
+#### Parameters
+- red number
+The red channel of the color with a range of 0...255
+- green number
+The green channel of the color with a range of 0...255
+- blue number
+The blue channel of the color with a range of 0...255
-* [List](#List)
- * [`new List()`](#new_List_new)
- * [`.addItem([item])`](#List+addItem) ⇒ [ListItem
](#ListItem)
- * [`.insertItem(position, item)`](#List+insertItem) ⇒ [ListItem
](#ListItem)
- * [`.getItem(position)`](#List+getItem) ⇒ [ListItem
](#ListItem) \| undefined
- * [`.getItems()`](#List+getItems) ⇒ [Array.<ListItem>
](#ListItem)
- * [`.removeItemAt(position)`](#List+removeItemAt) ⇒ [ListItem
](#ListItem) \| undefined
- * [`.clear()`](#List+clear)
- * [`.size()`](#List+size) ⇒ number
- * [`.toXml()`](#List+toXml)
+**Return value**
+[Color
](#Color) \| never
- A color parsed from the given value
+
+**Throws**:
+- Error
If any channel is outside the allowable range
+
+**Since**: 0.4.0
* * *
-
+
-### `new List()`
-Creates a list
+## ImageStyle
+This class represents the style of an image
+**Since**: 0.5.0
-* * *
+* [ImageStyle](#ImageStyle)
+ * [`new ImageStyle()`](#new_ImageStyle_new)
+ * [`.setAnchorType()`](#ImageStyle+setAnchorType)
+ * [`.getAnchorType()`](#ImageStyle+getAnchorType)
+ * [`.setHeight(height)`](#ImageStyle+setHeight)
+ * [`.getHeight()`](#ImageStyle+getHeight) ⇒ number
\| undefined
+ * [`.setWidth(width)`](#ImageStyle+setWidth)
+ * [`.getWidth()`](#ImageStyle+getWidth) ⇒ number
\| undefined
+ * [`.setSize(width, height)`](#ImageStyle+setSize)
+ * [`.toXml()`](#ImageStyle+toXml)
-
-### `list.addItem([item])` ⇒ [ListItem
](#ListItem)
-Adds a new list item with the specified text or adds the specified item to the list.
+* * *
-#### Parameters
-- [item] string
| [ListItem
](#ListItem)
-The text content of the new item or the item to add
+
-**Return value**
-[ListItem
](#ListItem) - The newly added list item
+### `new ImageStyle()`
+Constructor.
-**Since**: 0.2.0
* * *
-
+
-### `list.insertItem(position, item)` ⇒ [ListItem
](#ListItem)
-Inserts a new list item with the specified text or inserts the specified item at the specified position.
-The item is inserted before the item at the specified position.
+### `imageStyle.setAnchorType()`
-#### Parameters
-- position number
-The index at which to insert the list item (starting from 0).
-- item string
| [ListItem
](#ListItem)
-The text content of the new item or the item to insert
+* * *
-**Return value**
-[ListItem
](#ListItem) - The newly added list item
+
-**Since**: 0.2.0
+### `imageStyle.getAnchorType()`
* * *
-
+
-### `list.getItem(position)` ⇒ [ListItem
](#ListItem) \| undefined
-Returns the item at the specified position in this list.
-If an invalid position is given, undefined is returned.
+### `imageStyle.setHeight(height)`
+Sets the target height of the image.
#### Parameters
-- position number
-The index of the requested the list item (starting from 0).
-
-**Return value**
-[ListItem
](#ListItem) \| undefined
- The list item at the specified position
-or undefined if there is no list item at the specified position
+- height number
+The target height of the image in millimeter
-**Since**: 0.2.0
+**Since**: 0.5.0
* * *
-
+
-### `list.getItems()` ⇒ [Array.<ListItem>
](#ListItem)
-Returns all list items.
+### `imageStyle.getHeight()` ⇒ number
\| undefined
+Returns the target height of the image or `undefined` if no height was set.
**Return value**
-[Array.<ListItem>
](#ListItem) - A copy of the list of list items
+number
\| undefined
- The target height of the image in millimeter or `undefined` if no height was set
-**Since**: 0.2.0
+**Since**: 0.5.0
* * *
-
+
-### `list.removeItemAt(position)` ⇒ [ListItem
](#ListItem) \| undefined
-Removes the list item from the specified position.
+### `imageStyle.setWidth(width)`
+Sets the target width of the image.
#### Parameters
-- position number
-The index of the list item to remove (starting from 0).
-
-**Return value**
-[ListItem
](#ListItem) \| undefined
- The removed list item
-or undefined if there is no list item at the specified position
+- width number
+The target width of the image in millimeter
-**Since**: 0.2.0
+**Since**: 0.5.0
* * *
-
+
+
+### `imageStyle.getWidth()` ⇒ number
\| undefined
+Returns the target width of the image or `undefined` if no width was set.
-### `list.clear()`
-Removes all items from this list.
+**Return value**
+number
\| undefined
- The target width of the image in millimeter or `undefined` if no width was set
-**Since**: 0.2.0
+**Since**: 0.5.0
* * *
-
+
-### `list.size()` ⇒ number
-Returns the number of items in this list.
+### `imageStyle.setSize(width, height)`
+Sets the target size of the image.
-**Return value**
-number
- The number of items in this list
+#### Parameters
+- width number
+The target width of the image in millimeter
+- height number
+The target height of the image in millimeter
-**Since**: 0.2.0
+**Since**: 0.5.0
* * *
-
+
-### `list.toXml()`
+### `imageStyle.toXml()`
* * *
-
+
-## ListItem
-This class represents an item in a list.
+## ParagraphStyle
+This class represents the style of a paragraph
-**Since**: 0.2.0
+**Since**: 0.4.0
-* [ListItem](#ListItem)
- * [`new ListItem([text])`](#new_ListItem_new)
- * [`.toXml()`](#ListItem+toXml)
+* [ParagraphStyle](#ParagraphStyle)
+ * [`new ParagraphStyle()`](#new_ParagraphStyle_new)
+ * [`.setColor()`](#ParagraphStyle+setColor)
+ * [`.getColor()`](#ParagraphStyle+getColor)
+ * [`.setFontName()`](#ParagraphStyle+setFontName)
+ * [`.getFontName()`](#ParagraphStyle+getFontName)
+ * [`.setFontSize()`](#ParagraphStyle+setFontSize)
+ * [`.getFontSize()`](#ParagraphStyle+getFontSize)
+ * [`.setTextTransformation()`](#ParagraphStyle+setTextTransformation)
+ * [`.getTextTransformation()`](#ParagraphStyle+getTextTransformation)
+ * [`.setTypeface()`](#ParagraphStyle+setTypeface)
+ * [`.getTypeface()`](#ParagraphStyle+getTypeface)
+ * [`.setHorizontalAlignment()`](#ParagraphStyle+setHorizontalAlignment)
+ * [`.getHorizontalAlignment()`](#ParagraphStyle+getHorizontalAlignment)
+ * [`.setPageBreakBefore()`](#ParagraphStyle+setPageBreakBefore)
+ * [`.setKeepTogether()`](#ParagraphStyle+setKeepTogether)
+ * [`.getTabStops()`](#ParagraphStyle+getTabStops)
+ * [`.clearTabStops()`](#ParagraphStyle+clearTabStops)
+ * [`.toXml()`](#ParagraphStyle+toXml)
* * *
-
-
-### `new ListItem([text])`
-Creates a list item
+
-#### Parameters
-- [text] string
-The text content of the list item
+### `new ParagraphStyle()`
+Constructor.
* * *
-
+
-### `listItem.toXml()`
+### `paragraphStyle.setColor()`
* * *
-
+
-## Paragraph
-This class represents a paragraph.
+### `paragraphStyle.getColor()`
-**Since**: 0.1.0
+* * *
-* [Paragraph](#Paragraph)
- * [`new Paragraph([text])`](#new_Paragraph_new)
- * [`.addText(text)`](#Paragraph+addText)
- * [`.getText()`](#Paragraph+getText) ⇒ string
- * [`.setText(text)`](#Paragraph+setText)
- * [`.addHyperlink(text, uri)`](#Paragraph+addHyperlink) ⇒ [Hyperlink
](#Hyperlink)
- * [`.addImage(path)`](#Paragraph+addImage) ⇒ [Image
](#Image)
- * [`.setStyle(style)`](#Paragraph+setStyle)
- * [`.getStyle()`](#Paragraph+getStyle) ⇒ IParagraphStyle
\| undefined
- * [`.createElement(document)`](#Paragraph+createElement) ⇒ Element
- * [`.toXml()`](#Paragraph+toXml)
+
+### `paragraphStyle.setFontName()`
* * *
-
+
-### `new Paragraph([text])`
-Creates a paragraph
+### `paragraphStyle.getFontName()`
-#### Parameters
-- [text] string
-The text content of the paragraph
+* * *
+
+
+### `paragraphStyle.setFontSize()`
* * *
-
+
+
+### `paragraphStyle.getFontSize()`
-### `paragraph.addText(text)`
-Appends the specified text to the end of this paragraph.
+* * *
-#### Parameters
-- text string
-The additional text content
+
-**Since**: 0.1.0
+### `paragraphStyle.setTextTransformation()`
* * *
-
+
-### `paragraph.getText()` ⇒ string
-Returns the text content of this paragraph.
-Note: This will only return the text; other elements and markup will be omitted.
+### `paragraphStyle.getTextTransformation()`
-**Return value**
-string
- The text content of this paragraph
+* * *
-**Since**: 0.1.0
+
+
+### `paragraphStyle.setTypeface()`
* * *
-
+
-### `paragraph.setText(text)`
-Sets the text content of this paragraph.
-Note: This will replace any existing content of the paragraph.
+### `paragraphStyle.getTypeface()`
-#### Parameters
-- text string
-The new text content
+* * *
-**Since**: 0.1.0
+
+
+### `paragraphStyle.setHorizontalAlignment()`
* * *
-
+
-### `paragraph.addHyperlink(text, uri)` ⇒ [Hyperlink
](#Hyperlink)
-Appends the specified text as hyperlink to the end of this paragraph.
+### `paragraphStyle.getHorizontalAlignment()`
-#### Parameters
-- text string
-The text content of the hyperlink
-- uri string
-The target URI of the hyperlink
+* * *
-**Return value**
-[Hyperlink
](#Hyperlink) - The newly added hyperlink
+
-**Since**: 0.3.0
+### `paragraphStyle.setPageBreakBefore()`
* * *
-
+
-### `paragraph.addImage(path)` ⇒ [Image
](#Image)
-Appends the image of the denoted path to the end of this paragraph.
-The current paragraph will be set as anchor for the image.
+### `paragraphStyle.setKeepTogether()`
-#### Parameters
-- path string
-The path to the image file
+* * *
-**Return value**
-[Image
](#Image) - The newly added image
+
-**Since**: 0.3.0
+### `paragraphStyle.getTabStops()`
* * *
-
+
-### `paragraph.setStyle(style)`
-Sets the new style of this paragraph.
-To reset the style, `undefined` must be given.
+### `paragraphStyle.clearTabStops()`
-#### Parameters
-- style IParagraphStyle
| undefined
-The new style or `undefined` to reset the style
+* * *
-**Since**: 0.3.0
+
-* * *
+### `paragraphStyle.toXml()`
-
+* * *
-### `paragraph.getStyle()` ⇒ IParagraphStyle
\| undefined
-Returns the style of this paragraph.
+
-**Return value**
-IParagraphStyle
\| undefined
- The style of the paragraph or `undefined` if no style was set
+## StyleHelper
+Utility class for dealing with styles.
-**Since**: 0.3.0
+**Since**: 0.4.0
* * *
-
+
-### `paragraph.createElement(document)` ⇒ Element
-Creates the paragraph element.
+### `StyleHelper.getAutomaticStylesElement(document)` ⇒ Element
+Returns the `automatic-styles` element of the document.
+If there is no such element yet, it will be created.
#### Parameters
- document Document
The XML document
**Return value**
-Element
- The DOM element representing this paragraph
+Element
- The documents `automatic-styles` element
-**Since**: 0.1.0
+**Since**: 0.4.0
* * *
-
-
-### `paragraph.toXml()`
-
-* * *
+
-
+## TabStop
+This class represents a tab stop.
-## TextDocument
-This class represents an empty ODF text document.
+Tab stops are used to align text in a paragraph.
+To become effective they must be set to the style of the respective paragraph.
-**Since**: 0.1.0
+**Since**: 0.3.0
-* [TextDocument](#TextDocument)
- * [`.getMeta()`](#TextDocument+getMeta) ⇒ [Meta
](#Meta)
- * [`.declareFont(name, fontFamily, fontPitch)`](#TextDocument+declareFont)
- * [`.addHeading([text], [level])`](#TextDocument+addHeading) ⇒ [Heading
](#Heading)
- * [`.addList()`](#TextDocument+addList) ⇒ [List
](#List)
- * [`.addParagraph([text])`](#TextDocument+addParagraph) ⇒ [Paragraph
](#Paragraph)
- * [`.saveFlat(filePath)`](#TextDocument+saveFlat) ⇒ Promise.<void>
- * ~~[`.toString()`](#TextDocument+toString) ⇒ string
~~
- * [`.toXml()`](#TextDocument+toXml)
+* [TabStop](#TabStop)
+ * [`new TabStop([position], [type])`](#new_TabStop_new)
+ * [`.setPosition(position)`](#TabStop+setPosition)
+ * [`.getPosition()`](#TabStop+getPosition) ⇒ number
+ * [`.setType(type)`](#TabStop+setType)
+ * [`.getType()`](#TabStop+getType) ⇒ TabStopType
+ * [`.toXml(document, parent)`](#TabStop+toXml)
* * *
-
+
-### `textDocument.getMeta()` ⇒ [Meta
](#Meta)
-The `getMeta()` method returns the metadata of the document.
+### `new TabStop([position], [type])`
+Creates a tab stop to be set to the style of a paragraph.
-**Return value**
-[Meta
](#Meta) - An object holding the metadata of the document
+#### Parameters
+- [position] number
+The position of the tab stop in centimeters relative to the left margin.
+If a negative value is given, the `position` will be set to `0`.
+- [type] TabStopType
+The type of the tab stop. Defaults to `TabStopType.Left`.
-**See**: [Meta](#Meta)
**Example**
```js
-document.getMeta().setCreator('Lisa Simpson');
+// creates a right aligned tab stop with a distance of 4 cm from the left margin
+const tabStop4 = new TabStop(4, TabStopType.Right);
+paragraph.getStyle().addTabStop(tabStop4);
```
-**Since**: 0.6.0
* * *
-
-
-### `textDocument.declareFont(name, fontFamily, fontPitch)`
-Declares a font to be used in the document.
+
-**Note: There is no check whether the font exists.
-In order to be displayed properly, the font must be present on the target system.**
+### `tabStop.setPosition(position)`
+Sets the position of this tab stop.
#### Parameters
-- name string
-The name of the font; this name must be set to a [ParagraphStyle](#ParagraphStyle)
-- fontFamily string
-The name of the font family
-- fontPitch FontPitch
-The ptich of the fonr
+- position number
+The position of the tab stop in centimeters relative to the left margin.
+If a negative value is given, the `position` will be set to `0`.
-**Since**: 0.4.0
+**Since**: 0.3.0
* * *
-
-
-### `textDocument.addHeading([text], [level])` ⇒ [Heading
](#Heading)
-Adds a heading at the end of the document.
-If a text is given, this will be set as text content of the heading.
+
-#### Parameters
-- [text] string
-The text content of the heading
-- [level] number
= 1
-The heading level; defaults to 1 if omitted
+### `tabStop.getPosition()` ⇒ number
+Returns the position of this tab stop.
**Return value**
-[Heading
](#Heading) - The newly added heading
+number
- The position of this tab stop in centimeters
-**Since**: 0.1.0
+**Since**: 0.3.0
* * *
-
+
-### `textDocument.addList()` ⇒ [List
](#List)
-Adds an empty list at the end of the document.
+### `tabStop.setType(type)`
+Sets the type of this tab stop.
-**Return value**
-[List
](#List) - The newly added list
+#### Parameters
+- type TabStopType
+The type of the tab stop
-**Since**: 0.2.0
+**Since**: 0.3.0
* * *
-
-
-### `textDocument.addParagraph([text])` ⇒ [Paragraph
](#Paragraph)
-Adds a paragraph at the end of the document.
-If a text is given, this will be set as text content of the paragraph.
+
-#### Parameters
-- [text] string
-The text content of the paragraph
+### `tabStop.getType()` ⇒ TabStopType
+Returns the type of this tab stop.
**Return value**
-[Paragraph
](#Paragraph) - The newly added paragraph
+TabStopType
- The type of this tab stop
-**Since**: 0.1.0
+**Since**: 0.3.0
* * *
-
+
-### `textDocument.saveFlat(filePath)` ⇒ Promise.<void>
-Saves the document in flat open document xml format.
+### `tabStop.toXml(document, parent)`
+Transforms the tab stop into Open Document Format.
#### Parameters
-- filePath string
-The file path to write to
-
-**Since**: 0.1.0
-
-* * *
-
-
-
-### ~~`textDocument.toString()` ⇒ string
~~
-***Deprecated***
-
-Returns the string representation of this document in flat open document xml format.
-
-**Return value**
-string
- The string representation of this document
-
-**Since**: 0.1.0
-
-* * *
-
-
+- document Document
+The XML document
+- parent Element
+The parent node in the DOM (`style:tab-stops`)
-### `textDocument.toXml()`
+**Since**: 0.3.0
* * *
diff --git a/package.json b/package.json
index ca7e1d58..d10e5aaf 100644
--- a/package.json
+++ b/package.json
@@ -35,8 +35,8 @@
"test": "jest",
"watch-test": "jest --watch",
"coverage": "jest --coverage",
- "lint": "tslint -c tslint.json 'src/**/*.ts', 'test/**/*.ts'",
- "docs": "rm -r ./lib && tsc && jsdoc2md --name-format --param-list-format list --separators --partial ./jsdoc2md/body.hbs ./jsdoc2md/params-list.hbs ./jsdoc2md/returns.hbs ./jsdoc2md/scope.hbs --files ./lib/**/*.js ./lib/*.js > ./docs/API.md"
+ "lint": "tslint -c tslint.json 'src/**/*.ts'",
+ "docs": "rm -r ./lib && tsc && jsdoc2md --name-format --param-list-format list --separators --partial ./jsdoc2md/body.hbs ./jsdoc2md/params-list.hbs ./jsdoc2md/returns.hbs ./jsdoc2md/scope.hbs --files ./lib/api/**/*.js ./lib/style/**/*.js > ./docs/API.md"
},
"dependencies": {
"xmldom": "^0.1.27"
diff --git a/src/TextDocument.spec.ts b/src/TextDocument.spec.ts
deleted file mode 100644
index 53f23d2e..00000000
--- a/src/TextDocument.spec.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import { readFile, unlink } from "fs";
-import { promisify } from "util";
-import { Meta } from "./meta/Meta";
-import { FontPitch } from "./style/FontPitch";
-import { Heading } from "./text/Heading";
-import { List } from "./text/List";
-import { Paragraph } from "./text/Paragraph";
-import { TextDocument, XML_DECLARATION } from "./TextDocument";
-
-const FILEPATH = "./test.fodt";
-
-jest.mock("../src/meta/Meta");
-
-describe(TextDocument.name, () => {
-
- /* tslint:disable-next-line:max-line-length */
- const baseDocument = '';
- let document: TextDocument;
-
- beforeEach(() => {
- document = new TextDocument();
- });
-
- afterAll(async (done) => {
- const unlinkAsync = promisify(unlink);
-
- await unlinkAsync(FILEPATH);
-
- done();
- });
-
- describe("namespace declaration", () => {
- it("add dc namespace", () => {
- expect(document.toString()).toMatch(/xmlns:dc="http:\/\/purl.org\/dc\/elements\/1.1"/);
- });
-
- it("add draw namespace", () => {
- expect(document.toString()).toMatch(/xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"/);
- });
-
- it("add fo namespace", () => {
- expect(document.toString()).toMatch(/xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"/);
- });
-
- it("add meta namespace", () => {
- expect(document.toString()).toMatch(/xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"/);
- });
-
- it("add style namespace", () => {
- expect(document.toString()).toMatch(/xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"/);
- });
-
- it("add svg namespace", () => {
- expect(document.toString()).toMatch(/xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"/);
- });
-
- it("add text namespace", () => {
- expect(document.toString()).toMatch(/xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"/);
- });
-
- it("add xlink namespace", () => {
- expect(document.toString()).toMatch(/xmlns:xlink="http:\/\/www.w3.org\/1999\/xlink"/);
- });
- });
-
- describe("#getMeta", () => {
- it("return a meta object", () => {
- expect(document.getMeta()).toBeInstanceOf(Meta);
- });
- });
-
- describe("#declareFont", () => {
- it("add font declaration to document", () => {
- document.declareFont("Springfield", "Springfield", FontPitch.Variable);
-
- /* tslint:disable-next-line:max-line-length */
- expect(document.toString()).toMatch(/<\/office:font-face-decls>/);
- });
-
- it("add font declaration to document and wrap font family if it contains spaces", () => {
- document.declareFont("Homer Simpson", "Homer Simpson", FontPitch.Variable);
-
- /* tslint:disable-next-line:max-line-length */
- expect(document.toString()).toMatch(/<\/office:font-face-decls>/);
- });
- });
-
- describe("#addHeading", () => {
- it("return a heading", () => {
- const heading = document.addHeading();
-
- expect(heading).toBeInstanceOf(Heading);
- });
-
- it("add heading to document", () => {
- document.addHeading();
-
- expect(document.toString()).toMatch(/ {
- it("return a list", () => {
- const list = document.addList();
-
- expect(list).toBeInstanceOf(List);
- });
-
- it("add list to document", () => {
- document.addList().addItem();
-
- expect(document.toString()).toMatch(//);
- });
- });
-
- describe("#addParagraph", () => {
- it("return a paragraph", () => {
- const paragraph = document.addParagraph();
-
- expect(paragraph).toBeInstanceOf(Paragraph);
- });
-
- it("add paragraph to document", () => {
- document.addParagraph();
-
- expect(document.toString()).toMatch(/ {
- it("write a flat document", async (done) => {
- const readFileAsync = promisify(readFile);
-
- await document.saveFlat(FILEPATH);
-
- const fileContents = await readFileAsync(FILEPATH, "utf8");
-
- expect(fileContents).toEqual(XML_DECLARATION + baseDocument);
- done();
- });
- });
-
- describe("#toString", () => {
- it("return the basis document", () => {
- const result = document.toString();
-
- expect(result).toEqual(XML_DECLARATION + baseDocument);
- });
- });
-});
diff --git a/src/TextDocument.ts b/src/TextDocument.ts
deleted file mode 100644
index 06e009ab..00000000
--- a/src/TextDocument.ts
+++ /dev/null
@@ -1,206 +0,0 @@
-import { writeFile } from "fs";
-import { promisify } from "util";
-import { DOMImplementation, XMLSerializer } from "xmldom";
-
-import { Meta } from "./meta/Meta";
-import { OdfAttributeName } from "./OdfAttributeName";
-import { OdfElement } from "./OdfElement";
-import { OdfElementName } from "./OdfElementName";
-import { FontPitch } from "./style/FontPitch";
-import { Heading } from "./text/Heading";
-import { List } from "./text/List";
-import { Paragraph } from "./text/Paragraph";
-
-export const XML_DECLARATION = '\n';
-const OFFICE_VERSION = "1.2";
-
-/** This interface holds a font font declaration */
-interface IFont {
- name: string;
- fontFamily: string;
- fontPitch: FontPitch;
-}
-
-/**
- * This class represents an empty ODF text document.
- * @since 0.1.0
- */
-export class TextDocument extends OdfElement {
- private meta: Meta;
- private fonts: IFont[];
-
- public constructor() {
- super();
-
- this.meta = new Meta();
- this.fonts = [];
- }
-
- /**
- * The `getMeta()` method returns the metadata of the document.
- *
- * @example
- * document.getMeta().setCreator('Lisa Simpson');
- *
- * @returns {Meta} An object holding the metadata of the document
- * @see {@link Meta}
- * @since 0.6.0
- */
- public getMeta(): Meta {
- return this.meta;
- }
-
- /**
- * Declares a font to be used in the document.
- *
- * **Note: There is no check whether the font exists.
- * In order to be displayed properly, the font must be present on the target system.**
- *
- * @param {string} name The name of the font; this name must be set to a {@link ParagraphStyle}
- * @param {string} fontFamily The name of the font family
- * @param {FontPitch} fontPitch The ptich of the fonr
- * @since 0.4.0
- */
- public declareFont(name: string, fontFamily: string, fontPitch: FontPitch): void {
- this.fonts.push({ name, fontFamily, fontPitch });
- }
-
- /**
- * Adds a heading at the end of the document.
- * If a text is given, this will be set as text content of the heading.
- *
- * @param {string} [text] The text content of the heading
- * @param {number} [level=1] The heading level; defaults to 1 if omitted
- * @returns {Heading} The newly added heading
- * @since 0.1.0
- */
- public addHeading(text?: string, level = 1): Heading {
- const heading = new Heading(text, level);
- this.append(heading);
-
- return heading;
- }
-
- /**
- * Adds an empty list at the end of the document.
- *
- * @returns {List} The newly added list
- * @since 0.2.0
- */
- public addList(): List {
- const list = new List();
- this.append(list);
-
- return list;
- }
-
- /**
- * Adds a paragraph at the end of the document.
- * If a text is given, this will be set as text content of the paragraph.
- *
- * @param {string} [text] The text content of the paragraph
- * @returns {Paragraph} The newly added paragraph
- * @since 0.1.0
- */
- public addParagraph(text?: string): Paragraph {
- const paragraph = new Paragraph(text);
- this.append(paragraph);
-
- return paragraph;
- }
-
- /**
- * Saves the document in flat open document xml format.
- *
- * @param {string} filePath The file path to write to
- * @returns {Promise}
- * @since 0.1.0
- */
- public saveFlat(filePath: string): Promise {
- const writeFileAsync = promisify(writeFile);
- const xml = this.toString();
-
- return writeFileAsync(filePath, xml);
- }
-
- /**
- * Returns the string representation of this document in flat open document xml format.
- *
- * @returns {string} The string representation of this document
- * @since 0.1.0
- * @deprecated since version 0.3.0; use {@link TextDocument#saveFlat} instead
- */
- public toString(): string {
- const document = new DOMImplementation().createDocument(
- "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
- OdfElementName.OfficeDocument,
- null);
- const root = document.firstChild;
-
- this.toXml(document, root as Element);
-
- return XML_DECLARATION + new XMLSerializer().serializeToString(document);
- }
-
- /** @inheritDoc */
- protected toXml(document: Document, root: Element): void {
- this.setXmlNamespaces(root);
-
- root.setAttribute(OdfAttributeName.OfficeMimetype, "application/vnd.oasis.opendocument.text");
- root.setAttribute(OdfAttributeName.OfficeVersion, OFFICE_VERSION);
-
- this.meta.toXml(document, root);
-
- this.setFontFaceElements(document, root);
-
- const bodyElement = document.createElement(OdfElementName.OfficeBody);
- root.appendChild(bodyElement);
-
- const textElement = document.createElement(OdfElementName.OfficeText);
- bodyElement.appendChild(textElement);
-
- super.toXml(document, textElement);
- }
-
- /**
- * Declares the used XML namespaces.
- *
- * @param {Element} root The root element of the document which will be used as parent
- * @private
- */
- private setXmlNamespaces(root: Element): void {
- root.setAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1");
- root.setAttribute("xmlns:draw", "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0");
- root.setAttribute("xmlns:fo", "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0");
- root.setAttribute("xmlns:meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
- root.setAttribute("xmlns:style", "urn:oasis:names:tc:opendocument:xmlns:style:1.0");
- root.setAttribute("xmlns:svg", "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0");
- root.setAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0");
- root.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
- }
-
- /**
- * Adds the `font-face-decls` element and the font faces if any font needs to be declared.
- *
- * @param {Document} document The XML document
- * @param {Element} root The element which will be used as parent
- * @private
- */
- private setFontFaceElements(document: Document, root: Element): void {
- if (this.fonts.length === 0) {
- return;
- }
-
- const fontFaceDeclsElement = document.createElement(OdfElementName.OfficeFontFaceDeclarations);
- root.appendChild(fontFaceDeclsElement);
-
- this.fonts.forEach((font: IFont) => {
- const fontFaceElement = document.createElement(OdfElementName.StyleFontFace);
- fontFaceDeclsElement.appendChild(fontFaceElement);
- fontFaceElement.setAttribute("style:name", font.name);
- const encodedFontFamily = font.fontFamily.includes(" ") === true ? `'${font.fontFamily}'` : font.fontFamily;
- fontFaceElement.setAttribute("svg:font-family", encodedFontFamily);
- fontFaceElement.setAttribute("style:font-pitch", font.fontPitch);
- });
- }
-}
diff --git a/src/OdfElement.ts b/src/api/OdfElement.ts
similarity index 82%
rename from src/OdfElement.ts
rename to src/api/OdfElement.ts
index b599a162..5eb869d1 100644
--- a/src/OdfElement.ts
+++ b/src/api/OdfElement.ts
@@ -14,13 +14,23 @@ export class OdfElement {
this.children = [];
}
+ /**
+ * Returns all child elements.
+ *
+ * @returns {OdfElement[]} A copy of the list of child elements
+ * @since 0.2.0
+ */
+ public getAll(): OdfElement[] {
+ return Array.from(this.children);
+ }
+
/**
* Appends the element as a child element to this element.
*
* @param {OdfElement} element The element to append
* @since 0.1.0
*/
- public append(element: OdfElement): void {
+ protected append(element: OdfElement): void {
this.children.push(element);
}
@@ -64,16 +74,6 @@ export class OdfElement {
return this.children[position];
}
- /**
- * Returns all child elements.
- *
- * @returns {OdfElement[]} A copy of the list of child elements
- * @since 0.2.0
- */
- protected getAll(): OdfElement[] {
- return Array.from(this.children);
- }
-
/**
* Removes the child element from the specified position.
*
@@ -101,18 +101,4 @@ export class OdfElement {
protected hasChildren(): boolean {
return this.children.length > 0;
}
-
- /**
- * Transforms the element into Open Document Format.
- * Implementors of this class must add themselves to the document and afterwards call super.toXML(...)
.
- *
- * @param {Document} document The XML document
- * @param {Element} parent The parent node
- * @since 0.1.0
- */
- protected toXml(document: Document, parent: Element): void {
- this.children.forEach((child: OdfElement) => {
- child.toXml(document, parent);
- });
- }
}
diff --git a/src/api/draw/Image.spec.ts b/src/api/draw/Image.spec.ts
new file mode 100644
index 00000000..d8c37803
--- /dev/null
+++ b/src/api/draw/Image.spec.ts
@@ -0,0 +1,46 @@
+import { AnchorType } from "../../style/AnchorType";
+import { ImageStyle } from "../../style/ImageStyle";
+import { Image } from "./Image";
+
+describe(Image.name, () => {
+ const testImagePath = "/some/image.path.png";
+
+ let image: Image;
+
+ beforeEach(() => {
+ image = new Image(testImagePath);
+ });
+
+ describe("path", () => {
+ it("return initial path", () => {
+ expect(image.getPath()).toBe(testImagePath);
+ });
+ });
+
+ describe("style", () => {
+ let testStyle: ImageStyle;
+
+ beforeEach(() => {
+ testStyle = new ImageStyle();
+ });
+
+ it("return style by default", () => {
+ expect(image.getStyle()).toBeInstanceOf(ImageStyle);
+ });
+
+ it("return previous set style", () => {
+ testStyle.setAnchorType(AnchorType.AsChar);
+
+ image.setStyle(testStyle);
+
+ expect(image.getStyle()).toBe(testStyle);
+ });
+
+ it("ignore invalid input", () => {
+ image.setStyle(testStyle);
+ image.setStyle(null);
+
+ expect(image.getStyle()).toBe(testStyle);
+ });
+ });
+});
diff --git a/src/api/draw/Image.ts b/src/api/draw/Image.ts
new file mode 100644
index 00000000..0ac83910
--- /dev/null
+++ b/src/api/draw/Image.ts
@@ -0,0 +1,85 @@
+import { IImageStyle } from "../../style/IImageStyle";
+import { ImageStyle } from "../../style/ImageStyle";
+import { OdfElement } from "../OdfElement";
+
+/**
+ * This class represents an image in a paragraph.
+ *
+ * It is used to embed image data in BASE64 encoding.
+ *
+ * @example
+ * document.getBody()
+ * .addParagraph()
+ * .addImage("/home/homer/myself.png")
+ * .getStyle()
+ * .setSize(42, 23);
+ *
+ * @since 0.3.0
+ */
+export class Image extends OdfElement {
+ private style: IImageStyle;
+
+ /**
+ * Creates an image
+ *
+ * @example
+ * const image = new Image("/home/homer/myself.png");
+ *
+ * @param {string} path Path to the image file that should be embedded
+ * @since 0.3.0
+ */
+ public constructor(private path: string) {
+ super();
+
+ this.style = new ImageStyle();
+ }
+
+ /**
+ * The `getPath()` method returns the path to the image file that should be embedded.
+ *
+ * @example
+ * const image = new Image("/home/homer/myself.png");
+ * image.getPath(); // '/home/homer/myself.png'
+ *
+ * @returns {string} The path to the image file
+ * @since 0.7.0
+ */
+ public getPath(): string {
+ return this.path;
+ }
+
+ /**
+ * Sets the new style of this image.
+ *
+ * @example
+ * const image = new Image("/home/homer/myself.png");
+ * image.setStyle(new ImageStyle());
+ *
+ * @param {IImageStyle} style The new style
+ * @returns {Image} The `Image` object
+ * @since 0.5.0
+ */
+ public setStyle(style: IImageStyle): Image {
+ if (style instanceof ImageStyle) {
+ this.style = style;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns the style of this image.
+ *
+ * @example
+ * const image = new Image("/home/homer/myself.png");
+ * image.getStyle(); // default style
+ * image.setStyle(new ImageStyle());
+ * image.getStyle(); // previously set style
+ *
+ * @returns {IImageStyle} The style of the image
+ * @since 0.5.0
+ */
+ public getStyle(): IImageStyle {
+ return this.style;
+ }
+}
diff --git a/src/api/draw/index.ts b/src/api/draw/index.ts
new file mode 100644
index 00000000..d88656f5
--- /dev/null
+++ b/src/api/draw/index.ts
@@ -0,0 +1 @@
+export { Image } from "./Image";
diff --git a/src/meta/Meta.spec.ts b/src/api/meta/Meta.spec.ts
similarity index 75%
rename from src/meta/Meta.spec.ts
rename to src/api/meta/Meta.spec.ts
index 2abd8ba6..645a2bac 100644
--- a/src/meta/Meta.spec.ts
+++ b/src/api/meta/Meta.spec.ts
@@ -1,6 +1,5 @@
import { userInfo } from "os";
import { Meta } from "./Meta";
-import { TextDocument } from "../TextDocument";
describe(Meta.name, () => {
const timeOffset = 100;
@@ -357,74 +356,4 @@ describe(Meta.name, () => {
expect(meta.getTitle()).toBe(testTitle);
});
});
-
- describe("#toXml", () => {
- let document: TextDocument;
-
- beforeEach(() => {
- document = new TextDocument();
- });
-
- it("append creator, date, creation-date, editing-cycles and generator as default properties", () => {
- const regex = new RegExp(""
- + "simple-odf/\\d\\.\\d+\\.\\d+"
- + "" + userInfo().username + ""
- + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z"
- + "1"
- + "");
- expect(document.toString()).toMatch(regex);
- });
-
- it("ignore description, language, subject, title if they are empty", () => {
- document.getMeta()
- .setCreator("")
- .setDate(undefined)
- .setDescription("")
- .setInitialCreator("")
- .setLanguage("")
- .setSubject("")
- .setTitle("");
-
- const regex = new RegExp(""
- + "simple-odf/\\d\\.\\d+\\.\\d+"
- + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z"
- + "1"
- + "");
- expect(document.toString()).toMatch(regex);
- });
-
- it("append elements if they are set", () => {
- document.getMeta()
- .setCreator("Homer Simpson")
- .setDate(new Date(Date.UTC(2020, 11, 24, 13, 37, 23, 42)))
- .setDescription("some test description")
- .setInitialCreator("Marge Simpson")
- .addKeyword("some keyword")
- .addKeyword("some other keyword")
- .setLanguage("zu")
- .setPrintDate(new Date(Date.UTC(2021, 3, 1)))
- .setPrintedBy("Maggie Simpson")
- .setSubject("some test subject")
- .setTitle("some test title")
- ;
-
- const regex = new RegExp(""
- + "simple-odf/\\d\\.\\d+\\.\\d+"
- + "some test title"
- + "some test description"
- + "some test subject"
- + "some keyword"
- + "some other keyword"
- + "Marge Simpson"
- + "Homer Simpson"
- + "Maggie Simpson"
- + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z"
- + "2020-12-24T13:37:23.042Z"
- + "2021-04-01T00:00:00.000Z"
- + "zu"
- + "1"
- + "");
- expect(document.toString()).toMatch(regex);
- });
- });
});
diff --git a/src/meta/Meta.ts b/src/api/meta/Meta.ts
similarity index 65%
rename from src/meta/Meta.ts
rename to src/api/meta/Meta.ts
index 729e7659..be165746 100644
--- a/src/meta/Meta.ts
+++ b/src/api/meta/Meta.ts
@@ -1,6 +1,4 @@
import { userInfo } from "os";
-import { OdfElementName } from "../OdfElementName";
-import { MetaElementName } from "./MetaElementName";
/**
* This class represents the metadata of a document.
@@ -52,7 +50,7 @@ export class Meta {
* @since 0.6.0
*/
public constructor() {
- const packageJson = require("../../package.json");
+ const packageJson = require("../../../package.json");
this.generator = `${packageJson.name}/${packageJson.version}`;
this.keywords = [];
@@ -517,240 +515,4 @@ export class Meta {
public getTitle(): string | undefined {
return this.title;
}
-
- /**
- * Transforms the text style into Open Document Format.
- *
- * @param {Document} document The XML document
- * @param {Element} root The root node in the DOM
- * @since 0.6.0
- */
- public toXml(document: Document, root: Element): void {
- const metaElement = document.createElement(OdfElementName.OfficeMeta);
- root.appendChild(metaElement);
-
- this.setGeneratorElement(document, metaElement);
- this.setTitleElement(document, metaElement);
- this.setDescriptionElement(document, metaElement);
- this.setSubjectElement(document, metaElement);
- this.setKeywordElements(document, metaElement);
- this.setInitialCreatorElement(document, metaElement);
- this.setCreatorElement(document, metaElement);
- this.setPrintedByElement(document, metaElement);
- this.setCreationDateElement(document, metaElement);
- this.setDateElement(document, metaElement);
- this.setPrintDateElement(document, metaElement);
- this.setLanguageElement(document, metaElement);
- this.setEditingCyclesElement(document, metaElement);
- }
-
- /**
- * Sets the `meta:creation-date` element to the date and time this class was constructed.
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setCreationDateElement(document: Document, metaElement: Element): void {
- const creationDateElement = document.createElement(MetaElementName.MetaCreationDate);
- metaElement.appendChild(creationDateElement);
- creationDateElement.appendChild(document.createTextNode(this.creationDate.toISOString()));
- }
-
- /**
- * Sets the `dc:creator` element if creator is set.
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setCreatorElement(document: Document, metaElement: Element): void {
- if (this.creator === undefined || this.creator.length === 0) {
- return;
- }
-
- const creatorElement = document.createElement(MetaElementName.DcCreator);
- metaElement.appendChild(creatorElement);
- creatorElement.appendChild(document.createTextNode(this.creator));
- }
-
- /**
- * Sets the `dc:date` element if date is set.
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setDateElement(document: Document, metaElement: Element): void {
- if (this.date === undefined) {
- return;
- }
-
- const dateElement = document.createElement(MetaElementName.DcDate);
- metaElement.appendChild(dateElement);
- dateElement.appendChild(document.createTextNode(this.date.toISOString()));
- }
-
- /**
- * Sets the `dc:description` element if description is set.
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setDescriptionElement(document: Document, metaElement: Element): void {
- if (this.description === undefined || this.description.length === 0) {
- return;
- }
-
- const descriptionElement = document.createElement(MetaElementName.DcDescription);
- metaElement.appendChild(descriptionElement);
- descriptionElement.appendChild(document.createTextNode(this.description));
- }
-
- /**
- * Sets the `meta:editing-cycles` element to 1.
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setEditingCyclesElement(document: Document, metaElement: Element): void {
- const editingCyclesElement = document.createElement(MetaElementName.MetaEditingCycles);
- metaElement.appendChild(editingCyclesElement);
- editingCyclesElement.appendChild(document.createTextNode(this.editingCycles.toString()));
- }
-
- /**
- * Sets the `meta:generator` element to the name and version of this library (`simple-odf/x.y.z`).
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setGeneratorElement(document: Document, metaElement: Element): void {
- const generatorElement = document.createElement(MetaElementName.MetaGenerator);
- metaElement.appendChild(generatorElement);
- generatorElement.appendChild(document.createTextNode(this.generator));
- }
-
- /**
- * Sets the `meta:initial-creator` element to the current user.
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setInitialCreatorElement(document: Document, metaElement: Element): void {
- if (this.initialCreator === undefined || this.initialCreator.length === 0) {
- return;
- }
-
- const creatorElement = document.createElement(MetaElementName.MetaInitialCreator);
- metaElement.appendChild(creatorElement);
- creatorElement.appendChild(document.createTextNode(this.initialCreator));
- }
-
- /**
- * Sets the `meta:keyword` elements if any keyword is set.
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setKeywordElements(document: Document, metaElement: Element): void {
- if (this.keywords.length === 0) {
- return;
- }
-
- this.keywords.forEach((keyword: string) => {
- const subjectElement = document.createElement(MetaElementName.MetaKeyword);
- metaElement.appendChild(subjectElement);
- subjectElement.appendChild(document.createTextNode(keyword));
- });
- }
-
- /**
- * Sets the `dc:language` element if language is set.
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setLanguageElement(document: Document, metaElement: Element): void {
- if (this.language === undefined || this.language.length === 0) {
- return;
- }
-
- const languageElement = document.createElement(MetaElementName.DcLanguage);
- metaElement.appendChild(languageElement);
- languageElement.appendChild(document.createTextNode(this.language));
- }
-
- /**
- * Sets the `meta:print-date` element if print date is set.
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setPrintDateElement(document: Document, metaElement: Element): void {
- if (this.printDate === undefined) {
- return;
- }
-
- const printDateElement = document.createElement(MetaElementName.MetaPrintDate);
- metaElement.appendChild(printDateElement);
- printDateElement.appendChild(document.createTextNode(this.printDate.toISOString()));
- }
-
- /**
- * Sets the `meta:printed-by` element if printing name is set.
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setPrintedByElement(document: Document, metaElement: Element): void {
- if (this.printedBy === undefined || this.printedBy.length === 0) {
- return;
- }
- const printedByElement = document.createElement(MetaElementName.MetaPrintedBy);
- metaElement.appendChild(printedByElement);
- printedByElement.appendChild(document.createTextNode(this.printedBy));
- }
-
- /**
- * Sets the `dc:subject` element if subject is set.
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setSubjectElement(document: Document, metaElement: Element): void {
- if (this.subject === undefined || this.subject.length === 0) {
- return;
- }
-
- const subjectElement = document.createElement(MetaElementName.DcSubject);
- metaElement.appendChild(subjectElement);
- subjectElement.appendChild(document.createTextNode(this.subject));
- }
-
- /**
- * Sets the `dc:title` element if title is set.
- *
- * @param {Document} document The XML document
- * @param {Element} metaElement The meta element which will act as parent
- * @private
- */
- private setTitleElement(document: Document, metaElement: Element): void {
- if (this.title === undefined || this.title.length === 0) {
- return;
- }
-
- const titleElement = document.createElement(MetaElementName.DcTitle);
- metaElement.appendChild(titleElement);
- titleElement.appendChild(document.createTextNode(this.title));
- }
}
diff --git a/src/api/meta/index.ts b/src/api/meta/index.ts
new file mode 100644
index 00000000..f7500524
--- /dev/null
+++ b/src/api/meta/index.ts
@@ -0,0 +1 @@
+export { Meta } from "./Meta";
diff --git a/src/api/office/TextBody.spec.ts b/src/api/office/TextBody.spec.ts
new file mode 100644
index 00000000..931f3fcf
--- /dev/null
+++ b/src/api/office/TextBody.spec.ts
@@ -0,0 +1,34 @@
+import { Heading, List, Paragraph } from "../text";
+import { TextBody } from "./TextBody";
+
+describe(TextBody.name, () => {
+ let textBody: TextBody;
+
+ beforeEach(() => {
+ textBody = new TextBody();
+ });
+
+ describe("#addHeading", () => {
+ it("return a heading", () => {
+ const heading = textBody.addHeading();
+
+ expect(heading).toBeInstanceOf(Heading);
+ });
+ });
+
+ describe("#addList", () => {
+ it("return a list", () => {
+ const list = textBody.addList();
+
+ expect(list).toBeInstanceOf(List);
+ });
+ });
+
+ describe("#addParagraph", () => {
+ it("return a paragraph", () => {
+ const paragraph = textBody.addParagraph();
+
+ expect(paragraph).toBeInstanceOf(Paragraph);
+ });
+ });
+});
diff --git a/src/api/office/TextBody.ts b/src/api/office/TextBody.ts
new file mode 100644
index 00000000..30e6319f
--- /dev/null
+++ b/src/api/office/TextBody.ts
@@ -0,0 +1,59 @@
+import { OdfElement } from "../OdfElement";
+import { Heading, List, Paragraph } from "../text";
+
+/**
+ * This class represents the content of a text document.
+ *
+ * @example
+ * const body = document.getBody();
+ * body.addHeading("My document");
+ * body.addParagraph("This is the first paragraph");
+ * body.addHeading("Subheadline", 2);
+ *
+ * @since 0.7.0
+ */
+export class TextBody extends OdfElement {
+ /**
+ * Adds a heading at the end of the document.
+ * If a text is given, this will be set as text content of the heading.
+ *
+ * @param {string} [text] The text content of the heading
+ * @param {number} [level=1] The heading level; defaults to 1 if omitted
+ * @returns {Heading} The newly added heading
+ * @since 0.7.0
+ */
+ public addHeading(text?: string, level = 1): Heading {
+ const heading = new Heading(text, level);
+ this.append(heading);
+
+ return heading;
+ }
+
+ /**
+ * Adds an empty list at the end of the document.
+ *
+ * @returns {List} The newly added list
+ * @since 0.7.0
+ */
+ public addList(): List {
+ const list = new List();
+ this.append(list);
+
+ return list;
+ }
+
+ /**
+ * Adds a paragraph at the end of the document.
+ * If a text is given, this will be set as text content of the paragraph.
+ *
+ * @param {string} [text] The text content of the paragraph
+ * @returns {Paragraph} The newly added paragraph
+ * @since 0.7.0
+ */
+ public addParagraph(text?: string): Paragraph {
+ const paragraph = new Paragraph(text);
+ this.append(paragraph);
+
+ return paragraph;
+ }
+}
diff --git a/src/api/office/TextDocument.spec.ts b/src/api/office/TextDocument.spec.ts
new file mode 100644
index 00000000..6e9b61ba
--- /dev/null
+++ b/src/api/office/TextDocument.spec.ts
@@ -0,0 +1,84 @@
+import { readFile, unlink } from "fs";
+import { promisify } from "util";
+import { Meta } from "../meta/Meta";
+import { FontFace } from "../style";
+import { FontPitch } from "../style/FontPitch";
+import { TextBody } from "./TextBody";
+import { TextDocument, XML_DECLARATION } from "./TextDocument";
+
+const FILEPATH = "./test.fodt";
+
+jest.mock("../../xml/TextDocumentWriter");
+
+describe(TextDocument.name, () => {
+ let document: TextDocument;
+
+ beforeEach(() => {
+ document = new TextDocument();
+ });
+
+ describe("body", () => {
+ it("return a text body object", () => {
+ const body = document.getBody();
+
+ expect(body).toBeInstanceOf(TextBody);
+ });
+ });
+
+ describe("font", () => {
+ it("return an empty list of fonts by default", () => {
+ const fonts = document.getFonts();
+
+ expect(fonts).toEqual([]);
+ });
+
+ it("return a font face object", () => {
+ const font = document.declareFont("Springfield", "Springfield", FontPitch.Variable);
+
+ expect(font).toBeInstanceOf(FontFace);
+ });
+
+ it("add font face to list of fonts", () => {
+ document.declareFont("Springfield", "Springfield", FontPitch.Variable);
+
+ const fonts = document.getFonts();
+
+ expect(fonts).toEqual([new FontFace("Springfield", "Springfield", FontPitch.Variable)]);
+ });
+ });
+
+ describe("meta", () => {
+ it("return a meta object", () => {
+ expect(document.getMeta()).toBeInstanceOf(Meta);
+ });
+ });
+
+ describe("#saveFlat", () => {
+ afterEach(async (done) => {
+ const unlinkAsync = promisify(unlink);
+
+ await unlinkAsync(FILEPATH);
+
+ done();
+ });
+
+ it("write a flat document", async (done) => {
+ const readFileAsync = promisify(readFile);
+
+ await document.saveFlat(FILEPATH);
+
+ const fileContents = await readFileAsync(FILEPATH, "utf8");
+
+ expect(fileContents).toEqual(XML_DECLARATION + "??");
+ done();
+ });
+ });
+
+ describe("#toString", () => {
+ it("return the basis document", () => {
+ const result = document.toString();
+
+ expect(result).toEqual(XML_DECLARATION + "??");
+ });
+ });
+});
diff --git a/src/api/office/TextDocument.ts b/src/api/office/TextDocument.ts
new file mode 100644
index 00000000..248c7d9b
--- /dev/null
+++ b/src/api/office/TextDocument.ts
@@ -0,0 +1,132 @@
+import { writeFile } from "fs";
+import { promisify } from "util";
+import { XMLSerializer } from "xmldom";
+import { TextDocumentWriter } from "../../xml/TextDocumentWriter";
+import { Meta } from "../meta";
+import { FontFace, FontPitch } from "../style";
+import { TextBody } from "./TextBody";
+
+export const XML_DECLARATION = '\n';
+
+/**
+ * This class represents a text document in OpenDocument format.
+ *
+ * @example
+ * const document = new TextDocument();
+ * document.getMeta().setCreator("Homer Simpson");
+ * document.declareFont("FreeSans", "FreeSans", FontPitch.Variable);
+ * document.getBody().addHeading("My first document");
+ * document.saveFlat("/home/homer/document.fodt");
+ *
+ * @since 0.1.0
+ */
+export class TextDocument {
+ private meta: Meta;
+ private fonts: FontFace[];
+ private body: TextBody;
+
+ public constructor() {
+ this.meta = new Meta();
+ this.fonts = [];
+ this.body = new TextBody();
+ }
+
+ /**
+ * The `getBody()` method returns the content of the document.
+ *
+ * @example
+ * new TextDocument()
+ * .getBody()
+ * .addHeading('My first document');
+ *
+ * @returns {TextBody} A `TextBody` object that holds the content of the document
+ * @since 0.7.0
+ */
+ public getBody(): TextBody {
+ return this.body;
+ }
+
+ /**
+ * The `declareFont` method creates a font face to be used in the document.
+ *
+ * **Note: There is no check whether the font exists.
+ * In order to be displayed properly, the font must be present on the target system.**
+ *
+ * @example
+ * new TextDocument()
+ * .declareFont("FreeSans", "FreeSans", FontPitch.Variable);
+ *
+ * @param {string} name The name of the font; this name must be set to a {@link ParagraphStyle}
+ * @param {string} fontFamily The name of the font family
+ * @param {FontPitch} fontPitch The pitch of the font
+ * @returns {FontFace} The declared `FontFace` object
+ * @since 0.4.0
+ */
+ public declareFont(name: string, fontFamily: string, fontPitch: FontPitch): FontFace {
+ const fontFace = new FontFace(name, fontFamily, fontPitch);
+ this.fonts.push(fontFace);
+
+ return fontFace;
+ }
+
+ /**
+ * The `getFonts()` method returns all font face declarations for the document.
+ *
+ * @example
+ * const document = new TextDocument();
+ * document.declareFont("FreeSans", "FreeSans", FontPitch.Variable);
+ * document.getFonts();
+ *
+ * @returns {FontFace[]} A copy of the list of font face declarations for the document
+ * @since 0.7.0
+ */
+ public getFonts(): FontFace[] {
+ return Array.from(this.fonts);
+ }
+
+ /**
+ * The `getMeta()` method returns the metadata of the document.
+ *
+ * @example
+ * new TextDocument.getMeta()
+ * .setCreator('Homer Simpson');
+ *
+ * @returns {Meta} An object holding the metadata of the document
+ * @see {@link Meta}
+ * @since 0.6.0
+ */
+ public getMeta(): Meta {
+ return this.meta;
+ }
+
+ /**
+ * The `saveFlat()` method converts the document into an XML string and stores it in flat open document xml format.
+ *
+ * @example
+ * new TextDocument()
+ * .saveFlat("/home/homer/document.fodt");
+ *
+ * @param {string} filePath The file path to write to
+ * @returns {Promise}
+ * @since 0.1.0
+ */
+ public saveFlat(filePath: string): Promise {
+ const writeFileAsync = promisify(writeFile);
+ const xml = this.toString();
+
+ return writeFileAsync(filePath, xml);
+ }
+
+ /**
+ * Returns the string representation of this document in flat open document xml format.
+ *
+ * @returns {string} The string representation of this document
+ * @since 0.1.0
+ * @deprecated since version 0.3.0; use {@link TextDocument#saveFlat} instead
+ */
+ public toString(): string {
+ const document = new TextDocumentWriter().write(this);
+
+ return XML_DECLARATION + new XMLSerializer().serializeToString(document);
+ }
+}
diff --git a/src/api/office/index.ts b/src/api/office/index.ts
new file mode 100644
index 00000000..a0b107ae
--- /dev/null
+++ b/src/api/office/index.ts
@@ -0,0 +1,2 @@
+export { TextBody } from "./TextBody";
+export { TextDocument } from "./TextDocument";
diff --git a/src/api/style/FontFace.spec.ts b/src/api/style/FontFace.spec.ts
new file mode 100644
index 00000000..3cc11c94
--- /dev/null
+++ b/src/api/style/FontFace.spec.ts
@@ -0,0 +1,32 @@
+import { FontFace } from "./FontFace";
+import { FontPitch } from "./FontPitch";
+
+describe(FontFace.name, () => {
+ const testFamily = "someFontFamily";
+ const testFontPitch = FontPitch.Variable;
+ const testName = "someFontName";
+
+ let fontFace: FontFace;
+
+ beforeEach(() => {
+ fontFace = new FontFace(testName, testFamily, testFontPitch);
+ });
+
+ describe("font family", () => {
+ it("return initial font family", () => {
+ expect(fontFace.getFontFamily()).toBe(testFamily);
+ });
+ });
+
+ describe("font pitch", () => {
+ it("return initial font pitch", () => {
+ expect(fontFace.getFontPitch()).toBe(testFontPitch);
+ });
+ });
+
+ describe("name", () => {
+ it("return initial name", () => {
+ expect(fontFace.getName()).toBe(testName);
+ });
+ });
+});
diff --git a/src/api/style/FontFace.ts b/src/api/style/FontFace.ts
new file mode 100644
index 00000000..5a61e429
--- /dev/null
+++ b/src/api/style/FontFace.ts
@@ -0,0 +1,25 @@
+import { FontPitch } from "./FontPitch";
+
+export class FontFace {
+ private name: string;
+ private fontFamily: string;
+ private fontPitch: FontPitch;
+
+ public constructor(name: string, fontFamily: string, fontPitch: FontPitch) {
+ this.name = name;
+ this.fontFamily = fontFamily;
+ this.fontPitch = fontPitch;
+ }
+
+ public getFontFamily(): string {
+ return this.fontFamily;
+ }
+
+ public getFontPitch(): FontPitch {
+ return this.fontPitch;
+ }
+
+ public getName(): string {
+ return this.name;
+ }
+}
diff --git a/src/style/FontPitch.ts b/src/api/style/FontPitch.ts
similarity index 100%
rename from src/style/FontPitch.ts
rename to src/api/style/FontPitch.ts
diff --git a/src/api/style/index.ts b/src/api/style/index.ts
new file mode 100644
index 00000000..0f68a6fa
--- /dev/null
+++ b/src/api/style/index.ts
@@ -0,0 +1,2 @@
+export { FontFace } from "./FontFace";
+export { FontPitch } from "./FontPitch";
diff --git a/src/api/text/Heading.spec.ts b/src/api/text/Heading.spec.ts
new file mode 100644
index 00000000..da1a427b
--- /dev/null
+++ b/src/api/text/Heading.spec.ts
@@ -0,0 +1,52 @@
+import { Heading } from "./Heading";
+
+describe(Heading.name, () => {
+ const testLevel = 2;
+ const testText = "some text";
+
+ let heading: Heading;
+
+ beforeEach(() => {
+ heading = new Heading(testText, testLevel);
+ });
+
+ describe("level", () => {
+ it("return initial level", () => {
+ expect(heading.getLevel()).toBe(testLevel);
+ });
+
+ it("return default level if initial level is not set", () => {
+ heading = new Heading(testText);
+
+ expect(heading.getLevel()).toBe(Heading.DEFAULT_LEVEL);
+ });
+
+ it("return previous set level", () => {
+ heading.setLevel(3);
+
+ expect(heading.getLevel()).toBe(3);
+ });
+
+ it("use default level if invalid level is given", () => {
+ heading.setLevel(-2);
+
+ expect(heading.getLevel()).toBe(Heading.DEFAULT_LEVEL);
+
+ heading.setLevel(null);
+
+ expect(heading.getLevel()).toBe(Heading.DEFAULT_LEVEL);
+ });
+ });
+
+ describe("text", () => {
+ it("return initial text", () => {
+ expect(heading.getText()).toBe(testText);
+ });
+
+ it("return empty text if initial text is not set", () => {
+ heading = new Heading();
+
+ expect(heading.getText()).toBe("");
+ });
+ });
+});
diff --git a/src/api/text/Heading.ts b/src/api/text/Heading.ts
new file mode 100644
index 00000000..edf345f0
--- /dev/null
+++ b/src/api/text/Heading.ts
@@ -0,0 +1,64 @@
+import { Paragraph } from "./Paragraph";
+
+/**
+ * This class represents a heading in a document.
+ *
+ * It is used to structure a document into multiple sections.
+ * A chapter or section begins with a heading and extends to the next heading at the same or higher level.
+ *
+ * @example
+ * document.getBody().addHeading("First Headline", 1);
+ *
+ * @example
+ * document.getBody().addHeading()
+ * .setText("Second Headline")
+ * .setLevel(2);
+ *
+ * @extends {Paragraph}
+ * @since 0.1.0
+ */
+export class Heading extends Paragraph {
+ public static DEFAULT_LEVEL = 1;
+
+ /**
+ * Creates a `Heading` instance that represents a heading in a document.
+ *
+ * @example
+ * new Heading("First Headline", 1);
+ * new Heading("First Headline");
+ * new Heading();
+ *
+ * @param {string} [text=''] The text content of the heading; defaults to an empty string if omitted
+ * @param {number} [level=1] The level of the heading, starting with `1`; defaults to `1` if omitted
+ * @since 0.1.0
+ */
+ public constructor(text?: string, private level = Heading.DEFAULT_LEVEL) {
+ super(text);
+
+ this.setLevel(level);
+ }
+
+ /**
+ * The `setLevel()` method sets the level of the heading, starting with `1`.
+ * If an illegal value is provided, then the heading is assumed to be at level `1`.
+ *
+ * @param {number} level The level of the heading, starting with `1`
+ * @returns {Heading} The `Heading` object
+ * @since 0.1.0
+ */
+ public setLevel(level: number): Heading {
+ this.level = level > Heading.DEFAULT_LEVEL ? level : Heading.DEFAULT_LEVEL;
+
+ return this;
+ }
+
+ /**
+ * The `getLevel()` method returns the level of the heading.
+ *
+ * @returns {number} The level of the heading
+ * @since 0.1.0
+ */
+ public getLevel(): number {
+ return this.level;
+ }
+}
diff --git a/src/api/text/Hyperlink.spec.ts b/src/api/text/Hyperlink.spec.ts
new file mode 100644
index 00000000..fb71b2eb
--- /dev/null
+++ b/src/api/text/Hyperlink.spec.ts
@@ -0,0 +1,40 @@
+import { Hyperlink } from "./Hyperlink";
+
+describe(Hyperlink.name, () => {
+ const testText = "some text";
+ const testUri = "http://example.org/";
+
+ let hyperlink: Hyperlink;
+
+ beforeEach(() => {
+ hyperlink = new Hyperlink(testText, testUri);
+ });
+
+ describe("text", () => {
+ it("return initial text", () => {
+ expect(hyperlink.getText()).toBe(testText);
+ });
+ });
+
+ describe("URI", () => {
+ it("return initial URI", () => {
+ expect(hyperlink.getURI()).toBe(testUri);
+ });
+
+ it("return previous set URI", () => {
+ hyperlink.setURI("localhost");
+
+ expect(hyperlink.getURI()).toBe("localhost");
+ });
+
+ it("ignore invalid input", () => {
+ hyperlink.setURI("");
+
+ expect(hyperlink.getURI()).toBe(testUri);
+
+ hyperlink.setURI(null);
+
+ expect(hyperlink.getURI()).toBe(testUri);
+ });
+ });
+});
diff --git a/src/api/text/Hyperlink.ts b/src/api/text/Hyperlink.ts
new file mode 100644
index 00000000..417ae87f
--- /dev/null
+++ b/src/api/text/Hyperlink.ts
@@ -0,0 +1,64 @@
+import { OdfTextElement } from "./OdfTextElement";
+
+/**
+ * This class represents a hyperlink in a paragraph.
+ *
+ * @example
+ * document.getBody()
+ * .addParagraph('This is a ')
+ * .addHyperlink('link', 'https://example.com/');
+ *
+ * @since 0.3.0
+ */
+export class Hyperlink extends OdfTextElement {
+ /**
+ * Creates a hyperlink
+ *
+ * @example
+ * new Hyperlink('My website', 'https://example.com/');
+ *
+ * @param {string} text The text content of the hyperlink
+ * @param {string} uri The target URI of the hyperlink
+ * @since 0.3.0
+ */
+ public constructor(text: string, private uri: string) {
+ super(text);
+ }
+
+ /**
+ * The `setURI()` method sets the target URI for this hyperlink.
+ * If an illegal value is provided, the value will be ignored.
+ *
+ * @example
+ * const hyperlink = new Hyperlink('My website', 'https://example.com/');
+ * hyperlink.setURI('https://github.com'); // https://github.com
+ * hyperlink.setURI(''); // https://github.com
+ *
+ * @param {string} uri The target URI of this hyperlink
+ * @returns {Hyperlink} The `Hyperlink` object
+ * @since 0.3.0
+ */
+ public setURI(uri: string): Hyperlink {
+ if (typeof uri === "string" && uri.trim().length > 0) {
+ this.uri = uri;
+ }
+
+ return this;
+ }
+
+ /**
+ * The `getURI()` method returns the target URI of this hyperlink.
+ *
+ * @example
+ * const hyperlink = new Hyperlink('My website', 'https://example.com/');
+ * hyperlink.getURI(); // https://example.com
+ * hyperlink.setURI('https://github.com');
+ * hyperlink.getURI(); // https://github.com
+ *
+ * @returns {string} The target URI of this hyperlink
+ * @since 0.3.0
+ */
+ public getURI(): string {
+ return this.uri;
+ }
+}
diff --git a/src/text/List.spec.ts b/src/api/text/List.spec.ts
similarity index 88%
rename from src/text/List.spec.ts
rename to src/api/text/List.spec.ts
index 92f59d6a..5086b081 100644
--- a/src/text/List.spec.ts
+++ b/src/api/text/List.spec.ts
@@ -1,36 +1,20 @@
import { List } from "./List";
import { ListItem } from "./ListItem";
-import { TextDocument } from "../TextDocument";
describe(List.name, () => {
- let document: TextDocument;
let list: List;
let testItem1: ListItem;
let testItem2: ListItem;
let testItem3: ListItem;
beforeEach(() => {
- document = new TextDocument();
- list = document.addList();
+ list = new List();
testItem1 = new ListItem("first");
testItem2 = new ListItem("second");
testItem3 = new ListItem("third");
});
- it("NOT insert an empty list", () => {
- const documentAsString = document.toString();
- expect(documentAsString).not.toMatch(/ {
- list.addItem("first");
-
- const documentAsString = document.toString();
- /* tslint:disable-next-line:max-line-length */
- expect(documentAsString).toMatch(/first<\/text:p><\/text:list-item><\/text:list>/);
- });
-
describe("#addItem", () => {
beforeEach(() => {
list.addItem("first");
diff --git a/src/api/text/List.ts b/src/api/text/List.ts
new file mode 100644
index 00000000..6bf647e8
--- /dev/null
+++ b/src/api/text/List.ts
@@ -0,0 +1,179 @@
+import { OdfElement } from "../OdfElement";
+import { ListItem } from "./ListItem";
+
+/**
+ * This class represents a list and may contain any number list items.
+ *
+ * @example
+ * const list = document.getBody().addList();
+ * list.addItem("First item");
+ * list.addItem("Second item");
+ * list.insertItem(1, "After first item")
+ * list.removeItemAt(2);
+ *
+ * @since 0.2.0
+ */
+export class List extends OdfElement {
+ /**
+ * Creates a `List` instance that represents a list.
+ *
+ * @example
+ * new List();
+ *
+ * @since 0.2.0
+ */
+ public constructor() {
+ super();
+ }
+
+ /**
+ * The `addItem()` method adds a new list item with the specified text or adds the specified item to the list.
+ *
+ * @example
+ * const list = new List();
+ * list.addItem("First item");
+ * list.addItem(new ListItem("Second item"));
+ *
+ * @param {string | ListItem} [item] The text content of the new item or the item to add
+ * @returns {ListItem} The added `ListItem` object
+ * @since 0.2.0
+ */
+ public addItem(item?: string | ListItem): ListItem {
+ if (item instanceof ListItem) {
+ this.append(item);
+ return item;
+ }
+
+ const listItem = new ListItem(item);
+ this.append(listItem);
+
+ return listItem;
+ }
+
+ /**
+ * The `insertItem` method inserts a new list item with the specified text
+ * or inserts the specified item at the specified position.
+ * The item is inserted before the item at the specified position.
+ *
+ * If the position is greater than the current number items, the new item is appended at the end of the list.
+ * If the position is negative, the new item is inserted as first element.
+ *
+ * @example
+ * const list = new List();
+ * list.addItem("First item"); // "First item"
+ * list.addItem("Second item"); // "First item", "Second item"
+ * list.insertItem(1, "After first item"); // "First item", "After first item", "Second item"
+ *
+ * @param {number} position The index at which to insert the list item (starting from 0).
+ * @param {string | ListItem} item The text content of the new item or the item to insert
+ * @returns {ListItem} The inserted `ListItem` object
+ * @since 0.2.0
+ */
+ public insertItem(position: number, item: string | ListItem): ListItem {
+ if (item instanceof ListItem) {
+ this.insert(position, item);
+ return item;
+ }
+
+ const listItem = new ListItem(item);
+ this.insert(position, listItem);
+
+ return listItem;
+ }
+
+ /**
+ * The `getItem()` method returns the item at the specified position in the list.
+ * If an invalid position is given, undefined is returned.
+ *
+ * @example
+ * const list = new List();
+ * list.addItem("First item");
+ * list.addItem("Second item");
+ * list.getItem(1); // "Second item"
+ * list.getItem(2); // undefined
+ *
+ * @param {number} position The index of the requested list item (starting from 0).
+ * @returns {ListItem | undefined} The `ListItem` object at the specified position
+ * or `undefined` if there is no list item at the specified position
+ * @since 0.2.0
+ */
+ public getItem(position: number): ListItem | undefined {
+ return this.get(position) as ListItem;
+ }
+
+ /**
+ * The `getItems()` method returns all list items.
+ *
+ * @example
+ * const list = new List();
+ * list.getItems(); // []
+ * list.addItem("First item");
+ * list.addItem("Second item");
+ * list.getItems(); // ["First item", "Second item"]
+ *
+ * @returns {ListItem[]} A copy of the list of `ListItem` objects
+ * @since 0.2.0
+ */
+ public getItems(): ListItem[] {
+ return this.getAll() as ListItem[];
+ }
+
+ /**
+ * The `removeItemAt()` method removes the list item from the specified position.
+ *
+ * @example
+ * const list = new List();
+ * list.addItem("First item");
+ * list.addItem("Second item");
+ * list.removeItemAt(0); // "First item"
+ * list.getItems(); // ["Second item"]
+ * list.removeItemAt(2); // undefined
+ *
+ * @param {number} position The index of the list item to remove (starting from 0).
+ * @returns {ListItem | undefined} The removed `ListItem` object
+ * or undefined if there is no list item at the specified position
+ * @since 0.2.0
+ */
+ public removeItemAt(position: number): ListItem | undefined {
+ return this.removeAt(position) as ListItem;
+ }
+
+ /**
+ * The `clear()` method removes all items from the list.
+ *
+ * @example
+ * const list = new List();
+ * list.addItem("First item"); // "First item"
+ * list.addItem("Second item"); // "First item", "Second item"
+ * list.clear(); // -
+ *
+ * @returns {List} The `List` object
+ * @since 0.2.0
+ */
+ public clear(): List {
+ let removedElement;
+
+ do {
+ removedElement = this.removeAt(0);
+ } while (removedElement !== undefined);
+
+ return this;
+ }
+
+ /**
+ * The `size()` method returns the number of items in the list.
+ *
+ * @example
+ * const list = new List();
+ * list.size(); // 0
+ * list.addItem("First item");
+ * list.addItem("Second item");
+ * list.size(); // 2
+ *
+ * @returns {number} The number of items in this list
+ * @since 0.2.0
+ */
+ public size(): number {
+ return this.getAll().length;
+ }
+}
diff --git a/src/text/ListItem.ts b/src/api/text/ListItem.ts
similarity index 50%
rename from src/text/ListItem.ts
rename to src/api/text/ListItem.ts
index 451ad15a..ad9648eb 100644
--- a/src/text/ListItem.ts
+++ b/src/api/text/ListItem.ts
@@ -1,19 +1,26 @@
import { OdfElement } from "../OdfElement";
import { Paragraph } from "./Paragraph";
-import { TextElementName } from "./TextElementName";
/**
* This class represents an item in a list.
*
+ * @example
+ * const list = document.getBody()
+ * .addList()
+ * .addItem("First item");
+ *
* @since 0.2.0
*/
export class ListItem extends OdfElement {
private paragraph: Paragraph;
/**
- * Creates a list item
+ * Creates a `ListItem` instance that represents an item in a list.
+ *
+ * @example
+ * new ListItem("First item");
*
- * @param {string} [text] The text content of the list item
+ * @param {string} [text=""] The text content of the list item; defaults to an empty string if omitted
* @since 0.2.0
*/
public constructor(text?: string) {
@@ -22,12 +29,4 @@ export class ListItem extends OdfElement {
this.paragraph = new Paragraph(text);
this.append(this.paragraph);
}
-
- /** @inheritDoc */
- protected toXml(document: Document, parent: Element): void {
- const listItemElement = document.createElement(TextElementName.TextListItem);
- parent.appendChild(listItemElement);
-
- super.toXml(document, listItemElement);
- }
}
diff --git a/src/api/text/OdfTextElement.ts b/src/api/text/OdfTextElement.ts
new file mode 100644
index 00000000..1dcffb2c
--- /dev/null
+++ b/src/api/text/OdfTextElement.ts
@@ -0,0 +1,39 @@
+import { OdfElement } from "../OdfElement";
+
+/**
+ * This class represents text in a paragraph.
+ *
+ * @since 0.3.0
+ * @private
+ */
+export class OdfTextElement extends OdfElement {
+ /**
+ * Creates a text
+ *
+ * @param {string} text The text content
+ * @since 0.3.0
+ */
+ public constructor(private text: string) {
+ super();
+ }
+
+ /**
+ * Sets the new text content.
+ *
+ * @param {string} text The new text content
+ * @since 0.3.0
+ */
+ public setText(text: string): void {
+ this.text = text;
+ }
+
+ /**
+ * Returns the text content.
+ *
+ * @returns {string} The text content
+ * @since 0.3.0
+ */
+ public getText(): string {
+ return this.text;
+ }
+}
diff --git a/src/api/text/Paragraph.spec.ts b/src/api/text/Paragraph.spec.ts
new file mode 100644
index 00000000..6d6a57d2
--- /dev/null
+++ b/src/api/text/Paragraph.spec.ts
@@ -0,0 +1,94 @@
+import { ParagraphStyle } from "../../style/ParagraphStyle";
+import { TextTransformation } from "../../style/TextTransformation";
+import { Image } from "../draw";
+import { Hyperlink } from "./Hyperlink";
+import { Paragraph } from "./Paragraph";
+
+describe(Paragraph.name, () => {
+ const testText = "some text";
+
+ let paragraph: Paragraph;
+
+ beforeEach(() => {
+ paragraph = new Paragraph(testText);
+ });
+
+ describe("text", () => {
+ it("return initial text", () => {
+ expect(paragraph.getText()).toBe(testText);
+ });
+
+ it("return empty text if initial text is not set", () => {
+ paragraph = new Paragraph();
+
+ expect(paragraph.getText()).toBe("");
+ });
+
+ it("append the text", () => {
+ paragraph.addText(" some more text");
+
+ expect(paragraph.getText()).toEqual("some text some more text");
+ });
+
+ it("replace existing text with specified text", () => {
+ paragraph.setText("some other text");
+
+ expect(paragraph.getText()).toEqual("some other text");
+ });
+
+ it("return the text", () => {
+ paragraph.setText("some text");
+ paragraph.addText(" some\nmore text");
+ paragraph.addHyperlink(" link", "http://example.org/");
+ paragraph.addText(" even more text");
+
+ expect(paragraph.getText()).toEqual("some text some\nmore text link even more text");
+ });
+ });
+
+ describe("hyperlink", () => {
+ it("return a hyperlink", () => {
+ const hyperlink = paragraph.addHyperlink("some linked text", "http://example.org/");
+
+ expect(hyperlink).toBeInstanceOf(Hyperlink);
+ expect(hyperlink.getText()).toEqual("some linked text");
+ expect(hyperlink.getURI()).toEqual("http://example.org/");
+ });
+ });
+
+ describe("image", () => {
+ it("return an image", () => {
+ const image = paragraph.addImage("someImagePath");
+
+ expect(image).toBeInstanceOf(Image);
+ expect(image.getPath()).toEqual("someImagePath");
+ });
+ });
+
+ describe("style", () => {
+ let testStyle: ParagraphStyle;
+
+ beforeEach(() => {
+ testStyle = new ParagraphStyle();
+ });
+
+ it("return undefined by default", () => {
+ expect(paragraph.getStyle()).toBeUndefined();
+ });
+
+ it("return previous set style", () => {
+ testStyle.setTextTransformation(TextTransformation.Uppercase);
+
+ paragraph.setStyle(testStyle);
+
+ expect(paragraph.getStyle()).toBe(testStyle);
+ });
+
+ it("ignore invalid input", () => {
+ paragraph.setStyle(testStyle);
+ paragraph.setStyle(null);
+
+ expect(paragraph.getStyle()).toBe(testStyle);
+ });
+ });
+});
diff --git a/src/api/text/Paragraph.ts b/src/api/text/Paragraph.ts
new file mode 100644
index 00000000..681ce7ce
--- /dev/null
+++ b/src/api/text/Paragraph.ts
@@ -0,0 +1,195 @@
+import { IParagraphStyle } from "../../style/IParagraphStyle";
+import { ParagraphStyle } from "../../style/ParagraphStyle";
+import { Image } from "../draw";
+import { OdfElement } from "../OdfElement";
+import { Hyperlink } from "./Hyperlink";
+import { OdfTextElement } from "./OdfTextElement";
+
+/**
+ * This class represents a paragraph.
+ *
+ * @example
+ * document.getBody().addParagraph("Some text")
+ * .addText("\nEven more text")
+ * .addImage("/home/homer/myself.png");
+ *
+ * @since 0.1.0
+ */
+export class Paragraph extends OdfElement {
+ private style: IParagraphStyle | undefined;
+
+ /**
+ * Creates a `Paragraph` instance.
+ *
+ * @example
+ * new Paragraph("Some text");
+ * new Paragraph();
+ *
+ * @param {string} [text] The text content of the paragraph; defaults to an empty string if omitted
+ * @since 0.1.0
+ */
+ public constructor(text?: string) {
+ super();
+
+ this.addText(text || "");
+ }
+
+ /**
+ * Appends the specified text to the end of the paragraph.
+ *
+ * @example
+ * new Paragraph("Some text") // Some text
+ * .addText("\nEven more text"); // Some text\nEven more text
+ *
+ * @param {string} text The additional text content
+ * @returns {Paragraph} The `Paragraph` object
+ * @since 0.1.0
+ */
+ public addText(text: string): Paragraph {
+ const elements = this.getAll();
+
+ if (elements.length > 0 && elements[elements.length - 1].constructor.name === OdfTextElement.name) {
+ const lastElement = elements[elements.length - 1] as OdfTextElement;
+ lastElement.setText(lastElement.getText() + text);
+ return this;
+ }
+
+ this.append(new OdfTextElement(text));
+
+ return this;
+ }
+
+ /**
+ * Returns the text content of the paragraph.
+ * Note: This will only return the text; other elements and markup will be omitted.
+ *
+ * @example
+ * const paragraph = new Paragraph("Some text, ");
+ * paragraph.addHyperlink("some linked text");
+ * paragraph.addText(", even more text");
+ * paragraph.getText(); // Some text, some linked text, even more text
+ *
+ * @returns {string} The text content of the paragraph
+ * @since 0.1.0
+ */
+ public getText(): string {
+ return this.getAll()
+ .map((value: OdfElement) => {
+ return value instanceof OdfTextElement ? value.getText() : "";
+ })
+ .join("");
+ }
+
+ /**
+ * Sets the text content of the paragraph.
+ * Note: This will replace any existing content of the paragraph.
+ *
+ * @example
+ * new Paragraph("Some text") // Some text
+ * .setText("Some other text"); // Some other text
+ *
+ * @param {string} text The new text content
+ * @returns {Paragraph} The `Paragraph` object
+ * @since 0.1.0
+ */
+ public setText(text: string): Paragraph {
+ this.removeText();
+ this.addText(text || "");
+
+ return this;
+ }
+
+ /**
+ * Appends the specified text as hyperlink to the end of the paragraph.
+ *
+ * @example
+ * new Paragraph("Some text, ") // Some text,
+ * .addHyperlink("some linked text"); // Some text, some linked text
+ *
+ * @param {string} text The text content of the hyperlink
+ * @param {string} uri The target URI of the hyperlink
+ * @returns {Hyperlink} The added `Hyperlink` object
+ * @since 0.3.0
+ */
+ public addHyperlink(text: string, uri: string): Hyperlink {
+ const hyperlink = new Hyperlink(text, uri);
+ this.append(hyperlink);
+
+ return hyperlink;
+ }
+
+ /**
+ * Appends the image of the denoted path to the end of the paragraph.
+ * The current paragraph will be set as anchor for the image.
+ *
+ * @example
+ * new Paragraph("Some text")
+ * .addImage("/home/homer/myself.png");
+ *
+ * @param {string} path The path to the image file
+ * @returns {Image} The added `Image` object
+ * @since 0.3.0
+ */
+ public addImage(path: string): Image {
+ const image = new Image(path);
+ this.append(image);
+
+ return image;
+ }
+
+ /**
+ * Sets the new style of the paragraph.
+ * To reset the style, `undefined` must be given.
+ *
+ * @example
+ * new Paragraph("Some text")
+ * .setStyle(new ParagraphStyle());
+ *
+ * @param {IParagraphStyle | undefined} style The new style or `undefined` to reset the style
+ * @returns {Paragraph} The `Paragraph` object
+ * @since 0.3.0
+ */
+ public setStyle(style: IParagraphStyle | undefined): Paragraph {
+ if (style instanceof ParagraphStyle) {
+ this.style = style;
+ }
+
+ return this;
+ }
+
+ /**
+ * Returns the style of the paragraph.
+ *
+ * @example
+ * const paragraph = new Paragraph("Some text");
+ * paragraph.getStyle(); // undefined
+ * paragraph.setStyle(new ParagraphStyle());
+ * paragraph.getStyle(); // previously set style
+ *
+ * @returns {IParagraphStyle | undefined} The style of the paragraph or `undefined` if no style was set
+ * @since 0.3.0
+ */
+ public getStyle(): IParagraphStyle | undefined {
+ return this.style;
+ }
+
+ /**
+ * Removes the text content of the paragraph.
+ *
+ * @example
+ * new Paragraph("Some text") // Some text
+ * .removeText(); // ""
+ *
+ * @returns {Paragraph} The `Paragraph` object
+ * @private
+ */
+ private removeText(): Paragraph {
+ const elements = this.getAll();
+
+ for (let index = elements.length - 1; index >= 0; index--) {
+ this.removeAt(index);
+ }
+
+ return this;
+ }
+}
diff --git a/src/api/text/index.ts b/src/api/text/index.ts
new file mode 100644
index 00000000..4265e7ae
--- /dev/null
+++ b/src/api/text/index.ts
@@ -0,0 +1,6 @@
+export { Heading } from "./Heading";
+export { Hyperlink } from "./Hyperlink";
+export { List } from "./List";
+export { ListItem } from "./ListItem";
+export { Paragraph } from "./Paragraph";
+export { OdfTextElement } from "./OdfTextElement";
diff --git a/src/draw/Image.spec.ts b/src/draw/Image.spec.ts
deleted file mode 100644
index 7e4c9396..00000000
--- a/src/draw/Image.spec.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { join } from "path";
-import { Image } from "./Image";
-import { AnchorType } from "../style/AnchorType";
-import { ImageStyle } from "../style/ImageStyle";
-import { TextDocument } from "../TextDocument";
-
-describe(Image.name, () => {
- let document: TextDocument;
-
- beforeEach(() => {
- document = new TextDocument();
- });
-
- describe("#setStyle", () => {
- it("set text anchor attribute on frame", () => {
- document.addParagraph().addImage(join(__dirname, "..", "..", "test", "data", "ODF.png"));
-
- expect(document.toString()).toMatch(//);
- });
- });
-
- describe("#getStyle", () => {
- let image: Image;
-
- beforeEach(() => {
- image = new Image("somePath");
- });
-
- it("return style by default", () => {
- expect(image.getStyle()).toBeInstanceOf(ImageStyle);
- });
-
- it("return previous set style", () => {
- const testStyle = new ImageStyle();
- testStyle.setAnchorType(AnchorType.AsChar);
-
- image.setStyle(testStyle);
-
- expect(image.getStyle()).toBe(testStyle);
- });
- });
-
- describe("#toXml", () => {
- beforeEach(() => {
- document.addParagraph().addImage(join(__dirname, "..", "..", "test", "data", "ODF.png"));
- });
-
- it("append a draw frame with image and base64 encoded image", () => {
- const regex = new RegExp(""
- + ""
- + ""
- + ".*"
- + ""
- + ""
- + "");
- expect(document.toString()).toMatch(regex);
- });
- });
-});
diff --git a/src/draw/Image.ts b/src/draw/Image.ts
deleted file mode 100644
index 0bcc0826..00000000
--- a/src/draw/Image.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { readFileSync } from "fs";
-import { OdfElement } from "../OdfElement";
-import { OdfElementName } from "../OdfElementName";
-import { IImageStyle } from "../style/IImageStyle";
-import { ImageStyle } from "../style/ImageStyle";
-import { DrawElementName } from "./DrawElementName";
-
-const ENCODING = "base64";
-
-/**
- * This class represents an image in a paragraph.
- *
- * @since 0.3.0
- */
-export class Image extends OdfElement {
- private style: IImageStyle;
-
- /**
- * Creates an image
- *
- * @param {string} path Path to the image file that should be embedded
- * @since 0.3.0
- */
- public constructor(private path: string) {
- super();
-
- this.style = new ImageStyle();
- }
-
- /**
- * Sets the new style of this image.
- *
- * @param {IImageStyle} style The new style
- * @since 0.5.0
- */
- public setStyle(style: IImageStyle): void {
- this.style = style;
- }
-
- /**
- * Returns the style of this image.
- *
- * @returns {IImageStyle} The style of the image
- * @since 0.5.0
- */
- public getStyle(): IImageStyle {
- return this.style;
- }
-
- /** @inheritDoc */
- protected toXml(document: Document, parent: Element): void {
- const frameElement = document.createElement(DrawElementName.DrawFrame);
- parent.appendChild(frameElement);
-
- this.embedImage(document, frameElement);
-
- this.style.toXml(frameElement);
-
- super.toXml(document, frameElement);
- }
-
- /**
- * Creates the image element and embeds the denoted image base64 encoded binary data.
- *
- * @param {Document} document The XML document
- * @param {Element} frameElement The parent node in the DOM (`draw:frame`)
- * @private
- */
- private embedImage(document: Document, frameElement: Element): void {
- const image = document.createElement(DrawElementName.DrawImage);
- frameElement.appendChild(image);
-
- const binaryData = document.createElement(OdfElementName.OfficeBinaryData);
- image.appendChild(binaryData);
-
- const rawImage = readFileSync(this.path);
- const base64Image = rawImage.toString(ENCODING);
- const textNode = document.createTextNode(base64Image);
- binaryData.appendChild(textNode);
- }
-}
diff --git a/src/index.ts b/src/index.ts
index 0b3fab98..26b0d7a7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,15 +1,20 @@
-export { TextDocument } from "./TextDocument";
-
// draw
-export { Image } from "./draw/Image";
+export { Image } from "./api/draw/Image";
// meta
-export { Meta } from "./meta/Meta";
+export { Meta } from "./api/meta/Meta";
+
+// office
+export { TextBody } from "./api/office/TextBody";
+export { TextDocument } from "./api/office/TextDocument";
// style
+export { FontFace } from "./api/style/FontFace";
+export { FontPitch } from "./api/style/FontPitch";
+
+// style (legacy)
export { AnchorType } from "./style/AnchorType";
export { Color } from "./style/Color";
-export { FontPitch } from "./style/FontPitch";
export { HorizontalAlignment } from "./style/HorizontalAlignment";
export { IImageStyle } from "./style/IImageStyle";
export { ImageStyle } from "./style/ImageStyle";
@@ -21,8 +26,8 @@ export { TextTransformation } from "./style/TextTransformation";
export { Typeface } from "./style/Typeface";
// text
-export { Heading } from "./text/Heading";
-export { Hyperlink } from "./text/HyperLink";
-export { List } from "./text/List";
-export { ListItem } from "./text/ListItem";
-export { Paragraph } from "./text/Paragraph";
+export { Heading } from "./api/text/Heading";
+export { Hyperlink } from "./api/text/Hyperlink";
+export { List } from "./api/text/List";
+export { ListItem } from "./api/text/ListItem";
+export { Paragraph } from "./api/text/Paragraph";
diff --git a/src/style/Color.ts b/src/style/Color.ts
index 5fb94fef..3da9f786 100644
--- a/src/style/Color.ts
+++ b/src/style/Color.ts
@@ -16,7 +16,7 @@ export class Color {
public static fromHex(value: string): Color | never {
const matches = value.match(/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/);
if (matches === null) {
- throw new Error('Invalid color value');
+ throw new Error("Invalid color value");
}
return new Color(parseInt(matches[1], 16), parseInt(matches[2], 16), parseInt(matches[3], 16));
@@ -36,7 +36,7 @@ export class Color {
if (Color.checkRange(red) && Color.checkRange(green) && Color.checkRange(blue)) {
return new Color(red, green, blue);
}
- throw new Error('Invalid value for a color channel');
+ throw new Error("Invalid value for a color channel");
}
/**
diff --git a/src/style/ImageStyle.spec.ts b/src/style/ImageStyle.spec.ts
index 9139bb73..04d48bad 100644
--- a/src/style/ImageStyle.spec.ts
+++ b/src/style/ImageStyle.spec.ts
@@ -1,8 +1,8 @@
import { join } from "path";
-import { Image } from "../draw/Image";
+import { Image } from "../api/draw";
+import { TextDocument } from "../api/office";
import { AnchorType } from "./AnchorType";
import { ImageStyle } from "./ImageStyle";
-import { TextDocument } from "../TextDocument";
describe(ImageStyle.name, () => {
let document: TextDocument;
@@ -78,7 +78,7 @@ describe(ImageStyle.name, () => {
let image: Image;
beforeEach(() => {
- image = document.addParagraph().addImage(join(__dirname, "..", "..", "test", "data", "ODF.png"));
+ image = document.getBody().addParagraph().addImage(join(__dirname, "..", "..", "test", "data", "ODF.png"));
});
it("set the anchor type", () => {
diff --git a/src/style/ImageStyle.ts b/src/style/ImageStyle.ts
index 07de5a41..d720036b 100644
--- a/src/style/ImageStyle.ts
+++ b/src/style/ImageStyle.ts
@@ -1,4 +1,4 @@
-import { OdfAttributeName } from "../OdfAttributeName";
+import { OdfAttributeName } from "../xml/OdfAttributeName";
import { AnchorType } from "./AnchorType";
import { IImageStyle } from "./IImageStyle";
diff --git a/src/style/ParagraphProperties.spec.ts b/src/style/ParagraphProperties.spec.ts
index 7fff9312..6d9ad5a1 100644
--- a/src/style/ParagraphProperties.spec.ts
+++ b/src/style/ParagraphProperties.spec.ts
@@ -1,10 +1,10 @@
+import { TextDocument } from "../api/office";
+import { Paragraph } from "../api/text";
import { HorizontalAlignment } from "./HorizontalAlignment";
import { ParagraphProperties } from "./ParagraphProperties";
import { ParagraphStyle } from "./ParagraphStyle";
import { TabStop } from "./TabStop";
import { TabStopType } from "./TabStopType";
-import { Paragraph } from "../text/Paragraph";
-import { TextDocument } from "../TextDocument";
describe(ParagraphProperties.name, () => {
let properties: ParagraphProperties;
@@ -16,7 +16,7 @@ describe(ParagraphProperties.name, () => {
properties = new ParagraphProperties();
document = new TextDocument();
- paragraph = document.addParagraph();
+ paragraph = document.getBody().addParagraph();
testStyle = new ParagraphStyle();
});
diff --git a/src/style/ParagraphProperties.ts b/src/style/ParagraphProperties.ts
index f40c8fc8..a0337cdf 100644
--- a/src/style/ParagraphProperties.ts
+++ b/src/style/ParagraphProperties.ts
@@ -1,5 +1,5 @@
-import { OdfAttributeName } from "../OdfAttributeName";
-import { OdfElementName } from "../OdfElementName";
+import { OdfAttributeName } from "../xml/OdfAttributeName";
+import { OdfElementName } from "../xml/OdfElementName";
import { HorizontalAlignment } from "./HorizontalAlignment";
import { IParagraphProperties } from "./IParagraphProperties";
import { TabStop } from "./TabStop";
diff --git a/src/style/ParagraphStyle.spec.ts b/src/style/ParagraphStyle.spec.ts
index 56581630..2d247092 100644
--- a/src/style/ParagraphStyle.spec.ts
+++ b/src/style/ParagraphStyle.spec.ts
@@ -1,6 +1,6 @@
+import { TextDocument } from "../api/office";
+import { Paragraph } from "../api/text";
import { ParagraphStyle } from "./ParagraphStyle";
-import { Paragraph } from "../text/Paragraph";
-import { TextDocument } from "../TextDocument";
describe(ParagraphStyle.name, () => {
let document: TextDocument;
@@ -9,7 +9,7 @@ describe(ParagraphStyle.name, () => {
beforeEach(() => {
document = new TextDocument();
- paragraph = document.addParagraph("test");
+ paragraph = document.getBody().addParagraph("test");
testStyle = new ParagraphStyle();
});
@@ -40,7 +40,7 @@ describe(ParagraphStyle.name, () => {
testStyle.setPageBreakBefore();
paragraph.setStyle(testStyle);
- document.addParagraph().setStyle(testStyle);
+ document.getBody().addParagraph().setStyle(testStyle);
/* tslint:disable-next-line:max-line-length */
expect(document.toString()).toMatch(/<\/style:style><\/office:automatic-styles>/);
diff --git a/src/style/ParagraphStyle.ts b/src/style/ParagraphStyle.ts
index e694af64..1a10ef50 100644
--- a/src/style/ParagraphStyle.ts
+++ b/src/style/ParagraphStyle.ts
@@ -1,6 +1,6 @@
import { createHash } from "crypto";
-import { OdfAttributeName } from "../OdfAttributeName";
-import { OdfElementName } from "../OdfElementName";
+import { OdfAttributeName } from "../xml/OdfAttributeName";
+import { OdfElementName } from "../xml/OdfElementName";
import { Color } from "./Color";
import { HorizontalAlignment } from "./HorizontalAlignment";
import { IParagraphStyle } from "./IParagraphStyle";
diff --git a/src/style/StyleHelper.ts b/src/style/StyleHelper.ts
index 006f82ec..c4b48a36 100644
--- a/src/style/StyleHelper.ts
+++ b/src/style/StyleHelper.ts
@@ -1,4 +1,4 @@
-import { OdfElementName } from "../OdfElementName";
+import { OdfElementName } from "../xml/OdfElementName";
/**
* Utility class for dealing with styles.
diff --git a/src/style/TabStop.spec.ts b/src/style/TabStop.spec.ts
index d8ec2eb8..85e9abfa 100644
--- a/src/style/TabStop.spec.ts
+++ b/src/style/TabStop.spec.ts
@@ -1,7 +1,7 @@
+import { TextDocument } from "../api/office";
import { ParagraphStyle } from "./ParagraphStyle";
import { TabStop } from "./TabStop";
import { TabStopType } from "./TabStopType";
-import { TextDocument } from "../TextDocument";
describe(TabStop.name, () => {
describe("#constructor", () => {
@@ -87,7 +87,7 @@ describe(TabStop.name, () => {
describe("#toXml", () => {
it("return the current position", () => {
const document = new TextDocument();
- const paragraph = document.addParagraph();
+ const paragraph = document.getBody().addParagraph();
const style = new ParagraphStyle();
style.addTabStop(new TabStop(2, TabStopType.Center));
diff --git a/src/style/TabStop.ts b/src/style/TabStop.ts
index 5fc416e2..583cf158 100644
--- a/src/style/TabStop.ts
+++ b/src/style/TabStop.ts
@@ -1,5 +1,5 @@
-import { OdfAttributeName } from "../OdfAttributeName";
-import { OdfElementName } from "../OdfElementName";
+import { OdfAttributeName } from "../xml/OdfAttributeName";
+import { OdfElementName } from "../xml/OdfElementName";
import { TabStopType } from "./TabStopType";
/**
diff --git a/src/style/TextProperties.spec.ts b/src/style/TextProperties.spec.ts
index 92164038..063a2067 100644
--- a/src/style/TextProperties.spec.ts
+++ b/src/style/TextProperties.spec.ts
@@ -1,10 +1,10 @@
+import { TextDocument } from "../api/office";
+import { Paragraph } from "../api/text";
import { Color } from "./Color";
import { ParagraphStyle } from "./ParagraphStyle";
import { TextProperties } from "./TextProperties";
import { TextTransformation } from "./TextTransformation";
import { Typeface } from "./Typeface";
-import { Paragraph } from "../text/Paragraph";
-import { TextDocument } from "../TextDocument";
describe(TextProperties.name, () => {
let properties: TextProperties;
@@ -16,7 +16,7 @@ describe(TextProperties.name, () => {
properties = new TextProperties();
document = new TextDocument();
- paragraph = document.addParagraph("test");
+ paragraph = document.getBody().addParagraph("test");
testStyle = new ParagraphStyle();
});
diff --git a/src/style/TextProperties.ts b/src/style/TextProperties.ts
index 77d85e02..294ebef5 100644
--- a/src/style/TextProperties.ts
+++ b/src/style/TextProperties.ts
@@ -1,5 +1,5 @@
-import { OdfAttributeName } from "../OdfAttributeName";
-import { OdfElementName } from "../OdfElementName";
+import { OdfAttributeName } from "../xml/OdfAttributeName";
+import { OdfElementName } from "../xml/OdfElementName";
import { Color } from "./Color";
import { ITextProperties } from "./ITextProperties";
import { TextTransformation } from "./TextTransformation";
diff --git a/src/text/Heading.spec.ts b/src/text/Heading.spec.ts
deleted file mode 100644
index d339f4d3..00000000
--- a/src/text/Heading.spec.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { Heading } from "./Heading";
-import { TextDocument } from "../TextDocument";
-
-describe(Heading.name, () => {
- let document: TextDocument;
- let heading: Heading;
-
- beforeEach(() => {
- document = new TextDocument();
- });
-
- describe("#addHeading", () => {
- it("insert an empty heading with default level 1", () => {
- document.addHeading();
-
- const documentAsString = document.toString();
- expect(documentAsString).toMatch(//);
- });
-
- it("insert a heading with given text and default level 1", () => {
- document.addHeading("heading");
-
- const documentAsString = document.toString();
- expect(documentAsString).toMatch(/heading<\/text:h>/);
- });
-
- it("insert a heading with given text and given level", () => {
- document.addHeading("heading", 2);
-
- const documentAsString = document.toString();
- expect(documentAsString).toMatch(/heading<\/text:h>/);
- });
- });
-
- describe("#setLevel", () => {
- beforeEach(() => {
- heading = document.addHeading("Heading", 2);
- });
-
- it("change the current level to the given value", () => {
- heading.setLevel(3);
- const headingLevel = heading.getLevel();
-
- expect(headingLevel).toBe(3);
- });
-
- it("change the current level to the default value, if the given value is invalid", () => {
- heading.setLevel(-2);
- const headingLevel = heading.getLevel();
-
- expect(headingLevel).toBe(Heading.DEFAULT_LEVEL);
- });
- });
-
- describe("#getLevel", () => {
- beforeEach(() => {
- heading = document.addHeading("heading", 2);
- });
-
- it("return the current level", () => {
- const headingLevel = heading.getLevel();
-
- expect(headingLevel).toBe(2);
- });
- });
-});
diff --git a/src/text/Heading.ts b/src/text/Heading.ts
deleted file mode 100644
index 5246d7dc..00000000
--- a/src/text/Heading.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { OdfAttributeName } from "../OdfAttributeName";
-import { Paragraph } from "./Paragraph";
-import { TextElementName } from "./TextElementName";
-
-/**
- * This class represents a heading.
- *
- * @since 0.1.0
- */
-export class Heading extends Paragraph {
- public static DEFAULT_LEVEL = 1;
-
- /**
- * Creates a heading
- *
- * @param {string} [text] The text content of the heading
- * @param {number} [level] The heading level; defaults to 1 if omitted
- * @since 0.1.0
- */
- public constructor(text?: string, private level = Heading.DEFAULT_LEVEL) {
- super(text);
-
- this.setLevel(level);
- }
-
- /**
- * Sets the level of this heading.
- *
- * @param {number} level The heading level
- * @since 0.1.0
- */
- public setLevel(level: number): void {
- this.level = level > Heading.DEFAULT_LEVEL ? level : Heading.DEFAULT_LEVEL;
- }
-
- /**
- * Returns the level of this heading.
- *
- * @returns {number} The heading level
- * @since 0.1.0
- */
- public getLevel(): number {
- return this.level;
- }
-
- /** @inheritDoc */
- protected createElement(document: Document): Element {
- const heading = document.createElement(TextElementName.TextHeading);
- heading.setAttribute(OdfAttributeName.TextOutlineLevel, this.level.toString(10));
-
- return heading;
- }
-}
diff --git a/src/text/HyperLink.ts b/src/text/HyperLink.ts
deleted file mode 100644
index 67d0d4e1..00000000
--- a/src/text/HyperLink.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { OdfAttributeName } from "../OdfAttributeName";
-import { OdfTextElement } from "./OdfTextElement";
-import { TextElementName } from "./TextElementName";
-
-const LINK_TYPE = "simple";
-
-/**
- * This class represents a hyperlink in a paragraph.
- *
- * @since 0.3.0
- */
-export class Hyperlink extends OdfTextElement {
- /**
- * Creates a hyperlink
- *
- * @param {string} text The text content of the hyperlink
- * @param {string} uri The target URI of the hyperlink
- * @since 0.3.0
- */
- public constructor(text: string, private uri: string) {
- super(text);
- }
-
- /**
- * Sets the target URI for this hyperlink.
- *
- * @param {string} uri The new target URI
- * @since 0.3.0
- */
- public setURI(uri: string): void {
- this.uri = uri;
- }
-
- /**
- * Returns the target URI of this hyperlink.
- *
- * @returns {string} The target URI
- * @since 0.3.0
- */
- public getURI(): string {
- return this.uri;
- }
-
- /** @inheritDoc */
- protected toXml(document: Document, parent: Element): void {
- const text = this.getText();
-
- if (text === undefined || text === "") {
- return;
- }
-
- if (this.uri === undefined || this.uri === "") {
- return super.toXml(document, parent);
- }
-
- const hyperlink = document.createElement(TextElementName.TextHyperlink);
- parent.appendChild(hyperlink);
- hyperlink.setAttribute(OdfAttributeName.XlinkType, LINK_TYPE);
- hyperlink.setAttribute(OdfAttributeName.XlinkHref, this.uri);
-
- const textNode = document.createTextNode(text);
- hyperlink.appendChild(textNode);
- }
-}
diff --git a/src/text/Hyperlink.spec.ts b/src/text/Hyperlink.spec.ts
deleted file mode 100644
index 9bbdd4a6..00000000
--- a/src/text/Hyperlink.spec.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { Hyperlink } from "./HyperLink";
-import { TextDocument } from "../TextDocument";
-
-describe(Hyperlink.name, () => {
- const testText = "some text";
- const testUri = "http://example.org/";
-
- let document: TextDocument;
-
- beforeEach(() => {
- document = new TextDocument();
- });
-
- describe("#addHyperlink", () => {
- it("append a linked text", () => {
- document.addParagraph(testText).addHyperlink(" some linked text", testUri);
-
- const documentAsString = document.toString();
- /* tslint:disable-next-line:max-line-length */
- expect(documentAsString).toMatch(/some text some linked text<\/text:a><\/text:p>/);
- });
-
- it("not create a hyperlink if text is empty", () => {
- document.addParagraph(testText).addHyperlink("", testUri);
-
- const documentAsString = document.toString();
- expect(documentAsString).toMatch(/some text<\/text:p>/);
- });
-
- it("not create a hyperlink but add the text if URI is empty", () => {
- document.addParagraph(testText).addHyperlink(" some linked text", "");
-
- const documentAsString = document.toString();
- expect(documentAsString).toMatch(/some text some linked text<\/text:p>/);
- });
- });
-
- describe("#setURI", () => {
- let hyperlink: Hyperlink;
-
- beforeEach(() => {
- hyperlink = document.addParagraph().addHyperlink(testText, testUri);
- });
-
- it("change the current URI to the given value", () => {
- hyperlink.setURI("localhost");
-
- expect(hyperlink.getURI()).toBe("localhost");
- });
- });
-
- describe("#getURI", () => {
- let hyperlink: Hyperlink;
-
- beforeEach(() => {
- hyperlink = document.addParagraph().addHyperlink(testText, testUri);
- });
-
- it("return the current URI", () => {
- expect(hyperlink.getURI()).toBe(testUri);
- });
- });
-});
diff --git a/src/text/List.ts b/src/text/List.ts
deleted file mode 100644
index a742778d..00000000
--- a/src/text/List.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import { OdfElement } from "../OdfElement";
-import { ListItem } from "./ListItem";
-import { TextElementName } from "./TextElementName";
-
-/**
- * This class represents a list.
- * It can contain multiple list items.
- *
- * @since 0.2.0
- */
-export class List extends OdfElement {
- /**
- * Creates a list
- *
- * @since 0.2.0
- */
- public constructor() {
- super();
- }
-
- /**
- * Adds a new list item with the specified text or adds the specified item to the list.
- *
- * @param {string | ListItem} [item] The text content of the new item or the item to add
- * @returns {ListItem} The newly added list item
- * @since 0.2.0
- */
- public addItem(item?: string | ListItem): ListItem {
- if (item instanceof ListItem) {
- this.append(item);
- return item;
- }
-
- const listItem = new ListItem(item);
- this.append(listItem);
-
- return listItem;
- }
-
- /**
- * Inserts a new list item with the specified text or inserts the specified item at the specified position.
- * The item is inserted before the item at the specified position.
- *
- * @param {number} position The index at which to insert the list item (starting from 0).
- * @param {string | ListItem} item The text content of the new item or the item to insert
- * @returns {ListItem} The newly added list item
- * @since 0.2.0
- */
- public insertItem(position: number, item: string | ListItem): ListItem {
- if (item instanceof ListItem) {
- this.insert(position, item);
- return item;
- }
-
- const listItem = new ListItem(item);
- this.insert(position, listItem);
-
- return listItem;
- }
-
- /**
- * Returns the item at the specified position in this list.
- * If an invalid position is given, undefined is returned.
- *
- * @param {number} position The index of the requested the list item (starting from 0).
- * @returns {ListItem | undefined} The list item at the specified position
- * or undefined if there is no list item at the specified position
- * @since 0.2.0
- */
- public getItem(position: number): ListItem | undefined {
- return this.get(position) as ListItem;
- }
-
- /**
- * Returns all list items.
- *
- * @returns {ListItem[]} A copy of the list of list items
- * @since 0.2.0
- */
- public getItems(): ListItem[] {
- return this.getAll() as ListItem[];
- }
-
- /**
- * Removes the list item from the specified position.
- *
- * @param {number} position The index of the list item to remove (starting from 0).
- * @returns {ListItem | undefined} The removed list item
- * or undefined if there is no list item at the specified position
- * @since 0.2.0
- */
- public removeItemAt(position: number): ListItem | undefined {
- return this.removeAt(position) as ListItem;
- }
-
- /**
- * Removes all items from this list.
- *
- * @since 0.2.0
- */
- public clear(): void {
- let removedElement;
-
- do {
- removedElement = this.removeAt(0);
- } while (removedElement !== undefined);
- }
-
- /**
- * Returns the number of items in this list.
- *
- * @returns {number} The number of items in this list
- * @since 0.2.0
- */
- public size(): number {
- return this.getAll().length;
- }
-
- /** @inheritDoc */
- protected toXml(document: Document, parent: Element): void {
- if (this.hasChildren() === false) {
- return;
- }
-
- const listElement = document.createElement(TextElementName.TextList);
- parent.appendChild(listElement);
-
- super.toXml(document, listElement);
- }
-}
diff --git a/src/text/Paragraph.spec.ts b/src/text/Paragraph.spec.ts
deleted file mode 100644
index 0df5da81..00000000
--- a/src/text/Paragraph.spec.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-import { join } from "path";
-import { ParagraphStyle } from "../style/ParagraphStyle";
-import { Paragraph } from "./Paragraph";
-import { TextDocument } from "../TextDocument";
-
-describe(Paragraph.name, () => {
- let document: TextDocument;
-
- beforeEach(() => {
- document = new TextDocument();
- });
-
- describe("#addParagraph", () => {
- it("insert an empty paragraph", () => {
- document.addParagraph();
-
- expect(document.toString()).toMatch(//);
- });
-
- it("insert a paragraph with specified text", () => {
- document.addParagraph("some text");
-
- expect(document.toString()).toMatch(/some text<\/text:p>/);
- });
- });
-
- describe("#addText", () => {
- it("set the text if element is empty", () => {
- document.addParagraph().addText("some text");
-
- expect(document.toString()).toMatch(/some text<\/text:p>/);
- });
-
- it("append the text", () => {
- document.addParagraph("some text").addText(" some more text");
-
- expect(document.toString()).toMatch(/some text some more text<\/text:p>/);
- });
- });
-
- describe("#getText", () => {
- it("return the text", () => {
- const paragraph = document.addParagraph("some text");
- paragraph.addText(" some\nmore text");
- paragraph.addHyperlink(" link", "http://example.org/");
- paragraph.addText(" even more text");
-
- expect(paragraph.getText()).toEqual("some text some\nmore text link even more text");
- });
- });
-
- describe("#setText", () => {
- it("replace existing text with specified text", () => {
- document.addParagraph("some text").setText("some other text");
-
- expect(document.toString()).toMatch(/some other text<\/text:p>/);
- });
- });
-
- it("replace newline with line break", () => {
- document.addParagraph("some text\nsome more text");
-
- expect(document.toString()).toMatch(/some textsome more text<\/text:p>/);
- });
-
- it("replace tab with tabulation", () => {
- document.addParagraph("some\ttabbed\t\ttext");
-
- expect(document.toString()).toMatch(/sometabbedtext<\/text:p>/);
- });
-
- it("replace sequence of spaces with space node", () => {
- document.addParagraph(" some spacey text ");
-
- /* tslint:disable-next-line:max-line-length */
- expect(document.toString()).toMatch(/ some spacey text <\/text:p>/);
- });
-
- it("ignore carriage return character", () => {
- document.addParagraph("some text\r\nsome\r more text");
-
- expect(document.toString()).toMatch(/some textsome more text<\/text:p>/);
- });
-
- describe("#addHyperlink", () => {
- it("append a linked text", () => {
- document.addParagraph("some text").addHyperlink(" some linked text", "http://example.org/");
-
- /* tslint:disable-next-line:max-line-length */
- expect(document.toString()).toMatch(/some text some linked text<\/text:a><\/text:p>/);
- });
- });
-
- describe("#addImage", () => {
- it("append a draw frame with image and binary data", () => {
- document.addParagraph().addImage(join(__dirname, "..", "..", "test", "data", "ODF.png"));
-
- const regex = new RegExp(""
- + ""
- + ""
- + ".*"
- + ""
- + ""
- + "");
- expect(document.toString()).toMatch(regex);
- });
- });
-
- describe("#setStyle", () => {
- let paragraph: Paragraph;
- let testStyle: ParagraphStyle;
-
- beforeEach(() => {
- paragraph = document.addParagraph("some text");
- testStyle = new ParagraphStyle();
- });
-
- it("set style-name attribute on paragraph if any style property was set", () => {
- testStyle.setPageBreakBefore();
- paragraph.setStyle(testStyle);
-
- expect(document.toString()).toMatch(/some text<\/text:p>/);
- });
-
- it("not set style-name attribute if default style is set", () => {
- paragraph.setStyle(testStyle);
-
- expect(document.toString()).toMatch(/some text<\/text:p>/);
- });
- });
-
- describe("#getStyle", () => {
- let paragraph: Paragraph;
-
- beforeEach(() => {
- paragraph = document.addParagraph("some text");
- });
-
- it("return undefined if no style was set", () => {
- expect(paragraph.getStyle()).toBeUndefined();
- });
-
- it("return previous set style", () => {
- const testStyle = new ParagraphStyle();
- testStyle.setPageBreakBefore();
-
- paragraph.setStyle(testStyle);
-
- expect(paragraph.getStyle()).toBe(testStyle);
- });
- });
-});
diff --git a/src/text/Paragraph.ts b/src/text/Paragraph.ts
deleted file mode 100644
index fbe9717d..00000000
--- a/src/text/Paragraph.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import { Image } from "../draw/Image";
-import { OdfElement } from "../OdfElement";
-import { IParagraphStyle } from "../style/IParagraphStyle";
-import { Hyperlink } from "./HyperLink";
-import { OdfTextElement } from "./OdfTextElement";
-import { TextElementName } from "./TextElementName";
-
-/**
- * This class represents a paragraph.
- *
- * @since 0.1.0
- */
-export class Paragraph extends OdfElement {
- private style: IParagraphStyle | undefined;
-
- /**
- * Creates a paragraph
- *
- * @param {string} [text] The text content of the paragraph
- * @since 0.1.0
- */
- public constructor(text?: string) {
- super();
-
- this.addText(text || "");
- }
-
- /**
- * Appends the specified text to the end of this paragraph.
- *
- * @param {string} text The additional text content
- * @since 0.1.0
- */
- public addText(text: string): void {
- const elements = this.getAll();
-
- if (elements.length > 0 && elements[elements.length - 1].constructor.name === OdfTextElement.name) {
- const lastElement = elements[elements.length - 1] as OdfTextElement;
- lastElement.setText(lastElement.getText() + text);
- return;
- }
-
- this.append(new OdfTextElement(text));
- }
-
- /**
- * Returns the text content of this paragraph.
- * Note: This will only return the text; other elements and markup will be omitted.
- *
- * @returns {string} The text content of this paragraph
- * @since 0.1.0
- */
- public getText(): string {
- return this.getAll()
- .map((value: OdfElement) => {
- return value instanceof OdfTextElement ? value.getText() : "";
- })
- .join("");
- }
-
- /**
- * Sets the text content of this paragraph.
- * Note: This will replace any existing content of the paragraph.
- *
- * @param {string} text The new text content
- * @since 0.1.0
- */
- public setText(text: string): void {
- this.removeText();
- this.addText(text || "");
- }
-
- /**
- * Appends the specified text as hyperlink to the end of this paragraph.
- *
- * @param {string} text The text content of the hyperlink
- * @param {string} uri The target URI of the hyperlink
- * @returns {Hyperlink} The newly added hyperlink
- * @since 0.3.0
- */
- public addHyperlink(text: string, uri: string): Hyperlink {
- const hyperlink = new Hyperlink(text, uri);
- this.append(hyperlink);
-
- return hyperlink;
- }
-
- /**
- * Appends the image of the denoted path to the end of this paragraph.
- * The current paragraph will be set as anchor for the image.
- *
- * @param {string} path The path to the image file
- * @returns {Image} The newly added image
- * @since 0.3.0
- */
- public addImage(path: string): Image {
- const image = new Image(path);
- this.append(image);
-
- return image;
- }
-
- /**
- * Sets the new style of this paragraph.
- * To reset the style, `undefined` must be given.
- *
- * @param {IParagraphStyle | undefined} style The new style or `undefined` to reset the style
- * @since 0.3.0
- */
- public setStyle(style: IParagraphStyle | undefined): void {
- this.style = style;
- }
-
- /**
- * Returns the style of this paragraph.
- *
- * @returns {IParagraphStyle | undefined} The style of the paragraph or `undefined` if no style was set
- * @since 0.3.0
- */
- public getStyle(): IParagraphStyle | undefined {
- return this.style;
- }
-
- /**
- * Creates the paragraph element.
- *
- * @param {Document} document The XML document
- * @returns {Element} The DOM element representing this paragraph
- * @since 0.1.0
- */
- protected createElement(document: Document): Element {
- return document.createElement(TextElementName.TextParagraph);
- }
-
- /** @inheritDoc */
- protected toXml(document: Document, parent: Element): void {
- const paragraph = this.createElement(document);
- parent.appendChild(paragraph);
-
- if (this.style !== undefined) {
- this.style.toXml(document, paragraph);
- }
-
- super.toXml(document, paragraph);
- }
-
- /**
- * Removes the text content of this paragraph.
- * @private
- */
- private removeText(): void {
- const elements = this.getAll();
-
- for (let index = elements.length - 1; index >= 0; index--) {
- this.removeAt(index);
- }
- }
-}
diff --git a/src/xml/DomVisitor.spec.ts b/src/xml/DomVisitor.spec.ts
new file mode 100644
index 00000000..7c124a14
--- /dev/null
+++ b/src/xml/DomVisitor.spec.ts
@@ -0,0 +1,185 @@
+/* tslint:disable:max-line-length */
+import { join } from "path";
+import { DOMImplementation, XMLSerializer } from "xmldom";
+import { Image } from "../api/draw";
+import { Heading, Hyperlink, List, Paragraph } from "../api/text";
+import { ParagraphStyle } from "../style/ParagraphStyle";
+import { DomVisitor } from "./DomVisitor";
+import { OdfElementName } from "./OdfElementName";
+
+fdescribe(DomVisitor.name, () => {
+ describe("#visit", () => {
+ const testText = "some text";
+
+ let domVisitor: DomVisitor;
+ let testDocument: Document;
+ let testRoot: Element;
+
+ beforeEach(() => {
+ testDocument = new DOMImplementation().createDocument("someNameSpace", OdfElementName.OfficeDocument, null);
+ testRoot = testDocument.firstChild as Element;
+
+ domVisitor = new DomVisitor();
+ });
+
+ describe("#visitHeading", () => {
+ let heading: Heading;
+
+ beforeEach(() => {
+ heading = new Heading(testText, 2);
+ });
+
+ it("add a heading with level 2 and the text", () => {
+ domVisitor.visit(heading, testDocument, testRoot);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ expect(documentAsString).toMatch(/some text<\/text:h>/);
+ });
+
+ it("call `toXml` on a style if a style is set", () => {
+ const testStyle = new ParagraphStyle();
+ const styleToXmlSpy = jest.spyOn(testStyle, "toXml");
+
+ heading.setStyle(testStyle);
+
+ domVisitor.visit(heading, testDocument, testRoot);
+
+ expect(styleToXmlSpy).toHaveBeenCalledWith(testDocument, expect.any(Object));
+ });
+ });
+
+ describe("#visitHyperlink", () => {
+ let hyperlink: Hyperlink;
+
+ beforeEach(() => {
+ hyperlink = new Hyperlink(testText, "http://example.org/");
+ });
+
+ it("add a linked text", () => {
+ domVisitor.visit(hyperlink, testDocument, testRoot);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ expect(documentAsString).toMatch(/some text<\/text:a>/);
+ });
+ });
+
+ describe("#visitImage", () => {
+ let image: Image;
+
+ beforeEach(() => {
+ image = new Image(join(__dirname, "..", "..", "test", "data", "ODF.png"));
+ });
+
+ it("add a draw frame with image and base64 encoded image", () => {
+ domVisitor.visit(image, testDocument, testRoot);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ expect(documentAsString).toMatch(/.*<\/office:binary-data><\/draw:image><\/draw:frame>/);
+ });
+ });
+
+ describe("#visitList", () => {
+ let list: List;
+
+ beforeEach(() => {
+ list = new List();
+ });
+
+ it("NOT insert an empty list", () => {
+ domVisitor.visit(list, testDocument, testRoot);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ expect(documentAsString).not.toMatch(/ {
+ list.addItem("first");
+
+ domVisitor.visit(list, testDocument, testRoot);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ expect(documentAsString).toMatch(/first<\/text:p><\/text:list-item><\/text:list>/);
+ });
+ });
+
+ describe("visitOdfText", () => {
+ let paragraph: Paragraph;
+
+ beforeEach(() => {
+ paragraph = new Paragraph();
+ });
+
+ it("replace newline with line break", () => {
+ paragraph.setText("some text\nsome more text");
+
+ domVisitor.visit(paragraph, testDocument, testRoot);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ expect(documentAsString).toMatch(/some textsome more text<\/text:p>/);
+ });
+
+ it("replace tab with tabulation", () => {
+ paragraph.setText("some\ttabbed\t\ttext");
+
+ domVisitor.visit(paragraph, testDocument, testRoot);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ expect(documentAsString).toMatch(/sometabbedtext<\/text:p>/);
+ });
+
+ it("replace sequence of spaces with space node", () => {
+ paragraph.setText(" some spacey text ");
+
+ domVisitor.visit(paragraph, testDocument, testRoot);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ /* tslint:disable-next-line:max-line-length */
+ expect(documentAsString).toMatch(/ some spacey text <\/text:p>/);
+ });
+
+ it("ignore carriage return character", () => {
+ paragraph.setText("some text\r\nsome\r more text");
+
+ domVisitor.visit(paragraph, testDocument, testRoot);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ expect(documentAsString).toMatch(/some textsome more text<\/text:p>/);
+ });
+ });
+
+ describe("#visitParagraph", () => {
+ let paragraph: Paragraph;
+
+ beforeEach(() => {
+ paragraph = new Paragraph(testText);
+ });
+
+ it("add an empty paragraph", () => {
+ paragraph.setText("");
+
+ domVisitor.visit(paragraph, testDocument, testRoot);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ expect(documentAsString).toMatch(//);
+ });
+
+ it("add a paragraph with specified text", () => {
+ domVisitor.visit(paragraph, testDocument, testRoot);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ expect(documentAsString).toMatch(/some text<\/text:p>/);
+ });
+
+ it("call `toXml` on a style if a style is set", () => {
+ const testStyle = new ParagraphStyle();
+ const styleToXmlSpy = jest.spyOn(testStyle, "toXml");
+
+ paragraph.setStyle(testStyle);
+
+ domVisitor.visit(paragraph, testDocument, testRoot);
+
+ expect(styleToXmlSpy).toHaveBeenCalledWith(testDocument, expect.any(Object));
+ });
+ });
+ });
+});
diff --git a/src/xml/DomVisitor.ts b/src/xml/DomVisitor.ts
new file mode 100644
index 00000000..81c0fac9
--- /dev/null
+++ b/src/xml/DomVisitor.ts
@@ -0,0 +1,141 @@
+import { readFileSync } from "fs";
+import { Image } from "../api/draw";
+import { OdfElement } from "../api/OdfElement";
+import { TextBody } from "../api/office";
+import { Heading, Hyperlink, List, ListItem, OdfTextElement, Paragraph } from "../api/text";
+import { DrawElementName } from "./DrawElementName";
+import { OdfAttributeName } from "./OdfAttributeName";
+import { OdfElementName } from "./OdfElementName";
+import { OdfTextElementWriter } from "./OdfTextElementWriter";
+import { TextElementName } from "./TextElementName";
+
+const IMAGE_ENCODING = "base64";
+const HYPERLINK_LINK_TYPE = "simple";
+
+export class DomVisitor {
+ public visit(odfElement: OdfElement, document: Document, parent: Element): void {
+ let currentElement: Element;
+ if (odfElement instanceof Heading) {
+ currentElement = this.visitHeading(odfElement, document, parent);
+ } else if (odfElement instanceof Hyperlink) {
+ currentElement = this.visitHyperlink(odfElement, document, parent);
+ } else if (odfElement instanceof Image) {
+ currentElement = this.visitImage(odfElement, document, parent);
+ } else if (odfElement instanceof List) {
+ currentElement = this.visitList(odfElement, document, parent);
+ } else if (odfElement instanceof ListItem) {
+ currentElement = this.visitListItem(document, parent);
+ } else if (odfElement instanceof OdfTextElement) {
+ currentElement = this.visitOdfText(odfElement, document, parent);
+ } else if (odfElement instanceof Paragraph) {
+ currentElement = this.visitParagraph(odfElement, document, parent);
+ } else if (odfElement instanceof TextBody) {
+ currentElement = this.visitTextBody(document, parent);
+ }
+
+ odfElement.getAll().forEach((odfChildElement) => {
+ this.visit(odfChildElement, document, currentElement);
+ });
+ }
+
+ private visitHeading(heading: Heading, document: Document, parent: Element): Element {
+ const headingElement = document.createElement(TextElementName.TextHeading);
+ headingElement.setAttribute(OdfAttributeName.TextOutlineLevel, heading.getLevel().toString(10));
+ parent.appendChild(headingElement);
+
+ const style = heading.getStyle();
+ if (style !== undefined) {
+ style.toXml(document, headingElement);
+ }
+
+ return headingElement;
+ }
+
+ private visitHyperlink(hyperlink: Hyperlink, document: Document, parent: Element): Element {
+ const hyperlinkElement = document.createElement(TextElementName.TextHyperlink);
+ hyperlinkElement.setAttribute(OdfAttributeName.XlinkType, HYPERLINK_LINK_TYPE);
+ hyperlinkElement.setAttribute(OdfAttributeName.XlinkHref, hyperlink.getURI());
+ parent.appendChild(hyperlinkElement);
+
+ this.visitOdfText(hyperlink, document, hyperlinkElement);
+
+ return hyperlinkElement;
+ }
+
+ private visitImage(image: Image, document: Document, parent: Element): Element {
+ const frameElement = document.createElement(DrawElementName.DrawFrame);
+ parent.appendChild(frameElement);
+
+ this.embedImage(document, frameElement, image);
+
+ image.getStyle().toXml(frameElement);
+
+ return frameElement;
+ }
+
+ private visitList(list: List, document: Document, parent: Element): Element {
+ if (list.size() === 0) {
+ return parent;
+ }
+
+ const listElement = document.createElement(TextElementName.TextList);
+ parent.appendChild(listElement);
+
+ return listElement;
+ }
+
+ private visitListItem(document: Document, parent: Element): Element {
+ const listItemElement = document.createElement(TextElementName.TextListItem);
+ parent.appendChild(listItemElement);
+
+ return listItemElement;
+ }
+
+ private visitOdfText(odfText: OdfTextElement, document: Document, parent: Element): Element {
+ new OdfTextElementWriter().write(odfText, document, parent);
+ return parent;
+ }
+
+ private visitParagraph(paragraph: Paragraph, document: Document, parent: Element): Element {
+ const paragraphElement = document.createElement(TextElementName.TextParagraph);
+ parent.appendChild(paragraphElement);
+
+ const style = paragraph.getStyle();
+ if (style !== undefined) {
+ style.toXml(document, paragraphElement);
+ }
+
+ return paragraphElement;
+ }
+
+ private visitTextBody(document: Document, parent: Element): Element {
+ const bodyElement = document.createElement(OdfElementName.OfficeBody);
+ parent.appendChild(bodyElement);
+
+ const textElement = document.createElement(OdfElementName.OfficeText);
+ bodyElement.appendChild(textElement);
+
+ return textElement;
+ }
+
+ /**
+ * Creates the image element and embeds the denoted image base64 encoded binary data.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} frameElement The parent node in the DOM (`draw:frame`)
+ * @param {Image} image The image
+ * @private
+ */
+ private embedImage(document: Document, frameElement: Element, image: Image): void {
+ const imageElement = document.createElement(DrawElementName.DrawImage);
+ frameElement.appendChild(imageElement);
+
+ const binaryData = document.createElement(OdfElementName.OfficeBinaryData);
+ imageElement.appendChild(binaryData);
+
+ const rawImage = readFileSync(image.getPath());
+ const base64Image = rawImage.toString(IMAGE_ENCODING);
+ const textNode = document.createTextNode(base64Image);
+ binaryData.appendChild(textNode);
+ }
+}
diff --git a/src/draw/DrawElementName.ts b/src/xml/DrawElementName.ts
similarity index 100%
rename from src/draw/DrawElementName.ts
rename to src/xml/DrawElementName.ts
diff --git a/src/OdfAttributeName.ts b/src/xml/OdfAttributeName.ts
similarity index 100%
rename from src/OdfAttributeName.ts
rename to src/xml/OdfAttributeName.ts
diff --git a/src/OdfElementName.ts b/src/xml/OdfElementName.ts
similarity index 100%
rename from src/OdfElementName.ts
rename to src/xml/OdfElementName.ts
diff --git a/src/text/OdfTextElement.ts b/src/xml/OdfTextElementWriter.ts
similarity index 77%
rename from src/text/OdfTextElement.ts
rename to src/xml/OdfTextElementWriter.ts
index d38996b7..08daa155 100644
--- a/src/text/OdfTextElement.ts
+++ b/src/xml/OdfTextElementWriter.ts
@@ -1,59 +1,27 @@
-import { OdfElement } from "../OdfElement";
+import { OdfTextElement } from "../api/text/OdfTextElement";
import { TextElementName } from "./TextElementName";
const SPACE = " ";
-/**
- * This class represents text in a paragraph.
- *
- * @since 0.3.0
- * @private
- */
-export class OdfTextElement extends OdfElement {
+export class OdfTextElementWriter {
/**
- * Creates a text
- *
- * @param {string} text The text content
- * @since 0.3.0
- */
- public constructor(private text: string) {
- super();
- }
-
- /**
- * Sets the new text content.
- *
- * @param {string} text The new text content
- * @since 0.3.0
+ * @inheritdoc
+ * @since 0.7.0
*/
- public setText(text: string): void {
- this.text = text;
- }
-
- /**
- * Returns the text content.
- *
- * @returns {string} The text content
- * @since 0.3.0
- */
- public getText(): string {
- return this.text;
- }
-
- /** @inheritDoc */
- protected toXml(document: Document, parent: Element): void {
- if (this.text === undefined || this.text === "") {
+ public write(odfText: OdfTextElement, document: Document, parent: Element): void {
+ const text = odfText.getText();
+ if (text === undefined || text === "") {
return;
}
let str = "";
- for (let index = 0; index < this.text.length; index++) {
- const currentChar = this.text.charAt(index);
+ for (let index = 0; index < text.length; index++) {
+ const currentChar = text.charAt(index);
switch (currentChar) {
case SPACE:
str += currentChar;
- const count = this.findNextNonSpaceCharacter(this.text, index) - 1;
+ const count = this.findNextNonSpaceCharacter(text, index) - 1;
if (count > 0) {
this.appendTextNode(document, parent, str);
this.appendSpaceNode(document, parent, count);
diff --git a/src/xml/TextDocumentWriter.spec.ts b/src/xml/TextDocumentWriter.spec.ts
new file mode 100644
index 00000000..2d99b97a
--- /dev/null
+++ b/src/xml/TextDocumentWriter.spec.ts
@@ -0,0 +1,81 @@
+import { XMLSerializer } from "xmldom";
+import { TextDocument } from "../api/office";
+import { FontPitch } from "../api/style";
+import { TextDocumentWriter } from "./TextDocumentWriter";
+
+jest.mock("./meta/MetaWriter");
+jest.mock("./DomVisitor");
+
+describe(TextDocumentWriter.name, () => {
+ let documentWriter: TextDocumentWriter;
+ let textDocument: TextDocument;
+
+ beforeEach(() => {
+ textDocument = new TextDocument();
+
+ documentWriter = new TextDocumentWriter();
+ });
+
+ describe("namespace declaration", () => {
+ let documentAsString: string;
+
+ beforeEach(() => {
+ const document = documentWriter.write(textDocument);
+ documentAsString = new XMLSerializer().serializeToString(document);
+ });
+
+ it("add dc namespace", () => {
+ expect(documentAsString).toMatch(/xmlns:dc="http:\/\/purl.org\/dc\/elements\/1.1"/);
+ });
+
+ it("add draw namespace", () => {
+ expect(documentAsString).toMatch(/xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"/);
+ });
+
+ it("add fo namespace", () => {
+ expect(documentAsString).toMatch(/xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"/);
+ });
+
+ it("add meta namespace", () => {
+ expect(documentAsString).toMatch(/xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"/);
+ });
+
+ it("add style namespace", () => {
+ expect(documentAsString).toMatch(/xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"/);
+ });
+
+ it("add svg namespace", () => {
+ expect(documentAsString).toMatch(/xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"/);
+ });
+
+ it("add text namespace", () => {
+ expect(documentAsString).toMatch(/xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"/);
+ });
+
+ it("add xlink namespace", () => {
+ expect(documentAsString).toMatch(/xmlns:xlink="http:\/\/www.w3.org\/1999\/xlink"/);
+ });
+ });
+
+ describe("font face declarations", () => {
+ it("add font declaration to document", () => {
+ textDocument.declareFont("Springfield", "Springfield", FontPitch.Variable);
+
+ const document = documentWriter.write(textDocument);
+ const documentAsString = new XMLSerializer().serializeToString(document);
+
+ /* tslint:disable-next-line:max-line-length */
+ expect(documentAsString).toMatch(/<\/office:font-face-decls>/);
+ });
+
+ it("add font declaration to document and wrap font family if it contains spaces", () => {
+ textDocument.declareFont("Homer Simpson", "Homer Simpson", FontPitch.Fixed);
+
+ const document = documentWriter.write(textDocument);
+ const documentAsString = new XMLSerializer().serializeToString(document);
+
+ /* tslint:disable-next-line:max-line-length */
+ expect(documentAsString).toMatch(/<\/office:font-face-decls>/);
+ });
+ });
+});
diff --git a/src/xml/TextDocumentWriter.ts b/src/xml/TextDocumentWriter.ts
new file mode 100644
index 00000000..6500b2f9
--- /dev/null
+++ b/src/xml/TextDocumentWriter.ts
@@ -0,0 +1,89 @@
+import { DOMImplementation } from "xmldom";
+import { TextDocument } from "../api/office/TextDocument";
+import { FontFace } from "../api/style";
+import { DomVisitor } from "./DomVisitor";
+import { MetaWriter } from "./meta/MetaWriter";
+import { OdfAttributeName } from "./OdfAttributeName";
+import { OdfElementName } from "./OdfElementName";
+
+const OFFICE_VERSION = "1.2";
+
+/**
+ * Transforms a {@link TextDocument} object into ODF conform XML
+ *
+ * @since 0.7.0
+ */
+export class TextDocumentWriter {
+ /**
+ * Transforms the given {@link TextDocument} into Open Document Format.
+ *
+ * @param {TextDocument} textDocument The text document to serialize
+ * @returns {Document} The XML document
+ * @since 0.7.0
+ */
+ public write(textDocument: TextDocument): Document {
+ const document = new DOMImplementation().createDocument(
+ "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
+ OdfElementName.OfficeDocument,
+ null);
+ const root = document.firstChild as Element;
+
+ this.setXmlNamespaces(root);
+
+ root.setAttribute(OdfAttributeName.OfficeMimetype, "application/vnd.oasis.opendocument.text");
+ root.setAttribute(OdfAttributeName.OfficeVersion, OFFICE_VERSION);
+
+ new MetaWriter().write(document, root, textDocument.getMeta());
+
+ this.setFontFaceElements(textDocument.getFonts(), document, root);
+
+ new DomVisitor().visit(textDocument.getBody(), document, root);
+
+ return document;
+ }
+
+ /**
+ * Declares the used XML namespaces.
+ *
+ * @param {Element} root The root element of the document which will be used as parent
+ * @private
+ */
+ private setXmlNamespaces(root: Element): void {
+ root.setAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1");
+ root.setAttribute("xmlns:draw", "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0");
+ root.setAttribute("xmlns:fo", "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0");
+ root.setAttribute("xmlns:meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
+ root.setAttribute("xmlns:style", "urn:oasis:names:tc:opendocument:xmlns:style:1.0");
+ root.setAttribute("xmlns:svg", "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0");
+ root.setAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0");
+ root.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
+ }
+
+ /**
+ * Adds the `font-face-decls` element and the font faces if any font needs to be declared.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} root The element which will be used as parent
+ * @private
+ */
+ private setFontFaceElements(fonts: FontFace[], document: Document, root: Element): void {
+ if (fonts.length === 0) {
+ return;
+ }
+
+ const fontFaceDeclsElement = document.createElement(OdfElementName.OfficeFontFaceDeclarations);
+ root.appendChild(fontFaceDeclsElement);
+
+ fonts.forEach((font: FontFace) => {
+ const fontFaceElement = document.createElement(OdfElementName.StyleFontFace);
+ fontFaceDeclsElement.appendChild(fontFaceElement);
+ fontFaceElement.setAttribute("style:name", font.getName());
+
+ const fontFamily = font.getFontFamily();
+ const encodedFontFamily = fontFamily.includes(" ") === true ? `'${fontFamily}'` : fontFamily;
+ fontFaceElement.setAttribute("svg:font-family", encodedFontFamily);
+
+ fontFaceElement.setAttribute("style:font-pitch", font.getFontPitch());
+ });
+ }
+}
diff --git a/src/text/TextElementName.ts b/src/xml/TextElementName.ts
similarity index 100%
rename from src/text/TextElementName.ts
rename to src/xml/TextElementName.ts
diff --git a/src/meta/MetaElementName.ts b/src/xml/meta/MetaElementName.ts
similarity index 100%
rename from src/meta/MetaElementName.ts
rename to src/xml/meta/MetaElementName.ts
diff --git a/src/xml/meta/MetaWriter.spec.ts b/src/xml/meta/MetaWriter.spec.ts
new file mode 100644
index 00000000..650003a6
--- /dev/null
+++ b/src/xml/meta/MetaWriter.spec.ts
@@ -0,0 +1,91 @@
+import { userInfo } from "os";
+import { DOMImplementation, XMLSerializer } from "xmldom";
+import { Meta } from "../../api/meta";
+import { OdfElementName } from "../OdfElementName";
+import { MetaWriter } from "./MetaWriter";
+
+describe(MetaWriter.name, () => {
+ describe("#write", () => {
+ let metaWriter: MetaWriter;
+ let testDocument: Document;
+ let testRoot: Element;
+ let meta: Meta;
+
+ beforeEach(() => {
+ testDocument = new DOMImplementation().createDocument("someNameSpace", OdfElementName.OfficeDocument, null);
+ testRoot = testDocument.firstChild as Element;
+ meta = new Meta();
+
+ metaWriter = new MetaWriter();
+ });
+
+ it("append creator, date, creation-date, editing-cycles and generator as default properties", () => {
+ metaWriter.write(testDocument, testRoot, meta);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ const regex = new RegExp(""
+ + "simple-odf/\\d\\.\\d+\\.\\d+"
+ + "" + userInfo().username + ""
+ + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z"
+ + "1"
+ + "");
+ expect(documentAsString).toMatch(regex);
+ });
+
+ it("ignore description, language, subject, title if they are empty", () => {
+ meta.setCreator("")
+ .setDate(undefined)
+ .setDescription("")
+ .setInitialCreator("")
+ .setLanguage("")
+ .setSubject("")
+ .setTitle("");
+
+ metaWriter.write(testDocument, testRoot, meta);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ const regex = new RegExp(""
+ + "simple-odf/\\d\\.\\d+\\.\\d+"
+ + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z"
+ + "1"
+ + "");
+ expect(documentAsString).toMatch(regex);
+ });
+
+ it("append elements if they are set", () => {
+ meta.setCreator("Homer Simpson")
+ .setDate(new Date(Date.UTC(2020, 11, 24, 13, 37, 23, 42)))
+ .setDescription("some test description")
+ .setInitialCreator("Marge Simpson")
+ .addKeyword("some keyword")
+ .addKeyword("some other keyword")
+ .setLanguage("zu")
+ .setPrintDate(new Date(Date.UTC(2021, 3, 1)))
+ .setPrintedBy("Maggie Simpson")
+ .setSubject("some test subject")
+ .setTitle("some test title")
+ ;
+
+ metaWriter.write(testDocument, testRoot, meta);
+
+ const documentAsString = new XMLSerializer().serializeToString(testDocument);
+ const regex = new RegExp(""
+ + "simple-odf/\\d\\.\\d+\\.\\d+"
+ + "some test title"
+ + "some test description"
+ + "some test subject"
+ + "some keyword"
+ + "some other keyword"
+ + "Marge Simpson"
+ + "Homer Simpson"
+ + "Maggie Simpson"
+ + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z"
+ + "2020-12-24T13:37:23.042Z"
+ + "2021-04-01T00:00:00.000Z"
+ + "zu"
+ + "1"
+ + "");
+ expect(documentAsString).toMatch(regex);
+ });
+ });
+});
diff --git a/src/xml/meta/MetaWriter.ts b/src/xml/meta/MetaWriter.ts
new file mode 100644
index 00000000..96f00901
--- /dev/null
+++ b/src/xml/meta/MetaWriter.ts
@@ -0,0 +1,265 @@
+import { Meta } from "../../api/meta";
+import { OdfElementName } from "../OdfElementName";
+import { MetaElementName } from "./MetaElementName";
+
+/**
+ * Transforms a {@link Meta} object into ODF conform XML
+ *
+ * @since 0.7.0
+ */
+export class MetaWriter {
+ /**
+ * Transforms the given {@link Meta} into Open Document Format.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} parent The parent node in the DOM
+ * @param {Meta} meta The Meta to serialize
+ * @since 0.7.0
+ */
+ public write(document: Document, root: Element, meta: Meta): void {
+ const metaElement = document.createElement(OdfElementName.OfficeMeta);
+ root.appendChild(metaElement);
+
+ this.setGeneratorElement(document, metaElement, meta);
+ this.setTitleElement(document, metaElement, meta);
+ this.setDescriptionElement(document, metaElement, meta);
+ this.setSubjectElement(document, metaElement, meta);
+ this.setKeywordElements(document, metaElement, meta);
+ this.setInitialCreatorElement(document, metaElement, meta);
+ this.setCreatorElement(document, metaElement, meta);
+ this.setPrintedByElement(document, metaElement, meta);
+ this.setCreationDateElement(document, metaElement, meta);
+ this.setDateElement(document, metaElement, meta);
+ this.setPrintDateElement(document, metaElement, meta);
+ this.setLanguageElement(document, metaElement, meta);
+ this.setEditingCyclesElement(document, metaElement, meta);
+ }
+
+ /**
+ * Sets the `meta:creation-date` element to the date and time this class was constructed.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setCreationDateElement(document: Document, metaElement: Element, meta: Meta): void {
+ const creationDateElement = document.createElement(MetaElementName.MetaCreationDate);
+ metaElement.appendChild(creationDateElement);
+ creationDateElement.appendChild(document.createTextNode(meta.getCreationDate().toISOString()));
+ }
+
+ /**
+ * Sets the `dc:creator` element if creator is set.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setCreatorElement(document: Document, metaElement: Element, meta: Meta): void {
+ const creator = meta.getCreator();
+ if (creator === undefined || creator.length === 0) {
+ return;
+ }
+
+ const creatorElement = document.createElement(MetaElementName.DcCreator);
+ metaElement.appendChild(creatorElement);
+ creatorElement.appendChild(document.createTextNode(creator));
+ }
+
+ /**
+ * Sets the `dc:date` element if date is set.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setDateElement(document: Document, metaElement: Element, meta: Meta): void {
+ const date = meta.getDate();
+ if (date === undefined) {
+ return;
+ }
+
+ const dateElement = document.createElement(MetaElementName.DcDate);
+ metaElement.appendChild(dateElement);
+ dateElement.appendChild(document.createTextNode(date.toISOString()));
+ }
+
+ /**
+ * Sets the `dc:description` element if description is set.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setDescriptionElement(document: Document, metaElement: Element, meta: Meta): void {
+ const description = meta.getDescription();
+ if (description === undefined || description.length === 0) {
+ return;
+ }
+
+ const descriptionElement = document.createElement(MetaElementName.DcDescription);
+ metaElement.appendChild(descriptionElement);
+ descriptionElement.appendChild(document.createTextNode(description));
+ }
+
+ /**
+ * Sets the `meta:editing-cycles` element to 1.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setEditingCyclesElement(document: Document, metaElement: Element, meta: Meta): void {
+ const editingCyclesElement = document.createElement(MetaElementName.MetaEditingCycles);
+ metaElement.appendChild(editingCyclesElement);
+ editingCyclesElement.appendChild(document.createTextNode(meta.getEditingCycles().toString()));
+ }
+
+ /**
+ * Sets the `meta:generator` element to the name and version of this library (`simple-odf/x.y.z`).
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setGeneratorElement(document: Document, metaElement: Element, meta: Meta): void {
+ const generatorElement = document.createElement(MetaElementName.MetaGenerator);
+ metaElement.appendChild(generatorElement);
+ generatorElement.appendChild(document.createTextNode(meta.getGenerator()));
+ }
+
+ /**
+ * Sets the `meta:initial-creator` element to the current user.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setInitialCreatorElement(document: Document, metaElement: Element, meta: Meta): void {
+ const initialCreator = meta.getInitialCreator();
+ if (initialCreator === undefined || initialCreator.length === 0) {
+ return;
+ }
+
+ const creatorElement = document.createElement(MetaElementName.MetaInitialCreator);
+ metaElement.appendChild(creatorElement);
+ creatorElement.appendChild(document.createTextNode(initialCreator));
+ }
+
+ /**
+ * Sets the `meta:keyword` elements if any keyword is set.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setKeywordElements(document: Document, metaElement: Element, meta: Meta): void {
+ meta.getKeywords().forEach((keyword: string) => {
+ const subjectElement = document.createElement(MetaElementName.MetaKeyword);
+ metaElement.appendChild(subjectElement);
+ subjectElement.appendChild(document.createTextNode(keyword));
+ });
+ }
+
+ /**
+ * Sets the `dc:language` element if language is set.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setLanguageElement(document: Document, metaElement: Element, meta: Meta): void {
+ const language = meta.getLanguage();
+ if (language === undefined || language.length === 0) {
+ return;
+ }
+
+ const languageElement = document.createElement(MetaElementName.DcLanguage);
+ metaElement.appendChild(languageElement);
+ languageElement.appendChild(document.createTextNode(language));
+ }
+
+ /**
+ * Sets the `meta:print-date` element if print date is set.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setPrintDateElement(document: Document, metaElement: Element, meta: Meta): void {
+ const printDate = meta.getPrintDate();
+ if (printDate === undefined) {
+ return;
+ }
+
+ const printDateElement = document.createElement(MetaElementName.MetaPrintDate);
+ metaElement.appendChild(printDateElement);
+ printDateElement.appendChild(document.createTextNode(printDate.toISOString()));
+ }
+
+ /**
+ * Sets the `meta:printed-by` element if printing name is set.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setPrintedByElement(document: Document, metaElement: Element, meta: Meta): void {
+ const printedBy = meta.getPrintedBy();
+ if (printedBy === undefined || printedBy.length === 0) {
+ return;
+ }
+ const printedByElement = document.createElement(MetaElementName.MetaPrintedBy);
+ metaElement.appendChild(printedByElement);
+ printedByElement.appendChild(document.createTextNode(printedBy));
+ }
+
+ /**
+ * Sets the `dc:subject` element if subject is set.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setSubjectElement(document: Document, metaElement: Element, meta: Meta): void {
+ const subject = meta.getSubject();
+ if (subject === undefined || subject.length === 0) {
+ return;
+ }
+
+ const subjectElement = document.createElement(MetaElementName.DcSubject);
+ metaElement.appendChild(subjectElement);
+ subjectElement.appendChild(document.createTextNode(subject));
+ }
+
+ /**
+ * Sets the `dc:title` element if title is set.
+ *
+ * @param {Document} document The XML document
+ * @param {Element} metaElement The meta element which will act as parent
+ * @param {Meta} meta The metadata
+ * @private
+ */
+ private setTitleElement(document: Document, metaElement: Element, meta: Meta): void {
+ const title = meta.getTitle();
+ if (title === undefined || title.length === 0) {
+ return;
+ }
+
+ const titleElement = document.createElement(MetaElementName.DcTitle);
+ metaElement.appendChild(titleElement);
+ titleElement.appendChild(document.createTextNode(title));
+ }
+}
diff --git a/test/integration.spec.ts b/test/integration.spec.ts
index 639c4d5c..6b995a93 100644
--- a/test/integration.spec.ts
+++ b/test/integration.spec.ts
@@ -1,24 +1,26 @@
import { unlink } from "fs";
import { join } from "path";
import { promisify } from "util";
+import { TextBody, TextDocument } from "../src/api/office";
+import { FontPitch } from "../src/api/style";
import { AnchorType } from "../src/style/AnchorType";
import { Color } from "../src/style/Color";
-import { FontPitch } from "../src/style/FontPitch";
import { HorizontalAlignment } from "../src/style/HorizontalAlignment";
import { ParagraphStyle } from "../src/style/ParagraphStyle";
import { TabStop } from "../src/style/TabStop";
import { TabStopType } from "../src/style/TabStopType";
import { TextTransformation } from "../src/style/TextTransformation";
import { Typeface } from "../src/style/Typeface";
-import { TextDocument } from "../src/TextDocument";
const FILEPATH = "./integration.fodt";
xdescribe("integration", () => {
let document: TextDocument;
+ let body: TextBody;
beforeAll(() => {
document = new TextDocument();
+ body = document.getBody();
});
afterAll(async (done) => {
@@ -39,7 +41,7 @@ xdescribe("integration", () => {
});
it("image", () => {
- const paragraph = document.addParagraph();
+ const paragraph = body.addParagraph();
paragraph.setStyle(new ParagraphStyle());
paragraph.getStyle().setHorizontalAlignment(HorizontalAlignment.Center);
@@ -49,34 +51,34 @@ xdescribe("integration", () => {
});
it("add heading", () => {
- document.addHeading("First heading");
- document.addHeading("Second heading", 2);
+ body.addHeading("First heading");
+ body.addHeading("Second heading", 2);
- const para = document.addParagraph("The quick, brown fox jumps over a lazy dog.");
+ const para = body.addParagraph("The quick, brown fox jumps over a lazy dog.");
para.addText("\nSome more text");
});
describe("paragraph formatting", () => {
it("page break", () => {
- const heading = document.addHeading("Paragraph Formatting", 2);
+ const heading = body.addHeading("Paragraph Formatting", 2);
heading.setStyle(new ParagraphStyle());
heading.getStyle().setPageBreakBefore();
});
it("keep together", () => {
- const heading = document.addParagraph("Paragraph Formatting");
+ const heading = body.addParagraph("Paragraph Formatting");
heading.setStyle(new ParagraphStyle());
heading.getStyle().setKeepTogether();
});
it("align text", () => {
- const paragraph = document.addParagraph("Some centered text");
+ const paragraph = body.addParagraph("Some centered text");
paragraph.setStyle(new ParagraphStyle());
paragraph.getStyle().setHorizontalAlignment(HorizontalAlignment.Center);
});
it("tab stops", () => {
- const paragraph = document.addParagraph("first\tsecond\tthird");
+ const paragraph = body.addParagraph("first\tsecond\tthird");
paragraph.setStyle(new ParagraphStyle());
paragraph.getStyle().addTabStop(new TabStop(4));
paragraph.getStyle().addTabStop(new TabStop(12, TabStopType.Right));
@@ -85,13 +87,13 @@ xdescribe("integration", () => {
describe("text formatting", () => {
beforeAll(() => {
- const heading = document.addHeading("Text Formatting", 2);
+ const heading = body.addHeading("Text Formatting", 2);
heading.setStyle(new ParagraphStyle());
heading.getStyle().setPageBreakBefore();
});
it("color", () => {
- const paragraph = document.addParagraph("Some mint-colored text");
+ const paragraph = body.addParagraph("Some mint-colored text");
paragraph.setStyle(new ParagraphStyle());
paragraph.getStyle().setColor(Color.fromRgb(62, 180, 137));
});
@@ -99,46 +101,46 @@ xdescribe("integration", () => {
it("font name", () => {
document.declareFont("Open Sans", "Open Sans", FontPitch.Variable);
- const paragraph = document.addParagraph("Open Sans");
+ const paragraph = body.addParagraph("Open Sans");
paragraph.setStyle(new ParagraphStyle());
paragraph.getStyle().setFontName("Open Sans");
});
it("font size", () => {
- const paragraph = document.addParagraph("Some small text");
+ const paragraph = body.addParagraph("Some small text");
paragraph.setStyle(new ParagraphStyle());
paragraph.getStyle().setFontSize(8);
});
it("text transformation", () => {
- const paragraph = document.addParagraph("Some uppercase text");
+ const paragraph = body.addParagraph("Some uppercase text");
paragraph.setStyle(new ParagraphStyle());
paragraph.getStyle().setTextTransformation(TextTransformation.Uppercase);
});
it("typeface", () => {
- const paragraph = document.addParagraph("Some bold text");
+ const paragraph = body.addParagraph("Some bold text");
paragraph.setStyle(new ParagraphStyle());
paragraph.getStyle().setTypeface(Typeface.Bold);
});
});
it("hyperlink", () => {
- const heading = document.addHeading("Hyperlink", 2);
+ const heading = body.addHeading("Hyperlink", 2);
heading.setStyle(new ParagraphStyle());
heading.getStyle().setPageBreakBefore();
- const paragraph = document.addParagraph("This is just an ");
+ const paragraph = body.addParagraph("This is just an ");
paragraph.addHyperlink("example", "http://example.org");
paragraph.addText(".");
});
it("list", () => {
- const heading = document.addHeading("List", 2);
+ const heading = body.addHeading("List", 2);
heading.setStyle(new ParagraphStyle());
heading.getStyle().setPageBreakBefore();
- const list = document.addList();
+ const list = body.addList();
list.addItem("first item");
list.addItem("second item");
});
diff --git a/tsconfig.json b/tsconfig.json
index 87d7a22e..89d60a0e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -41,7 +41,7 @@
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
- "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
+ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
@@ -51,6 +51,11 @@
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
+ "plugins": [
+ {
+ "name": "typescript-tslint-plugin"
+ }
+ ]
},
"include": [
"src/**/*"
diff --git a/tslint.json b/tslint.json
index 32fa6e5e..b6d5a329 100644
--- a/tslint.json
+++ b/tslint.json
@@ -1,9 +1,19 @@
{
- "defaultSeverity": "error",
- "extends": [
- "tslint:recommended"
- ],
- "jsRules": {},
- "rules": {},
- "rulesDirectory": []
+ "defaultSeverity": "error",
+ "extends": [
+ "tslint:recommended"
+ ],
+ "jsRules": {},
+ "rules": {
+ "whitespace": {
+ "options": [
+ "check-branch",
+ "check-decl",
+ "check-operator",
+ "check-separator",
+ "check-type"
+ ]
+ }
+ },
+ "rulesDirectory": []
}
\ No newline at end of file