source: stable/HeuristicLab.Problems.DataAnalysis.Symbolic/3.4/Formatters/SymbolicDataAnalysisExpressionLatexFormatter.cs @ 14309

Last change on this file since 14309 was 14309, checked in by gkronber, 3 years ago

#2632: merged r14255 from trunk to stable

File size: 20.4 KB
RevLine 
[4327]1#region License Information
2/* HeuristicLab
[14186]3 * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
[4327]4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
[6975]22using System;
23using System.Collections.Generic;
24using System.Linq;
[4327]25using System.Text;
[6975]26using HeuristicLab.Common;
27using HeuristicLab.Core;
28using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
[4327]29using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
30
[5745]31namespace HeuristicLab.Problems.DataAnalysis.Symbolic {
[4969]32  [Item("LaTeX String Formatter", "Formatter for symbolic expression trees for import into LaTeX documents.")]
[5429]33  [StorableClass]
[5745]34  public sealed class SymbolicDataAnalysisExpressionLatexFormatter : NamedItem, ISymbolicExpressionTreeStringFormatter {
[8798]35    private readonly List<double> constants;
36    private int targetCount;
[5428]37    private int currentLag;
[14309]38    private string targetVariable;
39    private bool containsTimeSeriesSymbol;
[4969]40
[5429]41    [StorableConstructor]
[5745]42    private SymbolicDataAnalysisExpressionLatexFormatter(bool deserializing) : base(deserializing) { }
43    private SymbolicDataAnalysisExpressionLatexFormatter(SymbolicDataAnalysisExpressionLatexFormatter original, Cloner cloner)
[5429]44      : base(original, cloner) {
45      constants = new List<double>(original.constants);
46    }
[5745]47    public SymbolicDataAnalysisExpressionLatexFormatter()
48      : base() {
49      Name = ItemName;
50      Description = ItemDescription;
[4969]51      constants = new List<double>();
52    }
[4327]53
[4900]54    public override IDeepCloneable Clone(Cloner cloner) {
[5745]55      return new SymbolicDataAnalysisExpressionLatexFormatter(this, cloner);
[4900]56    }
57
[5745]58    public string Format(ISymbolicExpressionTree symbolicExpressionTree) {
[14309]59      return Format(symbolicExpressionTree, null);
60    }
61    public string Format(ISymbolicExpressionTree symbolicExpressionTree, string targetVariable) {
[4327]62      try {
63        StringBuilder strBuilder = new StringBuilder();
[4969]64        constants.Clear();
[14309]65        this.targetVariable = targetVariable;
66        containsTimeSeriesSymbol = symbolicExpressionTree.IterateNodesBreadth().Any(n => IsTimeSeriesSymbol(n.Symbol));
[4327]67        strBuilder.AppendLine(FormatRecursively(symbolicExpressionTree.Root));
68        return strBuilder.ToString();
69      }
70      catch (NotImplementedException ex) {
71        return ex.Message + Environment.NewLine + ex.StackTrace;
72      }
73    }
[14309]74    static bool IsTimeSeriesSymbol(ISymbol s) {
75      return s is TimeLag || s is Integral || s is Derivative || s is LaggedVariable;
76    }
[4327]77
[5745]78    private string FormatRecursively(ISymbolicExpressionTreeNode node) {
[4327]79      StringBuilder strBuilder = new StringBuilder();
[5428]80      currentLag = 0;
[4327]81      FormatBegin(node, strBuilder);
82
[6803]83      if (node.SubtreeCount > 0) {
[5745]84        strBuilder.Append(FormatRecursively(node.GetSubtree(0)));
[4327]85      }
[7140]86      int i = 1;
[5745]87      foreach (SymbolicExpressionTreeNode subTree in node.Subtrees.Skip(1)) {
[7140]88        FormatSep(node, strBuilder, i);
[4327]89        // format the whole subtree
90        strBuilder.Append(FormatRecursively(subTree));
[7140]91        i++;
[4327]92      }
93
94      FormatEnd(node, strBuilder);
95
96      return strBuilder.ToString();
97    }
98
[5745]99    private void FormatBegin(ISymbolicExpressionTreeNode node, StringBuilder strBuilder) {
[4327]100      if (node.Symbol is Addition) {
[7446]101        strBuilder.Append(@" \left( ");
[4327]102      } else if (node.Symbol is Subtraction) {
[6803]103        if (node.SubtreeCount == 1) {
[7446]104          strBuilder.Append(@"- \left( ");
[5428]105        } else {
[7446]106          strBuilder.Append(@" \left( ");
[5428]107        }
[4327]108      } else if (node.Symbol is Multiplication) {
109      } else if (node.Symbol is Division) {
[6803]110        if (node.SubtreeCount == 1) {
[8798]111          strBuilder.Append(@" \cfrac{1");
[5428]112        } else {
113          strBuilder.Append(@" \cfrac{ ");
114        }
[4969]115      } else if (node.Symbol is Average) {
[5428]116        // skip output of (1/1) if only one subtree
[6803]117        if (node.SubtreeCount > 1) {
118          strBuilder.Append(@" \cfrac{1}{" + node.SubtreeCount + @"}");
[5428]119        }
[7446]120        strBuilder.Append(@" \left( ");
[4969]121      } else if (node.Symbol is Logarithm) {
[7446]122        strBuilder.Append(@"\log \left( ");
[4969]123      } else if (node.Symbol is Exponential) {
[7446]124        strBuilder.Append(@"\exp \left( ");
[7695]125      } else if (node.Symbol is Square) {
126        strBuilder.Append(@"\left(");
127      } else if (node.Symbol is SquareRoot) {
128        strBuilder.Append(@"\sqrt{");
[4969]129      } else if (node.Symbol is Sine) {
[7446]130        strBuilder.Append(@"\sin \left( ");
[4969]131      } else if (node.Symbol is Cosine) {
[7446]132        strBuilder.Append(@"\cos \left( ");
[4969]133      } else if (node.Symbol is Tangent) {
[7446]134        strBuilder.Append(@"\tan \left( ");
[7696]135      } else if (node.Symbol is AiryA) {
136        strBuilder.Append(@"\operatorname{airy}_a \left( ");
137      } else if (node.Symbol is AiryB) {
138        strBuilder.Append(@"\operatorname{airy}_b \left( ");
139      } else if (node.Symbol is Bessel) {
140        strBuilder.Append(@"\operatorname{bessel}_1 \left( ");
141      } else if (node.Symbol is CosineIntegral) {
142        strBuilder.Append(@"\operatorname{cosInt} \left( ");
143      } else if (node.Symbol is Dawson) {
144        strBuilder.Append(@"\operatorname{dawson} \left( ");
145      } else if (node.Symbol is Erf) {
146        strBuilder.Append(@"\operatorname{erf} \left( ");
147      } else if (node.Symbol is ExponentialIntegralEi) {
148        strBuilder.Append(@"\operatorname{expInt}_i \left( ");
149      } else if (node.Symbol is FresnelCosineIntegral) {
[7708]150        strBuilder.Append(@"\operatorname{fresnel}_\operatorname{cosInt} \left( ");
[7696]151      } else if (node.Symbol is FresnelSineIntegral) {
[7708]152        strBuilder.Append(@"\operatorname{fresnel}_\operatorname{sinInt} \left( ");
[7696]153      } else if (node.Symbol is Gamma) {
154        strBuilder.Append(@"\Gamma \left( ");
155      } else if (node.Symbol is HyperbolicCosineIntegral) {
156        strBuilder.Append(@"\operatorname{hypCosInt} \left( ");
157      } else if (node.Symbol is HyperbolicSineIntegral) {
158        strBuilder.Append(@"\operatorname{hypSinInt} \left( ");
[7697]159      } else if (node.Symbol is Norm) {
160        strBuilder.Append(@"\operatorname{norm} \left( ");
[7696]161      } else if (node.Symbol is Psi) {
162        strBuilder.Append(@"\operatorname{digamma} \left( ");
163      } else if (node.Symbol is SineIntegral) {
164        strBuilder.Append(@"\operatorname{sinInt} \left( ");
[4969]165      } else if (node.Symbol is GreaterThan) {
[7446]166        strBuilder.Append(@"  \left( ");
[4969]167      } else if (node.Symbol is LessThan) {
[7446]168        strBuilder.Append(@"  \left( ");
[4969]169      } else if (node.Symbol is And) {
[7451]170        strBuilder.Append(@"  \left( \left( ");
[4969]171      } else if (node.Symbol is Or) {
[7451]172        strBuilder.Append(@"   \left( \left( ");
[4969]173      } else if (node.Symbol is Not) {
[7446]174        strBuilder.Append(@" \neg \left( ");
[4969]175      } else if (node.Symbol is IfThenElse) {
[7446]176        strBuilder.Append(@" \operatorname{if}  \left( ");
[4327]177      } else if (node.Symbol is Constant) {
[5428]178        strBuilder.Append("c_{" + constants.Count + "} ");
[4969]179        var constNode = node as ConstantTreeNode;
180        constants.Add(constNode.Value);
[5428]181      } else if (node.Symbol is LaggedVariable) {
182        var laggedVarNode = node as LaggedVariableTreeNode;
[7038]183        if (!laggedVarNode.Weight.IsAlmost(1.0)) {
184          strBuilder.Append("c_{" + constants.Count + "} \\cdot ");
185          constants.Add(laggedVarNode.Weight);
186        }
187        strBuilder.Append(EscapeLatexString(laggedVarNode.VariableName));
[5428]188        strBuilder.Append(LagToString(currentLag + laggedVarNode.Lag));
[7038]189
190      } else if (node.Symbol is Variable) {
[4327]191        var varNode = node as VariableTreeNode;
[7038]192        if (!varNode.Weight.IsAlmost((1.0))) {
193          strBuilder.Append("c_{" + constants.Count + "} \\cdot ");
194          constants.Add(varNode.Weight);
195        }
196        strBuilder.Append(EscapeLatexString(varNode.VariableName));
[5428]197        strBuilder.Append(LagToString(currentLag));
[4327]198      } else if (node.Symbol is ProgramRootSymbol) {
[7446]199        strBuilder
200          .AppendLine("\\begin{align*}")
201          .AppendLine("\\nonumber");
[4327]202      } else if (node.Symbol is Defun) {
203        var defunNode = node as DefunTreeNode;
[4969]204        strBuilder.Append(defunNode.FunctionName + " & = ");
[4327]205      } else if (node.Symbol is InvokeFunction) {
206        var invokeNode = node as InvokeFunctionTreeNode;
[7446]207        strBuilder.Append(invokeNode.Symbol.FunctionName + @" \left( ");
[4327]208      } else if (node.Symbol is StartSymbol) {
[14309]209        FormatStartSymbol(strBuilder);
[4900]210      } else if (node.Symbol is Argument) {
[4327]211        var argSym = node.Symbol as Argument;
[4900]212        strBuilder.Append(" ARG+" + argSym.ArgumentIndex + " ");
[5428]213      } else if (node.Symbol is Derivative) {
[7446]214        strBuilder.Append(@" \cfrac{d \left( ");
[5428]215      } else if (node.Symbol is TimeLag) {
216        var laggedNode = node as ILaggedTreeNode;
217        currentLag += laggedNode.Lag;
218      } else if (node.Symbol is Power) {
[7446]219        strBuilder.Append(@" \left( ");
[5428]220      } else if (node.Symbol is Root) {
[7446]221        strBuilder.Append(@" \left( ");
[5428]222      } else if (node.Symbol is Integral) {
223        // actually a new variable for t is needed in all subtrees (TODO)
224        var laggedTreeNode = node as ILaggedTreeNode;
[7446]225        strBuilder.Append(@"\sum_{t=" + (laggedTreeNode.Lag + currentLag) + @"}^0 \left( ");
[5468]226      } else if (node.Symbol is VariableCondition) {
227        var conditionTreeNode = node as VariableConditionTreeNode;
[7038]228        string p = @"1 /  1 + \exp  - c_{" + constants.Count + "} ";
[5468]229        constants.Add(conditionTreeNode.Slope);
[7038]230        p += @" \cdot " + EscapeLatexString(conditionTreeNode.VariableName) + LagToString(currentLag) + " - c_{" + constants.Count + @"}   ";
[5468]231        constants.Add(conditionTreeNode.Threshold);
[7446]232        strBuilder.Append(@" \left( " + p + @"\cdot ");
[4327]233      } else {
234        throw new NotImplementedException("Export of " + node.Symbol + " is not implemented.");
235      }
236    }
237
[7140]238    private void FormatSep(ISymbolicExpressionTreeNode node, StringBuilder strBuilder, int step) {
[4327]239      if (node.Symbol is Addition) {
240        strBuilder.Append(" + ");
241      } else if (node.Symbol is Subtraction) {
242        strBuilder.Append(" - ");
243      } else if (node.Symbol is Multiplication) {
[5428]244        strBuilder.Append(@" \cdot ");
[4327]245      } else if (node.Symbol is Division) {
[7140]246        if (step + 1 == node.SubtreeCount)
247          strBuilder.Append(@"}{");
248        else
249          strBuilder.Append(@" }{ \cfrac{ ");
[4969]250      } else if (node.Symbol is Average) {
251        strBuilder.Append(@" + ");
252      } else if (node.Symbol is Logarithm) {
253        throw new InvalidOperationException();
254      } else if (node.Symbol is Exponential) {
255        throw new InvalidOperationException();
[7695]256      } else if (node.Symbol is Square) {
257        throw new InvalidOperationException();
258      } else if (node.Symbol is SquareRoot) {
259        throw new InvalidOperationException();
[4969]260      } else if (node.Symbol is Sine) {
261        throw new InvalidOperationException();
262      } else if (node.Symbol is Cosine) {
263        throw new InvalidOperationException();
264      } else if (node.Symbol is Tangent) {
265        throw new InvalidOperationException();
[7696]266      } else if (node.Symbol is AiryA) {
267        throw new InvalidOperationException();
268      } else if (node.Symbol is AiryB) {
269        throw new InvalidOperationException();
270      } else if (node.Symbol is Bessel) {
271        throw new InvalidOperationException();
272      } else if (node.Symbol is CosineIntegral) {
273        throw new InvalidOperationException();
274      } else if (node.Symbol is Dawson) {
275        throw new InvalidOperationException();
276      } else if (node.Symbol is Erf) {
277        throw new InvalidOperationException();
278      } else if (node.Symbol is ExponentialIntegralEi) {
279        throw new InvalidOperationException();
280      } else if (node.Symbol is FresnelCosineIntegral) {
281        throw new InvalidOperationException();
282      } else if (node.Symbol is FresnelSineIntegral) {
283        throw new InvalidOperationException();
284      } else if (node.Symbol is Gamma) {
285        throw new InvalidOperationException();
286      } else if (node.Symbol is HyperbolicCosineIntegral) {
287        throw new InvalidOperationException();
288      } else if (node.Symbol is HyperbolicSineIntegral) {
289        throw new InvalidOperationException();
[7697]290      } else if (node.Symbol is Norm) {
291        throw new InvalidOperationException();
[7696]292      } else if (node.Symbol is Psi) {
293        throw new InvalidOperationException();
294      } else if (node.Symbol is SineIntegral) {
295        throw new InvalidOperationException();
[4969]296      } else if (node.Symbol is GreaterThan) {
297        strBuilder.Append(@" > ");
298      } else if (node.Symbol is LessThan) {
299        strBuilder.Append(@" < ");
300      } else if (node.Symbol is And) {
[7446]301        strBuilder.Append(@" > 0  \right) \land \left(");
[4969]302      } else if (node.Symbol is Or) {
[7446]303        strBuilder.Append(@" > 0  \right) \lor \left(");
[4969]304      } else if (node.Symbol is Not) {
305        throw new InvalidOperationException();
306      } else if (node.Symbol is IfThenElse) {
[7446]307        strBuilder.Append(@" , ");
[4327]308      } else if (node.Symbol is ProgramRootSymbol) {
309        strBuilder.Append(@"\\" + Environment.NewLine);
310      } else if (node.Symbol is Defun) {
311      } else if (node.Symbol is InvokeFunction) {
312        strBuilder.Append(" , ");
313      } else if (node.Symbol is StartSymbol) {
[8798]314        strBuilder.Append(@"\\" + Environment.NewLine);
[14309]315        FormatStartSymbol(strBuilder);
[5428]316      } else if (node.Symbol is Power) {
[7446]317        strBuilder.Append(@"\right) ^ { \operatorname{round} \left(");
[5428]318      } else if (node.Symbol is Root) {
[7446]319        strBuilder.Append(@"\right) ^ {  \cfrac{1}{ \operatorname{round} \left(");
[5468]320      } else if (node.Symbol is VariableCondition) {
321        var conditionTreeNode = node as VariableConditionTreeNode;
[7446]322        string p = @"1 / \left( 1 + \exp \left( - c_{" + constants.Count + "} ";
[5468]323        constants.Add(conditionTreeNode.Slope);
[7446]324        p += @" \cdot " + EscapeLatexString(conditionTreeNode.VariableName) + LagToString(currentLag) + " - c_{" + constants.Count + @"} \right) \right) \right)   ";
[5468]325        constants.Add(conditionTreeNode.Threshold);
[7446]326        strBuilder.Append(@" +  \left( 1 - " + p + @" \right) \cdot ");
[4327]327      } else {
328        throw new NotImplementedException("Export of " + node.Symbol + " is not implemented.");
329      }
330    }
331
[5745]332    private void FormatEnd(ISymbolicExpressionTreeNode node, StringBuilder strBuilder) {
[4327]333      if (node.Symbol is Addition) {
[7446]334        strBuilder.Append(@" \right) ");
[4327]335      } else if (node.Symbol is Subtraction) {
[7446]336        strBuilder.Append(@" \right) ");
[4327]337      } else if (node.Symbol is Multiplication) {
338      } else if (node.Symbol is Division) {
[7140]339        strBuilder.Append(" } ");
340        for (int i = 2; i < node.SubtreeCount; i++)
[5428]341          strBuilder.Append(" } ");
[4969]342      } else if (node.Symbol is Average) {
[7446]343        strBuilder.Append(@" \right) ");
[4969]344      } else if (node.Symbol is Logarithm) {
[7446]345        strBuilder.Append(@" \right) ");
[4969]346      } else if (node.Symbol is Exponential) {
[7446]347        strBuilder.Append(@" \right) ");
[7695]348      } else if (node.Symbol is Square) {
349        strBuilder.Append(@"\right)^2");
350      } else if (node.Symbol is SquareRoot) {
351        strBuilder.Append(@"}");
[4969]352      } else if (node.Symbol is Sine) {
[7446]353        strBuilder.Append(@" \right) ");
[4969]354      } else if (node.Symbol is Cosine) {
[7446]355        strBuilder.Append(@" \right) ");
[4969]356      } else if (node.Symbol is Tangent) {
[7446]357        strBuilder.Append(@" \right) ");
[7696]358      } else if (node.Symbol is AiryA) {
359        strBuilder.Append(@" \right) ");
360      } else if (node.Symbol is AiryB) {
361        strBuilder.Append(@" \right) ");
362      } else if (node.Symbol is Bessel) {
363        strBuilder.Append(@" \right) ");
364      } else if (node.Symbol is CosineIntegral) {
365        strBuilder.Append(@" \right) ");
366      } else if (node.Symbol is Dawson) {
367        strBuilder.Append(@" \right) ");
368      } else if (node.Symbol is Erf) {
369        strBuilder.Append(@" \right) ");
370      } else if (node.Symbol is ExponentialIntegralEi) {
371        strBuilder.Append(@" \right) ");
372      } else if (node.Symbol is FresnelCosineIntegral) {
373        strBuilder.Append(@" \right) ");
374      } else if (node.Symbol is FresnelSineIntegral) {
375        strBuilder.Append(@" \right) ");
376      } else if (node.Symbol is Gamma) {
377        strBuilder.Append(@" \right) ");
378      } else if (node.Symbol is HyperbolicCosineIntegral) {
379        strBuilder.Append(@" \right) ");
380      } else if (node.Symbol is HyperbolicSineIntegral) {
381        strBuilder.Append(@" \right) ");
[7697]382      } else if (node.Symbol is Norm) {
383        strBuilder.Append(@" \right) ");
[7696]384      } else if (node.Symbol is Psi) {
385        strBuilder.Append(@" \right) ");
386      } else if (node.Symbol is SineIntegral) {
387        strBuilder.Append(@" \right) ");
[4969]388      } else if (node.Symbol is GreaterThan) {
[7446]389        strBuilder.Append(@" \right) ");
[4969]390      } else if (node.Symbol is LessThan) {
[7446]391        strBuilder.Append(@" \right) ");
[4969]392      } else if (node.Symbol is And) {
[7446]393        strBuilder.Append(@" > 0 \right) \right) ");
[4969]394      } else if (node.Symbol is Or) {
[7446]395        strBuilder.Append(@" > 0 \right) \right) ");
[4969]396      } else if (node.Symbol is Not) {
[7446]397        strBuilder.Append(@" \right) ");
[4969]398      } else if (node.Symbol is IfThenElse) {
[7446]399        strBuilder.Append(@" \right) ");
[4327]400      } else if (node.Symbol is Constant) {
[5428]401      } else if (node.Symbol is LaggedVariable) {
[7038]402      } else if (node.Symbol is Variable) {
[4327]403      } else if (node.Symbol is ProgramRootSymbol) {
[7446]404        strBuilder
405          .AppendLine("\\end{align*}")
406          .AppendLine("\\begin{align*}")
407          .AppendLine("\\nonumber");
[4969]408        // output all constant values
409        if (constants.Count > 0) {
410          int i = 0;
411          foreach (var constant in constants) {
[7446]412            // replace "." with ".&" to align decimal points
413            var constStr = string.Format(System.Globalization.NumberFormatInfo.InvariantInfo, "{0:G5}", constant);
[7451]414            if (!constStr.Contains(".")) constStr = constStr + ".0";
[14003]415            constStr = constStr.Replace(".", "&.");  // fix problem in rendering of aligned expressions
[7446]416            strBuilder.Append("c_{" + i + "}& = & " + constStr);
417            strBuilder.Append(@"\\");
[4969]418            i++;
419          }
420        }
[7446]421        strBuilder.AppendLine("\\end{align*}");
[4327]422      } else if (node.Symbol is Defun) {
423      } else if (node.Symbol is InvokeFunction) {
[7446]424        strBuilder.Append(@" \right) ");
[4327]425      } else if (node.Symbol is StartSymbol) {
426      } else if (node.Symbol is Argument) {
[5428]427      } else if (node.Symbol is Derivative) {
[7446]428        strBuilder.Append(@" \right) }{dt} ");
[5428]429      } else if (node.Symbol is TimeLag) {
430        var laggedNode = node as ILaggedTreeNode;
431        currentLag -= laggedNode.Lag;
432      } else if (node.Symbol is Power) {
[7446]433        strBuilder.Append(@" \right) } ");
[5428]434      } else if (node.Symbol is Root) {
[7446]435        strBuilder.Append(@" \right) } } ");
[5428]436      } else if (node.Symbol is Integral) {
[7446]437        strBuilder.Append(@" \right) ");
[5468]438      } else if (node.Symbol is VariableCondition) {
[7446]439        strBuilder.Append(@"\right) ");
[4327]440      } else {
441        throw new NotImplementedException("Export of " + node.Symbol + " is not implemented.");
442      }
443    }
[6975]444
[14309]445    private void FormatStartSymbol(StringBuilder strBuilder) {
446      strBuilder.Append(targetVariable ?? "target_" + (targetCount++));
447      if (containsTimeSeriesSymbol)
448        strBuilder.Append("(t)");
449      strBuilder.Append(" & = ");
450    }
451
[5428]452    private string LagToString(int lag) {
453      if (lag < 0) {
454        return "(t" + lag + ")";
455      } else if (lag > 0) {
456        return "(t+" + lag + ")";
[7038]457      } else return "";
[5428]458    }
[6975]459
460    private string EscapeLatexString(string s) {
[7446]461      return "\\text{" +
462        s
463         .Replace("\\", "\\\\")
464         .Replace("{", "\\{")
465         .Replace("}", "\\}")
466        + "}";
[6975]467    }
[4327]468  }
469}
Note: See TracBrowser for help on using the repository browser.