Repository: geopandas/geopandas

Issue:
Escape special characters to avoid TemplateSyntaxError in explore
The following code has the error `TemplateSyntaxError: expected token ':', got 'a'BUG:`,

```python
import pandas as pd
import geopandas as gpd
data = [{"tweet":"I didnÃ¢â‚¬â„¢t want to do it. but I did it anyway. I looked at the Town Halls. {{{what a mess}}} they are so different. #Biden is a gentle caterpillar while #Trump is a rabid fox Ã°Å¸Â¥Âº #TownHalls #trustYourInstinct  #election2020 #holdontight #bumpyride",
"lat":40.7127281,"lon":-74.0060152}]
df = pd.DataFrame(data)
gdf = gpd.GeoDataFrame(df,geometry=gpd.points_from_xy(df.lon,df.lat),crs="epsg:4326")
gdf.explore()
```

I guess because of the {} brakets in `gdf`:

> I didnâ€™t want to do it. but I did it anyway. I looked at the Town Halls. {{{what a mess}}} they are so different. #Biden is a gentle caterpillar while #Trump is a rabid fox ðŸ¥º #TownHalls #trustYourInstinct #election2020 #holdontight #bumpyride

The bug can be reduplicated[ at colab](https://colab.research.google.com/drive/1fVtTDl1SkE1DzmPd_VvQqRe56vwmGz5Q?usp=sharing)




Escape special characters to avoid TemplateSyntaxError in explore
The following code has the error `TemplateSyntaxError: expected token ':', got 'a'BUG:`,

```python
import pandas as pd
import geopandas as gpd
data = [{"tweet":"I didnÃ¢â‚¬â„¢t want to do it. but I did it anyway. I looked at the Town Halls. {{{what a mess}}} they are so different. #Biden is a gentle caterpillar while #Trump is a rabid fox Ã°Å¸Â¥Âº #TownHalls #trustYourInstinct  #election2020 #holdontight #bumpyride",
"lat":40.7127281,"lon":-74.0060152}]
df = pd.DataFrame(data)
gdf = gpd.GeoDataFrame(df,geometry=gpd.points_from_xy(df.lon,df.lat),crs="epsg:4326")
gdf.explore()
```

I guess because of the {} brakets in `gdf`:

> I didnâ€™t want to do it. but I did it anyway. I looked at the Town Halls. {{{what a mess}}} they are so different. #Biden is a gentle caterpillar while #Trump is a rabid fox ðŸ¥º #TownHalls #trustYourInstinct #election2020 #holdontight #bumpyride

The bug can be reduplicated[ at colab](https://colab.research.google.com/drive/1fVtTDl1SkE1DzmPd_VvQqRe56vwmGz5Q?usp=sharing)






Discussion:
Thanks! The issue comes from conversion of gdf to geojson before sending it to folium/leaflet. We should escape special characters here to avoid invalid json, like in this case.
Thanks! The issue comes from conversion of gdf to geojson before sending it to folium/leaflet. We should escape special characters here to avoid invalid json, like in this case.

Resolving patch:
diff --git a/geopandas/explore.py b/geopandas/explore.py
--- a/geopandas/explore.py
+++ b/geopandas/explore.py
@@ -278,6 +278,7 @@ def _colormap_helper(_cmap, n_resample=None, idx=None):
     try:
         import branca as bc
         import folium
+        import re
         import matplotlib
         import matplotlib.colors as colors
         import matplotlib.pyplot as plt
@@ -618,10 +619,21 @@ def _style_highlight(x):
     else:
         tooltip = None
         popup = None
+    # escape the curly braces {{}} for jinja2 templates
+    feature_collection = gdf.__geo_interface__
+    for feature in feature_collection["features"]:
+        for k in feature["properties"]:
+            # escape the curly braces in values
+            if type(feature["properties"][k]) == str:
+                feature["properties"][k] = re.sub(
+                    r"\{{2,}",
+                    lambda x: "{% raw %}" + x.group(0) + "{% endraw %}",
+                    feature["properties"][k],
+                )
 
     # add dataframe to map
     folium.GeoJson(
-        gdf.__geo_interface__,
+        feature_collection,
         tooltip=tooltip,
         popup=popup,
         marker=marker,


Test patch:
diff --git a/geopandas/tests/test_explore.py b/geopandas/tests/test_explore.py
--- a/geopandas/tests/test_explore.py
+++ b/geopandas/tests/test_explore.py
@@ -466,6 +466,15 @@ def test_tooltip(self):
         out_str = self._fetch_map_string(m)
         assert "BoroName" in out_str
 
+    def test_escape_special_characters(self):
+        # check if special characters are escaped
+        gdf = self.world.copy()
+        gdf["name"] = """{{{what a mess}}} they are so different."""
+        m = gdf.explore()
+        out_str = self._fetch_map_string(m)
+        assert """{{{""" in out_str
+        assert """}}}""" in out_str
+
     def test_default_markers(self):
         # check overridden default for points
         m = self.cities.explore()