Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 82f9bb1

Browse files
leading, trailing, multiple underscores detected in dict path strings
need to add tests
1 parent c6e5b4d commit 82f9bb1

File tree

2 files changed

+103
-34
lines changed

2 files changed

+103
-34
lines changed

packages/python/plotly/_plotly_utils/utils.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,3 +362,41 @@ def display_string_positions(p, i=None, offset=0, length=1, char="^", trim=True)
362362
if trim:
363363
ret = ret[: maxaddr + 1]
364364
return ret
365+
366+
367+
def chomp_empty_strings(strings, c):
368+
"""
369+
Given a list of strings, some of which are the empty string "", replace the
370+
empty strings with "_" and combine them with the closest non-empty string on
371+
the left or "" if it is the first string.
372+
Examples:
373+
for c="_"
374+
['hey', '', 'why', '', '', 'whoa', '', ''] -> ['hey_', 'why__', 'whoa__']
375+
['', 'hi', '', "I'm", 'bob', '', ''] -> ['_', 'hi_', "I'm", 'bob__']
376+
['hi', "i'm", 'a', 'good', 'string'] -> ['hi', "i'm", 'a', 'good', 'string']
377+
Some special cases are:
378+
[] -> []
379+
[''] -> ['']
380+
['', ''] -> ['_']
381+
['', '', '', ''] -> ['___']
382+
"""
383+
if not len(strings):
384+
return strings
385+
if sum(map(len, strings)) == 0:
386+
return [c * (len(strings) - 1)]
387+
388+
class _Chomper:
389+
def __init__(self, c):
390+
self.c = c
391+
392+
def __call__(self, x, y):
393+
# x is list up to now
394+
# y is next item in list
395+
# x should be [""] initially, and then empty strings filtered out at the
396+
# end
397+
if len(y) == 0:
398+
return x[:-1] + [x[-1] + self.c]
399+
else:
400+
return x + [y]
401+
402+
return list(filter(len, reduce(_Chomper(c), strings, [""])))

packages/python/plotly/plotly/basedatatypes.py

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99
from contextlib import contextmanager
1010
from copy import deepcopy, copy
1111
import itertools
12+
from functools import reduce
1213

1314
from _plotly_utils.utils import (
1415
_natural_sort_strings,
1516
_get_int_type,
1617
split_multichar,
1718
split_string_positions,
1819
display_string_positions,
20+
chomp_empty_strings,
1921
)
2022
from _plotly_utils.exceptions import PlotlyKeyError
2123
from .optional_imports import get_module
@@ -63,40 +65,69 @@ def _str_to_dict_path_full(key_path_str):
6365
tuple[str | int]
6466
tuple [int]
6567
"""
66-
key_path2 = split_multichar([key_path_str], list(".[]"))
67-
# Split out underscore
68-
# e.g. ['foo', 'bar_baz', '1'] -> ['foo', 'bar', 'baz', '1']
69-
key_path3 = []
70-
underscore_props = BaseFigure._valid_underscore_properties
71-
72-
def _make_hyphen_key(key):
73-
if "_" in key[1:]:
74-
# For valid properties that contain underscores (error_x)
75-
# replace the underscores with hyphens to protect them
76-
# from being split up
77-
for under_prop, hyphen_prop in underscore_props.items():
78-
key = key.replace(under_prop, hyphen_prop)
79-
return key
80-
81-
def _make_underscore_key(key):
82-
return key.replace("-", "_")
83-
84-
key_path2b = map(_make_hyphen_key, key_path2)
85-
key_path2c = split_multichar(key_path2b, list("_"))
86-
key_path2d = list(map(_make_underscore_key, key_path2c))
87-
all_elem_idcs = tuple(split_string_positions(list(key_path2d)))
88-
# remove empty strings, and indices pointing to them
89-
key_elem_pairs = list(filter(lambda t: len(t[1]), enumerate(key_path2d)))
90-
key_path3 = [x for _, x in key_elem_pairs]
91-
elem_idcs = [all_elem_idcs[i] for i, _ in key_elem_pairs]
92-
93-
# Convert elements to ints if possible.
94-
# e.g. ['foo', 'bar', '0'] -> ['foo', 'bar', 0]
95-
for i in range(len(key_path3)):
96-
try:
97-
key_path3[i] = int(key_path3[i])
98-
except ValueError as _:
99-
pass
68+
# skip all the parsing if the string is empty
69+
if len(key_path_str):
70+
# split string on ".[]" and filter out empty strings
71+
key_path2 = split_multichar([key_path_str], list(".[]"))
72+
# Split out underscore
73+
# e.g. ['foo', 'bar_baz', '1'] -> ['foo', 'bar', 'baz', '1']
74+
key_path3 = []
75+
underscore_props = BaseFigure._valid_underscore_properties
76+
77+
def _make_hyphen_key(key):
78+
if "_" in key[1:]:
79+
# For valid properties that contain underscores (error_x)
80+
# replace the underscores with hyphens to protect them
81+
# from being split up
82+
for under_prop, hyphen_prop in underscore_props.items():
83+
key = key.replace(under_prop, hyphen_prop)
84+
return key
85+
86+
def _make_underscore_key(key):
87+
return key.replace("-", "_")
88+
89+
key_path2b = list(map(_make_hyphen_key, key_path2))
90+
# Here we want to split up each non-empty string in the list at
91+
# underscores and recombine the strings using chomp_empty_strings so
92+
# that leading, trailing and multiple _ will be preserved
93+
def _split_and_chomp(s):
94+
if not len(s):
95+
return s
96+
s_split = split_multichar([s], list("_"))
97+
# handle key paths like "a_path_", "_another_path", or
98+
# "yet__another_path" by joining extra "_" to the string to the left or
99+
# the empty string if at the beginning
100+
s_chomped = chomp_empty_strings(s_split, "_")
101+
return s_chomped
102+
103+
# after running _split_and_chomp on key_path2b, it will be a list
104+
# containing strings and lists of strings; concatenate the sublists with
105+
# the list ("lift" the items out of the sublists)
106+
key_path2c = list(
107+
reduce(
108+
lambda x, y: x + y if type(y) == type(list()) else x + [y],
109+
map(_split_and_chomp, key_path2b),
110+
[],
111+
)
112+
)
113+
114+
key_path2d = list(map(_make_underscore_key, key_path2c))
115+
all_elem_idcs = tuple(split_string_positions(list(key_path2d)))
116+
# remove empty strings, and indices pointing to them
117+
key_elem_pairs = list(filter(lambda t: len(t[1]), enumerate(key_path2d)))
118+
key_path3 = [x for _, x in key_elem_pairs]
119+
elem_idcs = [all_elem_idcs[i] for i, _ in key_elem_pairs]
120+
121+
# Convert elements to ints if possible.
122+
# e.g. ['foo', 'bar', '0'] -> ['foo', 'bar', 0]
123+
for i in range(len(key_path3)):
124+
try:
125+
key_path3[i] = int(key_path3[i])
126+
except ValueError as _:
127+
pass
128+
else:
129+
key_path3 = []
130+
elem_idcs = []
100131

101132
return (tuple(key_path3), elem_idcs)
102133

0 commit comments

Comments
 (0)