Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #24: Add splitMapper + customisable options #50

Merged
merged 3 commits into from
Oct 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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},
// three fields based on one cell but with different mapper
{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: ',',
});
100 changes: 100 additions & 0 deletions tests/unit/mappers/splitMapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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', () => {
const getWordInSentencesMapper = () =>{
const sentenceSplitter = splitMapper.separator('. ');
const wordSplitter = splitMapper.separator(' ');
return sentenceSplitter.itemMapper(wordSplitter);
}

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'],
['']
]

// prepare
const wordsInSentencesMapper = getWordInSentencesMapper();

// 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