66
77namespace pyRevitExtensionParser
88{
9+ /// <summary>
10+ /// Provides functionality for reading and writing INI configuration files using Windows API.
11+ /// Supports both standard key-value pairs and Python-style list values.
12+ /// </summary>
913 internal class IniFile
1014 {
15+ /// <summary>
16+ /// The full path to the INI file.
17+ /// </summary>
1118 private readonly string _path ;
1219
20+ /// <summary>
21+ /// Initializes a new instance of the <see cref="IniFile"/> class.
22+ /// </summary>
23+ /// <param name="iniPath">The full path to the INI file.</param>
1324 public IniFile ( string iniPath )
1425 {
1526 _path = iniPath ;
1627 }
1728
29+ /// <summary>
30+ /// Writes a value to an INI file using the Windows API.
31+ /// </summary>
32+ /// <param name="section">The section name in the INI file.</param>
33+ /// <param name="key">The key name within the section.</param>
34+ /// <param name="val">The value to write.</param>
35+ /// <param name="filePath">The path to the INI file.</param>
36+ /// <returns>Non-zero if successful; otherwise, zero.</returns>
1837 [ DllImport ( "kernel32" ) ]
1938 private static extern long WritePrivateProfileString ( string section , string key , string val , string filePath ) ;
2039
40+ /// <summary>
41+ /// Retrieves a value from an INI file using the Windows API.
42+ /// </summary>
43+ /// <param name="section">The section name in the INI file. Pass null to retrieve all section names.</param>
44+ /// <param name="key">The key name within the section. Pass null to retrieve all keys in a section.</param>
45+ /// <param name="def">The default value to return if the key is not found.</param>
46+ /// <param name="retVal">A StringBuilder to receive the retrieved value.</param>
47+ /// <param name="size">The size of the buffer (StringBuilder capacity).</param>
48+ /// <param name="filePath">The path to the INI file.</param>
49+ /// <returns>The number of characters copied to the buffer, excluding the null terminator.</returns>
2150 [ DllImport ( "kernel32" ) ]
2251 private static extern int GetPrivateProfileString ( string section , string key , string def , StringBuilder retVal , int size , string filePath ) ;
2352
53+ /// <summary>
54+ /// Reads a value from the INI file for the specified section and key.
55+ /// </summary>
56+ /// <param name="section">The section name to read from.</param>
57+ /// <param name="key">The key name within the section.</param>
58+ /// <returns>The value associated with the key, or an empty string if not found.</returns>
2459 public string IniReadValue ( string section , string key )
2560 {
26- var sb = new StringBuilder ( 512 ) ;
61+ // Increased capacity for larger ini values (like Python lists)
62+ var sb = new StringBuilder ( 2048 ) ;
2763 GetPrivateProfileString ( section , key , "" , sb , sb . Capacity , _path ) ;
2864 return sb . ToString ( ) ;
2965 }
3066
67+ /// <summary>
68+ /// Writes a value to the INI file for the specified section and key.
69+ /// </summary>
70+ /// <param name="section">The section name to write to.</param>
71+ /// <param name="key">The key name within the section.</param>
72+ /// <param name="value">The value to write.</param>
3173 public void IniWriteValue ( string section , string key , string value )
3274 {
3375 WritePrivateProfileString ( section , key , value , _path ) ;
3476 }
3577
78+ /// <summary>
79+ /// Retrieves all section names from the INI file.
80+ /// </summary>
81+ /// <returns>An enumerable collection of section names.</returns>
3682 public IEnumerable < string > GetSections ( )
3783 {
3884 var sb = new StringBuilder ( 2048 ) ;
3985 GetPrivateProfileString ( null , null , null , sb , sb . Capacity , _path ) ;
4086 return sb . ToString ( ) . Split ( new [ ] { '\0 ' } , StringSplitOptions . RemoveEmptyEntries ) ;
4187 }
42- // Adds a value to the Python-like list in the .ini file
88+ /// <summary>
89+ /// Adds a value to a Python-style list stored in the INI file.
90+ /// The list is stored as a string in the format: ["value1", "value2", "value3"]
91+ /// </summary>
92+ /// <param name="section">The section name containing the list.</param>
93+ /// <param name="key">The key name for the list.</param>
94+ /// <param name="value">The value to add to the list. Duplicate values are not added.</param>
4395 public void AddValueToPythonList ( string section , string key , string value )
4496 {
4597 var list = GetPythonList ( section , key ) ;
@@ -50,7 +102,12 @@ public void AddValueToPythonList(string section, string key, string value)
50102 }
51103 }
52104
53- // Removes a value from the Python-like list in the .ini file
105+ /// <summary>
106+ /// Removes a value from a Python-style list stored in the INI file.
107+ /// </summary>
108+ /// <param name="section">The section name containing the list.</param>
109+ /// <param name="key">The key name for the list.</param>
110+ /// <param name="value">The value to remove from the list.</param>
54111 public void RemoveValueFromPythonList ( string section , string key , string value )
55112 {
56113 var list = GetPythonList ( section , key ) ;
@@ -61,38 +118,143 @@ public void RemoveValueFromPythonList(string section, string key, string value)
61118 }
62119 }
63120
64- // Reads the Python-like list from the .ini file
121+ /// <summary>
122+ /// Reads a Python-style list from the INI file and parses it into a C# List.
123+ /// Expected format: ["value1", "value2", "value3"]
124+ /// </summary>
125+ /// <param name="section">The section name containing the list.</param>
126+ /// <param name="key">The key name for the list.</param>
127+ /// <returns>A List<string> containing the parsed values.</returns>
65128 public List < string > GetPythonList ( string section , string key )
66129 {
67130 string pythonListString = IniReadValue ( section , key ) ;
68131 return PythonListParser . Parse ( pythonListString ) ;
69132 }
70133
71- // Saves the Python-like list to the .ini file
134+ /// <summary>
135+ /// Converts a C# List to a Python-style list string and saves it to the INI file.
136+ /// The list is stored in the format: ["value1", "value2", "value3"]
137+ /// </summary>
138+ /// <param name="section">The section name to save the list to.</param>
139+ /// <param name="key">The key name for the list.</param>
140+ /// <param name="list">The list to save.</param>
72141 public void SavePythonList ( string section , string key , List < string > list )
73142 {
74143 string pythonListString = PythonListParser . ToPythonListString ( list ) ;
75144 IniWriteValue ( section , key , pythonListString ) ;
76145 }
77146 }
147+ /// <summary>
148+ /// Provides utilities for parsing and formatting Python-style list strings.
149+ /// Handles conversion between Python list format (["item1", "item2"]) and C# List<string>.
150+ /// Optimized for performance with compiled regex and minimal string allocations.
151+ /// </summary>
78152 public static class PythonListParser
79153 {
80- // Parses a Python-like list string into a C# List<string>
154+ /// <summary>
155+ /// Pre-compiled regex pattern for extracting quoted values from Python-style lists.
156+ /// Pattern matches content between double quotes: "([^"]*)"
157+ /// Compiled for better performance when used repeatedly.
158+ /// </summary>
159+ private static readonly Regex QuotedValueRegex = new Regex ( @"""([^""]*)""" , RegexOptions . Compiled ) ;
160+
161+ /// <summary>
162+ /// Parses a Python-style list string into a C# List<string>.
163+ /// Handles escaped backslashes in paths (e.g., "C:\\path\\to\\file" becomes "C:\path\to\file").
164+ /// </summary>
165+ /// <param name="pythonListString">
166+ /// The Python-style list string to parse. Expected format: ["value1", "value2", "value3"]
167+ /// Can be null, empty, or contain escaped backslashes.
168+ /// </param>
169+ /// <returns>
170+ /// A List<string> containing the parsed values. Returns an empty list if input is null or empty.
171+ /// Backslashes are unescaped (double backslash \\ becomes single backslash \).
172+ /// </returns>
173+ /// <example>
174+ /// Input: ["C:\\Users\\Documents", "C:\\Program Files"]
175+ /// Output: List with "C:\Users\Documents" and "C:\Program Files"
176+ /// </example>
81177 public static List < string > Parse ( string pythonListString )
82178 {
179+ if ( string . IsNullOrEmpty ( pythonListString ) )
180+ return new List < string > ( ) ;
181+
83182 var list = new List < string > ( ) ;
84- var matches = Regex . Matches ( pythonListString , @"""([^""]*)""" ) ;
183+ var matches = QuotedValueRegex . Matches ( pythonListString ) ;
184+
185+ // Pre-allocate capacity if we know the count to avoid resizing
186+ if ( matches . Count > 0 )
187+ {
188+ list . Capacity = matches . Count ;
189+ }
190+
85191 foreach ( Match match in matches )
86192 {
87- list . Add ( match . Groups [ 1 ] . Value . Replace ( @"\\" , @"\" ) ) ;
193+ // Only replace if backslashes are present to avoid unnecessary allocations
194+ string value = match . Groups [ 1 ] . Value ;
195+ if ( value . Contains ( @"\\" ) )
196+ {
197+ // Unescape: convert \\ to \
198+ list . Add ( value . Replace ( @"\\" , @"\" ) ) ;
199+ }
200+ else
201+ {
202+ list . Add ( value ) ;
203+ }
88204 }
89205 return list ;
90206 }
91207
92- // Converts a C# List<string> back to a Python-like list string
208+ /// <summary>
209+ /// Converts a C# List<string> to a Python-style list string.
210+ /// Escapes backslashes in paths (e.g., "C:\path\to\file" becomes "C:\\path\\to\\file").
211+ /// </summary>
212+ /// <param name="list">
213+ /// The list to convert. Can be null or empty.
214+ /// </param>
215+ /// <returns>
216+ /// A Python-style list string in the format: ["value1", "value2", "value3"]
217+ /// Returns "[]" for null or empty lists.
218+ /// Backslashes are escaped (single backslash \ becomes double backslash \\).
219+ /// </returns>
220+ /// <example>
221+ /// Input: List with "C:\Users\Documents" and "C:\Program Files"
222+ /// Output: ["C:\\Users\\Documents", "C:\\Program Files"]
223+ /// </example>
93224 public static string ToPythonListString ( List < string > list )
94225 {
95- return $ "[{ string . Join ( ", " , list . ConvertAll ( item => $ "\" { item . Replace ( @"\" , @"\\" ) } \" ") ) } ]";
226+ if ( list == null || list . Count == 0 )
227+ return "[]" ;
228+
229+ // Estimate capacity: brackets + items + quotes + commas + spaces
230+ // Average of 20 characters per item is a reasonable estimate for file paths
231+ int estimatedCapacity = 2 + ( list . Count * 20 ) ;
232+ var sb = new StringBuilder ( estimatedCapacity ) ;
233+ sb . Append ( '[' ) ;
234+
235+ for ( int i = 0 ; i < list . Count ; i ++ )
236+ {
237+ // Add comma separator after first item
238+ if ( i > 0 )
239+ sb . Append ( ", " ) ;
240+
241+ sb . Append ( '"' ) ;
242+ string item = list [ i ] ;
243+ // Only replace if backslashes are present to avoid unnecessary allocations
244+ if ( item . Contains ( @"\" ) )
245+ {
246+ // Escape: convert \ to \\
247+ sb . Append ( item . Replace ( @"\" , @"\\" ) ) ;
248+ }
249+ else
250+ {
251+ sb . Append ( item ) ;
252+ }
253+ sb . Append ( '"' ) ;
254+ }
255+
256+ sb . Append ( ']' ) ;
257+ return sb . ToString ( ) ;
96258 }
97259 }
98260}
0 commit comments