1 #region MIT license 2 // 3 // MIT license 4 // 5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 // 25 #endregion 26 27 using System; 28 using System.Collections.Generic; 29 using System.Globalization; 30 using System.Text; 31 using DbLinq.Factory; 32 using DbLinq.Language; 33 34 namespace DbLinq.Schema.Implementation 35 { 36 /// <summary> 37 /// INameFormatter default implementation 38 /// </summary> 39 internal class NameFormatter : INameFormatter 40 { 41 /// <summary> 42 /// Singularization type 43 /// </summary> 44 internal enum Singularization 45 { 46 /// <summary> 47 /// The word plural doesn't change 48 /// </summary> 49 DontChange, 50 /// <summary> 51 /// Singularize the word 52 /// </summary> 53 Singular, 54 /// <summary> 55 /// Pluralize the word 56 /// </summary> 57 Plural, 58 } 59 60 /// <summary> 61 /// Indicates the word position. Internally used for capitalization 62 /// </summary> 63 [Flags] 64 protected enum Position 65 { 66 /// <summary> 67 /// Word is first in sentence 68 /// </summary> 69 First = 0x01, 70 /// <summary> 71 /// Word is last in sentence 72 /// </summary> 73 Last = 0x02, 74 } 75 76 /// <summary> 77 /// ILanguageWords by culture info name 78 /// </summary> 79 private readonly IDictionary<string, ILanguageWords> languageWords = new Dictionary<string, ILanguageWords>(); 80 81 /// <summary> 82 /// Substitution char for invalid characters 83 /// </summary> 84 private const char SubstitutionChar = '_'; 85 86 /// <summary> 87 /// Gets the ILanguageWords by CultureInfo. 88 /// </summary> 89 /// <param name="cultureInfo">The culture info.</param> 90 /// <returns></returns> GetLanguageWords(CultureInfo cultureInfo)91 protected virtual ILanguageWords GetLanguageWords(CultureInfo cultureInfo) 92 { 93 lock (languageWords) 94 { 95 ILanguageWords words; 96 if (!languageWords.TryGetValue(cultureInfo.Name, out words)) 97 { 98 var languages = ObjectFactory.Get<ILanguages>(); 99 words = languages.Load(cultureInfo); 100 languageWords[cultureInfo.Name] = words; 101 } 102 return words; 103 } 104 } 105 106 /// <summary> 107 /// Formats the specified words. 108 /// </summary> 109 /// <param name="words">The words.</param> 110 /// <param name="oldName">The old name.</param> 111 /// <param name="newCase">The new case.</param> 112 /// <param name="singularization">The singularization.</param> 113 /// <returns></returns> Format(ILanguageWords words, string oldName, Case newCase, Singularization singularization)114 public virtual string Format(ILanguageWords words, string oldName, Case newCase, Singularization singularization) 115 { 116 var parts = ExtractWordsFromCaseAndLanguage(words, oldName); 117 return Format(words, parts, newCase, singularization); 118 } 119 120 /// <summary> 121 /// Formats the specified words. 122 /// </summary> 123 /// <param name="words">The words.</param> 124 /// <param name="parts">The parts.</param> 125 /// <param name="newCase">The new case.</param> 126 /// <param name="singularization">The singularization.</param> 127 /// <returns></returns> Format(ILanguageWords words, IList<string> parts, Case newCase, Singularization singularization)128 private string Format(ILanguageWords words, IList<string> parts, Case newCase, Singularization singularization) 129 { 130 var result = new StringBuilder(); 131 for (int partIndex = 0; partIndex < parts.Count; partIndex++) 132 { 133 Position position = 0; 134 if (partIndex == 0) 135 position |= Position.First; 136 if (partIndex == parts.Count - 1) 137 position |= Position.Last; 138 result.Append(AdjustPart(words, parts[partIndex], position, newCase, singularization)); 139 } 140 return result.ToString(); 141 } 142 143 /// <summary> 144 /// Toes the camel case. 145 /// </summary> 146 /// <param name="part">The part.</param> 147 /// <returns></returns> ToCamelCase(string part)148 public string ToCamelCase(string part) 149 { 150 return part.ToLower(); 151 } 152 153 /// <summary> 154 /// Toes the pascal case. 155 /// </summary> 156 /// <param name="part">The part.</param> 157 /// <returns></returns> ToPascalCase(string part)158 public string ToPascalCase(string part) 159 { 160 // we have a very special case here, for "ID" that goes to full uppercase even in PascalCase mode 161 if (string.Compare(part, "id", true) == 0) 162 return "ID"; 163 part = part.Substring(0, 1).ToUpper() + part.Substring(1).ToLower(); 164 return part; 165 } 166 167 /// <summary> 168 /// Toes the net case. 169 /// </summary> 170 /// <param name="part">The part.</param> 171 /// <returns></returns> ToNetCase(string part)172 public string ToNetCase(string part) 173 { 174 return ToPascalCase(part); 175 } 176 177 /// <summary> 178 /// Adjusts the part. 179 /// </summary> 180 /// <param name="words">The words.</param> 181 /// <param name="part">The part.</param> 182 /// <param name="position">The position.</param> 183 /// <param name="newCase">The new case.</param> 184 /// <param name="singularization">The singularization.</param> 185 /// <returns></returns> AdjustPart(ILanguageWords words, string part, Position position, Case newCase, Singularization singularization)186 protected virtual string AdjustPart(ILanguageWords words, string part, Position position, Case newCase, Singularization singularization) 187 { 188 if (singularization != Singularization.DontChange && (position & Position.Last) != 0) 189 { 190 if (singularization == Singularization.Singular) 191 part = words.Singularize(part); 192 else 193 part = words.Pluralize(part); 194 } 195 Case applyCase = newCase; 196 if (applyCase == Case.camelCase && (position & Position.First) == 0) 197 applyCase = Case.PascalCase; 198 switch (applyCase) 199 { 200 case Case.Leave: 201 break; 202 case Case.camelCase: 203 part = ToCamelCase(part); 204 break; 205 case Case.PascalCase: 206 part = ToPascalCase(part); 207 break; 208 case Case.NetCase: 209 part = ToNetCase(part); 210 break; 211 default: 212 throw new ArgumentOutOfRangeException(); 213 } 214 return part; 215 } 216 217 /// <summary> 218 /// Pushes the word on a collection 219 /// </summary> 220 /// <param name="words">The words.</param> 221 /// <param name="currentWord">The current word.</param> PushWord(ICollection<string> words, StringBuilder currentWord)222 private static void PushWord(ICollection<string> words, StringBuilder currentWord) 223 { 224 if (currentWord.Length > 0) 225 { 226 words.Add(currentWord.ToString()); 227 currentWord.Remove(0, currentWord.Length); 228 } 229 } 230 231 /// <summary> 232 /// Extracts words from uppercase and _ 233 /// A word can also be composed of several uppercase letters 234 /// </summary> 235 /// <param name="name"></param> 236 /// <param name="substitutionChar"></param> 237 /// <returns></returns> ExtractWordsFromCase(string name, char substitutionChar)238 protected virtual IList<string> ExtractWordsFromCase(string name, char substitutionChar) 239 { 240 var words = new List<string>(); 241 bool currentLowerCase = true; 242 var currentWord = new StringBuilder(); 243 for (int charIndex = 0; charIndex < name.Length; charIndex++) 244 { 245 char currentChar = name[charIndex]; 246 bool isLower = char.IsLower(currentChar); 247 // we switched to uppercase 248 if (!isLower && currentLowerCase) 249 { 250 PushWord(words, currentWord); 251 } 252 else if (isLower && !currentLowerCase) 253 { 254 // if the current word has several uppercase letters, it is one unique word 255 if (currentWord.Length > 1) 256 PushWord(words, currentWord); 257 } 258 // only letters or digits are allowed 259 if (char.IsLetterOrDigit(currentChar)) 260 currentWord.Append(currentChar); 261 // _ is the separator character, but all other characters will be kept 262 else if (currentChar != '_' && !char.IsSeparator(currentChar)) 263 currentWord.Append(substitutionChar); 264 currentLowerCase = isLower; 265 } 266 PushWord(words, currentWord); 267 268 return words; 269 } 270 271 /// <summary> 272 /// Extracts the words from case and language. 273 /// </summary> 274 /// <param name="words">The words.</param> 275 /// <param name="dbName">Name of the db.</param> 276 /// <returns></returns> ExtractWordsFromCaseAndLanguage(ILanguageWords words, string dbName)277 protected virtual IList<string> ExtractWordsFromCaseAndLanguage(ILanguageWords words, string dbName) 278 { 279 var extractedWords = new List<string>(); 280 foreach (var wordsMagma in ExtractWordsFromCase(dbName, SubstitutionChar)) 281 { 282 extractedWords.AddRange(words.GetWords(wordsMagma)); 283 } 284 return extractedWords; 285 } 286 287 /// <summary> 288 /// Extracts the words from given text. 289 /// </summary> 290 /// <param name="words">The words.</param> 291 /// <param name="dbName">Name of the db.</param> 292 /// <param name="extraction">The extraction type (case or language identification).</param> 293 /// <returns></returns> ExtractWords(ILanguageWords words, string dbName, WordsExtraction extraction)294 protected virtual IList<string> ExtractWords(ILanguageWords words, string dbName, WordsExtraction extraction) 295 { 296 switch (extraction) 297 { 298 case WordsExtraction.None: 299 return new[] { dbName }; 300 case WordsExtraction.FromCase: 301 return ExtractWordsFromCase(dbName, SubstitutionChar); 302 case WordsExtraction.FromDictionary: 303 return ExtractWordsFromCaseAndLanguage(words, dbName); 304 default: 305 throw new ArgumentOutOfRangeException("extraction"); 306 } 307 } 308 309 /// <summary> 310 /// Gets the singularization. 311 /// </summary> 312 /// <param name="singularization">The singularization.</param> 313 /// <param name="nameFormat">The name format.</param> 314 /// <returns></returns> GetSingularization(Singularization singularization, NameFormat nameFormat)315 protected virtual Singularization GetSingularization(Singularization singularization, NameFormat nameFormat) 316 { 317 if (!nameFormat.Pluralize) 318 return Singularization.DontChange; 319 return singularization; 320 } 321 322 /// <summary> 323 /// Reformats a name by adjusting its case. 324 /// </summary> 325 /// <param name="words">The words.</param> 326 /// <param name="newCase">The new case.</param> 327 /// <returns></returns> Format(string words, Case newCase)328 public string Format(string words, Case newCase) 329 { 330 return Format(null, ExtractWordsFromCase(words, SubstitutionChar), newCase, Singularization.DontChange); 331 } 332 333 /// <summary> 334 /// Gets the name of the schema. 335 /// </summary> 336 /// <param name="dbName">Name of the db.</param> 337 /// <param name="extraction">The extraction.</param> 338 /// <param name="nameFormat">The name format.</param> 339 /// <returns></returns> GetSchemaName(string dbName, WordsExtraction extraction, NameFormat nameFormat)340 public SchemaName GetSchemaName(string dbName, WordsExtraction extraction, NameFormat nameFormat) 341 { 342 var words = GetLanguageWords(nameFormat.Culture); 343 var schemaName = new SchemaName { DbName = dbName }; 344 schemaName.NameWords = ExtractWords(words, dbName, extraction); 345 schemaName.ClassName = Format(words, schemaName.NameWords, nameFormat.Case, Singularization.DontChange); 346 return schemaName; 347 } 348 349 /// <summary> 350 /// Gets the name of the procedure. 351 /// </summary> 352 /// <param name="dbName">Name of the db.</param> 353 /// <param name="extraction">The extraction.</param> 354 /// <param name="nameFormat">The name format.</param> 355 /// <returns></returns> GetProcedureName(string dbName, WordsExtraction extraction, NameFormat nameFormat)356 public ProcedureName GetProcedureName(string dbName, WordsExtraction extraction, NameFormat nameFormat) 357 { 358 var words = GetLanguageWords(nameFormat.Culture); 359 var procedureName = new ProcedureName { DbName = dbName }; 360 procedureName.NameWords = ExtractWords(words, dbName, extraction); 361 procedureName.MethodName = Format(words, procedureName.NameWords, nameFormat.Case, Singularization.DontChange); 362 return procedureName; 363 } 364 365 /// <summary> 366 /// Gets the name of the parameter. 367 /// </summary> 368 /// <param name="dbName">Name of the db.</param> 369 /// <param name="extraction">The extraction.</param> 370 /// <param name="nameFormat">The name format.</param> 371 /// <returns></returns> GetParameterName(string dbName, WordsExtraction extraction, NameFormat nameFormat)372 public ParameterName GetParameterName(string dbName, WordsExtraction extraction, NameFormat nameFormat) 373 { 374 var words = GetLanguageWords(nameFormat.Culture); 375 var parameterName = new ParameterName { DbName = dbName }; 376 parameterName.NameWords = ExtractWords(words, dbName, extraction); 377 parameterName.CallName = Format(words, parameterName.NameWords, Case.camelCase, Singularization.DontChange); 378 return parameterName; 379 } 380 381 /// <summary> 382 /// Gets the name of the table. 383 /// </summary> 384 /// <param name="dbName">Name of the db.</param> 385 /// <param name="extraction">The extraction.</param> 386 /// <param name="nameFormat">The name format.</param> 387 /// <returns></returns> GetTableName(string dbName, WordsExtraction extraction, NameFormat nameFormat)388 public TableName GetTableName(string dbName, WordsExtraction extraction, NameFormat nameFormat) 389 { 390 var words = GetLanguageWords(nameFormat.Culture); 391 var tableName = new TableName { DbName = dbName }; 392 tableName.NameWords = ExtractWords(words, dbName, extraction); 393 // if no extraction (preset name, just copy it) 394 if (extraction == WordsExtraction.None) 395 tableName.ClassName = tableName.DbName; 396 else 397 tableName.ClassName = Format(words, tableName.NameWords, nameFormat.Case, GetSingularization(Singularization.Singular, nameFormat)); 398 tableName.MemberName = Format(words, tableName.NameWords, nameFormat.Case, GetSingularization(Singularization.Plural, nameFormat)); 399 return tableName; 400 } 401 402 /// <summary> 403 /// Gets the name of the column. 404 /// </summary> 405 /// <param name="dbName">Name of the db.</param> 406 /// <param name="extraction">The extraction.</param> 407 /// <param name="nameFormat">The name format.</param> 408 /// <returns></returns> GetColumnName(string dbName, WordsExtraction extraction, NameFormat nameFormat)409 public ColumnName GetColumnName(string dbName, WordsExtraction extraction, NameFormat nameFormat) 410 { 411 var words = GetLanguageWords(nameFormat.Culture); 412 var columnName = new ColumnName { DbName = dbName }; 413 columnName.NameWords = ExtractWords(words, dbName, extraction); 414 // if no extraction (preset name, just copy it) 415 if (extraction == WordsExtraction.None) 416 columnName.PropertyName = dbName; 417 else 418 columnName.PropertyName = Format(words, columnName.NameWords, nameFormat.Case, Singularization.DontChange); 419 return columnName; 420 } 421 422 /// <summary> 423 /// Gets the name of the association. 424 /// </summary> 425 /// <param name="dbManyName">Name of the db many.</param> 426 /// <param name="dbOneName">Name of the db one.</param> 427 /// <param name="dbConstraintName">Name of the db constraint.</param> 428 /// <param name="foreignKeyName">Name of the foreign key.</param> 429 /// <param name="extraction">The extraction.</param> 430 /// <param name="nameFormat">The name format.</param> 431 /// <returns></returns> GetAssociationName(string dbManyName, string dbOneName, string dbConstraintName, string foreignKeyName, WordsExtraction extraction, NameFormat nameFormat)432 public AssociationName GetAssociationName(string dbManyName, string dbOneName, string dbConstraintName, 433 string foreignKeyName, WordsExtraction extraction, NameFormat nameFormat) 434 { 435 var words = GetLanguageWords(nameFormat.Culture); 436 var associationName = new AssociationName { DbName = dbManyName }; 437 associationName.NameWords = ExtractWords(words, dbManyName, extraction); 438 associationName.ManyToOneMemberName = Format(words, dbOneName, nameFormat.Case, GetSingularization(Singularization.Singular, nameFormat)); 439 // TODO: this works only for PascalCase 440 if (dbManyName == dbOneName) 441 associationName.ManyToOneMemberName = foreignKeyName.Replace(',', '_') + associationName.ManyToOneMemberName; 442 // TODO: support new extraction 443 associationName.OneToManyMemberName = Format(words, dbManyName, nameFormat.Case, GetSingularization(Singularization.Plural, nameFormat)); 444 return associationName; 445 } 446 } 447 } 448