Skip to content

Commit c77320a

Browse files
akoclaude
andcommitted
fix: escape single quotes in page DESCRIBE output via mdlQuote()
DESCRIBE PAGE output embedded raw strings in single-quoted MDL literals without escaping apostrophes, causing parse errors on roundtrip. Added mdlQuote() helper that doubles single quotes (MDL convention) and applied it to all string properties: Title, Caption, Content, Label, Class, Style, Folder, Url, Tooltip, Visible, DynamicCellClass, and design property keys/values. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 009abc9 commit c77320a

File tree

2 files changed

+32
-26
lines changed

2 files changed

+32
-26
lines changed

mdl/executor/cmd_pages_describe.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,16 @@ func (e *Executor) describePage(name ast.QualifiedName) error {
8989
header := fmt.Sprintf("CREATE OR REPLACE PAGE %s.%s", modName, foundPage.Name)
9090
props := []string{}
9191
if title != "" {
92-
props = append(props, fmt.Sprintf("Title: '%s'", title))
92+
props = append(props, fmt.Sprintf("Title: %s", mdlQuote(title)))
9393
}
9494
if layoutName != "" {
9595
props = append(props, fmt.Sprintf("Layout: %s", layoutName))
9696
}
9797
if foundPage.URL != "" {
98-
props = append(props, fmt.Sprintf("Url: '%s'", foundPage.URL))
98+
props = append(props, fmt.Sprintf("Url: %s", mdlQuote(foundPage.URL)))
9999
}
100100
if folderPath := h.BuildFolderPath(foundPage.ContainerID); folderPath != "" {
101-
props = append(props, fmt.Sprintf("Folder: '%s'", folderPath))
101+
props = append(props, fmt.Sprintf("Folder: %s", mdlQuote(folderPath)))
102102
}
103103
if len(foundPage.Parameters) > 0 {
104104
params := []string{}
@@ -125,7 +125,7 @@ func (e *Executor) describePage(name ast.QualifiedName) error {
125125
varTypeName = bsonTypeToMDLType(vtType)
126126
}
127127
}
128-
varParts = append(varParts, fmt.Sprintf("$%s: %s = '%s'", varName, varTypeName, defaultVal))
128+
varParts = append(varParts, fmt.Sprintf("$%s: %s = %s", varName, varTypeName, mdlQuote(defaultVal)))
129129
}
130130
props = append(props, fmt.Sprintf("Variables: { %s }", strings.Join(varParts, ", ")))
131131
}
@@ -235,7 +235,7 @@ func (e *Executor) describeSnippet(name ast.QualifiedName) error {
235235
snippetProps = append(snippetProps, fmt.Sprintf("Params: { %s }", strings.Join(paramParts, ", ")))
236236
}
237237
if folderPath != "" {
238-
snippetProps = append(snippetProps, fmt.Sprintf("Folder: '%s'", folderPath))
238+
snippetProps = append(snippetProps, fmt.Sprintf("Folder: %s", mdlQuote(folderPath)))
239239
}
240240
fmt.Fprintf(e.output, " (%s)", strings.Join(snippetProps, ", "))
241241
}

mdl/executor/cmd_pages_describe_output.go

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ import (
1212
"go.mongodb.org/mongo-driver/bson/primitive"
1313
)
1414

15+
// mdlQuote wraps a string in single quotes, escaping any embedded single quotes
16+
// by doubling them (MDL convention: 'it''s here').
17+
func mdlQuote(s string) string {
18+
return "'" + strings.ReplaceAll(s, "'", "''") + "'"
19+
}
20+
1521
// appendDataGridPagingProps appends non-default paging properties for DataGrid2.
1622
func appendDataGridPagingProps(props []string, w rawWidget) []string {
1723
if w.PageSize != "" && w.PageSize != "20" {
@@ -33,10 +39,10 @@ func appendDataGridPagingProps(props []string, w rawWidget) []string {
3339
// appendAppearanceProps appends Class, Style, and DesignProperties if present.
3440
func appendAppearanceProps(props []string, w rawWidget) []string {
3541
if w.Class != "" {
36-
props = append(props, fmt.Sprintf("Class: '%s'", w.Class))
42+
props = append(props, fmt.Sprintf("Class: %s", mdlQuote(w.Class)))
3743
}
3844
if w.Style != "" {
39-
props = append(props, fmt.Sprintf("Style: '%s'", w.Style))
45+
props = append(props, fmt.Sprintf("Style: %s", mdlQuote(w.Style)))
4046
}
4147
if len(w.DesignProperties) > 0 {
4248
props = append(props, formatDesignPropertiesMDL(w.DesignProperties))
@@ -51,9 +57,9 @@ func formatDesignPropertiesMDL(dps []rawDesignProp) string {
5157
for _, dp := range dps {
5258
switch dp.ValueType {
5359
case "toggle":
54-
entries = append(entries, fmt.Sprintf("'%s': ON", dp.Key))
60+
entries = append(entries, fmt.Sprintf("%s: ON", mdlQuote(dp.Key)))
5561
case "option":
56-
entries = append(entries, fmt.Sprintf("'%s': '%s'", dp.Key, dp.Option))
62+
entries = append(entries, fmt.Sprintf("%s: %s", mdlQuote(dp.Key), mdlQuote(dp.Option)))
5763
}
5864
}
5965
return fmt.Sprintf("DesignProperties: [%s]", strings.Join(entries, ", "))
@@ -109,7 +115,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) {
109115
header := fmt.Sprintf("GROUPBOX %s", w.Name)
110116
props := []string{}
111117
if w.Caption != "" {
112-
props = append(props, fmt.Sprintf("Caption: '%s'", w.Caption))
118+
props = append(props, fmt.Sprintf("Caption: %s", mdlQuote(w.Caption)))
113119
}
114120
if w.HeaderMode != "" && w.HeaderMode != "Div" {
115121
props = append(props, fmt.Sprintf("HeaderMode: %s", w.HeaderMode))
@@ -163,7 +169,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) {
163169
header := fmt.Sprintf("DYNAMICTEXT %s", w.Name)
164170
props := []string{}
165171
if w.Content != "" {
166-
props = append(props, fmt.Sprintf("Content: '%s'", w.Content))
172+
props = append(props, fmt.Sprintf("Content: %s", mdlQuote(w.Content)))
167173
}
168174
if w.RenderMode != "" && w.RenderMode != "Text" {
169175
props = append(props, fmt.Sprintf("RenderMode: %s", w.RenderMode))
@@ -178,7 +184,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) {
178184
header := fmt.Sprintf("ACTIONBUTTON %s", w.Name)
179185
props := []string{}
180186
if w.Caption != "" {
181-
props = append(props, fmt.Sprintf("Caption: '%s'", w.Caption))
187+
props = append(props, fmt.Sprintf("Caption: %s", mdlQuote(w.Caption)))
182188
}
183189
if len(w.Parameters) > 0 {
184190
props = append(props, fmt.Sprintf("ContentParams: [%s]", strings.Join(formatParametersV3(w.Parameters), ", ")))
@@ -195,7 +201,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) {
195201
case "Forms$Text", "Pages$Text":
196202
props := []string{}
197203
if w.Content != "" {
198-
props = append(props, fmt.Sprintf("Content: '%s'", w.Content))
204+
props = append(props, fmt.Sprintf("Content: %s", mdlQuote(w.Content)))
199205
}
200206
props = appendAppearanceProps(props, w)
201207
formatWidgetProps(e.output, prefix, "STATICTEXT", props, "\n")
@@ -204,7 +210,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) {
204210
header := fmt.Sprintf("TITLE %s", w.Name)
205211
props := []string{}
206212
if w.Caption != "" {
207-
props = append(props, fmt.Sprintf("Content: '%s'", w.Caption))
213+
props = append(props, fmt.Sprintf("Content: %s", mdlQuote(w.Caption)))
208214
}
209215
props = appendAppearanceProps(props, w)
210216
formatWidgetProps(e.output, prefix, header, props, "\n")
@@ -233,7 +239,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) {
233239
header := fmt.Sprintf("TEXTBOX %s", w.Name)
234240
props := []string{}
235241
if w.Caption != "" {
236-
props = append(props, fmt.Sprintf("Label: '%s'", w.Caption))
242+
props = append(props, fmt.Sprintf("Label: %s", mdlQuote(w.Caption)))
237243
}
238244
if w.Content != "" {
239245
props = append(props, fmt.Sprintf("Attribute: %s", w.Content))
@@ -245,7 +251,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) {
245251
header := fmt.Sprintf("TEXTAREA %s", w.Name)
246252
props := []string{}
247253
if w.Caption != "" {
248-
props = append(props, fmt.Sprintf("Label: '%s'", w.Caption))
254+
props = append(props, fmt.Sprintf("Label: %s", mdlQuote(w.Caption)))
249255
}
250256
if w.Content != "" {
251257
props = append(props, fmt.Sprintf("Attribute: %s", w.Content))
@@ -257,7 +263,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) {
257263
header := fmt.Sprintf("DATEPICKER %s", w.Name)
258264
props := []string{}
259265
if w.Caption != "" {
260-
props = append(props, fmt.Sprintf("Label: '%s'", w.Caption))
266+
props = append(props, fmt.Sprintf("Label: %s", mdlQuote(w.Caption)))
261267
}
262268
if w.Content != "" {
263269
props = append(props, fmt.Sprintf("Attribute: %s", w.Content))
@@ -269,7 +275,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) {
269275
header := fmt.Sprintf("RADIOBUTTONS %s", w.Name)
270276
props := []string{}
271277
if w.Caption != "" {
272-
props = append(props, fmt.Sprintf("Label: '%s'", w.Caption))
278+
props = append(props, fmt.Sprintf("Label: %s", mdlQuote(w.Caption)))
273279
}
274280
if w.Content != "" {
275281
props = append(props, fmt.Sprintf("Attribute: %s", w.Content))
@@ -281,7 +287,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) {
281287
header := fmt.Sprintf("CHECKBOX %s", w.Name)
282288
props := []string{}
283289
if w.Caption != "" {
284-
props = append(props, fmt.Sprintf("Label: '%s'", w.Caption))
290+
props = append(props, fmt.Sprintf("Label: %s", mdlQuote(w.Caption)))
285291
}
286292
if w.Content != "" {
287293
props = append(props, fmt.Sprintf("Attribute: %s", w.Content))
@@ -434,7 +440,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) {
434440
header := fmt.Sprintf("%s %s", widgetType, w.Name)
435441
props := []string{}
436442
if w.Caption != "" {
437-
props = append(props, fmt.Sprintf("Label: '%s'", w.Caption))
443+
props = append(props, fmt.Sprintf("Label: %s", mdlQuote(w.Caption)))
438444
}
439445
if w.Content != "" {
440446
props = append(props, fmt.Sprintf("Attribute: %s", w.Content))
@@ -483,7 +489,7 @@ func (e *Executor) outputWidgetMDLV3(w rawWidget, indent int) {
483489
fmt.Fprintf(e.output, "%s}\n", prefix)
484490

485491
case "Forms$Label", "Pages$Label":
486-
fmt.Fprintf(e.output, "%sSTATICTEXT (Content: '%s')\n", prefix, w.Content)
492+
fmt.Fprintf(e.output, "%sSTATICTEXT (Content: %s)\n", prefix, mdlQuote(w.Content))
487493

488494
case "Forms$Gallery", "Pages$Gallery":
489495
header := fmt.Sprintf("GALLERY %s", w.Name)
@@ -618,7 +624,7 @@ func (e *Executor) outputDataGrid2ColumnV3(prefix, colName string, col rawDataGr
618624
props = append(props, fmt.Sprintf("Attribute: %s", col.Attribute))
619625
}
620626
if col.Caption != "" {
621-
props = append(props, fmt.Sprintf("Caption: '%s'", col.Caption))
627+
props = append(props, fmt.Sprintf("Caption: %s", mdlQuote(col.Caption)))
622628
}
623629
if len(col.CaptionParams) > 0 {
624630
props = append(props, fmt.Sprintf("CaptionParams: [%s]", strings.Join(formatParametersV3(col.CaptionParams), ", ")))
@@ -629,7 +635,7 @@ func (e *Executor) outputDataGrid2ColumnV3(prefix, colName string, col rawDataGr
629635
}
630636
// Add DynamicText content when ShowContentAs is dynamicText
631637
if col.ShowContentAs == "dynamicText" && col.DynamicText != "" {
632-
props = append(props, fmt.Sprintf("Content: '%s'", col.DynamicText))
638+
props = append(props, fmt.Sprintf("Content: %s", mdlQuote(col.DynamicText)))
633639
if len(col.DynamicTextParams) > 0 {
634640
props = append(props, fmt.Sprintf("ContentParams: [%s]", strings.Join(formatParametersV3(col.DynamicTextParams), ", ")))
635641
}
@@ -667,13 +673,13 @@ func (e *Executor) outputDataGrid2ColumnV3(prefix, colName string, col rawDataGr
667673
props = append(props, fmt.Sprintf("Size: %s", col.Size))
668674
}
669675
if col.Visible != "" && col.Visible != "true" {
670-
props = append(props, fmt.Sprintf("Visible: '%s'", col.Visible))
676+
props = append(props, fmt.Sprintf("Visible: %s", mdlQuote(col.Visible)))
671677
}
672678
if col.DynamicCellClass != "" {
673-
props = append(props, fmt.Sprintf("DynamicCellClass: '%s'", col.DynamicCellClass))
679+
props = append(props, fmt.Sprintf("DynamicCellClass: %s", mdlQuote(col.DynamicCellClass)))
674680
}
675681
if col.Tooltip != "" {
676-
props = append(props, fmt.Sprintf("Tooltip: '%s'", col.Tooltip))
682+
props = append(props, fmt.Sprintf("Tooltip: %s", mdlQuote(col.Tooltip)))
677683
}
678684

679685
// Check if we have content widgets to display

0 commit comments

Comments
 (0)