Repository: RasaHQ/rasa

Issue:
Leave curly braces in template
**Rasa version**:
master

**Issue**:
Recently on the forum, this question was raised or issue was reported.

https://forum.rasa.com/t/how-to-print-curly-braces/12043

When template value has curly braces `{{"variable":"{value}"}}` it failed to replace slot.

proposed solution https://github.com/RasaHQ/rasa/pull/3951

Discussion:
akelad: Thanks for submitting this PR, we'll give it a review as soon as possible

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 56db9ebfb4f..61313e489fe 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -27,6 +27,7 @@ Fixed
 -----
 - ``rasa test core`` can handle compressed model files
 - Rasa can handle story files containing multi line comments
+- Template will retain `{` if escaped with `{`. e.g. `{{"foo": {bar}}}` will result in `{"foo": "replaced value"}`
 
 
 [1.1.8] - 2019-07-25
diff --git a/rasa/core/nlg/interpolator.py b/rasa/core/nlg/interpolator.py
index 43a4ac84c7f..fe2cb827ea2 100644
--- a/rasa/core/nlg/interpolator.py
+++ b/rasa/core/nlg/interpolator.py
@@ -10,10 +10,21 @@ def interpolate_text(template, values):
         # "{tag_name}" to "{0[tag_name]}"
         # as described here:
         # https://stackoverflow.com/questions/7934620/python-dots-in-the-name-of-variable-in-a-format-string#comment9695339_7934969
-        # assuming that slot_name do not contain newline character here
+        # black list character and make sure to not to allow
+        # (a) newline in slot name
+        # (b) { or } in slot name
         try:
-            text = re.sub(r"{([^\n]+?)}", r"{0[\1]}", template)
-            return text.format(values)
+            text = re.sub(r"{([^\n{}]+?)}", r"{0[\1]}", template)
+            text = text.format(values)
+            if "0[" in text:
+                # regex replaced tag but format did not replace
+                # likely cause would be that tag name was enclosed
+                # in double curly and format func simply escaped it.
+                # we don't want to return {0[SLOTNAME]} thus
+                # restoring original value with { being escaped.
+                return template.format({})
+
+            return text
         except KeyError as e:
             logger.exception(
                 "Failed to fill utterance template '{}'. "


Test patch:
diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py
index 48bead0ce91..3cf6c3241da 100644
--- a/tests/core/test_nlg.py
+++ b/tests/core/test_nlg.py
@@ -17,7 +17,7 @@ from rasa.core.agent import Agent
 from tests.core.conftest import DEFAULT_ENDPOINTS_FILE
 
 
-@pytest.fixture(scope="module")
+@pytest.fixture(scope="session")
 def loop():
     loop = asyncio.new_event_loop()
     asyncio.set_event_loop(loop)
@@ -165,8 +165,27 @@ def test_nlg_fill_template_custom(slot_name, slot_value):
     }
 
 
+@pytest.mark.parametrize(
+    "template_text, expected",
+    [
+        ('{{"variable":"{slot_1}"}}', '{"variable":"foo"}'),
+        ("{slot_1} and {slot_2}", "foo and bar"),
+        ("{{{slot_1}, {slot_2}!}}", "{foo, bar!}"),
+        ("{{{slot_1}}}", "{foo}"),
+        ("{{slot_1}}", "{slot_1}"),
+    ],
+)
+def test_nlg_fill_template_text_with_json(template_text, expected):
+    template = {"text": template_text}
+    t = TemplatedNaturalLanguageGenerator(templates=dict())
+    result = t._fill_template(
+        template=template, filled_slots={"slot_1": "foo", "slot_2": "bar"}
+    )
+    assert result == {"text": expected}
+
+
 @pytest.mark.parametrize("slot_name, slot_value", [("tag_w_\n", "a")])
-def test_nlg_fill_template_w_bad_slot_name2(slot_name, slot_value):
+def test_nlg_fill_template_with_bad_slot_name(slot_name, slot_value):
     template_text = "{" + slot_name + "}"
     t = TemplatedNaturalLanguageGenerator(templates=dict())
     result = t._fill_template(