diff --git a/README.md b/README.md index a521e87..e7002b7 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ var result = utterances(template, slots, dictionary); #### slots -The slots object is a simple Name:Type mapping. The type must be one of Amazon's supported slot types: LITERAL, NUMBER, DATE, TIME, DURATION +The slots object is a simple Name:Type mapping. The type must be one of Amazon's supported slot types: LITERAL, NUMBER, DATE, TIME, DURATION. You can use custom slot types, but you cannot integrate them with the slots object here and must instead do so with an [alternate syntax](#custom-slot-types). #### Using a Dictionary @@ -117,3 +117,16 @@ Number ranges can also increment in steps "what is your color" "what is your favorite color" ``` + +#### Custom Slot Types + +You may want to work with [Custom Slot Types](https://developer.amazon.com/appsandservices/solutions/alexa/alexa-skills-kit/docs/defining-the-voice-interface#The Speech Input Data) registered in your interaction model. You can use a special syntax to leave a curly-braced slot name unparsed. For example, if you have defined in your skill a `FRUIT_TYPE` with the values `Apple`, `Orange` and `Lemon` for the slot `Fruit`, you can keep `Fruit` a curly-braced literal as follows + +```javascript +"{my|your} {favorite|least favorite} snack is {-|Fruit}" +=> +"my favorite snack is {Fruit}" +"your favorite snack is {Fruit}" +"my least favorite snack is {Fruit}" +"your least favorite snack is {Fruit}" +``` diff --git a/index.js b/index.js index a714f03..4888873 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,12 @@ function expandNumberRange(start, end, by) { return converted; } +// Determine if a curly brace expression is a Slot name literal +// Returns true if expression is of the form {-|Name}, false otherwise +function isSlotLiteral(braceExpression) { + return braceExpression.substring(0, 3) == "{-|"; +} + // Recognize shortcuts in utterance definitions and swap them out with the actual values function expandShortcuts(str, slots, dictionary) { // If the string is found in the dictionary, just provide the matching values @@ -90,6 +96,11 @@ function generateUtterances(str, slots, dictionary, exhaustiveUtterances) { var placeholders=[], utterances=[], slotmap={}, slotValues=[]; // First extract sample placeholders values from the string str = str.replace(/\{([^\}]+)\}/g, function(match,p1) { + + if (isSlotLiteral(match)) { + return match; + } + var expandedValues=[], slot, values = p1.split("|"); // If the last of the values is a SLOT name, we need to keep the name in the utterances if (values && values.length && values.length>1 && slots && typeof slots[values[values.length-1]]!="undefined") { @@ -134,14 +145,20 @@ function generateUtterances(str, slots, dictionary, exhaustiveUtterances) { }); // Replace slot placeholders utterance = utterance.replace(/\{(.*?)\}/g,function(match,p1){ - return "{"+values[slotmap[p1]]+"|"+p1+"}"; + return (isSlotLiteral(match)) ? match : "{"+values[slotmap[p1]]+"|"+p1+"}"; }); utterances.push( utterance ); }); } else { - return [str]; + utterances = [str]; + } + + // Convert all {-|Name} to {Name} to accomodate slot literals + for (var idx in utterances) { + utterances[idx] = utterances[idx].replace(/\{\-\|/g, "{"); } + return utterances; } diff --git a/test/index.js b/test/index.js index b82500f..2e905b3 100644 --- a/test/index.js +++ b/test/index.js @@ -108,3 +108,18 @@ test('exhaustive vs non-exhaustive expansion', function (t) { ]); t.end(); }); + +test('raw curly braces for custom slot types', function (t) { + var dictionary = {}; + var slots = {"Artist": "CUSTOM_TYPE"}; + var template = "{my|your} {favorite|least favorite} fruit is {-|Fruit}"; + + var result = utterances(template, slots, dictionary); + t.deepEqual(result, [ + "my favorite fruit is {Fruit}", + "your favorite fruit is {Fruit}", + "my least favorite fruit is {Fruit}", + "your least favorite fruit is {Fruit}" + ]); + t.end(); +});