We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Enable or document how to render options in a List Parameter as Literal elements
## Request

Enable or document how to render options in a List Parameter as Literal elements

## Motivation

I'm trying to show a user how to create a custom dropdown component using ReactiveHTML in https://discourse.holoviz.org/t/customize-panel-components-widgets-design/4187.

There is already an example of a *child* template/ dropdown here https://panel.holoviz.org/user_guide/Custom_Components.html#child-templates

Unfortunately the drop down options are not rendered as Literal values but instead as panels, i.e. wrapping the literal value in `bk` `div`s.

In my example it makes the dropdown not look nice because a lot of margin is added.

![image](https://user-images.githubusercontent.com/42288570/188283341-7364227c-03d1-4aaf-ab07-b349acf4c214.png)

```python
from panel.reactive import ReactiveHTML
import param

class ShoelaceSelect(ReactiveHTML):
    value = param.Parameter()
    options = param.List()
    _template = """
<sl-dropdown>
  <sl-button slot="trigger" caret>Dropdown</sl-button>
  <sl-menu>
   {% for obj in options %}
    <sl-menu-item id="option">${obj}</sl-menu-item>
    {% endfor %}
  </sl-menu>
</sl-dropdown>
"""

    __javascript_modules__=["https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.82/dist/shoelace.js"]
    __css__ = ["https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.82/dist/themes/light.css"]
    # _child_config = {'obj': 'literal'}

import panel as pn

pn.extension()

select = ShoelaceSelect(options=["apple", "pear", "banana"])

pn.Column(select).servable()
```

I simple cannot find any documentation or example telling me how to provide these a literal values.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `panel/models/reactive_html.py`
Content:
```
1 import difflib
2 import re
3 
4 from collections import defaultdict
5 from html.parser import HTMLParser
6 
7 import bokeh.core.properties as bp
8 
9 from bokeh.events import ModelEvent
10 from bokeh.model import DataModel
11 from bokeh.models import HTMLBox, LayoutDOM
12 
13 endfor = '{%-? endfor -?%}'
14 list_iter_re = r'{%-? for (\s*[A-Za-z_]\w*\s*) in (\s*[A-Za-z_]\w*\s*) -?%}'
15 items_iter_re = r'{%-? for \s*[A-Za-z_]\w*\s*, (\s*[A-Za-z_]\w*\s*) in (\s*[A-Za-z_]\w*\s*)\.items\(\) -?%}'
16 values_iter_re = r'{%-? for (\s*[A-Za-z_]\w*\s*) in (\s*[A-Za-z_]\w*\s*)\.values\(\) -?%}'
17 
18 
19 class ReactiveHTMLParser(HTMLParser):
20 
21     def __init__(self, cls, template=True):
22         super().__init__()
23         self.template = template
24         self.cls = cls
25         self.attrs = defaultdict(list)
26         self.children = {}
27         self.nodes = []
28         self.looped = []
29         self._template_re = re.compile(r'\$\{[^}]+\}')
30         self._literal_re = re.compile(r'\{\{[^}]+\}\}')
31         self._current_node = None
32         self._node_stack = []
33         self._open_for = False
34         self.loop_map = {}
35         self.loop_var_map = defaultdict(list)
36 
37     def handle_starttag(self, tag, attrs):
38         attrs = dict(attrs)
39         dom_id = attrs.pop('id', None)
40         self._current_node = None
41         self._node_stack.append((tag, dom_id))
42 
43         if not dom_id:
44             for attr, value in attrs.items():
45                 if value is None:
46                     continue
47                 params, methods = [], []
48                 for match in self._template_re.findall(value):
49                     match = match[2:-1]
50                     if match.startswith('model.'):
51                         continue
52                     if match in self.cls.param:
53                         params.append(match)
54                     elif hasattr(self.cls, match):
55                         methods.append(match)
56                 if methods:
57                     raise ValueError(
58                         "DOM nodes with an attached callback must declare "
59                         f"an id. Found <{tag}> node with the `{attr}` callback "
60                         f"referencing the `{methods[0]}` method. Add an id "
61                         "attribute like this: "
62                         f"<{tag} id=\"{tag}\" {attr}=\"${{{methods[0]}}}>...</{tag}>."
63                     )
64                 elif params:
65                     literal = value.replace(f'${{{params[0]}}}', f'{{{{{params[0]}}}}}')
66                     raise ValueError(
67                         "DOM node with a linked parameter declaration "
68                         f"must declare an id. Found <{tag}> node with "
69                         f"the `{attr}` attribute referencing the `{params[0]}` "
70                         "parameter. Either declare an id on the node, "
71                         f"i.e. <{tag} id=\"{tag}\" {attr}=\"{value}\">...</{tag}>, "
72                         "or insert the value as a literal: "
73                         f"<{tag} {attr}=\"{literal}\">...</{tag}>."
74                     )
75             return
76 
77         if dom_id in self.nodes:
78             raise ValueError(f'Multiple DOM nodes with id="{dom_id}" found.')
79         self._current_node = dom_id
80         self.nodes.append(dom_id)
81         for attr, value in attrs.items():
82             if value is None:
83                 continue
84             matches = []
85             for match in self._template_re.findall(value):
86                 if not match[2:-1].startswith('model.'):
87                     matches.append(match[2:-1])
88             if matches:
89                 self.attrs[dom_id].append((attr, matches, value.replace('${', '{')))
90 
91     def handle_endtag(self, tag):
92         self._node_stack.pop()
93         self._current_node = self._node_stack[-1][1] if self._node_stack else None
94 
95     def handle_data(self, data):
96         if not self.template:
97             return
98 
99         dom_id = self._current_node
100         matches = []
101         for match in self._template_re.findall(data):
102             var = match[2:-1].strip()
103             if match[2:-1] not in self.loop_var_map[var]:
104                 self.loop_var_map[var].append(match[2:-1])
105             if var.endswith('.index0'):
106                 matches.append('${%s }}]}' % var)
107             else:
108                 matches.append('${%s}' % var)
109 
110         literal_matches = []
111         for match in self._literal_re.findall(data):
112             match = match[2:-2].strip()
113             if match.endswith('.index0'):
114                 literal_matches.append('{{%s }}]}' % match)
115             else:
116                 literal_matches.append('{{ %s }}' % match)
117 
118         # Detect templating for loops
119         list_loop = re.findall(list_iter_re, data)
120         values_loop = re.findall(values_iter_re, data)
121         items_loop = re.findall(items_iter_re, data)
122         nloops = len(list_loop) + len(values_loop) + len(items_loop)
123         if nloops > 1 and nloops and self._open_for:
124             raise ValueError('Nested for loops currently not supported in templates.')
125         elif nloops:
126             loop = [loop for loop in (list_loop, values_loop, items_loop) if loop][0]
127             var, obj = loop[0]
128             if var in self.cls.param:
129                 raise ValueError(
130                     f'Loop variable {var} clashes with parameter name. '
131                     'Ensure loop variables have a unique name. Relevant '
132                     f'template section:\n\n{data}'
133                 )
134             self.loop_map[var] = obj
135 
136         open_for = re.search(r'{%-? for', data)
137         end_for = re.search(endfor, data)
138         if open_for:
139             if self._current_node is None:
140                 node = self._node_stack[-1][0]
141                 raise ValueError(
142                     'Loops may only be used inside a DOM node with an assigned ID. '
143                     f'The following loop could not be expanded because the <{node}> node '
144                     f'did not have an assigned id:\n\n    {data.strip()}'
145                 )
146             self._open_for = True
147         if end_for and (not nloops or end_for.start() > open_for.start()):
148             self._open_for = False
149 
150         if self._current_node and literal_matches:
151             if len(literal_matches) == 1:
152                 literal_match = literal_matches[0][2:-2].strip()
153             else:
154                 literal_match = None
155 
156             if literal_match and (literal_match in self.loop_map) and self._open_for:
157                 literal_match = self.loop_map[literal_match]
158                 self.looped.append((dom_id, literal_match))
159 
160         if not (self._current_node and matches):
161             return
162 
163         if len(matches) == 1:
164             match = matches[0][2:-1].strip()
165         else:
166             for match in matches:
167                 mode = self.cls._child_config.get(match, 'model')
168                 if mode != 'template':
169                     raise ValueError(f"Cannot match multiple variables in '{mode}' mode.")
170             match = None
171 
172         # Handle looped variables
173         if match and (match in self.loop_map or '[' in match) and self._open_for:
174             if match in self.loop_map:
175                 matches[matches.index('${%s}' % match)] = '${%s}' % self.loop_map[match]
176                 match = self.loop_map[match]
177             elif '[' in match:
178                 match, _ = match.split('[')
179             dom_id = dom_id.replace('-{{ loop.index0 }}', '')
180             self.looped.append((dom_id, match))
181 
182         mode = self.cls._child_config.get(match, 'model')
183         if match in self.cls.param and mode != 'template':
184             self.children[dom_id] = match
185             return
186 
187         templates = []
188         for match in matches:
189             match = match[2:-1]
190             if match.startswith('model.'):
191                 continue
192             if match not in self.cls.param and '.' not in match:
193                 params = difflib.get_close_matches(match, list(self.cls.param))
194                 raise ValueError(f"{self.cls.__name__} HTML template references "
195                                  f"unknown parameter '{match}', similar parameters "
196                                  f"include {params}.")
197             templates.append(match)
198         self.attrs[dom_id].append(('children', templates, data.replace('${', '{')))
199 
200 
201 
202 def find_attrs(html):
203     p = ReactiveHTMLParser()
204     p.feed(html)
205     return p.attrs
206 
207 
208 
209 class DOMEvent(ModelEvent):
210 
211     event_name = 'dom_event'
212 
213     def __init__(self, model, node=None, data=None):
214         self.data = data
215         self.node = node
216         super().__init__(model=model)
217 
218 
219 class ReactiveHTML(HTMLBox):
220 
221     attrs = bp.Dict(bp.String, bp.List(bp.Tuple(bp.String, bp.List(bp.String), bp.String)))
222 
223     callbacks = bp.Dict(bp.String, bp.List(bp.Tuple(bp.String, bp.String)))
224 
225     children = bp.Dict(bp.String, bp.Either(bp.List(bp.Either(bp.Instance(LayoutDOM), bp.String)), bp.String))
226 
227     data = bp.Instance(DataModel)
228 
229     events = bp.Dict(bp.String, bp.Dict(bp.String, bp.Bool))
230 
231     html = bp.String()
232 
233     looped = bp.List(bp.String)
234 
235     nodes = bp.List(bp.String)
236 
237     scripts = bp.Dict(bp.String, bp.List(bp.String))
238 
239     def __init__(self, **props):
240         if 'attrs' not in props and 'html' in props:
241             props['attrs'] = find_attrs(props['html'])
242         super().__init__(**props)
243
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/panel/models/reactive_html.py b/panel/models/reactive_html.py
--- a/panel/models/reactive_html.py
+++ b/panel/models/reactive_html.py
@@ -136,10 +136,11 @@
             match = None
 
         # Handle looped variables
-        if match and (match in self.loop_map or '[' in match) and self._open_for:
-            if match in self.loop_map:
-                matches[matches.index('${%s}' % match)] = '${%s}' % self.loop_map[match]
-                match = self.loop_map[match]
+        if match and (match.strip() in self.loop_map or '[' in match) and self._open_for:
+            if match.strip() in self.loop_map:
+                loop_match = self.loop_map[match.strip()]
+                matches[matches.index('${%s}' % match)] = '${%s}' % loop_match
+                match = loop_match
             elif '[' in match:
                 match, _ = match.split('[')
             dom_id = dom_id.replace('-{{ loop.index0 }}', '')

{"golden_diff": "diff --git a/panel/models/reactive_html.py b/panel/models/reactive_html.py\n--- a/panel/models/reactive_html.py\n+++ b/panel/models/reactive_html.py\n@@ -136,10 +136,11 @@\n             match = None\n \n         # Handle looped variables\n-        if match and (match in self.loop_map or '[' in match) and self._open_for:\n-            if match in self.loop_map:\n-                matches[matches.index('${%s}' % match)] = '${%s}' % self.loop_map[match]\n-                match = self.loop_map[match]\n+        if match and (match.strip() in self.loop_map or '[' in match) and self._open_for:\n+            if match.strip() in self.loop_map:\n+                loop_match = self.loop_map[match.strip()]\n+                matches[matches.index('${%s}' % match)] = '${%s}' % loop_match\n+                match = loop_match\n             elif '[' in match:\n                 match, _ = match.split('[')\n             dom_id = dom_id.replace('-{{ loop.index0 }}', '')\n", "issue": "Enable or document how to render options in a List Parameter as Literal elements\n## Request\r\n\r\nEnable or document how to render options in a List Parameter as Literal elements\r\n\r\n## Motivation\r\n\r\nI'm trying to show a user how to create a custom dropdown component using ReactiveHTML in https://discourse.holoviz.org/t/customize-panel-components-widgets-design/4187.\r\n\r\nThere is already an example of a *child* template/ dropdown here https://panel.holoviz.org/user_guide/Custom_Components.html#child-templates\r\n\r\nUnfortunately the drop down options are not rendered as Literal values but instead as panels, i.e. wrapping the literal value in `bk` `div`s.\r\n\r\nIn my example it makes the dropdown not look nice because a lot of margin is added.\r\n\r\n![image](https://user-images.githubusercontent.com/42288570/188283341-7364227c-03d1-4aaf-ab07-b349acf4c214.png)\r\n\r\n```python\r\nfrom panel.reactive import ReactiveHTML\r\nimport param\r\n\r\nclass ShoelaceSelect(ReactiveHTML):\r\n    value = param.Parameter()\r\n    options = param.List()\r\n    _template = \"\"\"\r\n<sl-dropdown>\r\n  <sl-button slot=\"trigger\" caret>Dropdown</sl-button>\r\n  <sl-menu>\r\n   {% for obj in options %}\r\n    <sl-menu-item id=\"option\">${obj}</sl-menu-item>\r\n    {% endfor %}\r\n  </sl-menu>\r\n</sl-dropdown>\r\n\"\"\"\r\n\r\n    __javascript_modules__=[\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.82/dist/shoelace.js\"]\r\n    __css__ = [\"https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.82/dist/themes/light.css\"]\r\n    # _child_config = {'obj': 'literal'}\r\n\r\nimport panel as pn\r\n\r\npn.extension()\r\n\r\nselect = ShoelaceSelect(options=[\"apple\", \"pear\", \"banana\"])\r\n\r\npn.Column(select).servable()\r\n```\r\n\r\nI simple cannot find any documentation or example telling me how to provide these a literal values.\n", "before_files": [{"content": "import difflib\nimport re\n\nfrom collections import defaultdict\nfrom html.parser import HTMLParser\n\nimport bokeh.core.properties as bp\n\nfrom bokeh.events import ModelEvent\nfrom bokeh.model import DataModel\nfrom bokeh.models import HTMLBox, LayoutDOM\n\nendfor = '{%-? endfor -?%}'\nlist_iter_re = r'{%-? for (\\s*[A-Za-z_]\\w*\\s*) in (\\s*[A-Za-z_]\\w*\\s*) -?%}'\nitems_iter_re = r'{%-? for \\s*[A-Za-z_]\\w*\\s*, (\\s*[A-Za-z_]\\w*\\s*) in (\\s*[A-Za-z_]\\w*\\s*)\\.items\\(\\) -?%}'\nvalues_iter_re = r'{%-? for (\\s*[A-Za-z_]\\w*\\s*) in (\\s*[A-Za-z_]\\w*\\s*)\\.values\\(\\) -?%}'\n\n\nclass ReactiveHTMLParser(HTMLParser):\n\n    def __init__(self, cls, template=True):\n        super().__init__()\n        self.template = template\n        self.cls = cls\n        self.attrs = defaultdict(list)\n        self.children = {}\n        self.nodes = []\n        self.looped = []\n        self._template_re = re.compile(r'\\$\\{[^}]+\\}')\n        self._literal_re = re.compile(r'\\{\\{[^}]+\\}\\}')\n        self._current_node = None\n        self._node_stack = []\n        self._open_for = False\n        self.loop_map = {}\n        self.loop_var_map = defaultdict(list)\n\n    def handle_starttag(self, tag, attrs):\n        attrs = dict(attrs)\n        dom_id = attrs.pop('id', None)\n        self._current_node = None\n        self._node_stack.append((tag, dom_id))\n\n        if not dom_id:\n            for attr, value in attrs.items():\n                if value is None:\n                    continue\n                params, methods = [], []\n                for match in self._template_re.findall(value):\n                    match = match[2:-1]\n                    if match.startswith('model.'):\n                        continue\n                    if match in self.cls.param:\n                        params.append(match)\n                    elif hasattr(self.cls, match):\n                        methods.append(match)\n                if methods:\n                    raise ValueError(\n                        \"DOM nodes with an attached callback must declare \"\n                        f\"an id. Found <{tag}> node with the `{attr}` callback \"\n                        f\"referencing the `{methods[0]}` method. Add an id \"\n                        \"attribute like this: \"\n                        f\"<{tag} id=\\\"{tag}\\\" {attr}=\\\"${{{methods[0]}}}>...</{tag}>.\"\n                    )\n                elif params:\n                    literal = value.replace(f'${{{params[0]}}}', f'{{{{{params[0]}}}}}')\n                    raise ValueError(\n                        \"DOM node with a linked parameter declaration \"\n                        f\"must declare an id. Found <{tag}> node with \"\n                        f\"the `{attr}` attribute referencing the `{params[0]}` \"\n                        \"parameter. Either declare an id on the node, \"\n                        f\"i.e. <{tag} id=\\\"{tag}\\\" {attr}=\\\"{value}\\\">...</{tag}>, \"\n                        \"or insert the value as a literal: \"\n                        f\"<{tag} {attr}=\\\"{literal}\\\">...</{tag}>.\"\n                    )\n            return\n\n        if dom_id in self.nodes:\n            raise ValueError(f'Multiple DOM nodes with id=\"{dom_id}\" found.')\n        self._current_node = dom_id\n        self.nodes.append(dom_id)\n        for attr, value in attrs.items():\n            if value is None:\n                continue\n            matches = []\n            for match in self._template_re.findall(value):\n                if not match[2:-1].startswith('model.'):\n                    matches.append(match[2:-1])\n            if matches:\n                self.attrs[dom_id].append((attr, matches, value.replace('${', '{')))\n\n    def handle_endtag(self, tag):\n        self._node_stack.pop()\n        self._current_node = self._node_stack[-1][1] if self._node_stack else None\n\n    def handle_data(self, data):\n        if not self.template:\n            return\n\n        dom_id = self._current_node\n        matches = []\n        for match in self._template_re.findall(data):\n            var = match[2:-1].strip()\n            if match[2:-1] not in self.loop_var_map[var]:\n                self.loop_var_map[var].append(match[2:-1])\n            if var.endswith('.index0'):\n                matches.append('${%s }}]}' % var)\n            else:\n                matches.append('${%s}' % var)\n\n        literal_matches = []\n        for match in self._literal_re.findall(data):\n            match = match[2:-2].strip()\n            if match.endswith('.index0'):\n                literal_matches.append('{{%s }}]}' % match)\n            else:\n                literal_matches.append('{{ %s }}' % match)\n\n        # Detect templating for loops\n        list_loop = re.findall(list_iter_re, data)\n        values_loop = re.findall(values_iter_re, data)\n        items_loop = re.findall(items_iter_re, data)\n        nloops = len(list_loop) + len(values_loop) + len(items_loop)\n        if nloops > 1 and nloops and self._open_for:\n            raise ValueError('Nested for loops currently not supported in templates.')\n        elif nloops:\n            loop = [loop for loop in (list_loop, values_loop, items_loop) if loop][0]\n            var, obj = loop[0]\n            if var in self.cls.param:\n                raise ValueError(\n                    f'Loop variable {var} clashes with parameter name. '\n                    'Ensure loop variables have a unique name. Relevant '\n                    f'template section:\\n\\n{data}'\n                )\n            self.loop_map[var] = obj\n\n        open_for = re.search(r'{%-? for', data)\n        end_for = re.search(endfor, data)\n        if open_for:\n            if self._current_node is None:\n                node = self._node_stack[-1][0]\n                raise ValueError(\n                    'Loops may only be used inside a DOM node with an assigned ID. '\n                    f'The following loop could not be expanded because the <{node}> node '\n                    f'did not have an assigned id:\\n\\n    {data.strip()}'\n                )\n            self._open_for = True\n        if end_for and (not nloops or end_for.start() > open_for.start()):\n            self._open_for = False\n\n        if self._current_node and literal_matches:\n            if len(literal_matches) == 1:\n                literal_match = literal_matches[0][2:-2].strip()\n            else:\n                literal_match = None\n\n            if literal_match and (literal_match in self.loop_map) and self._open_for:\n                literal_match = self.loop_map[literal_match]\n                self.looped.append((dom_id, literal_match))\n\n        if not (self._current_node and matches):\n            return\n\n        if len(matches) == 1:\n            match = matches[0][2:-1].strip()\n        else:\n            for match in matches:\n                mode = self.cls._child_config.get(match, 'model')\n                if mode != 'template':\n                    raise ValueError(f\"Cannot match multiple variables in '{mode}' mode.\")\n            match = None\n\n        # Handle looped variables\n        if match and (match in self.loop_map or '[' in match) and self._open_for:\n            if match in self.loop_map:\n                matches[matches.index('${%s}' % match)] = '${%s}' % self.loop_map[match]\n                match = self.loop_map[match]\n            elif '[' in match:\n                match, _ = match.split('[')\n            dom_id = dom_id.replace('-{{ loop.index0 }}', '')\n            self.looped.append((dom_id, match))\n\n        mode = self.cls._child_config.get(match, 'model')\n        if match in self.cls.param and mode != 'template':\n            self.children[dom_id] = match\n            return\n\n        templates = []\n        for match in matches:\n            match = match[2:-1]\n            if match.startswith('model.'):\n                continue\n            if match not in self.cls.param and '.' not in match:\n                params = difflib.get_close_matches(match, list(self.cls.param))\n                raise ValueError(f\"{self.cls.__name__} HTML template references \"\n                                 f\"unknown parameter '{match}', similar parameters \"\n                                 f\"include {params}.\")\n            templates.append(match)\n        self.attrs[dom_id].append(('children', templates, data.replace('${', '{')))\n\n\n\ndef find_attrs(html):\n    p = ReactiveHTMLParser()\n    p.feed(html)\n    return p.attrs\n\n\n\nclass DOMEvent(ModelEvent):\n\n    event_name = 'dom_event'\n\n    def __init__(self, model, node=None, data=None):\n        self.data = data\n        self.node = node\n        super().__init__(model=model)\n\n\nclass ReactiveHTML(HTMLBox):\n\n    attrs = bp.Dict(bp.String, bp.List(bp.Tuple(bp.String, bp.List(bp.String), bp.String)))\n\n    callbacks = bp.Dict(bp.String, bp.List(bp.Tuple(bp.String, bp.String)))\n\n    children = bp.Dict(bp.String, bp.Either(bp.List(bp.Either(bp.Instance(LayoutDOM), bp.String)), bp.String))\n\n    data = bp.Instance(DataModel)\n\n    events = bp.Dict(bp.String, bp.Dict(bp.String, bp.Bool))\n\n    html = bp.String()\n\n    looped = bp.List(bp.String)\n\n    nodes = bp.List(bp.String)\n\n    scripts = bp.Dict(bp.String, bp.List(bp.String))\n\n    def __init__(self, **props):\n        if 'attrs' not in props and 'html' in props:\n            props['attrs'] = find_attrs(props['html'])\n        super().__init__(**props)\n", "path": "panel/models/reactive_html.py"}], "after_files": [{"content": "import difflib\nimport re\n\nfrom collections import defaultdict\nfrom html.parser import HTMLParser\n\nimport bokeh.core.properties as bp\n\nfrom bokeh.events import ModelEvent\nfrom bokeh.model import DataModel\nfrom bokeh.models import HTMLBox, LayoutDOM\n\nendfor = '{%-? endfor -?%}'\nlist_iter_re = r'{%-? for (\\s*[A-Za-z_]\\w*\\s*) in (\\s*[A-Za-z_]\\w*\\s*) -?%}'\nitems_iter_re = r'{%-? for \\s*[A-Za-z_]\\w*\\s*, (\\s*[A-Za-z_]\\w*\\s*) in (\\s*[A-Za-z_]\\w*\\s*)\\.items\\(\\) -?%}'\nvalues_iter_re = r'{%-? for (\\s*[A-Za-z_]\\w*\\s*) in (\\s*[A-Za-z_]\\w*\\s*)\\.values\\(\\) -?%}'\n\n\nclass ReactiveHTMLParser(HTMLParser):\n\n    def __init__(self, cls, template=True):\n        super().__init__()\n        self.template = template\n        self.cls = cls\n        self.attrs = defaultdict(list)\n        self.children = {}\n        self.nodes = []\n        self.looped = []\n        self._template_re = re.compile(r'\\$\\{[^}]+\\}')\n        self._current_node = None\n        self._node_stack = []\n        self._open_for = False\n        self.loop_map = {}\n\n    def handle_starttag(self, tag, attrs):\n        attrs = dict(attrs)\n        dom_id = attrs.pop('id', None)\n        self._current_node = None\n        self._node_stack.append((tag, dom_id))\n\n        if not dom_id:\n            for attr, value in attrs.items():\n                if value is None:\n                    continue\n                params, methods = [], []\n                for match in self._template_re.findall(value):\n                    match = match[2:-1]\n                    if match.startswith('model.'):\n                        continue\n                    if match in self.cls.param:\n                        params.append(match)\n                    elif hasattr(self.cls, match):\n                        methods.append(match)\n                if methods:\n                    raise ValueError(\n                        \"DOM nodes with an attached callback must declare \"\n                        f\"an id. Found <{tag}> node with the `{attr}` callback \"\n                        f\"referencing the `{methods[0]}` method. Add an id \"\n                        \"attribute like this: \"\n                        f\"<{tag} id=\\\"{tag}\\\" {attr}=\\\"${{{methods[0]}}}>...</{tag}>.\"\n                    )\n                elif params:\n                    literal = value.replace(f'${{{params[0]}}}', f'{{{{{params[0]}}}}}')\n                    raise ValueError(\n                        \"DOM node with a linked parameter declaration \"\n                        f\"must declare an id. Found <{tag}> node with \"\n                        f\"the `{attr}` attribute referencing the `{params[0]}` \"\n                        \"parameter. Either declare an id on the node, \"\n                        f\"i.e. <{tag} id=\\\"{tag}\\\" {attr}=\\\"{value}\\\">...</{tag}>, \"\n                        \"or insert the value as a literal: \"\n                        f\"<{tag} {attr}=\\\"{literal}\\\">...</{tag}>.\"\n                    )\n            return\n\n        if dom_id in self.nodes:\n            raise ValueError(f'Multiple DOM nodes with id=\"{dom_id}\" found.')\n        self._current_node = dom_id\n        self.nodes.append(dom_id)\n        for attr, value in attrs.items():\n            if value is None:\n                continue\n            matches = []\n            for match in self._template_re.findall(value):\n                if not match[2:-1].startswith('model.'):\n                    matches.append(match[2:-1])\n            if matches:\n                self.attrs[dom_id].append((attr, matches, value.replace('${', '{')))\n\n    def handle_endtag(self, tag):\n        self._node_stack.pop()\n        self._current_node = self._node_stack[-1][1] if self._node_stack else None\n\n    def handle_data(self, data):\n        if not self.template:\n            return\n\n        dom_id = self._current_node\n        matches = [\n            '%s}]}' % match if match.endswith('.index0 }') else match\n            for match in self._template_re.findall(data)\n        ]\n\n        # Detect templating for loops\n        list_loop = re.findall(list_iter_re, data)\n        values_loop = re.findall(values_iter_re, data)\n        items_loop = re.findall(items_iter_re, data)\n        nloops = len(list_loop) + len(values_loop) + len(items_loop)\n        if nloops > 1 and nloops and self._open_for:\n            raise ValueError('Nested for loops currently not supported in templates.')\n        elif nloops:\n            loop = [loop for loop in (list_loop, values_loop, items_loop) if loop][0]\n            var, obj = loop[0]\n            if var in self.cls.param:\n                raise ValueError(f'Loop variable {var} clashes with parameter name. '\n                                 'Ensure loop variables have a unique name. Relevant '\n                                 f'template section:\\n\\n{data}')\n            self.loop_map[var] = obj\n\n        open_for = re.search(r'{%-? for', data)\n        end_for = re.search(endfor, data)\n        if open_for:\n            self._open_for = True\n        if end_for and (not nloops or end_for.start() > open_for.start()):\n            self._open_for = False\n\n        if not (self._current_node and matches):\n            return\n\n        if len(matches) == 1:\n            match = matches[0][2:-1]\n        else:\n            for match in matches:\n                mode = self.cls._child_config.get(match, 'model')\n                if mode != 'template':\n                    raise ValueError(f\"Cannot match multiple variables in '{mode}' mode.\")\n            match = None\n\n        # Handle looped variables\n        if match and (match.strip() in self.loop_map or '[' in match) and self._open_for:\n            if match.strip() in self.loop_map:\n                loop_match = self.loop_map[match.strip()]\n                matches[matches.index('${%s}' % match)] = '${%s}' % loop_match\n                match = loop_match\n            elif '[' in match:\n                match, _ = match.split('[')\n            dom_id = dom_id.replace('-{{ loop.index0 }}', '')\n            self.looped.append((dom_id, match))\n\n        mode = self.cls._child_config.get(match, 'model')\n        if match in self.cls.param and mode != 'template':\n            self.children[dom_id] = match\n            return\n\n        templates = []\n        for match in matches:\n            match = match[2:-1]\n            if match.startswith('model.'):\n                continue\n            if match not in self.cls.param and '.' not in match:\n                params = difflib.get_close_matches(match, list(self.cls.param))\n                raise ValueError(f\"{self.cls.__name__} HTML template references \"\n                                 f\"unknown parameter '{match}', similar parameters \"\n                                 f\"include {params}.\")\n            templates.append(match)\n        self.attrs[dom_id].append(('children', templates, data.replace('${', '{')))\n\n\n\ndef find_attrs(html):\n    p = ReactiveHTMLParser()\n    p.feed(html)\n    return p.attrs\n\n\n\nclass DOMEvent(ModelEvent):\n\n    event_name = 'dom_event'\n\n    def __init__(self, model, node=None, data=None):\n        self.data = data\n        self.node = node\n        super().__init__(model=model)\n\n\nclass ReactiveHTML(HTMLBox):\n\n    attrs = bp.Dict(bp.String, bp.List(bp.Tuple(bp.String, bp.List(bp.String), bp.String)))\n\n    callbacks = bp.Dict(bp.String, bp.List(bp.Tuple(bp.String, bp.String)))\n\n    children = bp.Dict(bp.String, bp.Either(bp.List(bp.Either(bp.Instance(LayoutDOM), bp.String)), bp.String))\n\n    data = bp.Instance(DataModel)\n\n    events = bp.Dict(bp.String, bp.Dict(bp.String, bp.Bool))\n\n    html = bp.String()\n\n    looped = bp.List(bp.String)\n\n    nodes = bp.List(bp.String)\n\n    scripts = bp.Dict(bp.String, bp.List(bp.String))\n\n    def __init__(self, **props):\n        if 'attrs' not in props and 'html' in props:\n            props['attrs'] = find_attrs(props['html'])\n        super().__init__(**props)\n", "path": "panel/models/reactive_html.py"}]}