diff --git a/CHANGELOG.md b/CHANGELOG.md index f7424427..22b51d03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - **paragraph:** Add hyperlinks to a paragraph, closes [#5](https://github.com/connium/simple-odf/issues/5) - **paragraph:** Add images to a paragraph, closes [#7](https://github.com/connium/simple-odf/issues/7) +- **paragraph:** Replace tab character with tab element, closes [#10](https://github.com/connium/simple-odf/issues/10) +- **paragraph:** Replace sequence of space characters with space element, closes [#10](https://github.com/connium/simple-odf/issues/10) ### Changed - **general:** Full rewrite of the public API to use the terminology of the Open Document Format diff --git a/src/OdfElementName.ts b/src/OdfElementName.ts index 1abbd743..15ad0792 100644 --- a/src/OdfElementName.ts +++ b/src/OdfElementName.ts @@ -18,4 +18,6 @@ export enum OdfElementName { TextList = "text:list", TextListItem = "text:list-item", TextParagraph = "text:p", + TextSpace = "text:s", + TextTabulation = "text:tab", } diff --git a/src/text/OdfTextElement.ts b/src/text/OdfTextElement.ts index 8d57c208..aa03021c 100644 --- a/src/text/OdfTextElement.ts +++ b/src/text/OdfTextElement.ts @@ -1,6 +1,8 @@ import { OdfElement } from "../OdfElement"; import { OdfElementName } from "../OdfElementName"; +const SPACE = " "; + /** * This class represents text in a paragraph. * @@ -43,16 +45,113 @@ export class OdfTextElement extends OdfElement { return; } - const lines = this.text.split("\n"); + let str = ""; + for (let index = 0; index < this.text.length; index++) { + const currentChar = this.text.charAt(index); + switch (currentChar) { + case SPACE: + str += currentChar; - for (let i = 0; i < lines.length; i++) { - if (i > 0) { - const lineBreak = document.createElement(OdfElementName.TextLineBreak); - parent.appendChild(lineBreak); + const count = this.findNextNonSpaceCharacter(this.text, index) - 1; + if (count > 0) { + this.appendTextNode(document, parent, str); + this.appendSpaceNode(document, parent, count); + str = ""; + index += count; + } + break; + case "\n": + this.appendTextNode(document, parent, str); + this.appendLineBreakNode(document, parent); + str = ""; + break; + case "\t": + this.appendTextNode(document, parent, str); + this.appendTabNode(document, parent); + str = ""; + break; + default: + str += currentChar; + break; } + } + + this.appendTextNode(document, parent, str); + } + + /** + * Creates a text node with the provided text and appends it to the parent element. + * + * @param {Document} document The XML document + * @param {Element} parent The parent node + * @param {string} text The text value of the text node + * @since 0.3.0 + */ + private appendTextNode(document: Document, parent: Element, text: string): void { + if (text.length === 0) { + return; + } + + const textNode = document.createTextNode(text); + parent.appendChild(textNode); + } + + /** + * Creates a space node representing the specified number of space characters and appends it to the parent element. + * If a single space character should be represented, the `c` attribute is omitted. + * + * @param {Document} document The XML document + * @param {Element} parent The parent node + * @param {number} count The number of space characters the node should represent + * @since 0.3.0 + */ + private appendSpaceNode(document: Document, parent: Element, count: number): void { + const space = document.createElement(OdfElementName.TextSpace); + parent.appendChild(space); + + if (count > 1) { + space.setAttribute("c", count.toString(10)); + } + } + + /** + * Creates a tabulation node and appends it to the parent element. + * + * @param {Document} document The XML document + * @param {Element} parent The parent node + * @since 0.3.0 + */ + private appendTabNode(document: Document, parent: Element): void { + const tabulation = document.createElement(OdfElementName.TextTabulation); + parent.appendChild(tabulation); + } - const textNode = document.createTextNode(lines[i]); - parent.appendChild(textNode); + /** + * Creates a line break node and appends it to the parent element. + * + * @param {Document} document The XML document + * @param {Element} parent The parent node + * @since 0.3.0 + */ + private appendLineBreakNode(document: Document, parent: Element): void { + const lineBreak = document.createElement(OdfElementName.TextLineBreak); + parent.appendChild(lineBreak); + } + + /** + * Finds the next non-space character and returns the number of space characters that occur before. + * + * @param {string} text The text to search in + * @param {number} offset The index at which to start the search + * @returns {number} The number of space characters before the next non-space character + * @since 0.3.0 + */ + private findNextNonSpaceCharacter(text: string, offset: number): number { + for (let index = offset; index < text.length; index++) { + if (text.charAt(index) !== SPACE) { + return index - offset; + } } + return text.length - offset; } } diff --git a/test/text/Paragraph.spec.ts b/test/text/Paragraph.spec.ts index 157f72f4..1c480886 100644 --- a/test/text/Paragraph.spec.ts +++ b/test/text/Paragraph.spec.ts @@ -69,6 +69,19 @@ describe(Paragraph.name, () => { 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>/); + }); + describe("#addHyperlink", () => { it("append a linked text", () => { document.addParagraph("some text").addHyperlink(" some linked text", "http://example.org/");