Transformations: Support escaped characters in key-value pair parsing (#47901)

* Additional logic to handle quoted values

* Simple test with quoted values that include spaces

* Update test description

* Updating logic to account for nested quotes

- Adding additional test for nested quotes

* Strip out line breaks and carriage returns pre-processing

* Fix typo in test result

* Update key-value logic to avoid regexp

- Minor changes to account for null values

* Correct escaping on test

* Additional tests

- Test for null values
- Test for nested separator characters
- Update quoting
pull/47969/head
Andreas Christou 3 years ago committed by GitHub
parent 459c64fd44
commit d0b41f882e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 60
      public/app/features/transformers/extractFields/fieldExtractor.test.ts
  2. 110
      public/app/features/transformers/extractFields/fieldExtractors.ts

@ -14,6 +14,66 @@ describe('Extract fields from text', () => {
`);
});
it('Test key-values with single/double quotes', async () => {
const extractor = fieldExtractors.get(FieldExtractorID.KeyValues);
const out = extractor.parse('a="1", "b"=\'2\',c=3 x:y ;\r\nz="d and 4"');
expect(out).toMatchInlineSnapshot(`
Object {
"a": "1",
"b": "2",
"c": "3",
"x": "y",
"z": "d and 4",
}
`);
});
it('Test key-values with nested single/double quotes', async () => {
const extractor = fieldExtractors.get(FieldExtractorID.KeyValues);
const out = extractor.parse(
`a="1", "b"=\'2\',c=3 x:y ;\r\nz="dbl_quotes=\\"Double Quotes\\" sgl_quotes='Single Quotes'"`
);
expect(out).toMatchInlineSnapshot(`
Object {
"a": "1",
"b": "2",
"c": "3",
"x": "y",
"z": "dbl_quotes=\\"Double Quotes\\" sgl_quotes='Single Quotes'",
}
`);
});
it('Test key-values with nested separator characters', async () => {
const extractor = fieldExtractors.get(FieldExtractorID.KeyValues);
const out = extractor.parse(`a="1", "b"=\'2\',c=3 x:y ;\r\nz="This is; testing& validating, 1=:2"`);
expect(out).toMatchInlineSnapshot(`
Object {
"a": "1",
"b": "2",
"c": "3",
"x": "y",
"z": "This is; testing& validating, 1=:2",
}
`);
});
it('Test key-values where some values are null', async () => {
const extractor = fieldExtractors.get(FieldExtractorID.KeyValues);
const out = extractor.parse(`a=, "b"=\'2\',c=3 x: `);
expect(out).toMatchInlineSnapshot(`
Object {
"a": "",
"b": "2",
"c": "3",
"x": "",
}
`);
});
it('Split key+values', async () => {
const extractor = fieldExtractors.get(FieldExtractorID.KeyValues);
const out = extractor.parse('a="1", "b"=\'2\',c=3 x:y ;\r\nz="7"');

@ -19,33 +19,99 @@ const extJSON: FieldExtractor = {
},
};
// strips quotes and leading/trailing braces in prom labels
const stripDecor = /['"]|^\{|\}$/g;
// splits on whitespace and other label pair delimiters
const splitLines = /[\s,;&]+/g;
// splits kv pairs
const splitPair = /[=:]/g;
function parseKeyValuePairs(raw: string): Record<string, string> {
const buff: string[] = []; // array of characters
let esc = '';
let key = '';
const obj: Record<string, string> = {};
for (let i = 0; i < raw.length; i++) {
let c = raw[i];
if (c === esc) {
esc = '';
c = raw[++i];
}
const extLabels: FieldExtractor = {
id: FieldExtractorID.KeyValues,
name: 'Key+value pairs',
description: 'Look for a=b, c: d values in the line',
parse: (v: string) => {
const obj: Record<string, any> = {};
const isEscaped = c === '\\';
if (isEscaped) {
c = raw[++i];
}
// When escaped just append
if (isEscaped || esc.length) {
buff.push(c);
continue;
}
v.trim()
.replace(stripDecor, '')
.split(splitLines)
.forEach((pair) => {
let [k, v] = pair.split(splitPair);
if (c === `"` || c === `'`) {
esc = c;
}
if (k != null) {
obj[k] = v;
switch (c) {
case ':':
case '=':
if (buff.length) {
if (key) {
obj[key] = '';
}
key = buff.join('');
buff.length = 0; // clear values
}
});
break;
return obj;
},
// escape chars
case `"`:
case `'`:
// whitespace
case ` `:
case `\n`:
case `\t`:
case `\r`:
case `\n`:
if (buff.length && key === '') {
obj[buff.join('')] = '';
buff.length = 0;
}
// seperators
case ',':
case ';':
case '&':
case '{':
case '}':
if (buff.length) {
const val = buff.join('');
if (key.length) {
obj[key] = val;
key = '';
} else {
key = val;
}
buff.length = 0; // clear values
}
break;
// append our buffer
default:
buff.push(c);
if (i === raw.length - 1) {
if (key === '' && buff.length) {
obj[buff.join('')] = '';
buff.length = 0;
}
}
}
}
if (key.length) {
obj[key] = buff.join('');
}
return obj;
}
const extLabels: FieldExtractor = {
id: FieldExtractorID.KeyValues,
name: 'Key+value pairs',
description: 'Look for a=b, c: d values in the line',
parse: parseKeyValuePairs,
};
const fmts = [extJSON, extLabels];

Loading…
Cancel
Save