Skip to content

Commit

Permalink
Provide content options for HTML attribute extractor (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
arm1n authored Oct 25, 2022
1 parent 4fc74a4 commit f1e7fbf
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 39 deletions.
3 changes: 2 additions & 1 deletion src/html/extractors/common.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Validate } from '../../utils/validate';
import { IContentExtractorOptions } from '../../utils/content';

export interface IAttributeMapping {
textPlural?: string;
context?: string;
comment?: string;
}

export interface IHtmlExtractorOptions {
export interface IHtmlExtractorOptions extends IContentExtractorOptions {
attributes?: IAttributeMapping;
}

Expand Down
14 changes: 10 additions & 4 deletions src/html/extractors/factories/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IElementSelector, ElementSelectorSet } from '../../selector';
import { Element, Node } from '../../parser';
import { HtmlUtils } from '../../utils';
import { IHtmlExtractorOptions } from '../common';
import { getContentOptions, IContentOptions } from '../../../utils/content';

export type ITextExtractor = (element: Element) => string | null;

Expand All @@ -21,18 +22,23 @@ export function elementExtractor(selector: string | IElementSelector[], textExtr
if (selectors.anyMatch(element)) {
let context: string | undefined,
textPlural: string | undefined,
comments: string[] = [];
comments: string[] = [],
contentOptions: IContentOptions = getContentOptions(options, {
trimWhiteSpace: true,
preserveIndentation: false,
replaceNewLines: false
});

if (options.attributes && options.attributes.context) {
context = HtmlUtils.getAttributeValue(element, options.attributes.context) || undefined;
context = HtmlUtils.getNormalizedAttributeValue(element, options.attributes.context, contentOptions) || undefined;
}

if (options.attributes && options.attributes.textPlural) {
textPlural = HtmlUtils.getAttributeValue(element, options.attributes.textPlural) || undefined;
textPlural = HtmlUtils.getNormalizedAttributeValue(element, options.attributes.textPlural, contentOptions) || undefined;
}

if (options.attributes && options.attributes.comment) {
let comment = HtmlUtils.getAttributeValue(element, options.attributes.comment);
let comment = HtmlUtils.getNormalizedAttributeValue(element, options.attributes.comment, contentOptions);
if (comment) {
comments.push(comment);
}
Expand Down
10 changes: 9 additions & 1 deletion src/html/extractors/factories/elementAttribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ import { HtmlUtils } from '../../utils';
import { elementExtractor } from './element';
import { Validate } from '../../../utils/validate';
import { IHtmlExtractorOptions, validateOptions } from '../common';
import { IContentOptions, getContentOptions, validateContentOptions } from '../../../utils/content';

export function elementAttributeExtractor(selector: string, textAttribute: string, options: IHtmlExtractorOptions = {}): IHtmlExtractorFunction {
Validate.required.nonEmptyString({selector, textAttribute});
validateOptions(options);
validateContentOptions(options);

let contentOptions: IContentOptions = getContentOptions(options, {
trimWhiteSpace: false,
preserveIndentation: true,
replaceNewLines: false
});

return elementExtractor(selector, element => {
return HtmlUtils.getAttributeValue(element, textAttribute);
return HtmlUtils.getNormalizedAttributeValue(element, textAttribute, contentOptions);
}, options);
}
22 changes: 4 additions & 18 deletions src/html/extractors/factories/elementContent.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,20 @@
import { IHtmlExtractorFunction } from '../../parser';
import { HtmlUtils } from '../../utils';
import { Validate } from '../../../utils/validate';
import { IContentOptions, IContentExtractorOptions, validateContentOptions } from '../../../utils/content';
import { getContentOptions, IContentOptions, validateContentOptions } from '../../../utils/content';
import { IHtmlExtractorOptions, validateOptions } from '../common';
import { elementExtractor } from './element';

export interface IElementContentExtractorOptions extends IHtmlExtractorOptions, IContentExtractorOptions {}

export function elementContentExtractor(selector: string, options: IElementContentExtractorOptions = {}): IHtmlExtractorFunction {
export function elementContentExtractor(selector: string, options: IHtmlExtractorOptions = {}): IHtmlExtractorFunction {
Validate.required.nonEmptyString({selector});
validateOptions(options);
validateContentOptions(options);

let contentOptions: IContentOptions = {
let contentOptions: IContentOptions = getContentOptions(options, {
trimWhiteSpace: true,
preserveIndentation: false,
replaceNewLines: false
};

if (options.content) {
if (options.content.trimWhiteSpace !== undefined) {
contentOptions.trimWhiteSpace = options.content.trimWhiteSpace;
}
if (options.content.preserveIndentation !== undefined) {
contentOptions.preserveIndentation = options.content.preserveIndentation;
}
if (options.content.replaceNewLines !== undefined) {
contentOptions.replaceNewLines = options.content.replaceNewLines;
}
}
});

return elementExtractor(selector, element => {
return HtmlUtils.getElementContent(element, contentOptions);
Expand Down
10 changes: 10 additions & 0 deletions src/html/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,19 @@ export abstract class HtmlUtils {
return attribute.value;
}
}

return null;
}

public static getNormalizedAttributeValue(element: Element, attributeName: string, options: IContentOptions): string | null {
let value = HtmlUtils.getAttributeValue(element, attributeName);
if (value === null) {
return null;
}

return normalizeContent(value, options);
}

public static getElementContent(element: Element, options: IContentOptions): string {
let content = parse5.serialize(element, {});

Expand Down
18 changes: 3 additions & 15 deletions src/js/extractors/factories/callExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as ts from 'typescript';

import { IJsExtractorFunction } from '../../parser';
import { Validate } from '../../../utils/validate';
import { IContentOptions, normalizeContent, validateContentOptions } from '../../../utils/content';
import { IContentOptions, normalizeContent, getContentOptions, validateContentOptions } from '../../../utils/content';
import { IJsExtractorOptions, validateOptions, IArgumentIndexMapping } from '../common';
import { JsUtils } from '../../utils';
import { IAddMessageCallback, IMessageData } from '../../../parser';
Expand All @@ -22,23 +22,11 @@ export function callExpressionExtractor(calleeName: string | string[], options:
validateOptions(options);
validateContentOptions(options);

let contentOptions: IContentOptions = {
let contentOptions: IContentOptions = getContentOptions(options, {
trimWhiteSpace: false,
preserveIndentation: true,
replaceNewLines: false
};

if (options.content) {
if (options.content.trimWhiteSpace !== undefined) {
contentOptions.trimWhiteSpace = options.content.trimWhiteSpace;
}
if (options.content.preserveIndentation !== undefined) {
contentOptions.preserveIndentation = options.content.preserveIndentation;
}
if (options.content.replaceNewLines !== undefined) {
contentOptions.replaceNewLines = options.content.replaceNewLines;
}
}
});

return (node: ts.Node, sourceFile: ts.SourceFile, addMessage: IAddMessageCallback) => {
if (node.kind === ts.SyntaxKind.CallExpression) {
Expand Down
18 changes: 18 additions & 0 deletions src/utils/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@ export interface IContentExtractorOptions {
content?: Partial<IContentOptions>;
}

export function getContentOptions(extractorOptions: IContentExtractorOptions, defaultOptions: IContentOptions): IContentOptions {
let contentOptions = defaultOptions;

if (extractorOptions.content) {
if (extractorOptions.content.trimWhiteSpace !== undefined) {
contentOptions.trimWhiteSpace = extractorOptions.content.trimWhiteSpace;
}
if (extractorOptions.content.preserveIndentation !== undefined) {
contentOptions.preserveIndentation = extractorOptions.content.preserveIndentation;
}
if (extractorOptions.content.replaceNewLines !== undefined) {
contentOptions.replaceNewLines = extractorOptions.content.replaceNewLines;
}
}

return contentOptions;
}

export function validateContentOptions(options: IContentExtractorOptions): void {
Validate.optional.booleanProperty(options, 'options.content.trimWhiteSpace');
Validate.optional.booleanProperty(options, 'options.content.preserveIndentation');
Expand Down
48 changes: 48 additions & 0 deletions tests/e2e/html/elementAttribute.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,54 @@ describe('argument validation', () => {
});
}).toThrowError(`Property 'options.attributes.comment' must be a string`);
});

test('options.content: wrong type', () => {
expect(() => {
(<any>HtmlExtractors.elementAttribute)('[translate]', 'translate', {
content: 'foo'
});
}).toThrowError(`Property 'options.content' must be an object`);
});

test('options.content.trimWhiteSpace: wrong type', () => {
expect(() => {
(<any>HtmlExtractors.elementAttribute)('[translate]', 'translate', {
content: {
trimWhiteSpace: 'foo'
}
});
}).toThrowError(`Property 'options.content.trimWhiteSpace' must be a boolean`);
});

test('options.content.preserveIndentation: wrong type', () => {
expect(() => {
(<any>HtmlExtractors.elementAttribute)('[translate]', 'translate', {
content: {
preserveIndentation: 'foo'
}
});
}).toThrowError(`Property 'options.content.preserveIndentation' must be a boolean`);
});

test('options.content.replaceNewLines: wrong type', () => {
expect(() => {
(<any>HtmlExtractors.elementAttribute)('[translate]', 'translate', {
content: {
replaceNewLines: 42
}
});
}).toThrowError(`Property 'options.content.replaceNewLines' must be false or a string`);
});

test('options.content.replaceNewLines: true', () => {
expect(() => {
(<any>HtmlExtractors.elementAttribute)('[translate]', 'translate', {
content: {
replaceNewLines: true
}
});
}).toThrowError(`Property 'options.content.replaceNewLines' must be false or a string`);
});
});

function assertMessages(extractorFunction: IHtmlExtractorFunction, source: string, ...expected: Partial<IMessage>[]): () => void {
Expand Down
93 changes: 93 additions & 0 deletions tests/html/extractors/factories/elementAttribute.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HtmlParser } from '../../../../src/html/parser';
import { CatalogBuilder, IMessage } from '../../../../src/builder';
import { elementAttributeExtractor } from '../../../../src/html/extractors/factories/elementAttribute';
import { elementContentExtractor } from '../../../../src/html/extractors/factories/elementContent';

describe('HTML: Element Attribute Extractor', () => {

Expand Down Expand Up @@ -248,5 +249,97 @@ describe('HTML: Element Attribute Extractor', () => {
});
}).toThrowError(`Property 'options.attributes.comment' must be a string`);
});

test('options.content: wrong type', () => {
expect(() => {
(<any>elementContentExtractor)('[translate]', {
content: 'foo'
});
}).toThrowError(`Property 'options.content' must be an object`);
});

test('options.content.trimWhiteSpace: wrong type', () => {
expect(() => {
(<any>elementContentExtractor)('[translate]', {
content: {
trimWhiteSpace: 'foo'
}
});
}).toThrowError(`Property 'options.content.trimWhiteSpace' must be a boolean`);
});

test('options.content.preserveIndentation: wrong type', () => {
expect(() => {
(<any>elementContentExtractor)('[translate]', {
content: {
preserveIndentation: 'foo'
}
});
}).toThrowError(`Property 'options.content.preserveIndentation' must be a boolean`);
});

test('options.content.replaceNewLines: wrong type', () => {
expect(() => {
(<any>elementContentExtractor)('[translate]', {
content: {
replaceNewLines: 42
}
});
}).toThrowError(`Property 'options.content.replaceNewLines' must be false or a string`);
});

test('options.content.replaceNewLines: true', () => {
expect(() => {
(<any>elementContentExtractor)('[translate]', {
content: {
replaceNewLines: true
}
});
}).toThrowError(`Property 'options.content.replaceNewLines' must be false or a string`);
});
});

describe('argument proxying', () => {
test('options.content.options: applies for all attributes', () => {
parser = new HtmlParser(builder, [
elementAttributeExtractor('translate', 'text', {
attributes: {
textPlural: 'plural',
context: 'context',
comment: 'comment'
},
content: {
preserveIndentation: false,
replaceNewLines: '',
trimWhiteSpace: true
}
})
]);

parser.parseString(`
<translate
text="
Foo
"
plural="
Foos
"
comment="
Comment
"
context="
Context
"/>
`);

expect(messages).toEqual([
{
text: 'Foo',
textPlural: 'Foos',
context: 'Context',
comments: ['Comment']
}
]);
});
});
});
Loading

0 comments on commit f1e7fbf

Please sign in to comment.