Skip to content

Commit

Permalink
Issue #24: Add splitMapper, Add splitMapper customizable separator, …
Browse files Browse the repository at this point in the history
…Add splitMapper itemMapper, Add tests+documentaion+samples
  • Loading branch information
Siemienik committed Oct 17, 2020
1 parent 9969b70 commit d62900e
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 14 deletions.
53 changes: 46 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

# Getting Started:

1. install package
1. Install the package:

```
npm i xlsx-import --save
```

2. write config
2. Write a config:
```ts
const config = {
books: {
Expand Down Expand Up @@ -59,9 +59,11 @@ Mapper is a function that transforms values. You can use [built-in mappers](#Map
fields:[
{row: 2, col: 1, key: 'FirstName'},
{row: 2, col: 2, key: 'SecondName', mapper: upperCaseMapper},
{row: 2, col: 3, key: 'ArtistName', mapper: isEmpty},
{row: 3, col: 1, key: 'Age', mapper: Number.parseInt},
{row: 3, col: 2, key: 'Height', mapper: isFilled},

{row: 2, col: 3, key: 'EmployedIn'},
{row: 2, col: 3, key: 'IsUnemployed', mapper: isEmpty},
{row: 2, col: 3, key: 'IsEmployed', mapper: isFilled},
]
},
};
Expand All @@ -79,10 +81,14 @@ interface Person {
FirstName: string;
SecondName: string;
Age: number;

EmployedIn: string;
IsUnemployed:boolean;
IsEmployed:boolean;
}
```

5. Import:
5. Import data:
```ts
const factory = new ImporterFactory();

Expand Down Expand Up @@ -116,7 +122,7 @@ It is a string, indicates which worksheet should be used for data source.

***What in case of performing incorrect `type` parameter value?***

Here is implemented fallback mechanism to attempting to parse data as ListVertical, which is the common type used in this library.<br/> *In that case `console.warn` will be write.*
Here is a implementation of fallback mechanism to attempting to parse data as ListVertical, which is the common type used in this library.<br/> *In that case `console.warn` will be written.*

## `fields` or `columns`

Expand All @@ -131,6 +137,39 @@ This is `type` related configuration, for more information please study examples
|jsonMapper|Transforms a json string to a TJsonResponse or to null if parsing was not possible
|isEmpty|Examines if input is empty
|isFilled|Examines if input is not empty
|splitMapper|Transforms string into array of items

### `splitMapper`

Configurable and immutable **splitMapper** with possibility to use specific `itemMapper<TReturnType>(mapper)` or `separator(string)`.

* `.separator(';'): SplitMapper` - set separator
* `.itemMapper(itemMapper): SplitMapper` - set mapper for items,

Setting separator or item mapper do not change origin mapper but create new one. As a item mapper may use also another `splitMapper` like below:

```ts
// Building a mapper
const sentenceSplitter = splitMapper.separator('. ');
const wordSplitter = splitMapper.separator(' ');
const wordsInSentencesMapper = sentenceSplitter.itemMapper<string[]>(wordSplitter);

// Standalone usage:
const input = 'Lorem ipsum dolor sit amet. consectetur adipiscing elit. Nullam placerat massa nec efficir. ';

const result = wordsInSentencesMapper(input);
// [
// ['Lorem', 'ipsum', 'dolor', 'sit', 'amet'],
// ['consectetur', 'adipiscing', 'elit'],
// ['Nullam', 'placerat', 'massa', 'nec', 'efficir'],
// ['']
// ]


// In a config:
// {row: 3, col: 1, key: 'words', mapper: wordsInSentencesMapper},

```

# See also

Expand All @@ -141,7 +180,7 @@ This is `type` related configuration, for more information please study examples
# Supported Node version:

8 | 9 | 10 | 11 | 12 | 13 | 14
--|---|---|---|----|---|---
--|---|----|----|----|----|---
❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅

Node v8 and v9 compatibly was drop after upgrade `ExcelJS` to version 4+ and it is able to turn on by downgrading `xlsx-import` to version 2.2.1 or if needed really important by requesting me directly.
1 change: 1 addition & 0 deletions src/mappers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { lowerCaseMapper } from './lowerCaseMapper';
export { isEmpty } from './isEmpty';
export { isFilled } from './isFilled';
export { jsonMapper } from './jsonMapper';
export { splitMapper } from './splitMapper';
8 changes: 3 additions & 5 deletions src/mappers/jsonMapper.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { ValueMapper } from '../abstracts/ValueMapper';

type JsonMapper = <TJsonResult>(value: string) => TJsonResult | null;

export const jsonMapper: JsonMapper = <TJsonResult extends any>(value: string): TJsonResult | null => {
try {
return JSON.parse(value)
return JSON.parse(value);
} catch (e) {
return null
return null;
}
}
};
55 changes: 55 additions & 0 deletions src/mappers/splitMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @description Configurable, immutable **splitMapper** with possibility to use specific `itemMapper` or `separator`.
* @see https://github.com/Siemienik/xlsx-import/issues/24
* @see https://github.com/Siemienik/xlsx-import#mappers
* @example standalone usaffe
* ```js
* // import {splitMapper} from ...;
* splitMapper('a,b,c,,d,e'); // ["a", "b", "c", "", "d", "e"]
* splitMapper.itemMapper<boolean>(isFilled)('a,b,c,,d,e'); // [true, true, true, false, true, true]
* splitMapper.itemMapper<boolean>(isFilled).separator('.')('a,b,,c,d'); // [true]
*
* // or:
* const dotMapper = splitMapper.itemMapper<string>(upperCaseMapper).separator('.');
* dotMapper('a,b,,c,d'); // ["A,B,,C,D"]
* dotMapper('a,b,.,c,d'); // ["A,B,", ",C,D"]
* dotMapper('a.b.c.d'); // ["A", "B", "C", "D"]
* ```
*
* @example example cfg
* ```js
* {
* key: "interests",
* index:1,
* // map value from: `"Cycling | SKIING | HikiNg"` to: `["cycling", "skiing", "hiking"]`
* mapper: splitMapper.separator(" | ").itemMapper(lowerCaseMapper)
* }
* ```
*/

import { ValueMapper } from '../abstracts/ValueMapper';
import { stringMapper } from './stringMapper';

export type SplitMapper<TItem> = ValueMapper<TItem[]> & {
separator: (separator: string) => SplitMapper<TItem>;
itemMapper: <TMapper>(itemMapper: ValueMapper<TMapper>) => SplitMapper<TMapper>;
};

interface ISplitMapperOptions<TItem> {
separator: string;
itemMapper: ValueMapper<TItem>;
}

const factory = <TItem>(options: Readonly<ISplitMapperOptions<TItem>>): SplitMapper<TItem> => {
const mapper: SplitMapper<TItem> = (value: string) => value.split(options.separator).map(options.itemMapper);

mapper.separator = separator => factory({ ...options, separator });
mapper.itemMapper = <TMapper>(itemMapper: ValueMapper<TMapper>) => factory<TMapper>({ ...options, itemMapper });

return mapper;
};

export const splitMapper: SplitMapper<string> = factory<string>({
itemMapper: stringMapper,
separator: ',',
});
96 changes: 96 additions & 0 deletions tests/unit/mappers/splitMapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as chai from 'chai';
import { isEmpty, lowerCaseMapper, upperCaseMapper } from '../../../src/mappers';
import { splitMapper } from '../../../src/mappers/splitMapper';

describe('UNIT TEST: src/mappers/', () => {
describe('splitMapper default', () => {
const dataProvider = [
// mappers are designed for string input only, like this:
{inValue: '', expectedResult: ['']},
{inValue: '0', expectedResult: ['0']},
{inValue: ',', expectedResult: ['', '']},
{inValue: '0,0', expectedResult: ['0', '0']},
{inValue: 'asd,dsa', expectedResult: ['asd','dsa']},
{inValue: ',asd,dsa', expectedResult: ['','asd','dsa']},
{inValue: ',asd,dsa,', expectedResult: ['','asd','dsa','']},
{inValue: ',asd;dsa,', expectedResult: ['','asd;dsa','']},
{inValue: '[1,2,3]', expectedResult: ['[1','2','3]']},

// invalid inputs won't to check
// {inValue: {}, expectedResult: ... },
];

dataProvider.forEach(({inValue, expectedResult}) => {
it(`the default splitMapper for input "${inValue}" SHOULD return "${JSON.stringify(expectedResult)}"`, () => {
chai.expect(splitMapper(inValue)).eql(expectedResult);
});
});
});

describe('splitMapper custom separator', () => {
const dataProvider = [
// mappers are designed for string input only, like this:
{inValue: '', inSeparator: ';', expectedResult: ['']},
{inValue: '0', inSeparator: ';', expectedResult: ['0']},
{inValue: ';', inSeparator: ';', expectedResult: ['', '']},
{inValue: '0;0', inSeparator: ';', expectedResult: ['0', '0']},
{inValue: 'asd;dsa', inSeparator: ';', expectedResult: ['asd','dsa']},
{inValue: ';asd;dsa', inSeparator: ';', expectedResult: ['','asd','dsa']},
{inValue: ';asd;dsa;', inSeparator: ';', expectedResult: ['','asd','dsa','']},
{inValue: ';asd.dsa;', inSeparator: ';', expectedResult: ['','asd.dsa','']},
{inValue: ',', inSeparator: ';', expectedResult: [',']},
{inValue: '0,0', inSeparator: ';', expectedResult: ['0,0']},
{inValue: 'asd,dsa', inSeparator: ';', expectedResult: ['asd,dsa']},
{inValue: ',asd,dsa', inSeparator: ';', expectedResult: [',asd,dsa']},
{inValue: ',asd,dsa,', inSeparator: ';', expectedResult: [',asd,dsa,']},
{inValue: ',asd;dsa,', inSeparator: ';', expectedResult: [',asd','dsa,']},
{inValue: '[1,2,3]', inSeparator: ';', expectedResult: ['[1,2,3]']},
{inValue: 'Ala ma kota, a kot ma Ale', inSeparator: ', a ', expectedResult: ['Ala ma kota', 'kot ma Ale']},
{inValue: 'bbbBBBbbbBBBaaa', inSeparator: 'BBB', expectedResult: ['bbb', 'bbb', 'aaa']},

// invalid inputs won't to check
// {inValue: {}, expectedResult: ... },
];
dataProvider.forEach(({inValue,inSeparator, expectedResult}) => {
it(`splitMapper with separator ${inSeparator} for input "${inValue}" SHOULD return "${JSON.stringify(expectedResult)}"`, () => {
const mapper = splitMapper.separator(inSeparator);
chai.expect(mapper(inValue)).eql(expectedResult);
});
});
});
describe('splitMapper custom mapper', () => {
const dataProvider = [
{inValue: 'Ala,ma,kota,,Kot,ma,Ale', inMapper:upperCaseMapper, expectedResult: ['ALA','MA','KOTA','','KOT','MA','ALE']},
{inValue: 'Ala,ma,kota,,Kot,ma,Ale', inMapper:lowerCaseMapper, expectedResult: ['ala','ma','kota','','kot','ma','ale']},
{inValue: 'Ala,ma,kota,,Kot,ma,Ale', inMapper:isEmpty, expectedResult: [false,false,false,true,false,false,false]},
{inValue: 'Ala,ma,kota,,Kot,ma,Ale', inMapper:(v:string) => v.length, expectedResult: [3,2,4,0,3,2,3]},
];
dataProvider.forEach(({inValue,inMapper, expectedResult}) => {
it(`splitMapper with separator ${inMapper.constructor.name} for input "${inValue}" SHOULD return "${JSON.stringify(expectedResult)}"`, () => {
const mapper = splitMapper.itemMapper<unknown>(inMapper);
chai.expect(mapper(inValue)).eql(expectedResult);
});
});
});
describe('splitMapper complex / advanced', () => {
it(`Map sentence into matrix word in sentence`, () => {
// assumptions
const input = 'Lorem ipsum dolor sit amet. consectetur adipiscing elit. Nullam placerat massa nec efficir. ';
const expectedResult = [
['Lorem', 'ipsum', 'dolor', 'sit', 'amet'],
['consectetur', 'adipiscing', 'elit'],
['Nullam', 'placerat', 'massa', 'nec', 'efficir'],
['']
]

// building a mapper
const sentenceSplitter = splitMapper.separator('. ');
const wordSplitter = splitMapper.separator(' ');
const wordsInSentencesMapper = sentenceSplitter.itemMapper(wordSplitter);

// testing
chai.expect(wordsInSentencesMapper(input)).eql(expectedResult);

});
});
});
9 changes: 7 additions & 2 deletions tests/unit/mappers/stringMapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '../../../src/mappers';
import { stringMapper } from '../../../src/mappers/stringMapper';

describe('Mappers, unit tests', () => {
describe('UNIT TEST: src/mappers/', () => {
describe('stringMapper', () => {
const dataProvider = [
// it is designed for string input only
Expand Down Expand Up @@ -36,6 +36,7 @@ describe('Mappers, unit tests', () => {
});
});

// todo move to upperCaseMapper.test.ts
describe('upperCaseMapper', () => {
const dataProvider = [
{ inValue: '', expectedResult: '' },
Expand All @@ -51,7 +52,8 @@ describe('Mappers, unit tests', () => {
});
});

describe('lowerCaseMapper', () => {
// todo move to lowerCaseMapper.test.ts
describe('lowerCaseMapper', () => {
const dataProvider = [
{ inValue: '', expectedResult: '' },
{ inValue: 'asd', expectedResult: 'asd' },
Expand All @@ -66,6 +68,7 @@ describe('Mappers, unit tests', () => {
});
});

// todo move to jsonMapper.test.ts
describe('jsonMapper', () => {
const dataProvider = [
{ inValue: '', expectedResult: null },
Expand All @@ -89,6 +92,7 @@ describe('Mappers, unit tests', () => {
});
});

// todo move to isEmpty.test.ts
describe('isEmpty', () => {
const dataProvider = [
{ inValue: '', expectedResult: true },
Expand All @@ -105,6 +109,7 @@ describe('Mappers, unit tests', () => {
});
});

// todo move to isFilled.test.ts
describe('isFilled', () => {
const dataProvider = [
{ inValue: '', expectedResult: false },
Expand Down

0 comments on commit d62900e

Please sign in to comment.