Skip to content

Commit 73ef02c

Browse files
committed
Add multi tags covering all tags with the fixed prefix
1 parent 359b264 commit 73ef02c

File tree

7 files changed

+191
-9
lines changed

7 files changed

+191
-9
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3434
- Added `styles: { '!!null': 'empty' }` option for dumper
3535
(serializes `{ foo: null }` as "`foo: `"), #570.
3636
- Added `replacer` option (similar to option in JSON.stringify), #339.
37+
- Custom `Tag` can now handle all tags or multiple tags with the same prefix, #385.
3738

3839
### Fixed
3940
- Astral characters are no longer encoded by dump/safeDump, #587.

examples/handle_unknown_types.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
3+
/*eslint-disable no-console*/
4+
5+
var fs = require('fs');
6+
var path = require('path');
7+
var util = require('util');
8+
var yaml = require('../');
9+
10+
11+
var tags = [ 'scalar', 'sequence', 'mapping' ].map(function (kind) {
12+
// first argument here is a prefix, so this type will handle anything starting with !
13+
return new yaml.Type('!', {
14+
kind: kind,
15+
multi: true,
16+
construct: function (data, type) {
17+
return { type: type, data: data };
18+
}
19+
});
20+
});
21+
22+
var SCHEMA = yaml.DEFAULT_SCHEMA.extend(tags);
23+
24+
// do not execute the following if file is required (http://stackoverflow.com/a/6398335)
25+
if (require.main === module) {
26+
27+
// And read a document using that schema.
28+
fs.readFile(path.join(__dirname, 'handle_unknown_types.yml'), 'utf8', function (error, data) {
29+
var loaded;
30+
31+
if (!error) {
32+
loaded = yaml.load(data, { schema: SCHEMA });
33+
console.log(util.inspect(loaded, false, 20, true));
34+
} else {
35+
console.error(error.stack || error.message || String(error));
36+
}
37+
});
38+
}
39+
40+
// There are some exports to play with this example interactively.
41+
module.exports.tags = tags;
42+
module.exports.SCHEMA = SCHEMA;

examples/handle_unknown_types.yml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
subject: Handling unknown types in JS-YAML
2+
scalar: !unknown_scalar_tag 123
3+
sequence: !unknown_sequence_tag [ 1, 2, 3 ]
4+
mapping: !unknown_mapping_tag { foo: 1, bar: 2 }

lib/loader.js

+22-6
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,7 @@ function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact
13341334
hasContent = false,
13351335
typeIndex,
13361336
typeQuantity,
1337+
typeList,
13371338
type,
13381339
flowIndent,
13391340
blockIndent;
@@ -1459,23 +1460,38 @@ function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact
14591460
break;
14601461
}
14611462
}
1462-
} else if (_hasOwnProperty.call(state.typeMap[state.kind || 'fallback'], state.tag)) {
1463-
type = state.typeMap[state.kind || 'fallback'][state.tag];
1463+
} else {
1464+
if (_hasOwnProperty.call(state.typeMap[state.kind || 'fallback'], state.tag)) {
1465+
type = state.typeMap[state.kind || 'fallback'][state.tag];
1466+
} else {
1467+
// looking for multi type
1468+
type = null;
1469+
typeList = state.typeMap.multi[state.kind || 'fallback'];
1470+
1471+
for (typeIndex = 0, typeQuantity = typeList.length; typeIndex < typeQuantity; typeIndex += 1) {
1472+
if (state.tag.slice(0, typeList[typeIndex].tag.length) === typeList[typeIndex].tag) {
1473+
type = typeList[typeIndex];
1474+
break;
1475+
}
1476+
}
1477+
}
1478+
1479+
if (!type) {
1480+
throwError(state, 'unknown tag !<' + state.tag + '>');
1481+
}
14641482

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

1469-
if (!type.resolve(state.result)) { // `state.result` updated in resolver if matched
1487+
if (!type.resolve(state.result, state.tag)) { // `state.result` updated in resolver if matched
14701488
throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag');
14711489
} else {
1472-
state.result = type.construct(state.result);
1490+
state.result = type.construct(state.result, state.tag);
14731491
if (state.anchor !== null) {
14741492
state.anchorMap[state.anchor] = state.result;
14751493
}
14761494
}
1477-
} else {
1478-
throwError(state, 'unknown tag !<' + state.tag + '>');
14791495
}
14801496
}
14811497

lib/schema.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ function compileList(schema, name, result) {
1111

1212
schema[name].forEach(function (currentType) {
1313
result.forEach(function (previousType, previousIndex) {
14-
if (previousType.tag === currentType.tag && previousType.kind === currentType.kind) {
14+
if (previousType.tag === currentType.tag &&
15+
previousType.kind === currentType.kind &&
16+
previousType.multi === currentType.multi) {
17+
1518
exclude.push(previousIndex);
1619
}
1720
});
@@ -30,11 +33,22 @@ function compileMap(/* lists... */) {
3033
scalar: {},
3134
sequence: {},
3235
mapping: {},
33-
fallback: {}
36+
fallback: {},
37+
multi: {
38+
scalar: [],
39+
sequence: [],
40+
mapping: [],
41+
fallback: []
42+
}
3443
}, index, length;
3544

3645
function collectType(type) {
37-
result[type.kind][type.tag] = result['fallback'][type.tag] = type;
46+
if (type.multi) {
47+
result.multi[type.kind].push(type);
48+
result.multi['fallback'].push(type);
49+
} else {
50+
result[type.kind][type.tag] = result['fallback'][type.tag] = type;
51+
}
3852
}
3953

4054
for (index = 0, length = arguments.length; index < length; index += 1) {
@@ -79,6 +93,10 @@ Schema.prototype.extend = function extend(definition) {
7993
if (type.loadKind && type.loadKind !== 'scalar') {
8094
throw new YAMLException('There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.');
8195
}
96+
97+
if (type.multi) {
98+
throw new YAMLException('There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.');
99+
}
82100
});
83101

84102
explicit.forEach(function (type) {

lib/type.js

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var YAMLException = require('./exception');
44

55
var TYPE_CONSTRUCTOR_OPTIONS = [
66
'kind',
7+
'multi',
78
'resolve',
89
'construct',
910
'instanceOf',
@@ -51,6 +52,7 @@ function Type(tag, options) {
5152
this.predicate = options['predicate'] || null;
5253
this.represent = options['represent'] || null;
5354
this.defaultStyle = options['defaultStyle'] || null;
55+
this.multi = options['multi'] || false;
5456
this.styleAliases = compileStyleAliases(options['styleAliases'] || null);
5557

5658
if (YAML_NODE_KINDS.indexOf(this.kind) === -1) {

test/issues/0385.js

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
'use strict';
2+
3+
4+
const assert = require('assert');
5+
const yaml = require('../../');
6+
7+
8+
describe('Multi tag', function () {
9+
it('should process multi tags', function () {
10+
let tags = [ 'scalar', 'mapping', 'sequence' ].map(kind =>
11+
new yaml.Type('!', {
12+
kind,
13+
multi: true,
14+
resolve: function () {
15+
return true;
16+
},
17+
construct: function (value, tag) {
18+
return { kind, tag, value };
19+
}
20+
})
21+
);
22+
23+
let schema = yaml.DEFAULT_SCHEMA.extend(tags);
24+
25+
let expected = [
26+
{
27+
kind: 'scalar',
28+
tag: '!t1',
29+
value: '123'
30+
},
31+
{
32+
kind: 'sequence',
33+
tag: '!t2',
34+
value: [ 1, 2, 3 ]
35+
},
36+
{
37+
kind: 'mapping',
38+
tag: '!t3',
39+
value: { a: 1, b: 2 }
40+
}
41+
];
42+
43+
assert.deepStrictEqual(yaml.load(`
44+
- !t1 123
45+
- !t2 [ 1, 2, 3 ]
46+
- !t3 { a: 1, b: 2 }
47+
`, {
48+
schema: schema
49+
}), expected);
50+
});
51+
52+
53+
it('should process tags depending on prefix', function () {
54+
let tags = [ '!foo', '!bar', '!' ].map(prefix =>
55+
new yaml.Type(prefix, {
56+
kind: 'scalar',
57+
multi: true,
58+
resolve: function () {
59+
return true;
60+
},
61+
construct: function (value, tag) {
62+
return { prefix, tag, value };
63+
}
64+
})
65+
);
66+
67+
tags.push(
68+
new yaml.Type('!bar', {
69+
kind: 'scalar',
70+
resolve: function () {
71+
return true;
72+
},
73+
construct: function (value) {
74+
return { single: true, value };
75+
}
76+
})
77+
);
78+
79+
let schema = yaml.DEFAULT_SCHEMA.extend(tags);
80+
81+
let expected = [
82+
{ prefix: '!foo', tag: '!foo', value: '1' },
83+
{ prefix: '!foo', tag: '!foo2', value: '2' },
84+
{ single: true, value: '3' },
85+
{ prefix: '!bar', tag: '!bar2', value: '4' },
86+
{ prefix: '!', tag: '!baz', value: '5' }
87+
];
88+
89+
assert.deepStrictEqual(yaml.load(`
90+
- !foo 1
91+
- !foo2 2
92+
- !bar 3
93+
- !bar2 4
94+
- !baz 5
95+
`, {
96+
schema: schema
97+
}), expected);
98+
});
99+
});

0 commit comments

Comments
 (0)