diff --git a/Tests/CultureSensitivityTests.cs b/Tests/CultureSensitivityTests.cs index edef26423..824b0e5d0 100644 --- a/Tests/CultureSensitivityTests.cs +++ b/Tests/CultureSensitivityTests.cs @@ -1,5 +1,8 @@ using System.Globalization; +using Xbim.Ifc; using Xbim.Ifc4; +using Xbim.Ifc4.Interfaces; +using Xbim.Ifc4.MeasureResource; using Xbim.IO.Memory; using Xunit; @@ -32,5 +35,91 @@ public void Can_read_ifcxml_regardless_of_current_culture(string culture) CultureInfo.CurrentCulture = previous; } } + + [Theory] + [InlineData("en-US")] + [InlineData("tr-TR")] + public void Can_query_OfType_by_type_name_regardless_of_current_culture(string culture) + { + var previous = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(culture); + + // a threshold of 0 forces the Esent database store rather than the memory model + using var store = IfcStore.Open(@"TestFiles\SampleHouse4.ifc", null, 0); + + // the type name has to contain a lower case 'i' for the Turkish casing to bite: + // "IfcWall" upper-cases identically in every culture, "IfcBuilding" does not + Assert.NotEmpty(store.Instances.OfType("IfcBuilding", false)); + } + finally + { + CultureInfo.CurrentCulture = previous; + } + } + + [Theory] + [InlineData("en-US")] + [InlineData("tr-TR")] + public void Imperial_unit_symbol_is_detected_regardless_of_current_culture(string culture) + { + var previous = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(culture); + + using var model = new MemoryModel(new EntityFactoryIfc4()); + using var txn = model.BeginTransaction("unit"); + var unit = model.Instances.New(u => + { + u.Name = "inch"; + u.UnitType = IfcUnitEnum.LENGTHUNIT; + u.Dimensions = model.Instances.New(d => + { + d.LengthExponent = 1; + d.MassExponent = 0; + d.TimeExponent = 0; + d.ElectricCurrentExponent = 0; + d.ThermodynamicTemperatureExponent = 0; + d.AmountOfSubstanceExponent = 0; + d.LuminousIntensityExponent = 0; + }); + }); + + // "inch".ToUpper() is "İNCH" under tr-TR and does not contain "INCH" + Assert.Equal("in", unit.Symbol); + } + finally + { + CultureInfo.CurrentCulture = previous; + } + } + + [Theory] + [InlineData("en-US")] + [InlineData("tr-TR")] + public void Case_insensitive_property_set_lookup_works_regardless_of_current_culture(string culture) + { + var previous = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(culture); + + using var model = new MemoryModel(new EntityFactoryIfc4()); + using var txn = model.BeginTransaction("psets"); + var pile = model.Instances.New(); + var pset = model.Instances.New(p => p.Name = "Pset_WindowCommon"); + pile.AddPropertySet(pset); + + // 'i' and 'I' are different letters under the Turkish casing rules, so a + // culture-sensitive case-insensitive comparison does not match here + Assert.NotNull(pile.GetPropertySet("PSET_WINDOWCOMMON", caseSensitive: false)); + } + finally + { + CultureInfo.CurrentCulture = previous; + } + } } } diff --git a/Xbim.IO.Esent/Esent/PersistedEntityInstanceCache.cs b/Xbim.IO.Esent/Esent/PersistedEntityInstanceCache.cs index 598c5820a..aa9e9bc50 100644 --- a/Xbim.IO.Esent/Esent/PersistedEntityInstanceCache.cs +++ b/Xbim.IO.Esent/Esent/PersistedEntityInstanceCache.cs @@ -2536,7 +2536,7 @@ internal XbimGeometryHandle GetGeometryHandle(int geometryLabel) internal IEnumerable OfType(string stringType, bool activate) { - var ot = Model.Metadata.ExpressType(stringType.ToUpper()); + var ot = Model.Metadata.ExpressType(stringType.ToUpperInvariant()); if (ot == null) { // it could be that we're searching for an interface diff --git a/Xbim.Ifc2x3/Kernel/IfcObjectPartial.cs b/Xbim.Ifc2x3/Kernel/IfcObjectPartial.cs index 4e426d2ee..53826b680 100644 --- a/Xbim.Ifc2x3/Kernel/IfcObjectPartial.cs +++ b/Xbim.Ifc2x3/Kernel/IfcObjectPartial.cs @@ -98,11 +98,20 @@ public void AddPropertySet(IfcPropertySet pSet) /// /// /// + public IfcPropertySet GetPropertySet(string pSetName, bool caseSensitive = true) { - return PropertySets.FirstOrDefault(ps=>string.Compare(ps.Name,pSetName,!caseSensitive)==0); + return PropertySets.FirstOrDefault(ps => string.Equals(ps.Name, pSetName, Comparison(caseSensitive))); + } + + // Property set names are IFC identifiers, so a case-insensitive match must be ordinal: + // the culture-sensitive comparison treats 'i' and 'I' as different letters under tr-TR. + private static StringComparison Comparison(bool caseSensitive) + { + return caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; } + public IfcPropertySingleValue GetPropertySingleValue(string pSetName, string propertyName) { var pset = GetPropertySet(pSetName); @@ -208,7 +217,7 @@ public Ifc4.Interfaces.IIfcElementQuantity GetElementQuantity(string pSetName, b { IfcRelDefinesByProperties rel = caseSensitive ? IsDefinedByProperties.FirstOrDefault(r => r.RelatingPropertyDefinition.Name == pSetName && r.RelatingPropertyDefinition is Ifc4.Interfaces.IIfcElementQuantity) - : IsDefinedByProperties.FirstOrDefault(r => r.RelatingPropertyDefinition.Name.ToString().ToLower() == pSetName.ToLower() && r.RelatingPropertyDefinition is Ifc4.Interfaces.IIfcElementQuantity); + : IsDefinedByProperties.FirstOrDefault(r => string.Equals(r.RelatingPropertyDefinition.Name, pSetName, StringComparison.OrdinalIgnoreCase) && r.RelatingPropertyDefinition is Ifc4.Interfaces.IIfcElementQuantity); if (rel != null) return rel.RelatingPropertyDefinition as Ifc4.Interfaces.IIfcElementQuantity; return null; } @@ -399,6 +408,7 @@ public enum XbimQuantityTypeEnum Count, Weight, Time + } } diff --git a/Xbim.Ifc2x3/Kernel/IfcTypeObjectPartial.cs b/Xbim.Ifc2x3/Kernel/IfcTypeObjectPartial.cs index 1e21db933..0e78240fa 100644 --- a/Xbim.Ifc2x3/Kernel/IfcTypeObjectPartial.cs +++ b/Xbim.Ifc2x3/Kernel/IfcTypeObjectPartial.cs @@ -1,34 +1,34 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Xbim.Ifc2x3.MeasureResource; -using Xbim.Ifc2x3.ProductExtension; -using Xbim.Ifc2x3.PropertyResource; -using Xbim.Ifc2x3.QuantityResource; - -namespace Xbim.Ifc2x3.Kernel -{ - public partial class IfcTypeObject - { - public void AddPropertySet(IfcPropertySetDefinition pSetDefinition) - { - HasPropertySets.Add(pSetDefinition); - } - - /// - /// Returns the propertyset of the specified name, null if it does not exist - /// - /// - /// - /// - public IfcPropertySet GetPropertySet(string pSetName, bool caseSensitive = true) - { - if (HasPropertySets == null) return null; +using System; +using System.Collections.Generic; +using System.Linq; +using Xbim.Ifc2x3.MeasureResource; +using Xbim.Ifc2x3.ProductExtension; +using Xbim.Ifc2x3.PropertyResource; +using Xbim.Ifc2x3.QuantityResource; + +namespace Xbim.Ifc2x3.Kernel +{ + public partial class IfcTypeObject + { + public void AddPropertySet(IfcPropertySetDefinition pSetDefinition) + { + HasPropertySets.Add(pSetDefinition); + } + + /// + /// Returns the propertyset of the specified name, null if it does not exist + /// + /// + /// + /// + public IfcPropertySet GetPropertySet(string pSetName, bool caseSensitive = true) + { + if (HasPropertySets == null) return null; return caseSensitive ? HasPropertySets.FirstOrDefault(r => r.Name == pSetName) : - HasPropertySets.FirstOrDefault(r => string.Equals(r.Name.ToString(), pSetName, StringComparison.CurrentCultureIgnoreCase)); - } - + HasPropertySets.FirstOrDefault(r => string.Equals(r.Name, pSetName, StringComparison.OrdinalIgnoreCase)); + } + public IfcPropertySingleValue GetPropertySingleValue(string pSetName, string propertyName) { var pset = GetPropertySet(pSetName); @@ -44,36 +44,36 @@ public IfcValue GetPropertySingleValueValue(string pSetName, string propertyName } public IEnumerable PropertySets - { - get - { - if (HasPropertySets != null) return HasPropertySets.OfType(); - return Enumerable.Empty(); - } + { + get + { + if (HasPropertySets != null) return HasPropertySets.OfType(); + return Enumerable.Empty(); + } } public IDictionary> PropertySingleValues - { - get - { - var result = new Dictionary>(); - var pSets = HasPropertySets; - if (pSets == null) return result; - var pSetsPure = pSets.OfType(); - foreach (var pSet in pSetsPure) - { - var value = new Dictionary(); - IfcLabel psetName = pSet.Name ?? new IfcLabel("Undefined"); - foreach (var prop in pSet.HasProperties) - { - var singleVal = prop as IfcPropertySingleValue; - if (singleVal == null) continue; - value.Add(prop.Name, singleVal.NominalValue); - } - result.Add(psetName, value); - } - return result; - } + { + get + { + var result = new Dictionary>(); + var pSets = HasPropertySets; + if (pSets == null) return result; + var pSetsPure = pSets.OfType(); + foreach (var pSet in pSetsPure) + { + var value = new Dictionary(); + IfcLabel psetName = pSet.Name ?? new IfcLabel("Undefined"); + foreach (var prop in pSet.HasProperties) + { + var singleVal = prop as IfcPropertySingleValue; + if (singleVal == null) continue; + value.Add(prop.Name, singleVal.NominalValue); + } + result.Add(psetName, value); + } + return result; + } } public IfcPropertySingleValue SetPropertySingleValue(string pSetName, string propertyName, IfcValue value) @@ -106,7 +106,7 @@ public IfcPropertySingleValue SetPropertySingleValue(string pSetName, string pro public IfcPhysicalSimpleQuantity GetElementPhysicalSimpleQuantity(string pSetName, string qualityName) { var elementQuality = GetElementQuantity(pSetName); - return elementQuality != null ? elementQuality.Quantities.FirstOrDefault(sq => sq.Name == qualityName) : null; + return elementQuality != null ? elementQuality.Quantities.FirstOrDefault(sq => sq.Name == qualityName) : null; } @@ -115,11 +115,11 @@ public IfcPhysicalSimpleQuantity GetElementPhysicalSimpleQuantity(string pSetNam /// /// All physical simple quantities (like length, area, volume, count, etc.) public IEnumerable PhysicalSimpleQuantities - { - get - { - return ElementQuantities.SelectMany(eq => eq.Quantities).OfType(); - } + { + get + { + return ElementQuantities.SelectMany(eq => eq.Quantities).OfType(); + } } /// @@ -127,12 +127,12 @@ public IEnumerable PhysicalSimpleQuantities /// /// All related element quantities public IEnumerable ElementQuantities - { - get - { - if (HasPropertySets != null) return HasPropertySets.OfType(); - return Enumerable.Empty(); - } + { + get + { + if (HasPropertySets != null) return HasPropertySets.OfType(); + return Enumerable.Empty(); + } } public IfcElementQuantity GetElementQuantity(string pSetName, bool caseSensitive = true) @@ -141,10 +141,10 @@ public IfcElementQuantity GetElementQuantity(string pSetName, bool caseSensitiv return caseSensitive ? HasPropertySets.FirstOrDefault(r => r.Name == pSetName) : - HasPropertySets.FirstOrDefault(r => r.Name.ToString().ToLower() == pSetName.ToLower()); + HasPropertySets.FirstOrDefault(r => string.Equals(r.Name, pSetName, StringComparison.OrdinalIgnoreCase)); } - - } -} + + } +} diff --git a/Xbim.Ifc2x3/MeasureResource/IfcConversionBasedUnitPartial.cs b/Xbim.Ifc2x3/MeasureResource/IfcConversionBasedUnitPartial.cs index 330b97dda..ded4fea26 100644 --- a/Xbim.Ifc2x3/MeasureResource/IfcConversionBasedUnitPartial.cs +++ b/Xbim.Ifc2x3/MeasureResource/IfcConversionBasedUnitPartial.cs @@ -1,4 +1,5 @@ -namespace Xbim.Ifc2x3.MeasureResource +using System; +namespace Xbim.Ifc2x3.MeasureResource { public partial class IfcConversionBasedUnit { @@ -24,10 +25,10 @@ public partial class IfcConversionBasedUnit if (Dimensions.LengthExponent == 3) //volume pow += '\u00B3'; //((char)0x00B3)add ³ - if ((name.ToUpper().Contains("FEET")) || (name.ToUpper().Contains("FOOT"))) + if (Contains(name, "FEET") || Contains(name, "FOOT")) return "ft" + pow; - if (name.ToUpper().Contains("INCH")) + if (Contains(name, "INCH")) return "in" + pow; return name + pow; @@ -35,5 +36,12 @@ public partial class IfcConversionBasedUnit return name; } } + + // Ordinal, case-insensitive: name.ToUpper() is culture sensitive, so under the Turkish + // casing rules "inch" upper-cases to "İNCH" and never contains "INCH". + private static bool Contains(string name, string token) + { + return name != null && name.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0; + } } } diff --git a/Xbim.Ifc4/Kernel/IfcObjectPartial.cs b/Xbim.Ifc4/Kernel/IfcObjectPartial.cs index e0d9f7fd7..6e2173a82 100644 --- a/Xbim.Ifc4/Kernel/IfcObjectPartial.cs +++ b/Xbim.Ifc4/Kernel/IfcObjectPartial.cs @@ -58,10 +58,19 @@ public void AddPropertySet(IfcPropertySet pSet) /// /// /// + public IIfcPropertySet GetPropertySet(string pSetName, bool caseSensitive = true) { - return PropertySets.FirstOrDefault(pset => string.Compare(pSetName, pset.Name, !caseSensitive) == 0); + return PropertySets.FirstOrDefault(pset => string.Equals(pSetName, pset.Name, Comparison(caseSensitive))); + } + + // Property set names are IFC identifiers, so a case-insensitive match must be ordinal: + // the culture-sensitive comparison treats 'i' and 'I' as different letters under tr-TR. + private static StringComparison Comparison(bool caseSensitive) + { + return caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; } + public IIfcPropertySingleValue GetPropertySingleValue(string pSetName, string propertyName) { var pset = GetPropertySet(pSetName); @@ -169,7 +178,7 @@ public IEnumerable GetExternalElements(IModel model) public IIfcElementQuantity GetElementQuantity(string pSetName, bool caseSensitive = true) { var qSets = IsDefinedBy.SelectMany(r => r.RelatingPropertyDefinition.PropertySetDefinitions).OfType(); - return qSets.FirstOrDefault(qset=>string.Compare(pSetName,qset.Name,!caseSensitive)==0); + return qSets.FirstOrDefault(qset => string.Equals(pSetName, qset.Name, Comparison(caseSensitive))); } /// @@ -363,6 +372,7 @@ public enum XbimQuantityTypeEnum Count, Weight, Time + } } diff --git a/Xbim.Ifc4/MeasureResource/IfcConversionBasedUnitPartial.cs b/Xbim.Ifc4/MeasureResource/IfcConversionBasedUnitPartial.cs index 7fafc42c0..caaa920ff 100644 --- a/Xbim.Ifc4/MeasureResource/IfcConversionBasedUnitPartial.cs +++ b/Xbim.Ifc4/MeasureResource/IfcConversionBasedUnitPartial.cs @@ -1,4 +1,5 @@ -using Xbim.Ifc4.Interfaces; +using System; +using Xbim.Ifc4.Interfaces; namespace Xbim.Ifc4.MeasureResource { @@ -27,10 +28,10 @@ public partial class IfcConversionBasedUnit if (Dimensions.LengthExponent == 3) //volume pow += '\u00B3'; //((char)0x00B3)add ³ - if ((name.ToUpper().Contains("FEET")) || (name.ToUpper().Contains("FOOT"))) + if (Contains(name, "FEET") || Contains(name, "FOOT")) return "ft" + pow; - if (name.ToUpper().Contains("INCH")) + if (Contains(name, "INCH")) return "in" + pow; return name + pow; @@ -38,5 +39,12 @@ public partial class IfcConversionBasedUnit return name; } } + + // Ordinal, case-insensitive: name.ToUpper() is culture sensitive, so under the Turkish + // casing rules "inch" upper-cases to "İNCH" and never contains "INCH". + private static bool Contains(string name, string token) + { + return name != null && name.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0; + } } } diff --git a/Xbim.Ifc4x3/MeasureResource/IfcConversionBasedUnit.Partial.cs b/Xbim.Ifc4x3/MeasureResource/IfcConversionBasedUnit.Partial.cs index 5aa7bff3c..411580fba 100644 --- a/Xbim.Ifc4x3/MeasureResource/IfcConversionBasedUnit.Partial.cs +++ b/Xbim.Ifc4x3/MeasureResource/IfcConversionBasedUnit.Partial.cs @@ -1,4 +1,5 @@ -using Xbim.Ifc4.Interfaces; +using System; +using Xbim.Ifc4.Interfaces; namespace Xbim.Ifc4x3.MeasureResource { @@ -27,10 +28,10 @@ public partial class IfcConversionBasedUnit if (Dimensions.LengthExponent == 3) //volume pow += '\u00B3'; //((char)0x00B3)add ³ - if ((name.ToUpper().Contains("FEET")) || (name.ToUpper().Contains("FOOT"))) + if (Contains(name, "FEET") || Contains(name, "FOOT")) return "ft" + pow; - if (name.ToUpper().Contains("INCH")) + if (Contains(name, "INCH")) return "in" + pow; return name + pow; @@ -38,5 +39,12 @@ public partial class IfcConversionBasedUnit return name; } } + + // Ordinal, case-insensitive: name.ToUpper() is culture sensitive, so under the Turkish + // casing rules "inch" upper-cases to "İNCH" and never contains "INCH". + private static bool Contains(string name, string token) + { + return name != null && name.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0; + } } }