Skip to content

Commit

Permalink
Add multi tags covering all tags with the fixed prefix
Browse files Browse the repository at this point in the history
  • Loading branch information
rlidwka committed Dec 24, 2020
1 parent 359b264 commit 73ef02c
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `styles: { '!!null': 'empty' }` option for dumper
(serializes `{ foo: null }` as "`foo: `"), #570.
- Added `replacer` option (similar to option in JSON.stringify), #339.
- Custom `Tag` can now handle all tags or multiple tags with the same prefix, #385.

### Fixed
- Astral characters are no longer encoded by dump/safeDump, #587.
Expand Down
42 changes: 42 additions & 0 deletions examples/handle_unknown_types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use strict';

/*eslint-disable no-console*/

var fs = require('fs');
var path = require('path');
var util = require('util');
var yaml = require('../');


var tags = [ 'scalar', 'sequence', 'mapping' ].map(function (kind) {
// first argument here is a prefix, so this type will handle anything starting with !
return new yaml.Type('!', {
kind: kind,
multi: true,
construct: function (data, type) {
return { type: type, data: data };
}
});
});

var SCHEMA = yaml.DEFAULT_SCHEMA.extend(tags);

// do not execute the following if file is required (http://stackoverflow.com/a/6398335)
if (require.main === module) {

// And read a document using that schema.
fs.readFile(path.join(__dirname, 'handle_unknown_types.yml'), 'utf8', function (error, data) {
var loaded;

if (!error) {
loaded = yaml.load(data, { schema: SCHEMA });
console.log(util.inspect(loaded, false, 20, true));
} else {
console.error(error.stack || error.message || String(error));
}
});
}

// There are some exports to play with this example interactively.
module.exports.tags = tags;
module.exports.SCHEMA = SCHEMA;
4 changes: 4 additions & 0 deletions examples/handle_unknown_types.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
subject: Handling unknown types in JS-YAML
scalar: !unknown_scalar_tag 123
sequence: !unknown_sequence_tag [ 1, 2, 3 ]
mapping: !unknown_mapping_tag { foo: 1, bar: 2 }
28 changes: 22 additions & 6 deletions lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,7 @@ function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact
hasContent = false,
typeIndex,
typeQuantity,
typeList,
type,
flowIndent,
blockIndent;
Expand Down Expand Up @@ -1459,23 +1460,38 @@ function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact
break;
}
}
} else if (_hasOwnProperty.call(state.typeMap[state.kind || 'fallback'], state.tag)) {
type = state.typeMap[state.kind || 'fallback'][state.tag];
} else {
if (_hasOwnProperty.call(state.typeMap[state.kind || 'fallback'], state.tag)) {
type = state.typeMap[state.kind || 'fallback'][state.tag];
} else {
// looking for multi type
type = null;
typeList = state.typeMap.multi[state.kind || 'fallback'];

for (typeIndex = 0, typeQuantity = typeList.length; typeIndex < typeQuantity; typeIndex += 1) {
if (state.tag.slice(0, typeList[typeIndex].tag.length) === typeList[typeIndex].tag) {
type = typeList[typeIndex];
break;
}
}
}

if (!type) {
throwError(state, 'unknown tag !<' + state.tag + '>');
}

if (state.result !== null && type.kind !== state.kind) {
throwError(state, 'unacceptable node kind for !<' + state.tag + '> tag; it should be "' + type.kind + '", not "' + state.kind + '"');
}

if (!type.resolve(state.result)) { // `state.result` updated in resolver if matched
if (!type.resolve(state.result, state.tag)) { // `state.result` updated in resolver if matched
throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag');
} else {
state.result = type.construct(state.result);
state.result = type.construct(state.result, state.tag);
if (state.anchor !== null) {
state.anchorMap[state.anchor] = state.result;
}
}
} else {
throwError(state, 'unknown tag !<' + state.tag + '>');
}
}

Expand Down
24 changes: 21 additions & 3 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ function compileList(schema, name, result) {

schema[name].forEach(function (currentType) {
result.forEach(function (previousType, previousIndex) {
if (previousType.tag === currentType.tag && previousType.kind === currentType.kind) {
if (previousType.tag === currentType.tag &&
previousType.kind === currentType.kind &&
previousType.multi === currentType.multi) {

exclude.push(previousIndex);
}
});
Expand All @@ -30,11 +33,22 @@ function compileMap(/* lists... */) {
scalar: {},
sequence: {},
mapping: {},
fallback: {}
fallback: {},
multi: {
scalar: [],
sequence: [],
mapping: [],
fallback: []
}
}, index, length;

function collectType(type) {
result[type.kind][type.tag] = result['fallback'][type.tag] = type;
if (type.multi) {
result.multi[type.kind].push(type);
result.multi['fallback'].push(type);
} else {
result[type.kind][type.tag] = result['fallback'][type.tag] = type;
}
}

for (index = 0, length = arguments.length; index < length; index += 1) {
Expand Down Expand Up @@ -79,6 +93,10 @@ Schema.prototype.extend = function extend(definition) {
if (type.loadKind && type.loadKind !== 'scalar') {
throw new YAMLException('There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.');
}

if (type.multi) {
throw new YAMLException('There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.');
}
});

explicit.forEach(function (type) {
Expand Down
2 changes: 2 additions & 0 deletions lib/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var YAMLException = require('./exception');

var TYPE_CONSTRUCTOR_OPTIONS = [
'kind',
'multi',
'resolve',
'construct',
'instanceOf',
Expand Down Expand Up @@ -51,6 +52,7 @@ function Type(tag, options) {
this.predicate = options['predicate'] || null;
this.represent = options['represent'] || null;
this.defaultStyle = options['defaultStyle'] || null;
this.multi = options['multi'] || false;
this.styleAliases = compileStyleAliases(options['styleAliases'] || null);

if (YAML_NODE_KINDS.indexOf(this.kind) === -1) {
Expand Down
99 changes: 99 additions & 0 deletions test/issues/0385.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
'use strict';


const assert = require('assert');
const yaml = require('../../');


describe('Multi tag', function () {
it('should process multi tags', function () {
let tags = [ 'scalar', 'mapping', 'sequence' ].map(kind =>
new yaml.Type('!', {
kind,
multi: true,
resolve: function () {
return true;
},
construct: function (value, tag) {
return { kind, tag, value };
}
})
);

let schema = yaml.DEFAULT_SCHEMA.extend(tags);

let expected = [
{
kind: 'scalar',
tag: '!t1',
value: '123'
},
{
kind: 'sequence',
tag: '!t2',
value: [ 1, 2, 3 ]
},
{
kind: 'mapping',
tag: '!t3',
value: { a: 1, b: 2 }
}
];

assert.deepStrictEqual(yaml.load(`
- !t1 123
- !t2 [ 1, 2, 3 ]
- !t3 { a: 1, b: 2 }
`, {
schema: schema
}), expected);
});


it('should process tags depending on prefix', function () {
let tags = [ '!foo', '!bar', '!' ].map(prefix =>
new yaml.Type(prefix, {
kind: 'scalar',
multi: true,
resolve: function () {
return true;
},
construct: function (value, tag) {
return { prefix, tag, value };
}
})
);

tags.push(
new yaml.Type('!bar', {
kind: 'scalar',
resolve: function () {
return true;
},
construct: function (value) {
return { single: true, value };
}
})
);

let schema = yaml.DEFAULT_SCHEMA.extend(tags);

let expected = [
{ prefix: '!foo', tag: '!foo', value: '1' },
{ prefix: '!foo', tag: '!foo2', value: '2' },
{ single: true, value: '3' },
{ prefix: '!bar', tag: '!bar2', value: '4' },
{ prefix: '!', tag: '!baz', value: '5' }
];

assert.deepStrictEqual(yaml.load(`
- !foo 1
- !foo2 2
- !bar 3
- !bar2 4
- !baz 5
`, {
schema: schema
}), expected);
});
});

0 comments on commit 73ef02c

Please sign in to comment.