Skip to content

Commit

Permalink
feat: add parseFixed api
Browse files Browse the repository at this point in the history
  • Loading branch information
yunnysunny committed Nov 17, 2023
1 parent f911146 commit 7e9c2a3
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 26 deletions.
25 changes: 19 additions & 6 deletions README.md
Expand Up @@ -20,34 +20,47 @@ A tool for parsing command-line tabular output data.
npm install @whyun/read-table --save

## Usage
### Basic use
### Parse field by space
#### Basic use
```javascript
const { parse } = require('@whyun/read-table');
const { parseRaw } = require('@whyun/read-table');
const table = `
key1 key2 key3
aaa bbb ccc
111 222 333
`;
const rv = parse(table);
const rv = parseRaw(table);
console.log(JSON.stringify(rv));
```
Will print `{"lines":[["aaa","bbb","ccc"],["111","222","333"]],"firstLine":["key1","key2","key3"]}`.
### With quotation fields
#### With quotation fields
If field value has space, you can use quotation to wrapper it.
```javascript
const { parse } = require('@whyun/read-table');
const { parseRaw } = require('@whyun/read-table');
const table2 = `
key1 key2 key3
aaa bbb "ccc xxx"
111 "222 yyy" 333
"222 zzz" 777 888
"333 888" "444 000" 444
`
const rv2 = parse(table2);
const rv2 = parseRaw(table2);
console.log(JSON.stringify(rv2));
```
Will print `{"lines":[["aaa","bbb","ccc xxx"],["111","222 yyy","333"],["222 zzz","777","888"],["333 888","444 000","444"]],"firstLine":["key1","key2","key3"]}`


### Parse with fixed table cell length
If all columns have fixed length, you can call function `parseFixed`.
```javascript

const { parseFixed } = require('');
const { lines } = parseFixed(`head1 head2 head3
value10 value11 value12
value20 value21 value22`);
console.log(lines);
```
Will print `[{"head1":"value10","head2":"value11","head3":"value12"},{"head1":"value20","head2":"value21","head3":"value22"}]`.
## License

[MIT](LICENSE)
Expand Down
40 changes: 30 additions & 10 deletions index.d.ts
@@ -1,19 +1,39 @@

interface ParseOptions {
export interface ParseOptions {
/**
* @defaultValue [' ', '/t']
*/
splitChars?: string[]
}
interface ReadTable {
parse(output: string, options?: ParseOptions): {
fistLine: string[],
lines: string[][]
}
parseObject(output: string, options?: ParseOptions) : {
fistLine: string[],
records: Map<string, string>[]
}
export interface FieldName {
begin: number
end: number
name: string
len: number
}
export interface Line {
[fieldName: string]: string
}
export interface RawLines {
fistLine: string[],
lines: string[][]
}
export interface ObjectLines {
fistLine: string[],
records: Map<string, string>[]
}
export interface Head {
fieldNames: FieldName[]
firstLenEnd: number
}
export interface FixedLines {
lines: Line[]
fieldNames: FieldName[]
}
export interface ReadTable {
parseRaw(output: string, options?: ParseOptions): RawLines
parseRaw2Object(output: string, options?: ParseOptions) : ObjectLines
parseFixed(output: string): FixedLines
}
declare const readTable: ReadTable
export default readTable
128 changes: 123 additions & 5 deletions index.js
Expand Up @@ -2,16 +2,18 @@ const SPACE_CHARS = [
' ',
'\t',
];
const SPACE = ' ';
const QUOTATION_CHAR = '"';
function isSpace(char, splitChars) {
return splitChars.indexOf(char) !== -1 ? char : null;
}
/**
* Parsing table like output to object array
* @param {string} output
* @param {ParseOptions} options
* @param {import('.').ParseOptions} options
* @returns {import('.').RawLines}
*/
exports.parse = function(output, options) {
exports.parseRaw = function(output, options) {
options = {
splitChars: SPACE_CHARS,
...(options || {})
Expand Down Expand Up @@ -77,7 +79,7 @@ exports.parse = function(output, options) {
if (!quotationStart) {
quotationStart = true;
quotationEnd = false;
// ignore first quotation char
// check if currentField has value to ignore first quotation char
if (currentField) {
addField();
}
Expand Down Expand Up @@ -112,12 +114,128 @@ exports.parse = function(output, options) {
firstLine
};
};
/**
*
* @param {string} output
* @returns {import('.').Head}
*/
exports.parseFixedHead = function(output) {
const len = output.length;
let firstLenEnd = 0;
let fieldName = '';
const fieldNames = [];
let beginIndex = -1;
let lastChar = '';
// const fieldCount = 0;
let count = 0;
function addField(i) {
if (fieldName) {
if (count === 0) {
fieldNames[0].name = fieldName;
fieldNames[0].end = i - 1;
fieldNames[0].len = i - beginIndex;
} else {
const lastFiled = fieldNames[count-1];

fieldNames[count] = {
name: fieldName,
begin: lastFiled.end + 1,
end: i - 1,
len: i - 1 - lastFiled.end,
};
}
count++;
fieldName = '';
}
}
firstLen: for (let i=0; i < len; i++) {
const char = output[i];
switch (char) {
case'\r':
lastChar = char;
break;
case '\n':
if (fieldNames.length > 0) {
firstLenEnd = i;
addField(i);
break firstLen;
}
lastChar = char;
break;
case SPACE:
lastChar = SPACE;
break;
default:
if (beginIndex === -1) {
beginIndex = i;
if (!fieldNames[0]) {
fieldNames[0] = {begin: i, end: i, name: '', len: 0};
}
}
if (lastChar === SPACE) {//to create field
addField(i);
}
fieldName += char;
lastChar = char;
break;
}
}
return { fieldNames, firstLenEnd };
};
/**
* Parse table with fixed table cells.
* @param {string} output
* @returns {import('.').FixedLines}
*/
exports.parseFixed = function(output) {
const { fieldNames, firstLenEnd } = exports.parseFixedHead(output);
const len = output.length;
let pos = firstLenEnd + 1;

const fieldCount = fieldNames.length;
let fieldStep = 0;
let line = {};
const lines = [];
while(pos < len) {
const field = fieldNames[fieldStep];
let fullFieldValue = '';
if (fieldStep === 0) {//newline begin
if (output[pos] === '\n' || output[pos] === '\r') {
pos++;
continue;// skip empty line
}
}
if (fieldStep < fieldCount - 1) {
fullFieldValue = output.substring(pos, pos + field.len);
line[field.name] = fullFieldValue.trimEnd();
pos += field.len;
fieldStep++;
} else {
let char = output[pos];

while(char !== '\n') {
if (char !== '\r') {
fullFieldValue += char;
}
pos++;
char = output[pos];
}
line[field.name] = fullFieldValue.trimEnd();
fieldStep = 0;
lines.push(line);
line = {};
}
}
return { lines, fieldNames };

};
/**
* Parsing table like output to object array
* @param {string} output
* @returns {import('.').ObjectLines}
*/
exports.parse2Object = function(output, options) {
const parsed = exports.parse(output, options);
exports.parseRaw2Object = function(output, options) {
const parsed = exports.parseRaw(output, options);
const { firstLine, lines } = parsed;
const keyLen = firstLine.length;
if (lines.length === 0) {
Expand Down
10 changes: 5 additions & 5 deletions test/basic.test.js
@@ -1,16 +1,16 @@
const { parse } = require('..');
const { parseRaw } = require('..');
const { expect } = require('chai');
const fs = require('fs');
const path = require('path');
const { parse2Object } = require('..');
const { parseRaw2Object } = require('..');
describe('basic test', function () {
it('plain field', function () {
const table = `
key1 key2 key3
aaa bbb ccc
111 222 333
`;
const rv = parse(table);
const rv = parseRaw(table);
expect(rv).to.deep.equal({
'lines': [
['aaa', 'bbb', 'ccc'],
Expand All @@ -27,7 +27,7 @@ aaa bbb "ccc xxx"
"222 zzz" 777 888
"333 888" "444 000" 444
`;
const rv2 = parse(table2);
const rv2 = parseRaw(table2);
expect(rv2).to.deep.equal({
'lines': [
['aaa', 'bbb', 'ccc xxx'],
Expand All @@ -40,7 +40,7 @@ aaa bbb "ccc xxx"
});
it('process test', function() {
const content = fs.readFileSync(path.join(__dirname, 'resources/process.txt'));
const rv = parse2Object(content.toString(), {splitChars: ['\t']});
const rv = parseRaw2Object(content.toString(), {splitChars: ['\t']});
expect(rv.records.length).to.gt(0);
});
});
Expand Down

0 comments on commit 7e9c2a3

Please sign in to comment.