@@ -46,13 +46,19 @@ def _plural_name_tag(singular, plural_name, amount, no_amount):
4646 """
4747 if plural_name in (None , "" ):
4848 return html .escape (singular )
49+
50+ try :
51+ amount_val = float (amount )
52+ except (ValueError , TypeError ):
53+ amount_val = 0.0
54+
4955 return (
5056 f"<plural-name"
5157 f' singular="{ html .escape (singular )} "'
5258 f' plural="{ html .escape (plural_name )} "'
53- f" v-bind:amount='{ float ( amount ) } '"
59+ f" v-bind:amount='{ amount_val } '"
5460 f" v-bind:factor='ingredient_factor'"
55- f" :no-amount='{ str ( no_amount ). lower () } '"
61+ f" :no-amount='{ 'true' if no_amount else 'false' } '"
5662 f"></plural-name>"
5763 )
5864
@@ -68,8 +74,12 @@ def __init__(self, ingredient):
6874 if ingredient .no_amount :
6975 self .amount = ""
7076 else :
71- self .amount = f"<scalable-number v-bind:number='{ bleach .clean (str (ingredient .amount ))} ' v-bind:factor='ingredient_factor'></scalable-number>"
72- self .numeric_amount = float (ingredient .amount )
77+ try :
78+ amount_val = float (ingredient .amount )
79+ except (ValueError , TypeError ):
80+ amount_val = 0.0
81+ self .amount = f"<scalable-number v-bind:number='{ amount_val } ' v-bind:factor='ingredient_factor'></scalable-number>"
82+ self .numeric_amount = amount_val
7383 self .unit = bleach .clean (_resolve_unit_name (ingredient ))
7484 if ingredient .food :
7585 if ingredient .food .plural_name in (None , "" ):
@@ -95,7 +105,7 @@ def __str__(self):
95105def render_instructions (step ): # TODO deduplicate markdown cleanup code
96106 instructions = step .instruction
97107
98- tags = {
108+ allowed_tags = [
99109 "h1" , "h2" , "h3" , "h4" , "h5" , "h6" ,
100110 "b" , "i" , "strong" , "em" , "tt" ,
101111 "p" , "br" ,
@@ -105,33 +115,43 @@ def render_instructions(step): # TODO deduplicate markdown cleanup code
105115 "a" ,
106116 "sub" , "sup" ,
107117 'pre' , 'table' , 'td' , 'tr' , 'th' , 'tbody' , 'thead' ,
108- 'scalable-number' ,
118+ ]
119+
120+ allowed_attributes = {
121+ "*" : ["id" , "class" , 'width' , 'height' ],
122+ "img" : ["src" , "alt" , "title" ],
123+ "a" : ["href" , "alt" , "title" ],
109124 }
110- parsed_md = md .markdown (
125+
126+ # do a first, strict round of cleaning
127+ instructions = bleach .clean (instructions , allowed_tags , allowed_attributes )
128+
129+ # parse markdown
130+ instructions = md .markdown (
111131 instructions ,
112132 extensions = [
113133 'markdown.extensions.fenced_code' , 'markdown.extensions.sane_lists' , 'markdown.extensions.nl2br' , TableExtension (),
114134 UrlizeExtension (), MarkdownFormatExtension ()
115135 ]
116136 )
117- markdown_attrs = {
118- "*" : ["id" , "class" , 'width' , 'height' ],
119- "img" : ["src" , "alt" , "title" ],
120- "a" : ["href" , "alt" , "title" ],
121- "scalable-number" : ["v-bind:number" , "v-bind:factor" ]
122- }
123137
138+ # prepare template context
124139 ingredients = []
125140
126141 for i in step .ingredients .all ():
127142 ingredients .append (IngredientObject (i ))
128143
129144 def scale (number ):
130- return f"<scalable-number v-bind:number='{ bleach .clean (str (number ))} ' v-bind:factor='ingredient_factor'></scalable-number>"
145+ try :
146+ number_val = float (number )
147+ except (ValueError , TypeError ):
148+ number_val = 0.0
149+ return f"<scalable-number v-bind:number='{ number_val } ' v-bind:factor='ingredient_factor'></scalable-number>"
131150
151+ # compile template
132152 try :
133153 env = SandboxedEnvironment ()
134- instructions = env .from_string (parsed_md ).render (ingredients = ingredients , scale = scale )
154+ instructions = env .from_string (instructions ).render (ingredients = ingredients , scale = scale )
135155 except TemplateSyntaxError :
136156 return _ ('Could not parse template code.' ) + ' Error: Template Syntax broken'
137157 except TypeError :
@@ -143,8 +163,42 @@ def scale(number):
143163 except Exception as e :
144164 return _ ('Could not parse template code.' ) + f' Error generating template.'
145165
146- instructions = bleach .clean (instructions , tags , markdown_attrs )
147-
166+ # do second cleaning that allows scalable-number
167+ def validate_scalable_number_attributes (tag , name , value ):
168+ if name == 'v-bind:number' :
169+ try :
170+ float (value )
171+ return True
172+ except (ValueError , TypeError ):
173+ return False
174+ if name == 'v-bind:factor' :
175+ return value == 'ingredient_factor'
176+ return False
177+
178+ def validate_plural_name_attributes (tag , name , value ):
179+ if name == 'v-bind:amount' :
180+ try :
181+ float (value )
182+ return True
183+ except (ValueError , TypeError ):
184+ return False
185+ if name == 'v-bind:factor' :
186+ return value == 'ingredient_factor'
187+ if name == ':no-amount' :
188+ return value == 'true' or value == 'false'
189+ if name in ["singular" , "plural" , ]:
190+ return True
191+ return False
192+
193+ allowed_attributes ["scalable-number" ] = validate_scalable_number_attributes
194+ allowed_attributes ["plural-name" ] = validate_plural_name_attributes
195+
196+ allowed_tags .append ('scalable-number' )
197+ allowed_tags .append ('plural-name' )
198+
199+ instructions = bleach .clean (instructions , allowed_tags , allowed_attributes )
200+
201+ # remove any left over { }
148202 instructions = instructions .replace ('{' ,'' )
149203 instructions = instructions .replace ('}' ,'' )
150204
0 commit comments