diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0ce3688e..913345b8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,11 +6,33 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
-## 0.1.0 (2017-06-20)
+## [Unreleased] (2018-??-??)
+### Added
+- **paragraph:** Add hyperlinks to a paragraph (), closes [#5](https://github.com/connium/simple-odf/issues/5)
+## [0.2.0] (2018-01-12)
### Added
-* **heading:** add headings to a document and modify their outline level
-* **paragraph:** add paragraphs to a document and modify their text content
-* **paragraph:** set page break before paragraph
-* **paragraph:** set horizontal alignment
-* **text-document:** create text documents and save them as flat XML ODF document
+- **docs:** Add CHANGELOG
+- **docs:** Improve and extend README
+- **docs:** Add badges (dependencies, known vulnerabilities, version) to README
+- **list:** Add basic list support (add/insert/get/set/remove item)
+- **paragraph:** Overwrite text content
+- **style:** Get horizontal alignment
+- **test:** Add integration test
+
+### Changed
+- **general:** Export public API from / (no namespaces)
+- **paragraph:** Rename text related functions in paragraph
+- **heading:** Rename headline to heading
+
+## 0.1.0 (2018-01-08)
+### Added
+- **heading:** Add headings to a document and modify their outline level
+- **paragraph:** Add paragraphs to a document and modify their text content
+- **paragraph:** Set page break before paragraph
+- **paragraph:** Set horizontal alignment
+- **text-document:** Create text documents and save them as flat XML ODF document
+
+[Unreleased]: https://github.com/connium/simple-odf/compare/v0.2.0...HEAD
+[0.3.0]: https://github.com/connium/simple-odf/compare/v0.2.0...v0.3.0
+[0.2.0]: https://github.com/connium/simple-odf/compare/v0.1.0...v0.2.0
diff --git a/README.md b/README.md
index 5d8677de..05846d68 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,8 @@ const document = new simpleOdf.TextDocument();
document.addHeadline("My First Document");
const p1 = document.addParagraph("The quick, brown fox jumps over a lazy dog.");
-p1.appendTextContent("\nThe five boxing wizards jump quickly");
+p1.appendTextContent("\nThe five boxing wizards jump quickly\n\n");
+p1.appendHyperlink("Visit me", "http://example.org/");
document.addHeadline("Credits", 2);
diff --git a/src/OdfAttributeName.ts b/src/OdfAttributeName.ts
index 118038a5..75e25b03 100644
--- a/src/OdfAttributeName.ts
+++ b/src/OdfAttributeName.ts
@@ -5,6 +5,9 @@ export enum OdfAttributeName {
StyleFamily = "style:family",
StyleName = "style:name",
- TextStyleName = "text:style-name",
TextOutlineLevel = "text:outline-level",
+ TextStyleName = "text:style-name",
+
+ XlinkHref = "xlink:href",
+ XlinkType = "xlink:type",
}
diff --git a/src/OdfElementName.ts b/src/OdfElementName.ts
index 550abd4f..c1f67d2c 100644
--- a/src/OdfElementName.ts
+++ b/src/OdfElementName.ts
@@ -8,6 +8,7 @@ export enum OdfElementName {
StyleTextProperties = "style:text-properties",
StyleParagraphProperties = "style:paragraph-properties",
+ TextHyperlink = "text:a",
TextHeading = "text:h",
TextLineBreak = "text:line-break",
TextList = "text:list",
diff --git a/src/text/HyperLink.ts b/src/text/HyperLink.ts
new file mode 100644
index 00000000..46da737e
--- /dev/null
+++ b/src/text/HyperLink.ts
@@ -0,0 +1,61 @@
+import { OdfAttributeName } from "../OdfAttributeName";
+import { OdfElementName } from "../OdfElementName";
+import { Text } from "./Text";
+
+/**
+ * This class represents a hyperlink in a paragraph.
+ *
+ * @since 0.3.0
+ */
+export class Hyperlink extends Text {
+ /**
+ * Creates a hyperlink
+ *
+ * @param {string} text The text content of the hyperlink
+ * @param {string} uri The URI of the hyperlink
+ * @since 0.3.0
+ */
+ public constructor(text: string, private uri: string) {
+ super(text);
+ }
+
+ /**
+ * Returns the URI of this hyperlink.
+ *
+ * @returns {string} The URI of this hyperlink
+ * @since 0.3.0
+ */
+ public getURI(): string {
+ return this.uri;
+ }
+
+ /**
+ * Sets the URI for this hyperlink.
+ *
+ * @param {string} uri The new URI of this hyperlink
+ * @since 0.3.0
+ */
+ public setURI(uri: string): void {
+ this.uri = uri;
+ }
+
+ /** @inheritDoc */
+ protected toXML(document: Document, parent: Element): void {
+ const text = this.getText();
+
+ if (text === undefined || text === "") {
+ return;
+ }
+
+ (document.firstChild as Element).setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
+
+ const hyperlink = document.createElement(OdfElementName.TextHyperlink);
+ hyperlink.setAttribute(OdfAttributeName.XlinkType, "simple");
+ hyperlink.setAttribute(OdfAttributeName.XlinkHref, this.uri);
+
+ const textNode = document.createTextNode(text);
+ hyperlink.appendChild(textNode);
+
+ parent.appendChild(hyperlink);
+ }
+}
diff --git a/src/text/Paragraph.ts b/src/text/Paragraph.ts
index 094390da..c5f2d642 100644
--- a/src/text/Paragraph.ts
+++ b/src/text/Paragraph.ts
@@ -3,6 +3,8 @@ import { OdfElement } from "../OdfElement";
import { OdfElementName } from "../OdfElementName";
import { HorizontalAlignment } from "../style/HorizontalAlignment";
import { Style } from "../style/Style";
+import { Hyperlink } from "./HyperLink";
+import { Text } from "./Text";
/**
* This class represents a paragraph.
@@ -11,7 +13,6 @@ import { Style } from "../style/Style";
* @since 0.1.0
*/
export class Paragraph extends OdfElement {
- private text: string | undefined;
private style: Style;
/**
@@ -23,18 +24,24 @@ export class Paragraph extends OdfElement {
public constructor(text?: string) {
super();
- this.text = text;
+ this.appendText(text || "");
+
this.style = new Style();
}
/**
* Returns the text content of this paragraph.
+ * Note: This will only return the text; other elements and markup will be omitted.
*
- * @returns {string | undefined} The text content of this paragraph
+ * @returns {string} The text content of this paragraph
* @since 0.1.0
*/
- public getText(): string | undefined {
- return this.text;
+ public getText(): string {
+ return this.getElements()
+ .map((value: OdfElement) => {
+ return value instanceof Text ? value.getText() : "";
+ })
+ .join("");
}
/**
@@ -44,22 +51,27 @@ export class Paragraph extends OdfElement {
* @since 0.1.0
*/
public appendText(text: string): void {
- if (this.text === undefined) {
- this.text = text;
+ const elements = this.getElements();
+
+ if (elements.length > 0 && elements[elements.length - 1].constructor.name === Text.name) {
+ const lastElement = elements[elements.length - 1] as Text;
+ lastElement.setText(lastElement.getText() + text);
return;
}
- this.text += text;
+ this.appendElement(new Text(text));
}
/**
* Sets the text content of this paragraph.
+ * Note: This will replace any existing content of the paragraph.
*
* @param {string} text The text content
* @since 0.1.0
*/
public setText(text: string): void {
- this.text = text;
+ this.removeText();
+ this.appendText(text || "");
}
/**
@@ -68,7 +80,22 @@ export class Paragraph extends OdfElement {
* @since 0.1.0
*/
public removeText(): void {
- this.text = undefined;
+ const elements = this.getElements();
+
+ for (let i = elements.length - 1; i >= 0; i--) {
+ this.removeElement(i);
+ }
+ }
+
+ /**
+ * 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 URI of the hyperlink
+ * @since 0.3.0
+ */
+ public appendHyperlink(text: string, uri: string): void {
+ this.appendElement(new Hyperlink(text, uri));
}
/**
@@ -112,10 +139,11 @@ export class Paragraph extends OdfElement {
/** @inheritDoc */
protected toXML(document: Document, parent: Element): void {
+ (document.firstChild as Element).setAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0");
+
const paragraph = this.createElement(document);
this.appendStyle(document, paragraph);
- this.appendTextContent(document, paragraph);
parent.appendChild(paragraph);
@@ -135,31 +163,4 @@ export class Paragraph extends OdfElement {
paragraph.setAttribute(OdfAttributeName.TextStyleName, this.style.getName());
}
}
-
- /**
- * Appends the text of the paragraph to the paragraph element.
- * Newlines will be replaced with line breaks.
- *
- * @param {Document} document The XML document
- * @param {Element} paragraph The paragraph the text belongs to
- */
- private appendTextContent(document: Document, paragraph: Element): void {
- if (this.text === undefined) {
- return;
- }
-
- (document.firstChild as Element).setAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0");
-
- const lines = this.text.split("\n");
-
- for (let i = 0; i < lines.length; i++) {
- if (i > 0) {
- const lineBreak = document.createElement(OdfElementName.TextLineBreak);
- paragraph.appendChild(lineBreak);
- }
-
- const textNode = document.createTextNode(lines[i]);
- paragraph.appendChild(textNode);
- }
- }
}
diff --git a/src/text/Text.ts b/src/text/Text.ts
new file mode 100644
index 00000000..3b986908
--- /dev/null
+++ b/src/text/Text.ts
@@ -0,0 +1,58 @@
+import { OdfElement } from "../OdfElement";
+import { OdfElementName } from "../OdfElementName";
+
+/**
+ * This class represents text in a paragraph.
+ *
+ * @since 0.3.0
+ */
+export class Text extends OdfElement {
+ /**
+ * Creates a text
+ *
+ * @param {string} text The text content
+ * @since 0.3.0
+ */
+ public constructor(private text: string) {
+ super();
+ }
+
+ /**
+ * Returns the text content.
+ *
+ * @returns {string} The text content
+ * @since 0.3.0
+ */
+ public getText(): string {
+ return this.text;
+ }
+
+ /**
+ * Sets the new text content.
+ *
+ * @param {string} text The new text content
+ * @since 0.3.0
+ */
+ public setText(text: string): void {
+ this.text = text;
+ }
+
+ /** @inheritDoc */
+ protected toXML(document: Document, parent: Element): void {
+ if (this.text === undefined || this.text === "") {
+ return;
+ }
+
+ const lines = this.text.split("\n");
+
+ for (let i = 0; i < lines.length; i++) {
+ if (i > 0) {
+ const lineBreak = document.createElement(OdfElementName.TextLineBreak);
+ parent.appendChild(lineBreak);
+ }
+
+ const textNode = document.createTextNode(lines[i]);
+ parent.appendChild(textNode);
+ }
+ }
+}
diff --git a/test/integration.spec.ts b/test/integration.spec.ts
index be455833..86acf500 100644
--- a/test/integration.spec.ts
+++ b/test/integration.spec.ts
@@ -34,6 +34,10 @@ describe(TextDocument.name, () => {
const heading30 = document.addHeading("Another chapter");
heading30.setPageBreak();
+ const para2 = document.addParagraph("This is just an ");
+ para2.appendHyperlink("example", "http://example.org");
+ para2.appendText(".");
+
await document.saveFlat(FILEPATH);
done();
});
diff --git a/test/text/Heading.spec.ts b/test/text/Heading.spec.ts
index 36a717d8..e9b8e3f1 100644
--- a/test/text/Heading.spec.ts
+++ b/test/text/Heading.spec.ts
@@ -9,12 +9,18 @@ describe(Heading.name, () => {
document = new TextDocument();
});
+ it("add text namespace", () => {
+ document.addHeading();
+
+ const documentAsString = document.toString();
+ expect(documentAsString).toMatch(/xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"/);
+ });
+
it("insert an empty heading with default level 1", () => {
document.addHeading();
const documentAsString = document.toString();
expect(documentAsString).toMatch(//);
- expect(documentAsString).not.toMatch(/xmlns:text/);
});
it("insert a heading with given text and default level 1", () => {
@@ -22,7 +28,6 @@ describe(Heading.name, () => {
const documentAsString = document.toString();
expect(documentAsString).toMatch(/heading<\/text:h>/);
- expect(documentAsString).toMatch(/xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"/);
});
it("insert a heading with given text and given level", () => {
diff --git a/test/text/Paragraph.spec.ts b/test/text/Paragraph.spec.ts
index 0c19beb5..ebac79a8 100644
--- a/test/text/Paragraph.spec.ts
+++ b/test/text/Paragraph.spec.ts
@@ -1,6 +1,6 @@
+import { HorizontalAlignment } from "../../src/style/HorizontalAlignment";
import { Paragraph } from "../../src/text/Paragraph";
import { TextDocument } from "../../src/TextDocument";
-import { HorizontalAlignment } from "../../src/style/HorizontalAlignment";
describe(Paragraph.name, () => {
let document: TextDocument;
@@ -9,26 +9,34 @@ describe(Paragraph.name, () => {
document = new TextDocument();
});
+ it("add text namespace", () => {
+ document.addParagraph();
+
+ const documentAsString = document.toString();
+ expect(documentAsString).toMatch(/xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"/);
+ });
+
it("insert an empty paragraph", () => {
document.addParagraph();
const documentAsString = document.toString();
expect(documentAsString).toMatch(//);
- expect(documentAsString).not.toMatch(/xmlns:text/);
});
- it("insert a paragraph with specified text and add text namespace", () => {
+ it("insert a paragraph with specified text", () => {
document.addParagraph("some text");
const documentAsString = document.toString();
expect(documentAsString).toMatch(/some text<\/text:p>/);
- expect(documentAsString).toMatch(/xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"/);
});
it("return the text", () => {
const paragraph = document.addParagraph("some text");
+ paragraph.appendText(" some\nmore text");
+ paragraph.appendHyperlink(" link", "http://example.org/");
+ paragraph.appendText(" even more text");
- expect(paragraph.getText()).toEqual("some text");
+ expect(paragraph.getText()).toEqual("some text some\nmore text link even more text");
});
describe("#appendText", () => {
@@ -57,13 +65,12 @@ describe(Paragraph.name, () => {
expect(documentAsString).toMatch(/some other text<\/text:p>/);
});
- it("remove text from paragraph and not add text namespace", () => {
+ it("remove text from paragraph", () => {
const paragraph = document.addParagraph("some text");
paragraph.removeText();
const documentAsString = document.toString();
expect(documentAsString).toMatch(//);
- expect(documentAsString).not.toMatch(/xmlns:text/);
});
it("replace newline with line break", () => {
@@ -73,6 +80,34 @@ describe(Paragraph.name, () => {
expect(documentAsString).toMatch(/some textsome more text<\/text:p>/);
});
+ describe("#appendHyperlink", () => {
+ it("add xlink namespace", () => {
+ const paragraph = document.addParagraph();
+ paragraph.appendHyperlink("some linked text", "http://example.org/");
+
+ const documentAsString = document.toString();
+ expect(documentAsString).toMatch(/xmlns:xlink="http:\/\/www.w3.org\/1999\/xlink"/);
+ });
+
+ it("append a linked text", () => {
+ const paragraph = document.addParagraph("some text");
+ paragraph.appendHyperlink(" some linked text", "http://example.org/");
+
+ 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", () => {
+ const paragraph = document.addParagraph("some text");
+ paragraph.appendHyperlink("", "http://example.org/");
+
+ const documentAsString = document.toString();
+ /* tslint:disable-next-line:max-line-length */
+ expect(documentAsString).toMatch(/some text<\/text:p>/);
+ });
+ });
+
describe("style", () => {
let paragraph: Paragraph;