/* $NoKeywords: $ */
/*
//
// Copyright (c) 1993-2013 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//				
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/

#include "opennurbs.h"

#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined 
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif

#include "opennurbs_parse.h"

#define ON_UNICODE_QUOTATION_MARK 0x0022
#define ON_WCHAR_QUOTATION_MARK L"\""

#define ON_UNICODE_APOSTROPHE 0x0027
#define ON_WCHAR_APOSTROPHE L"\'"

#define ON_UNICODE_LOW_LINE 0x005F

#define ON_UNICODE_DEGREE_SYMBOL 0x00B0
#define ON_WCHAR_DEGREE_SYMBOL L"\x00B0"

// The unicode masuline ordinal indicator (Spanish) 
// is often mistakenlyused as a degrees symbol.
#define ON_UNICODE_MASCULINE_ORDINAL_INDICATOR 0x00BA
#define ON_WCHAR_MASCULINE_ORDINAL_INDICATOR L"\x00BA"

#define ON_UNICODE_LATIN_SMALL_LETTER_SHARP_S 0x00DF
#define ON_WCHAR_LATIN_SMALL_LETTER_SHARP_S L"\x00DF"

#define ON_UNICODE_LATIN_CAPITAL_LETTER_U_WITH_RING_ABOVE 0x016E
#define ON_WCHAR_LATIN_CAPITAL_LETTER_U_WITH_RING_ABOVE L"\x016E"

#define ON_UNICODE_LATIN_SMALL_LETTER_U_WITH_RING_ABOVE 0x016F
#define ON_WCHAR_LATIN_SMALL_LETTER_U_WITH_RING_ABOVE L"\x016F"

#define ON_UNICODE_LATIN_CAPITAL_LETTER_SHARP_S 0x1E9E

#define ON_UNICODE_GREEK_CAPITAL_LETTER_TAU 0x03A4

#define ON_UNICODE_GREEK_SMALL_LETTER_TAU 0x03C4
#define ON_WCHAR_GREEK_SMALL_LETTER_TAU L"\x03C4"

#define ON_RHINO_LOCALE_ID 1
#define ON_INVALID_LOCALE_ID 2
#define ON_CS_CZ_LOCALE_ID ON_Locale::WindowsLCID::cs_CZ_LCID
#define ON_DE_DE_LOCALE_ID ON_Locale::WindowsLCID::de_DE_LCID
#define ON_EN_CA_LOCALE_ID ON_Locale::WindowsLCID::en_CA_LCID
#define ON_EN_US_LOCALE_ID ON_Locale::WindowsLCID::en_US_LCID
#define ON_ES_ES_LOCALE_ID ON_Locale::WindowsLCID::es_ES_LCID
#define ON_FR_FR_LOCALE_ID ON_Locale::WindowsLCID::fr_FR_LCID
#define ON_IT_IT_LOCALE_ID ON_Locale::WindowsLCID::it_IT_LCID
#define ON_PL_PL_LOCALE_ID ON_Locale::WindowsLCID::pl_PL_LCID
#define ON_PT_PT_LOCALE_ID ON_Locale::WindowsLCID::pt_PT_LCID

class ON_UnitName
{
public:
  // The size and field alignment of ON_UnitName must exactly match
  // that of ON_LengthUnitName and ON_AngleUnitName
  // Microsoft locale id // http://msdn.microsoft.com/en-us/library/ms912047(v=winembedded.10).aspx
  unsigned int m_locale_id; 

  // ON::LengthUnitSystem or ON::AngleUnitSystem enum value
  unsigned char m_unit_system;       

  bool m_bIsSingular;
  bool m_bIsPlural;

  const wchar_t* m_name;

};

class ON_UnitNameEx : public ON_UnitName
{
public:


  enum
  {
    m_utf32_name_capacity = 24
  };

  // Simplified name - used for speedy unit name searches.
  ON__UINT32 m_utf32_name[ON_UnitNameEx::m_utf32_name_capacity]; // null terminated utf-32 encoded lower case simple latin name string.
  unsigned int m_utf32_name_count; // number of nonzero wchar_t elements in m_name[] array

  /*
  Description:
    Parses m_name and sets the m_utf32_name[] and m_utf32_name_count
    fields.
  Returns:
    Number of elments of m_name that were parsed; 0 indicates failure.
  Remarks:
    m_name must contain a character that terminates unit system name parsing.
    This can be a null, digit, punctuation, aritmetic operator, or a 
    unicode code point <= 0x0020 (0x0020 = space = 32 decimal).
  */
  int SetSimplifiedName();

  static int Compare(const ON_UnitNameEx*, const ON_UnitNameEx*);
  static int Compare_m_utf32_name(const ON_UnitNameEx*, const ON_UnitNameEx*);
  static int Compare_m_name(const ON_UnitNameEx*, const ON_UnitNameEx*);
};

static ON_UnitName si_length_units[] =
{
  //////////////////////////////////////////////////////////////
  //
  // SI length units universal
  //
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, true, L"mm" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, true, L"cm" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, true,      L"m" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, true,  L"km" },

  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, true, L"_mm" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, true, L"_cm" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, true,      L"_m" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, true,  L"_km" },

  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"_millimeters" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, false, L"_millimeter" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), false, true, L"_centimeters" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, false, L"_centimeter" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), false, true,      L"_meters" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, false,      L"_meter"},
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), false, true,  L"_kilometers" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, false,  L"_kilometer" },

  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"_millimetres" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, false, L"_millimetre" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), false, true, L"_centimetres" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, false, L"_centimetre" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), false, true,      L"_metres" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, false,      L"_metre" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), false, true,  L"_kilometres" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, false,  L"_kilometre" },


  //////////////////////////////////////////////////////////////
  //
  // SI length units cs-* Czech
  //
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"milimetr" ON_WCHAR_LATIN_SMALL_LETTER_U_WITH_RING_ABOVE },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"milimetry" },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, false, L"milimetr" },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), false, true, L"centimetr" ON_WCHAR_LATIN_SMALL_LETTER_U_WITH_RING_ABOVE },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), false, true, L"centimetry" },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, false, L"centimetr" },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), false, true,      L"metr" ON_WCHAR_LATIN_SMALL_LETTER_U_WITH_RING_ABOVE },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), false, true,      L"metry" },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, false,      L"metr" },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), false, true,  L"kilometr" ON_WCHAR_LATIN_SMALL_LETTER_U_WITH_RING_ABOVE },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), false, true,  L"kilometry" },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, false,  L"kilometr" },

  //////////////////////////////////////////////////////////////
  //
  // SI length units de-* German
  //
  {ON_DE_DE_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, true, L"Millimeter" },
  {ON_DE_DE_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, true, L"Zentimeter" },
  {ON_DE_DE_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, true,      L"Meter" },
  {ON_DE_DE_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, true,  L"Kilometer" },
  // http://mcneel.myjetbrains.com/youtrack/issue/RH-34051
  // The words ending in n are not plural forms, they are inflected forms used in complete sentences.
  // They are included so information copied, possibly automatically, from documents into scripts will parse as expected.
  {ON_DE_DE_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, true, L"Millimetern" },
  {ON_DE_DE_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, true, L"Zentimetern" },
  {ON_DE_DE_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, true,      L"Metern" },
  {ON_DE_DE_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, true,  L"Kilometern" },
  // http://mcneel.myjetbrains.com/youtrack/issue/RH-34051
  // "zm" is not the way German speakers abbreviate zentimeter
  // NO // {ON_DE_DE_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, true,  L"zm" },

  //////////////////////////////////////////////////////////////
  //
  // SI length units en-CA (metric Canadian English)
  //
  {ON_EN_CA_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"millimetres" },
  {ON_EN_CA_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, false, L"millimetre" },
  {ON_EN_CA_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), false, true, L"centimetres" },
  {ON_EN_CA_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, false, L"centimetre" },
  {ON_EN_CA_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), false, true,      L"metres" },
  {ON_EN_CA_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, false,      L"metre" },
  {ON_EN_CA_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), false, true,  L"kilometres" },
  {ON_EN_CA_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, false,  L"kilometre" },

  //////////////////////////////////////////////////////////////
  //
  // SI length units en-US (metric American English)
  //
  {ON_EN_US_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"millimeters" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, false, L"millimeter" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), false, true, L"centimeters" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, false, L"centimeter" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), false, true,      L"meters" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, false,      L"meter" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), false, true,  L"kilometers" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, false,  L"kilometer" },

  //////////////////////////////////////////////////////////////
  //
  // SI length units es-* Spanish
  //
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"mil\x00EDmetros" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, false, L"mil\x00EDmetro" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), false, true, L"cent\x00EDmetros" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, false, L"cent\x00EDmetro" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), false, true, L"metros" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, false, L"metro" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), false, true, L"kil\x00F3metros" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, false, L"kil\x00F3metro" },

  //////////////////////////////////////////////////////////////
  //
  // SI length units fr-* French
  //
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"millim\x00E8tres" },
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, false, L"millim\x00E8tre" },
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), false, true, L"centim\x00E8tres" },
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, false, L"centim\x00E8tre" },
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), false, true,      L"m\x00E8tres" },
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, false,      L"m\x00E8tre" },
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), false, true,  L"kilom\x00E8tres" },
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, false,  L"kilom\x00E8tre" },

  //////////////////////////////////////////////////////////////
  //
  // SI length units it-* Italian
  //
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"millimetri" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"millimetro" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), false, true, L"centimetri" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, false, L"centimetro" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), false, true,      L"metri" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, false ,     L"metro"},
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), false, true,  L"chilometri" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, false,  L"chilometro" },
  // it-* abbreviation for chilometri is "km"

  //////////////////////////////////////////////////////////////
  //
  // SI length units pl-* Polish
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, false, L"milimetr" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"milimetr\x00F3w" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"milimetry" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, false, L"centymetr" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), false, true, L"centymetr\x00F3w" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), false, true, L"centymetry" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, false,      L"metr" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), false, true,      L"metr\x00F3w" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), false, true,      L"metry" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, false,  L"kilometr" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), false, true,  L"kilometr\x00F3w" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), false, true,  L"kilometry" },

  //////////////////////////////////////////////////////////////
  //
  // SI length units pt-* Portuguese
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), false, true, L"mil\x00EDmetros" },
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Millimeters), true, false, L"mil\x00EDmetro" },
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), false, true, L"cent\x00EDmetros" },
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Centimeters), true, false, L"cent\x00EDmetro" },
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), false, true,      L"metros" },
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Meters), true, false,      L"metro" },
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), false, true,  L"quil\x00F4metros" },
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, false,  L"quil\x00F4metro" },
  // "qm" uilometro is abbreviated in Brazil and Portugual
  // NO // {ON_PT_PT_LOCALE_ID, static_cast<unsigned char>(ON::LengthUnitSystem::Kilometers), true, true,   L"qm" },
};

static ON_UnitName en_US_customary_length_units[] =
{
  //////////////////////////////////////////////////////////////
  //
  // United States customary units (Rhino - all locales)
  //
  //   All conversions to meters are exact.
  //   microinch =    2.54e-8 meters (1.0e-6 inches)
  //   mil       =    2.54e-5 meters (0.001 inches)
  //   inch      =    0.0254  meters (1/12 foot)
  //   foot      =    0.3048  meters (12 inches)
  //   yard      =    0.9144  meters (3 feet, 36 inches)
  //   mile      = 1609.344   meters (5280 feet)
  //
  // United States customary units are supported in en-US for two reasons.
  // 1) There is a internationally accepted definition of US customary
  //    lengths in terms of meters.  The international yard and pound
  //    agreement of July 1, 1959 defined one yard to be exactly 
  //    0.9144 meters which implies one inch is exactly 0.0254 meters.
  //    http://www.ngs.noaa.gov/PUBS_LIB/FedRegister/FRdoc59-5442.pdf
  // 2) These customary units are currently and widely used in the 
  //    United States. 
  //
  // United States customary units are not supported in any other
  // language or culture because there are no internationally 
  // accepted and commonly used definitions in terms of meters
  // that match the ones used in the United States and the units
  // are not commonly used in precise digital models.
  //
  // For an inlking of the peril of assuming a length unit in
  // Czech, French, Italian, Portuguese or Spanish that linguistically
  // translates to English "inch", "foot", or "mile" has the same physical
  // length as the "United States customary unit", see the references below.
  //   http://en.wikipedia.org/wiki/Foot_(unit)
  //   http://en.wikipedia.org/wiki/German_obsolete_units_of_measurement
  //   http://en.wikipedia.org/wiki/Spanish_customary_units
  //   http://en.wikipedia.org/wiki/Portuguese_customary_units
  //   http://en.wikipedia.org/wiki/Units_of_measurement_in_France
  //   http://www.convert-me.com/en/convert/length
  //   http://www.wordreference.com/czen/palec
  //   http://www.onlineunitconversion.com/braccio.Italy_to_miglio.html
  //
  // All conversions to meters and other US customary units are exact.
  //
  // 1 microinch = 2.54e-8 meters (1.0e-6 inches)
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Microinches), false, true, L"microinches" }, 
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Microinches), true, false, L"microinch" },
  // 1 mil = 2.54e-5 meters (0.001 inches)
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Mils), false, true, L"mils" },     
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Mils), true, false, L"mil" },
  // 1 inch = 0.0254 meters = 1/12 foot
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Inches), false, true, L"inches" }, 
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Inches), true, false, L"inch" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Inches), false, true, L"ins" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Inches), true, true, L"in" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Inches), true, true, ON_WCHAR_QUOTATION_MARK },
  // 1 foot = 0.3048 meters = 12 inches
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Feet), false, true, L"feet" },     
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Feet), true, false, L"foot" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Feet), true, true, L"ft" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Feet), true, true, ON_WCHAR_APOSTROPHE },
  // 1 yard = 0.9144 meters = 3 feet = 36 inches
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Yards), false, true, L"yards" },   
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Yards), true, false, L"yard" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Yards), false, true, L"yds" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Yards), true, true, L"yd" },    
  // 1 US statute mile = 1609.34 meters = 5280 feet
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Miles), false, true, L"miles" }, 
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Miles), true, false, L"mile" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Miles), true, true, L"mi" },

  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Microinches), true, true, L"_microinches" }, 
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Microinches), true, true, L"_microinch" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Mils), false, true, L"_mils" },     
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Mils), true, false, L"_mil" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Inches), false, true, L"_inches" }, 
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Inches), true, false, L"_inch" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Inches), false, true, L"_ins" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Inches), true, true, L"_in" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Feet), false, true, L"_feet" },     
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Feet), true, false, L"_foot" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Feet), true, true, L"_ft" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Yards), false, true, L"_yards" },   
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Yards), true, false, L"_yard" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Yards), false, true, L"_yds" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Yards), true, true, L"_yd" },    
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Miles), false, true, L"_miles" }, 
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Miles), true, false, L"_mile" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::LengthUnitSystem::Miles), true, true, L"_mi" },
};

static ON_UnitName angle_no_units[] =
{
  // These entries prevent parsing the strings unless an
  // entry for a locale explicitly inludes the string and
  // the parsing prefered local id matches exactly.
  // The purpose is to prevent incorrectly parsing strings
  // the define different unit systems in different 
  // locales.

  // Many strings that begin with "g" can mean degrees
  // in one locale and gradians in another locale.
  {ON_INVALID_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::None), true, true,  L"g" },
  {ON_INVALID_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::None), true, true,  L"gs" },
  {ON_INVALID_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::None), true, true,  L"gc" },
  {ON_INVALID_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::None), true, true,  L"gd" },
  {ON_INVALID_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::None), true, true, L"grad" },
  {ON_INVALID_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::None), true, true, L"grads" },

  // "s" could mean degrees in Czech or Polish and seconds in English.
  {ON_INVALID_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::None), true, true,  L"s" },
};

static ON_UnitName angle_radian_units[] =
{

  //////////////////////////////////////////////////////////////
  //
  // radian units universal
  //
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), false, true, L"radians" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, false, L"radian" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), false, true, L"rads" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, false, L"rad" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, true,  L"r" },

  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), false, true, L"_radians" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, false, L"_radian" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), false, true, L"_rads" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, false, L"_rad" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, true,  L"_r" },

  //////////////////////////////////////////////////////////////
  //
  // radian units cs-* Czech
  //
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), false, true, L"radi\x00E1ny" },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, false, L"radi\x00E1n" },

  //////////////////////////////////////////////////////////////
  //
  // radian units en-* (radians English)
  //
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), false, true, L"radians" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, false, L"radian" },

  //////////////////////////////////////////////////////////////
  //
  // radian units de-* German
  //
  {ON_DE_DE_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, true, L"Bogenma" ON_WCHAR_LATIN_SMALL_LETTER_SHARP_S },

  //////////////////////////////////////////////////////////////
  //
  // radian units es-* Spanish
  //
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), false, true, L"radianes" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, false, L"radi\x00E1n" },

  //////////////////////////////////////////////////////////////
  //
  // radian units fr-* French
  //
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), false, true, L"radians" },
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, false, L"radian" },

  //////////////////////////////////////////////////////////////
  //
  // radian units it-* Italian
  //
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), false, true, L"radianti" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, false, L"radiante" },

  //////////////////////////////////////////////////////////////
  //
  // radian units pl-* Polish
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), false, true, L"radiany" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, false, L"radian" },

  //////////////////////////////////////////////////////////////
  //
  // radian units pt-* Portuguese
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), false, true, L"radians" },
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Radians), true, false, L"radian" },

};

static ON_UnitName angle_degree_units[] =
{
  //////////////////////////////////////////////////////////////
  //
  // arc degree unit abbreviations (Rhino - all locales)
  //
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, true, ON_WCHAR_DEGREE_SYMBOL },
  // The unicode masuline ordinal indicator (Spanish) 
  // is often mistakenlyused as a degrees symbol.
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, true, ON_WCHAR_MASCULINE_ORDINAL_INDICATOR },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), false, true, L"degrees" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, false, L"degree" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), false, true, L"degs" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, false, L"deg" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, true, L"d" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), false, true, L"_degrees" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, false, L"_degree" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), false, true, L"_degs" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, false, L"_deg" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, true, L"_d" },

  //////////////////////////////////////////////////////////////
  //
  // arc degree units cs-* (Czech)
  //
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, true, L"stupn\x011B" },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, true, L"stupe\x0148" },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, true, L"stup" },

  //////////////////////////////////////////////////////////////
  //
  // arc degree units en-* (English)
  //
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), false, true, L"degrees" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, false, L"degree" },

  //////////////////////////////////////////////////////////////
  //
  // arc degree units de-* (German)
  //
  {ON_DE_DE_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, true, L"Grad" },

  //////////////////////////////////////////////////////////////
  //
  // arc degree units es-* (Spanish)
  //
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), false, true, L"grados" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, false, L"grado" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), false, true, L"grads" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, false, L"grad" },

  //////////////////////////////////////////////////////////////
  //
  // arc degree units fr-* (French)
  //
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), false, true, L"degr\x00E9s" },
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, false, L"degr\x00E9" },

  //////////////////////////////////////////////////////////////
  //
  // arc degree units it-* (Italian)
  //
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), false, true, L"gradisessagesimali" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, false, L"gradosessagesimale" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), false, true, L"gradi" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, false, L"grado" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, true,  L"gs" },

  //////////////////////////////////////////////////////////////
  //
  // arc degree units pl-* Polish
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, true, L"stopnie" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, true, L"stopie\x0144" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, true, L"stop" },

  //////////////////////////////////////////////////////////////
  //
  // arc degree units pt-* (Portuguese)
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), false, true, L"graus" },
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Degrees), true, false, L"grau" },
};

static ON_UnitName angle_minute_units[] =
{
  //////////////////////////////////////////////////////////////
  //
  // arc minute unit abbreviations (Rhino - all locales)
  //
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Minutes), true, true,  ON_WCHAR_APOSTROPHE },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Minutes), false, true, L"mins" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Minutes), true, false, L"min" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Minutes), true, true,  L"m" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Minutes), false, true, L"_minutes" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Minutes), true, false, L"_minute" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Minutes), false, true, L"_mins" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Minutes), true, false, L"_min" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Minutes), true, true,  L"_m" },

  //////////////////////////////////////////////////////////////
  //
  // arc minute units cs-* (Czech)
  //

  //////////////////////////////////////////////////////////////
  //
  // arc minute units en-* (English)
  //
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Minutes), false, true, L"minutes" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Minutes), true, false, L"minute" },

  //////////////////////////////////////////////////////////////
  //
  // arc minute units de-* (German)
  //

  //////////////////////////////////////////////////////////////
  //
  // arc minute units es-* (Spanish)
  //

  //////////////////////////////////////////////////////////////
  //
  // arc minute units fr-* (French)
  //

  //////////////////////////////////////////////////////////////
  //
  // arc minute units it-* (Italian)
  //

  //////////////////////////////////////////////////////////////
  //
  // arc minute units pt-* (Portuguese)
};

static ON_UnitName angle_second_units[] =
{
  //////////////////////////////////////////////////////////////
  //
  // arc second unit abbreviations (Rhino - all locales)
  //
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Seconds), true, true,  ON_WCHAR_QUOTATION_MARK },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Seconds), false, true, L"seconds" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Seconds), true, false, L"second" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Seconds), false, true, L"secs" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Seconds), true, false, L"sec" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Seconds), false, true, L"_seconds" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Seconds), true, false, L"_second" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Seconds), false, true, L"_secs" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Seconds), true, false, L"_sec" },

  //////////////////////////////////////////////////////////////
  //
  // arc second units cs-* (Czech)
  //

  //////////////////////////////////////////////////////////////
  //
  // arc second units en-* (English)
  //
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Seconds), false, true, L"seconds" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Seconds), true, false, L"second" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Seconds), true, true,  L"s" },

  //////////////////////////////////////////////////////////////
  //
  // arc second units de-* (German)
  //

  //////////////////////////////////////////////////////////////
  //
  // arc second units es-* (Spanish)
  //

  //////////////////////////////////////////////////////////////
  //
  // arc second units fr-* (French)
  //

  //////////////////////////////////////////////////////////////
  //
  // arc second units it-* (Italian)
  //

  //////////////////////////////////////////////////////////////
  //
  // arc second units pt-* (Portuguese)
};

static ON_UnitName angle_turn_units[] =
{
  //////////////////////////////////////////////////////////////
  //
  // turn unit universal
  //
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Turns), true, true,  ON_WCHAR_GREEK_SMALL_LETTER_TAU }, // unicode small tau symbol
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Turns), false, true, L"turns" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Turns), true, false, L"turn" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Turns), true, true,  L"t" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Turns), true, true,  L"_t" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Turns), false, true, L"_turns" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Turns), true, false, L"_turn" },

  //////////////////////////////////////////////////////////////
  //
  // turn units cs-* Czech
  //

  //////////////////////////////////////////////////////////////
  //
  // turn units en-* English
  //
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Turns), false, true, L"turns" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Turns), true, false, L"turn" },

  //////////////////////////////////////////////////////////////
  //
  // turn units de-* German
  //

  //////////////////////////////////////////////////////////////
  //
  // turn units es-* Spanish
  //

  //////////////////////////////////////////////////////////////
  //
  // turn units fr-* French
  //

  //////////////////////////////////////////////////////////////
  //
  // turn units it-* Italian
  //

  //////////////////////////////////////////////////////////////
  //
  // turn units pt-* Portuguese

};

static ON_UnitName angle_gradian_units[] =
{
  //////////////////////////////////////////////////////////////
  //
  // gradian units universal
  //
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"gons" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"gon" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"_gons" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"_gon" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"_gradians" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"_gradian" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"_grads" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"_grad" },
  {ON_RHINO_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, true,  L"_g" },

  //////////////////////////////////////////////////////////////
  //
  // gradian units cs-* Czech
  //
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"gradi\x00E1ny" },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"grad" },
  {ON_CS_CZ_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, true,  L"g" },

  //////////////////////////////////////////////////////////////
  //
  // gradian units en-* English
  //
  {ON_EN_CA_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"gradians" },
  {ON_EN_CA_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"gradian" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"gradians" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"gradian" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"grads" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"grad" },
  {ON_EN_US_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, true,  L"g" },

  //////////////////////////////////////////////////////////////
  //
  // gradian units de-* German
  //
  // German uses the universal "gon" which is in ON_RHINO_LOCALE_ID

  //////////////////////////////////////////////////////////////
  //
  // gradian units es-* Spanish
  //
  // https://es.wikipedia.org/wiki/Grado_centesimal
  // http://blog.utp.edu.co/adriamec/files/2012/10/NTC1000.pdf (document page 8, PDF page 11)
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"gradianes" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"gradi\x00E1n" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"gons" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"gon" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"gradoscent" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"gradcent" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"gonios" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"gonio" },
  {ON_ES_ES_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, true,  L"gd" },

  //////////////////////////////////////////////////////////////
  //
  // gradian units fr-* French
  //
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"grades" },
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"grade" },
  {ON_FR_FR_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, true,  L"g" },

  //////////////////////////////////////////////////////////////
  //
  // gradian units it-* Italian
  //
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"gradicent" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"gradocent" },
  {ON_IT_IT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, true,  L"gc" },

  //////////////////////////////////////////////////////////////
  //
  // gradian units pl-* Polish
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"grads" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"grad" },
  {ON_PL_PL_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, true,  L"g" },

  //////////////////////////////////////////////////////////////
  //
  // gradian units pt-* Portuguese
  //
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), false, true, L"grads" },
  {ON_PT_PT_LOCALE_ID, static_cast<unsigned int>(ON::AngleUnitSystem::Gradians), true, false, L"grad" },
};

static unsigned int MapPreferedLocaleId(
  unsigned int prefered_locale_id
  )
{
  switch (prefered_locale_id)
  {
  case ON_Locale::WindowsLCID::es_ES_tradnl_LCID:
    prefered_locale_id = ON_Locale::WindowsLCID::es_ES_LCID;
    break;
  }

  return prefered_locale_id;
}

static int CompareNullPointers(const void* a, const void* b)
{
  if ( 0 == a || 0 == b )
  {
    if ( 0 != b )
      return -1;
    if ( 0 != a )
      return 1;
  }
  return 0;
}

int ON_UnitNameEx::Compare(const ON_UnitNameEx* a, const ON_UnitNameEx* b)
{
  int  rc = Compare_m_utf32_name(a,b);
  if ( 0 == rc && 0 != a && 0 != b )
  {
    if ( a->m_locale_id < b->m_locale_id )
      rc = -1;
    else if ( a->m_locale_id > b->m_locale_id )
      rc = 1;
    else
      rc = Compare_m_name(a,b);
  }
  return rc;
}

int ON_UnitNameEx::Compare_m_utf32_name(const ON_UnitNameEx* a, const ON_UnitNameEx* b)
{
  int rc = CompareNullPointers(a,b);
  if ( 0 != rc || 0 == a )
    return rc;

  if ( a->m_utf32_name_count < b->m_utf32_name_count )
    return -1;

  if ( a->m_utf32_name_count > b->m_utf32_name_count )
    return 1;

  ON__UINT32 x, y;
  const unsigned int capacity = ON_UnitNameEx::m_utf32_name_capacity;
  unsigned int i;

  for ( i = 0; i < capacity; i++ )
  {
    x = a->m_utf32_name[i];
    y = b->m_utf32_name[i];
    if ( x < y )
      return -1;
    if ( y < x )
      return 1;
    if ( 0 == x )
      break;
  }

  return 0;
}

int ON_UnitNameEx::Compare_m_name(const ON_UnitNameEx* a, const ON_UnitNameEx* b)
{
  int rc = CompareNullPointers(a,b);
  if ( 0 != rc || 0 == a )
    return rc;

  rc = CompareNullPointers(a->m_name,b->m_name);
  if ( 0 != rc || 0 == a->m_name )
    return rc;

  wchar_t x, y;
  unsigned int i = 0;
  for(;;)
  {
    x = a->m_name[i];
    y = b->m_name[i];
    if ( x < y )
      return -1;
    if ( y < x )
      return 1;
    if ( 0 == x )
      break;
    i++;
  }
  return 0;
}

static ON__UINT32 ON_ToLower( ON__UINT32 c )
{
  // This is a specialized and fast version that converts selected 
  // unicode capital latin letter code points to the corresponding
  // unicode latin small letter code points.  It is designed to be
  // used in code that has to quickly parse common unit names
  // in English, Spanish, French, German, Protuguese and Czech. 
  // If other languages need to be supported, this funcition
  // may need to be enhanced.  
  //
  // In many situations, "to lower" requires more context than a single
  // unicode code point.  If those cases need to be handled correctly,
  // then this function will need to be discarded.
  //
  // The reason this function exists is that there is not a robust
  // C library function that does "to lower" on wchar_t strings
  // that works on Microsoft's, Apple's, Google's or Gnu's
  // compilers.  There are solutions, but many depend on other
  // app state variables that define an app/machine locale.
  // This meeans the result is not "predictably consistent".
  if ( c < 'A' )
    return c;

  if ( c <= 'Z' )
    return c + 0x0020; // A to a, .... Z to z
 
  if ( c < 0x00C0 )
    return c;
  
 if ( c <= 0x00DE )
  {
    if ( 0x00D7 == c )
      return c; // multiplication sign
    return c + 0x0020;
  }
  
  if ( c < 0x0100 )
    return c;

  if ( c <= 0x0137 )
  {
    // add 1 if c is even
    c |= 0x0001;
    return c;
  }
  
  if ( c < 0x0139 )
    return c;  
  
  if ( c < 0x0148 )
  {
    if ( 0 != (c % 2) )
      c++;
    return c;
  }

  if ( c < 0x014A )
    return c;  

  if ( c < 0x0177 )
  {
    // add 1 if c is odd
    c |= 0x0001;
    return c;
  }

  if ( c < 0x0179 )
    return c;  

  if ( c < 0x017E )
  {
    if ( 0 != (c % 2) )
      c++;
    return c;
  }

  if ( c < ON_UNICODE_LATIN_CAPITAL_LETTER_SHARP_S )
  {
    // NOTE:
    //  This skips some "obvious" to lower conversions, but none 
    //  of these conversions are needed for common unit 
    //  names in English, Spanish, French, German, Protuguese 
    //  or Czech.
    return c;
  }

  switch(c)
  {
  case ON_UNICODE_GREEK_CAPITAL_LETTER_TAU: // symbol for angle "turns"
    c = ON_UNICODE_GREEK_SMALL_LETTER_TAU;
    break;

  case ON_UNICODE_LATIN_CAPITAL_LETTER_SHARP_S:
    c = ON_UNICODE_LATIN_SMALL_LETTER_SHARP_S;
    break;
  }

  return c;
}

static ON__UINT32 ON_ToLatinAtoZ( ON__UINT32 c )
{
  // This is a specialized and fast version that converts selected 
  // unicode code points whose glyph is a latin letter A to Z with a 
  // diacritical mark to the corresponding latin A to Z letter.  
  // It also converts sharp s (eszett) to latin letter S and 
  // greek tau to to latin letter T.
  //
  // This code is designed to be used efficently parse common unit 
  // names in English, Spanish, French, German, Protuguese and Czech.
  // If other languages need to be supported, this function will need
  // to be enhanced, redone, or removed.  The unit names being parsed
  // by this code are often input on devices, like English language 
  // keyboards, where it is not convenient for the user to type a 
  // letter or symbol in the language being used.
  //
  // Discarding diacritical marks is a risky approach.  Specifically,
  // this function cannot be used in any context where different 
  // relevant words will be mapped to the same "A to Z" version.

  if ( c < 0x00C0 )
    return c;

  if ( c < 0x0100 )
  {
    if ( c >= 0x00C0 && c <= 0x00C5 )
      c = 'A'; // capital letter A with grave, acute, circumflex, tilde, diaeresis, ring above,
    else if ( c >= 0x00C8 && c <= 0x00CB )
      c = 'E'; // capital letter E with grave, acute, circumflex, diaeresis,
    else if ( c >= 0x00CC && c <= 0x00CF )
      c = 'I'; // capital letter I with grave, acute, circumflex, diaeresis,
    else if ( c >= 0x00D2 && c <= 0x00D6 )
      c = 'O'; // capital letter O with grave, acute, circumflex, tilde, diaeresis,
    else if ( c >= 0x00D9 && c <= 0x00DC )
      c = 'U'; // capital letter U with grave, acute, circumflex, diaeresis,
    else if ( c >= 0x00E0 && c <= 0x00E5 )
      c = 'a'; // small letter a with grave, acute, circumflex, tilde, diaeresis, ring above,
    else if ( c == 00E7 )
      c = 'c'; // small letter c with dedilla
    else if ( c >= 0x00E8 && c <= 0x00EB )
      c = 'e'; // small letter e with grave, acute, circumflex, tilde, diaeresis, ring above,
    else if ( c >= 0x00EC && c <= 0x00EF )
      c = 'i'; // small letter i with grave, acute, circumflex, tilde, diaeresis, ring above,
    else if ( c == 0x00F1 )
      c = 'n'; // small letter n with tilde
    else if ( c >= 0x00F2 && c <= 0x00F6 )
      c = 'o'; // small letter o with grave, acute, circumflex, tilde, diaeresis,
    else if ( c == 0x00F8 )
      c = 'o'; // small letter o with stroke
    else if ( c >= 0x00F9 && c <= 0x00FC )
      c = 'u'; // small letter u with grave, acute, circumflex, tilde, diaeresis,
    else if ( c == 0x00FD )
      c = 'y'; // small letter y with acute
    else if ( c == 0x00FF )
      c = 'y'; // small letter y with diaeresis
    else
    {
      switch (c)
      {
      case 0x00D0: // capital letter Eth
        c = 'D';
        break;
      case 0x00D1: // capital letter N with tilde
        c = 'N';
        break;
      case 0x00D8: // capital letter O with stroke
        c = 'O';
        break;
      case ON_UNICODE_LATIN_SMALL_LETTER_SHARP_S: // small sharp s (German eszet)
        c = 's';
        break;
      case 0x00C7: // capital letter C with cedilla
        c = 'C';
        break;
      case 0x00DD: // capital letter Y with acute
        c = 'Y';
        break;
      case 0x00E7: // small letter c with cedilla
        c = 'c';
        break;
      case 0x00F0: // small letter Eth
        c = 'd';
        break;
      }
    }
  }
  else if ( c < 0x0200 )
  {
    if ( c >= 0x0100 && c <= 0x0105 )
      c = ((0 != (1&c)) ? 'a' : 'A'); // odds -> small A
    else if ( c >= 0x0106 && c <= 0x010D )
      c = ((0 != (1&c)) ? 'c' : 'C'); // odds -> small C
    else if ( c >= 0x010E && c <= 0x0111 )
      c = ((0 != (1&c)) ? 'd' : 'D'); // odds -> small D
    else if ( c >= 0x0112 && c <= 0x011B )
      c = ((0 != (1&c)) ? 'e' : 'E'); // odds -> small E
    else if ( c >= 0x011C && c <= 0x0123 )
      c = ((0 != (1&c)) ? 'g' : 'G'); // odds -> small G
    else if ( c >= 0x0124 && c <= 0x0127 )
      c = ((0 != (1&c)) ? 'h' : 'H'); // odds -> small H
    else if ( c >= 0x0128 && c <= 0x0131 )
      c = ((0 != (1&c)) ? 'i' : 'I'); // odds -> small I
    else if ( c >= 0x0134 && c <= 0x0135 )
      c = ((0 != (1&c)) ? 'j' : 'J'); // odds -> small J
    else if ( c >= 0x0136 && c <= 0x0137 )
      c = ((0 != (1&c)) ? 'k' : 'K'); // odds -> small K
    else if ( c == 0x0138 )
      c = 'k'; // small kra
    else if ( c >= 0x0139 && c <= 0x0142 )
      c = ((0 != (1&c)) ? 'L' : 'l'); // odds -> capital L
    else if ( c >= 0x0143 && c <= 0x0148 )
      c = ((0 != (1&c)) ? 'N' : 'n'); // odds -> capital N
    else if ( c == 0x0149 )
      c = 'n'; //small n preceded by apostrophe
    else if ( c >= 0x014C && c <= 0x0151 )
      c = ((0 != (1&c)) ? 'o' : 'O'); // odds -> small O
    else if ( c >= 0x0154 && c <= 0x0159 )
      c = ((0 != (1&c)) ? 'r' : 'R'); // odds -> small R
    else if ( c >= 0x015A && c <= 0x0161 )
      c = ((0 != (1&c)) ? 's' : 'S'); // odds -> small S
    else if ( c >= 0x0162 && c <= 0x0167 )
      c = ((0 != (1&c)) ? 't' : 'T'); // odds -> small t
    else if ( c >= 0x0168 && c <= 0x0173 )
      c = ((0 != (1&c)) ? 'u' : 'U'); // odds -> small u
    else if ( c >= 0x0174 && c <= 0x0175 )
      c = ((0 != (1&c)) ? 'w' : 'W'); // odds -> small w
    else if ( c >= 0x0176 && c <= 0x0178 )
      c = ((0 != (1&c)) ? 'y' : 'Y'); // odds -> small y
    else if ( c >= 0x0179 && c <= 0x017E )
      c = ((0 != (1&c)) ? 'z' : 'Z'); // odds -> capital Z
  }
  else
  {
    switch (c)
    {
    case ON_UNICODE_GREEK_CAPITAL_LETTER_TAU: // (angle turn units)
      c = 'T';
      break;
    case ON_UNICODE_GREEK_SMALL_LETTER_TAU: // (angle turn units)
      c = 't';
      break;
    case ON_UNICODE_LATIN_CAPITAL_LETTER_SHARP_S: // (German eszett)
      c = 'S'; 
    }
  }

  return c;
}

/*
Description:
  This function is designed to be used to map common unit names
  and abbreviations in Czech, English, French, German, Spanish and
  Portuguese to a lower case latin name that has unicode code points
  'a' to 'z' that can be used in code that parses input streams 
  that specify angles, lengths or point coordinates.

Parameters:
  name - [in]
    String to test to see if it begins with a common 
    unit name or abbreviation in Czech, English, French,
    German, Italian, Portuguese or Spanish.
  utf32_small_simple_name - [out]
    A buffer where a null termintated UTF-32 encoded 
    simplified small letter version of the input 
    unit name is returned.
  utf32_small_simple_name_capacity - [in]
    Number of elements that can be used in utf32_small_simple_name[]

Returns:
   0: name was not a unit recognized by this function.
  >0: number of elements of name[] that were parsed as a 
      unit name or abbreviation.

Remarks:
  This code is used to quickly parse optional embedded unit
  names, abreviations in input streams that specify angles,
  lengths, points in cartesian coordinates, and points in
  polar coordinates.  
  
  The unit systems in the strings may be different from the
  language the applications user interface is using or the unit
  names may be entered on a device, like and english language
  keyboard, that does not provide easy access to latin letters
  with diacritical marks.
  
  If it becomes necessary to correctly handle languages like 
  Russian, Greek, Arabic, Hebrew, Asian languages or anything
  else that does not use latin-esque letters with diacritical
  marks, then this code will be useless and must be discarded.
*/
static
unsigned int ON_GetSmallSimpleUnitsName(
  const wchar_t* name,
  ON__UINT32* utf32_small_simple_name,
  size_t utf32_small_simple_name_capacity
  )
{
  ON_UnicodeErrorParameters e = {0};
  int decode_count;
  ON__UINT32 c, lower_c, simple_c;

  if ( 0 == utf32_small_simple_name || utf32_small_simple_name_capacity <= 0 )
  {
    // failure
    return 0;
  }

  if ( utf32_small_simple_name_capacity > 1 )
  {
    // check for common single glyph symbols that are used
    // when specify length and angle units
    switch(name[0])
    {
    case ON_UNICODE_QUOTATION_MARK:         // quote (inches, arc seconds)
    case ON_UNICODE_APOSTROPHE:             // apostophe (feet, arc minutes)
    case ON_UNICODE_DEGREE_SYMBOL:          // degree symbol (arc degrees)
    case ON_UNICODE_GREEK_SMALL_LETTER_TAU: // small tau (turns)
      utf32_small_simple_name[0] = (ON__UINT32)(name[0]);
      utf32_small_simple_name[1] = 0;
      return 1;  
    case ON_UNICODE_GREEK_CAPITAL_LETTER_TAU: // capital tau (turns)
      // This is done so strings that have passed through 
      // a "toupper" converter will parse correctly.
      utf32_small_simple_name[0] = (ON__UINT32)ON_UNICODE_GREEK_SMALL_LETTER_TAU;
      utf32_small_simple_name[1] = 0;
      return 1;  
    case ON_UNICODE_MASCULINE_ORDINAL_INDICATOR: 
      // The unicode masuline ordinal indicator (Spanish) 
      // is often mistakenlyused as a degrees symbol.
      utf32_small_simple_name[0] = ON_UNICODE_DEGREE_SYMBOL;
      utf32_small_simple_name[1] = 0;
      return 1;  
    }
  }

  bool bLeadingUnderbar = false;

  utf32_small_simple_name[0] = 0;

  // return quickly when beginning code point cannot possibly
  // be a unit.

  if ( name[0] >= 0 && name[0] < 'A' )
  {
    return 0;
  }

  if ( name[0] > 'Z' && name[0] < 'a' )
  {
    // leading underbar is allowed
    if ( ON_UNICODE_LOW_LINE != name[0] )
      return 0;
    if ( name[1] >= 0 && name[1] < 'A' )
      return 0;
    if ( name[1] > 'Z' && name[1] < 'a' )
      return 0;
    if ( name[1] > 'z' && name[1] < 0x80 )
      return 0;
    bLeadingUnderbar = true;
  }

  if ( name[0] > 'z' && name[0] < 0x80 )
  {
    return 0;
  }

  size_t count = 0;  
  int name_index = 0;
  while ( count < utf32_small_simple_name_capacity && 0 != name[name_index] )
  {
    e.m_error_status = 0;
    c = 0xFFFFFFFF;

    if ( name[name_index] <= 127 && name[name_index] >= 0 )
    {
      // The code point value for all UTF-* encodings map 
      // values from 0 to 127 (ASCII) to the same code point
      // value. These values are common and this special case
      // handling speeds up parsing.
      c = (ON__UINT32)(name[name_index]);
      decode_count = 1;
    }
    else
    {
      switch(sizeof(wchar_t))
      {
      case 1: // assume name is UTF-8 encoded
        {
          int name_count = name_index+1;
          while( 0 != name[name_count] && 0 != (name[name_count] & 0x80) && name_count < name_index+6 )
            name_count++;
          decode_count = ON_DecodeUTF8((const char*)(name+name_index),name_count-name_index,&e,&c);
        }
        break;

      case 2: // assume name is UTF-16 encoded
        decode_count = ON_DecodeUTF16((const ON__UINT16*)(name+name_index),2,&e,&c);
        break;

      case 4: // assume name is UTF-32 encoded
        c = (ON__UINT32)(name[name_index]);
        decode_count = 1;
        break;

      default:
        // unsupported wchar_t size
        decode_count = 0;
        break;
      }
    }

    if ( decode_count < 1 || !ON_IsValidUnicodeCodePoint(c) )
    {
      count = 0;
      break;
    }

    lower_c = ON_ToLower(c);
    simple_c = ON_ToLatinAtoZ(lower_c);
    if (    (simple_c <= 0x40)
         || (simple_c >= 0x5B && simple_c <= 0x60)
         || (simple_c >= 0x7B && simple_c <= 0xB4)
         || (simple_c >= 0xB6 && simple_c <= 0xBF)
         || (0xD7 == simple_c)
         || (0xF7 == simple_c)
       )
    {
      // This character terminates parsing unless it is a leading underbar.
      if ( count != 0 || !bLeadingUnderbar || ON_UNICODE_LOW_LINE != simple_c )
        break;
    }

    name_index += decode_count;
    utf32_small_simple_name[count++] = simple_c;
  }

  if ( count >= utf32_small_simple_name_capacity )
    count = 0;

  utf32_small_simple_name[count] = 0;

  return ((count > 0 && name_index > 0) ? name_index : 0);
}

int ON_UnitNameEx::SetSimplifiedName()
{
  const unsigned int capacity = ON_UnitNameEx::m_utf32_name_capacity;
   m_utf32_name[capacity-1] = 0;
  const int name_index = ON_GetSmallSimpleUnitsName(m_name,m_utf32_name,capacity);
  unsigned int count = 0;
  if (    name_index > 0
        && 0 != m_utf32_name[0]
        && 0 == m_utf32_name[capacity-1]
        )
  {
    while ( 0 != m_utf32_name[count] )
      count++;
  }
  if ( count > 0 && count < capacity
       && 0 == m_utf32_name[count]
       && 0 != m_utf32_name[count-1]
     )
  {
    m_utf32_name_count = count;
  }
  else
  {
    m_utf32_name_count = 0;
  }
  for ( count = m_utf32_name_count; count < capacity; count++ )
    m_utf32_name[count] = 0;
  return (count > 0 && name_index > 0) ? name_index : 0;
}

class ON_UnitSystemNameCache
{
public:
  // m_unit_list[] has m_unit_list_count elements sorted
  // by ON_UnitNameEx.m_utf32_name_count (shortest first)
  // and then by ON_UnitNameEx.m_utf32_name
  const ON_UnitNameEx* m_unit_list;
  unsigned int m_unit_list_count;
  ON_2dex m_unit_index[ON_UnitNameEx::m_utf32_name_capacity];
};

static bool GetUnitSystemNameCache(
  unsigned int (*GetUnitDictionary)(size_t,ON_UnitName*,ON_UnitNameEx*,bool),
  bool bIncludeInvalidLocaleIdNames,
  ON_UnitSystemNameCache& cache
  )
{
  unsigned int i;

  const unsigned int utf32_name_capacity = ON_UnitNameEx::m_utf32_name_capacity;
  cache.m_unit_list = 0;
  cache.m_unit_list_count = 0;

  for ( i = 0; i < utf32_name_capacity; i++ )
  {
    cache.m_unit_index[i].i = 0;
    cache.m_unit_index[i].j = 0;
  }

  // make a list
  unsigned int capacity = GetUnitDictionary(0,0,0,bIncludeInvalidLocaleIdNames);
  if ( capacity <= 0 )
    return false;

  // Dale Lear September 16, 2016
  // https://mcneel.myjetbrains.com/youtrack/issue/RH-28754
  // This allocation appears as a leak but it is not.
  // This pointer is allocated once when needed, saved as cache.m_unit_list,
  // and is used for the lifetime of the application.
  ON_UnitNameEx* unit_list = new (std::nothrow) ON_UnitNameEx[capacity];

  if ( nullptr == unit_list )
    return false;
  capacity = GetUnitDictionary(capacity,0,unit_list,bIncludeInvalidLocaleIdNames);
  if ( capacity <= 0 )
  {
    delete[] unit_list;
    return false;
  }

  // Sort the returned list
  ON_qsort(
    unit_list,
    capacity,
    sizeof(unit_list[0]),
    (int (*)(const void*,const void*))ON_UnitNameEx::Compare
    );

  // Cull duplicates and set m_name = 0;
  unit_list[0].m_name = 0;
  unsigned int count = 0;

  for ( i = count+1; i < capacity; i++ )
  {
    unit_list[i].m_name = 0;
    if (    0 == ON_UnitNameEx::Compare_m_utf32_name(unit_list+count,unit_list+i) 
         && unit_list[count].m_locale_id == unit_list[i].m_locale_id
       )
    {
      continue;
    }
    count++;
    if (count < i )
    {
      unit_list[count] = unit_list[i];
    }
  }
  count++;

  // The array length_unit_list[] is sorted so the value of 
  // length_unit_list[].m_utf32_name_count increases.  
  // Use that fact to build an index based on the value of 
  // length_unit_list[].m_utf32_name_count so searches can
  // easily be restriced to the region of length_unit_list[]
  // where a possible match exists.
  for ( i = 0; i < count; i++ )
  {
    cache.m_unit_index[unit_list[i].m_utf32_name_count].i = (int)i;
    while(i+1 < count && unit_list[i].m_utf32_name_count == unit_list[i+1].m_utf32_name_count )
      i++;
    cache.m_unit_index[unit_list[i].m_utf32_name_count].j = (int)(i+1);
  }

  cache.m_unit_list = unit_list;

  if ( 0 != cache.m_unit_list )
    cache.m_unit_list_count = count;

  return (cache.m_unit_list_count > 0);
}

static unsigned char UnitSystemEnumValue(
  const ON_UnitSystemNameCache& cache,
  unsigned int prefered_locale_id,
  const ON_UnitNameEx* usname
  )
{
  prefered_locale_id = MapPreferedLocaleId(prefered_locale_id);

  if ( 0 == usname
       || usname->m_utf32_name_count <= 0
       || usname->m_utf32_name_count >= ON_UnitNameEx::m_utf32_name_capacity
       || 0 == usname->m_utf32_name[0] 
       || 0 != usname->m_utf32_name[ON_UnitNameEx::m_utf32_name_capacity-1] 
     )
  {
    return 0;
  }

  if ( 0 == cache.m_unit_list || cache.m_unit_list_count <= 0 )
  {
    return 0;
  }

  ON_2dex range = cache.m_unit_index[usname->m_utf32_name_count];
  // All m_utf32_name entries in s_length_unit_list[range.i,...,range.j-1]
  // have the same length as usname->m_utf32_name[] and they are sorted by
  // m_utf32_name.  Do a binary search on m_utf32_name after checking
  // that the first character indicates a match may exist.
  if (    range.i < range.j 
       && usname->m_utf32_name[0] >= cache.m_unit_list[range.i].m_utf32_name[0]
       && usname->m_utf32_name[0] <= cache.m_unit_list[range.j-1].m_utf32_name[0]
     )
  {
    // binary search on m_utf32_name[] value
    do
    {
      int k = (range.i+range.j)/2;
      int rc = ON_UnitNameEx::Compare_m_utf32_name(usname,cache.m_unit_list+k);
      if (rc < 0)
        range.j = k;
      else if (rc > 0)
        range.i = k+1;
      else
      {
        // string matches - make sure the locale id is valid
        if ( prefered_locale_id == cache.m_unit_list[k].m_locale_id )
          return cache.m_unit_list[k].m_unit_system;

        unsigned int common_angle_unit_system = cache.m_unit_list[k].m_unit_system;

        int k0 = k;
        while ( k0 > range.i
                && 0 == ON_UnitNameEx::Compare_m_utf32_name(usname,cache.m_unit_list+(k0-1))
              )
        {
          k0--;
          if ( prefered_locale_id == cache.m_unit_list[k0].m_locale_id )
            return cache.m_unit_list[k0].m_unit_system;
          if ( common_angle_unit_system != cache.m_unit_list[k0].m_unit_system )
            common_angle_unit_system = 0;
        }

        int k1 = k+1;
        while ( k1 < range.j
                && 0 == ON_UnitNameEx::Compare_m_utf32_name(usname,cache.m_unit_list+k1)
              )
        {
          if ( prefered_locale_id == cache.m_unit_list[k1].m_locale_id )
            return cache.m_unit_list[k1].m_unit_system;
          if ( common_angle_unit_system != cache.m_unit_list[k1].m_unit_system )
            common_angle_unit_system = 0;
          k1++;
        }

        // All matching strings specify the same unit system so
        // we don't care about locale id.
        if ( common_angle_unit_system != 0 )
        {
          return cache.m_unit_list[k].m_unit_system;
        }
        
        if ( k0+1 == k1 )
        {
          // There is a single entry for this string so
          // we don't care about locale id.
          return cache.m_unit_list[k].m_unit_system;
        }

        // If we get this far, there are 2 or more strings that
        // match usname and none of these exactly match the locale 
        // id.
        for ( k = k0; k < k1; k++ )
        {
          if ( ON_RHINO_LOCALE_ID ==  cache.m_unit_list[k].m_locale_id )
            return cache.m_unit_list[k].m_unit_system; // "universal" string

          if ( ON_INVALID_LOCALE_ID ==  cache.m_unit_list[k].m_locale_id )
            return 0; // this string requires an exact locale id match

          // Check for a language match; i.e., if cache.m_unit_list[] is
          // is for locale en-US and the prefered_locale_id is en-UK,
          // then use the en-US entry.  The "language" part of a locale id
          // is encoded in the low byte of the locale id.
          if ( (0xFF & prefered_locale_id) == (0xFF & cache.m_unit_list[k].m_locale_id) )
          {
            // the language part of the local matched
            return cache.m_unit_list[k].m_unit_system;
          }
        }

        return 0;
      }
    }
    while ( range.i < range.j );
  }

  return 0;
}


///////////////////////////////////////////////////////////////////////////////
//
// Length unit names
//

/*
  length_unit_system - [out]]
    If name is parsed as a length unit system name, then
    *length_unit_system is set to the corresponding length unit system.
    If name is not parsed, then *length_unit_system is set to 
    ON::LengthUnitSystem::None.
*/
unsigned int GetLengthUnitList( 
  size_t length_unit_list_capacity,
  ON_UnitName* length_unit_list, 
  ON_UnitNameEx* length_unit_ex_list,
  bool bIncludeInvalidLocaleIdNames
  )
{
  size_t si_length_units_count = sizeof(si_length_units)/sizeof(si_length_units[0]);
  size_t en_US_customary_length_units_count = sizeof(en_US_customary_length_units)/sizeof(en_US_customary_length_units[0]);

  size_t capacity = si_length_units_count
                  + en_US_customary_length_units_count;

  if (    0 == length_unit_list_capacity
       && 0 == length_unit_list
       && 0 == length_unit_ex_list
     )
  {
    return (unsigned int)capacity;
  }

  if (   (0 == length_unit_list  && 0 == length_unit_ex_list)
       || length_unit_list_capacity < capacity 
      )
  {
    return 0;
  }


  size_t count = 0;
  ON_UnitName unit_name;
  ON_UnitNameEx x;
  const ON_UnitName* input_list;
  size_t input_list_count;

  for ( size_t list_index = 0; list_index < 2; list_index++ )
  {
    switch(list_index)
    {
    case 0:
      input_list = si_length_units;
      input_list_count = si_length_units_count;
      break;
    case 1:
      input_list = en_US_customary_length_units;
      input_list_count = en_US_customary_length_units_count;
      break;
    default:
      input_list = 0;
      input_list_count = 0;
      break;
    }

    for ( size_t i = 0; i < input_list_count; i++ )
    {
      if ( 0 != length_unit_list )
      {
        length_unit_list[count++] = input_list[i];
      }
      else if ( 0 != length_unit_ex_list )
      {
        unit_name = input_list[i];
        x.m_locale_id   = unit_name.m_locale_id;
        x.m_unit_system = unit_name.m_unit_system;
        x.m_name        = unit_name.m_name;
        if ( x.SetSimplifiedName() > 0 
             && 0 != x.m_utf32_name_count > 0
             && 0 != x.m_utf32_name_count < ON_UnitNameEx::m_utf32_name_capacity
             && 0 != x.m_utf32_name[0] 
             && 0 != x.m_utf32_name[x.m_utf32_name_count-1] 
             && 0 == x.m_utf32_name[x.m_utf32_name_count] 
             && 0 == x.m_utf32_name[ON_UnitNameEx::m_utf32_name_capacity-1]
           )
        {
          length_unit_ex_list[count++] = x;
        }
        else
        {
          ON_ERROR("Length unit list conatins invalid element.");
        }
      }
    }
  }

  return (unsigned int)count;
}

unsigned int ON_LengthUnitName::GetLengthUnitNameList( 
  size_t length_unit_list_capacity,
  ON_LengthUnitName* length_unit_list
  )
{
  return GetLengthUnitList(length_unit_list_capacity,(ON_UnitName*)length_unit_list,0,false);
}

int ON_ParseLengthUnitName(
  const wchar_t* str,
  int str_count,
  int prefered_locale_id,
  ON::LengthUnitSystem* length_unit_system
)
{
  ON_ParseSettings parse_settings;
  parse_settings.SetPreferedLocaleId(prefered_locale_id);
  return ON_ParseLengthUnitName(str, str_count, parse_settings, length_unit_system);
}

int ON_ParseLengthUnitName( 
  const wchar_t* str,
  int str_count,
  ON_ParseSettings parse_settings,
  ON::LengthUnitSystem* length_unit_system
  )
{
  unsigned int prefered_locale_id = MapPreferedLocaleId(parse_settings.PreferedLocaleId());

  static ON_UnitSystemNameCache s_length_unit_cache = {0};

  if ( 0 != length_unit_system )
  {
    *length_unit_system = ON::LengthUnitSystem::None;
  }

  ON_UnitNameEx x;
  const unsigned int utf32_name_capacity = ON_UnitNameEx::m_utf32_name_capacity;
  unsigned int count = 0;
  int str_index = 0;
  x.m_locale_id = 0;
  x.m_unit_system = static_cast<unsigned int>(ON::LengthUnitSystem::None);
  x.m_name = str;
  x.m_utf32_name_count = 0;
  x.m_utf32_name[0] = 0;

  int whitespace_count = 0;

  for (;;)
  {
    if ( 0 == str_count || str_count < -1 )
      break;

    if ( nullptr == str )
      break;

    if (parse_settings.ParseLeadingWhiteSpace())
    {
      // skip over leading white space
      while ((-1 == str_count || whitespace_count < str_count) && parse_settings.IsLeadingWhiteSpace(str[str_index+whitespace_count]))
      {
        whitespace_count++;
      }
    }
    else if (parse_settings.ParseWhiteSpaceBetweenValueAndUnitSystem())
    {
      // skip over interior white space (needed to make ON_LengthValue() parsing work correctly
      while ((-1 == str_count || whitespace_count < str_count) && parse_settings.IsInteriorWhiteSpace(str[str_index+whitespace_count]))
      {
        whitespace_count++;
      }
    }
    str += whitespace_count;
    if (-1 != str_count)
      str_count -= whitespace_count;

    if (    str[0] >= 0 
         && str[0] < 'A' 
         && ON_UNICODE_QUOTATION_MARK != str[0] 
         && ON_UNICODE_APOSTROPHE != str[0]
       )
    {
      break;
    }

    if ( 0 == s_length_unit_cache.m_unit_list )
    {
      if ( !GetUnitSystemNameCache(GetLengthUnitList,true,s_length_unit_cache) )
        break;
    }

    if (ON_UNICODE_QUOTATION_MARK == str[0] )
    {
      // double quote = US inches abbreviation
      str_index = 1;
      x.m_name = str;
      x.m_unit_system = static_cast<unsigned int>(ON::LengthUnitSystem::Inches);
      x.m_utf32_name[0] = ON_UNICODE_QUOTATION_MARK;
      x.m_utf32_name[1] = 0;
      x.m_utf32_name_count = 1;
      break;
    }

    if (ON_UNICODE_APOSTROPHE == str[0] )
    {
      // apostrophe = US feet abbreviation
      str_index = 1;
      x.m_name = str;
      x.m_unit_system = static_cast<unsigned int>(ON::LengthUnitSystem::Feet);
      x.m_utf32_name[0] = ON_UNICODE_APOSTROPHE;
      x.m_utf32_name[1] = 0;
      x.m_utf32_name_count = 1;
      break;
    }

    str_index = ON_GetSmallSimpleUnitsName(str,x.m_utf32_name,utf32_name_capacity);
    if ( str_index <= 0 )
    {
      str_index = 0;
      break;
    }

    while (count < utf32_name_capacity && 0 != x.m_utf32_name[count] )
      count++;
    if ( count >= utf32_name_capacity )
    {
      str_index = 0;
      count = 0;
      break;
    }
    x.m_utf32_name_count = count;
    while (count < utf32_name_capacity)
      x.m_utf32_name[count++] = 0;

    x.m_unit_system = UnitSystemEnumValue(
      s_length_unit_cache,
      prefered_locale_id,
      &x);
    if ( static_cast<unsigned int>(ON::LengthUnitSystem::None) == x.m_unit_system )
    {
      str_index = 0;
      count = 0;
    }
    break;
  }

  if ( str_index > 0 && 0 != length_unit_system )
    *length_unit_system = ON::LengthUnitSystemFromUnsigned(x.m_unit_system);

  return (str_index > 0) ? (str_index+whitespace_count) : 0;
}



///////////////////////////////////////////////////////////////////////////////
//
// Angle unit names
//
static unsigned int GetAngleUnitList( 
  size_t angle_unit_list_capacity,
  ON_UnitName* angle_unit_list, 
  ON_UnitNameEx* angle_unit_ex_list,
  bool bIncludeInvalidLocaleIdNames
  )
{
  const size_t angle_radian_units_count = sizeof(angle_radian_units)/sizeof(angle_radian_units[0]);
  const size_t angle_degree_units_count = sizeof(angle_degree_units)/sizeof(angle_degree_units[0]);
  const size_t angle_minute_units_count = sizeof(angle_minute_units)/sizeof(angle_minute_units[0]);
  const size_t angle_second_units_count = sizeof(angle_second_units)/sizeof(angle_second_units[0]);
  const size_t angle_turn_units_count = sizeof(angle_turn_units)/sizeof(angle_turn_units[0]);
  const size_t angle_gradian_units_count = sizeof(angle_gradian_units)/sizeof(angle_gradian_units[0]);
  const size_t angle_no_units_count = sizeof(angle_no_units)/sizeof(angle_no_units[0]);

  const size_t capacity = angle_radian_units_count
                  + angle_degree_units_count
                  + angle_minute_units_count
                  + angle_second_units_count
                  + angle_turn_units_count
                  + angle_gradian_units_count
                  + (bIncludeInvalidLocaleIdNames ? angle_no_units_count : 0)
                  ;

  if ( 0 == angle_unit_list_capacity && 0 == angle_unit_list && 0 == angle_unit_ex_list )
    return (unsigned int)capacity;

  if (    angle_unit_list_capacity < capacity
       || (0 == angle_unit_list && 0 == angle_unit_ex_list)
     )
  {
    return 0;
  }

  if ( 0 != angle_unit_list && 0 != angle_unit_ex_list )
    return 0;

  size_t count = 0;
  ON_UnitName unit_name;
  ON_UnitNameEx x;
  const ON_UnitName* input_list;
  size_t input_list_count;

  for ( size_t list_index = 0; list_index < 7; list_index++ )
  {
    switch(list_index)
    {
    case 0:
      input_list = angle_radian_units;
      input_list_count = angle_radian_units_count;
      break;
    case 1:
      input_list = angle_turn_units;
      input_list_count = angle_turn_units_count;
      break;
    case 2:
      input_list = angle_degree_units;
      input_list_count = angle_degree_units_count;
      break;
    case 3:
      input_list = angle_minute_units;
      input_list_count = angle_minute_units_count;
      break;
    case 4:
      input_list = angle_second_units;
      input_list_count = angle_second_units_count;
      break;
    case 5:
      input_list = angle_gradian_units;
      input_list_count = angle_gradian_units_count;
      break;
    case 6:
      if ( bIncludeInvalidLocaleIdNames )
      {
        input_list = angle_no_units;
        input_list_count = angle_no_units_count;
      }
      else
      {
        input_list = 0;
        input_list_count = 0;
      }
      break;
    default:
      input_list = 0;
      input_list_count = 0;
      break;
    }

    for ( size_t i = 0; i < input_list_count; i++ )
    {
      if ( 0 != angle_unit_list )
      {
        angle_unit_list[count++] = input_list[i];
      }
      else
      {
        unit_name = input_list[i];
        x.m_locale_id = unit_name.m_locale_id;
        x.m_unit_system = unit_name.m_unit_system;
        x.m_name = unit_name.m_name;
        if (    x.SetSimplifiedName() > 0 
             && 0 != x.m_utf32_name_count > 0
             && 0 != x.m_utf32_name_count < ON_UnitNameEx::m_utf32_name_capacity
             && 0 != x.m_utf32_name[0] 
             && 0 != x.m_utf32_name[x.m_utf32_name_count-1] 
             && 0 == x.m_utf32_name[x.m_utf32_name_count] 
             && 0 == x.m_utf32_name[ON_UnitNameEx::m_utf32_name_capacity-1]
           )
        {
          angle_unit_ex_list[count++] = x;
        }
        else
        {
          ON_ERROR("Angle unit list conatins invalid element.");
        }
      }
    }
  }

  return (unsigned int)count;
}

int ON_ParseAngleUnitName(
  const wchar_t* str,
  int str_count,
  int prefered_locale_id,
  ON::AngleUnitSystem* angle_unit_system
)
{
  ON_ParseSettings parse_settings;
  parse_settings.SetPreferedLocaleId(prefered_locale_id);
  parse_settings.SetParseLeadingWhiteSpace(false);
  return ON_ParseAngleUnitName(str, str_count, parse_settings, angle_unit_system);
}

int ON_ParseAngleUnitName( 
  const wchar_t* str,
  int str_count,
  ON_ParseSettings parse_settings,
  ON::AngleUnitSystem* angle_unit_system
  )
{
  static ON_UnitSystemNameCache s_angle_unit_cache = {0};

  if ( 0 != angle_unit_system )
    *angle_unit_system = ON::AngleUnitSystem::None;

  unsigned int prefered_locale_id = MapPreferedLocaleId(parse_settings.PreferedLocaleId());

  ON_UnitNameEx x;
  const unsigned int utf32_name_capacity = ON_UnitNameEx::m_utf32_name_capacity;
  unsigned int count = 0;
  int str_index = 0;
  x.m_locale_id = 0;
  x.m_unit_system = static_cast<unsigned int>(ON::AngleUnitSystem::None);
  x.m_name = str;
  x.m_utf32_name_count = 0;
  x.m_utf32_name[0] = 0;
  
  int whitespace_count = 0;

  for (;;)
  {
    if ( 0 == str_count && str_count < -1 )
      break;

    if ( 0 == str )
      break;

    if (parse_settings.ParseLeadingWhiteSpace())
    {
      // skip over leading white space
      while ((-1 == str_count || whitespace_count < str_count) && parse_settings.IsLeadingWhiteSpace(str[str_index+whitespace_count]))
      {
        whitespace_count++;
      }
    }
    else if (parse_settings.ParseWhiteSpaceBetweenValueAndUnitSystem())
    {
      // skip over interior white space (needed to make ON_AngleValue() parsing work correctly
      while ((-1 == str_count || whitespace_count < str_count) && parse_settings.IsInteriorWhiteSpace(str[str_index+whitespace_count]))
      {
        whitespace_count++;
      }
    }
    str += whitespace_count;
    if (-1 != str_count)
      str_count -= whitespace_count;

    if (    str[0] >= 0 
         && str[0] < 'A' 
         && ON_UNICODE_QUOTATION_MARK != str[0] 
         && ON_UNICODE_APOSTROPHE != str[0]
       )
    {
      break;
    }


    if ( 0 == s_angle_unit_cache.m_unit_list )
    {
      if ( !GetUnitSystemNameCache(GetAngleUnitList,true,s_angle_unit_cache) )
        break;
    }

    if (ON_UNICODE_QUOTATION_MARK == str[0] )
    {
      // double quote = arc seconds
      str_index = 1;
      x.m_name = str;
      x.m_unit_system = static_cast<unsigned int>(ON::AngleUnitSystem::Seconds);
      x.m_utf32_name[0] = ON_UNICODE_QUOTATION_MARK;
      x.m_utf32_name[1] = 0;
      x.m_utf32_name_count = 1;
      break;
    }

    if ( ON_UNICODE_APOSTROPHE == str[0] )
    {
      // apostrophe = arc minutes
      str_index = 1;
      x.m_name = str;
      x.m_unit_system = static_cast<unsigned int>(ON::AngleUnitSystem::Minutes);
      x.m_utf32_name[0] = ON_UNICODE_APOSTROPHE;
      x.m_utf32_name[1] = 0;
      x.m_utf32_name_count = 1;
      break;
    }

    if ( ON_UNICODE_DEGREE_SYMBOL == str[0] )
    {
      // unicode degree symbol = arc degrees
      str_index = 1;
      x.m_name = str;
      x.m_unit_system = static_cast<unsigned int>(ON::AngleUnitSystem::Degrees);
      x.m_utf32_name[0] = ON_UNICODE_DEGREE_SYMBOL;
      x.m_utf32_name[1] = 0;
      x.m_utf32_name_count = 1;
      break;
    }

    if ( ON_UNICODE_MASCULINE_ORDINAL_INDICATOR == str[0] )
    {
      // The unicode masuline ordinal indicator (Spanish) 
      // is often mistakenlyused as a degrees symbol.
      str_index = 1;
      x.m_name = str;
      x.m_unit_system = static_cast<unsigned int>(ON::AngleUnitSystem::Degrees);
      x.m_utf32_name[0] = ON_UNICODE_DEGREE_SYMBOL; // correct - convert to degree symbol
      x.m_utf32_name[1] = 0;
      x.m_utf32_name_count = 1;
      break;
    }

    if ( ON_UNICODE_GREEK_SMALL_LETTER_TAU == str[0] )
    {
      // unicode greek tau = turns
      str_index = 1;
      x.m_name = str;
      x.m_unit_system = static_cast<unsigned int>(ON::AngleUnitSystem::Turns);
      x.m_utf32_name[0] = ON_UNICODE_GREEK_SMALL_LETTER_TAU;
      x.m_utf32_name[1] = 0;
      x.m_utf32_name_count = 1;
      break;
    }

    if ( ON_UNICODE_GREEK_CAPITAL_LETTER_TAU == str[0] )
    {
      // unicode capital greek tau = turns
      str_index = 1;
      x.m_name = str;
      x.m_unit_system = static_cast<unsigned int>(ON::AngleUnitSystem::Turns);
      x.m_utf32_name[0] = ON_UNICODE_GREEK_SMALL_LETTER_TAU; // correct - convert to lower case
      x.m_utf32_name[1] = 0;
      x.m_utf32_name_count = 1;
      break;
    }

    str_index = ON_GetSmallSimpleUnitsName(str,x.m_utf32_name,utf32_name_capacity);
    if ( str_index <= 0 )
    {
      str_index = 0;
      break;
    }

    while (count < utf32_name_capacity && 0 != x.m_utf32_name[count] )
      count++;
    if ( count >= utf32_name_capacity )
    {
      str_index = 0;
      count = 0;
      break;
    }
    x.m_utf32_name_count = count;
    while (count < utf32_name_capacity)
      x.m_utf32_name[count++] = 0;

    x.m_unit_system = UnitSystemEnumValue(
      s_angle_unit_cache,
      prefered_locale_id,
      &x
      );
    if ( static_cast<unsigned int>(ON::AngleUnitSystem::None) == x.m_unit_system )
    {
      str_index = 0;
      count = 0;
    }
    break;
  }

  if ( str_index > 0 && angle_unit_system )
    *angle_unit_system = ON::AngleUnitSystemFromUnsigned(x.m_unit_system);

  return (str_index > 0) ? (str_index+whitespace_count) : 0;
}

unsigned int ON_AngleUnitName::GetAngleUnitNameList( 
  size_t angle_unit_list_capacity,
  ON_AngleUnitName* angle_unit_list
  )
{
  return GetAngleUnitList(angle_unit_list_capacity,(ON_UnitName*)angle_unit_list,0,false);
}

static const wchar_t* ON_Internal_GetUnitsName(
  unsigned int locale_id,
  const unsigned int unit_system_as_unsigned,
  bool bPlural,
  size_t count,
  const ON_UnitName* names
  )
{
  if (count <= 0 || nullptr == names)
    return nullptr;

  const unsigned int special_id_limit = 2;

  if (locale_id <= special_id_limit && locale_id != ON_RHINO_LOCALE_ID)
  {
    if (0 == locale_id)
      locale_id = ON_Locale::CurrentCulture.WindowsLCID();
    if ( locale_id <= special_id_limit && locale_id != ON_RHINO_LOCALE_ID )
      locale_id = ON_RHINO_LOCALE_ID;
  }
  else if (locale_id > special_id_limit && ON_EN_US_LOCALE_ID != locale_id)
  {
    unsigned int prefered_locale_id = MapPreferedLocaleId(locale_id);
    if (locale_id != prefered_locale_id && prefered_locale_id > special_id_limit)
      locale_id = prefered_locale_id;
  }

  const wchar_t* singular_name = nullptr;
  const wchar_t* plural_name = nullptr;
  for (size_t i = 0; i < count; i++)
  {
    if (locale_id == names[i].m_locale_id && unit_system_as_unsigned == names[i].m_unit_system )
    {
      const wchar_t* name = names[i].m_name;
      if (nullptr != name && 0 != name[0])
      {
        if (nullptr == singular_name && names[i].m_bIsSingular)
        {
          if (!bPlural)
            return name;
          singular_name = name;
        }
        if (nullptr == singular_name && names[i].m_bIsPlural)
        {
          if (bPlural)
            return name;
          plural_name = name;
        }
      }
    }
  }

  if (nullptr != plural_name)
    return plural_name;
  if (nullptr != singular_name)
    return singular_name;

  if (ON_RHINO_LOCALE_ID == locale_id)
    return nullptr; // nothing left to try
  
  if (locale_id > special_id_limit && ON_EN_US_LOCALE_ID != locale_id)
  {
    // try English - US
    return ON_Internal_GetUnitsName(ON_EN_US_LOCALE_ID, unit_system_as_unsigned, bPlural, count, names);
  }

  // Try "rhino" locale
  return ON_Internal_GetUnitsName(ON_RHINO_LOCALE_ID, unit_system_as_unsigned, bPlural, count, names);
}


ON_LengthUnitName ON_LengthUnitName::Create(
  unsigned int locale_id,
  ON::LengthUnitSystem length_unit_system,
  bool bPlural
)
{
  if (0 == locale_id)
    locale_id = ON_Locale::CurrentCulture.WindowsLCID();

  size_t count = 0;
  const ON_UnitName* names = nullptr;

  length_unit_system = ON::LengthUnitSystemFromUnsigned(static_cast<unsigned int>(length_unit_system));

  switch (length_unit_system)
  {
  case ON::LengthUnitSystem::None:
    break;

  case ON::LengthUnitSystem::Angstroms:
  case ON::LengthUnitSystem::Nanometers:
  case ON::LengthUnitSystem::Microns:
  case ON::LengthUnitSystem::Millimeters:
  case ON::LengthUnitSystem::Centimeters:
  case ON::LengthUnitSystem::Decimeters:
  case ON::LengthUnitSystem::Meters:
  case ON::LengthUnitSystem::Dekameters:
  case ON::LengthUnitSystem::Hectometers:
  case ON::LengthUnitSystem::Kilometers:
  case ON::LengthUnitSystem::Megameters:
  case ON::LengthUnitSystem::Gigameters:
    names = si_length_units;
    count = sizeof(si_length_units)/sizeof(si_length_units[0]);    
    break;

  case ON::LengthUnitSystem::Microinches:
  case ON::LengthUnitSystem::Mils:
  case ON::LengthUnitSystem::Inches:
  case ON::LengthUnitSystem::Feet:
  case ON::LengthUnitSystem::Yards:
  case ON::LengthUnitSystem::Miles:
    names = en_US_customary_length_units;
    count = sizeof(en_US_customary_length_units)/sizeof(en_US_customary_length_units[0]);    
    break;
    
  case ON::LengthUnitSystem::PrinterPoints:
    break;
  case ON::LengthUnitSystem::PrinterPicas:
    break;
  case ON::LengthUnitSystem::NauticalMiles:
    break;
  case ON::LengthUnitSystem::AstronomicalUnits:
    break;
  case ON::LengthUnitSystem::LightYears:
    break;
  case ON::LengthUnitSystem::Parsecs:
    break;
  case ON::LengthUnitSystem::CustomUnits:
    break;
  case ON::LengthUnitSystem::Unset:
    break;
  default:
    ON_ERROR("Invalid length_unit_system parameter.");
    length_unit_system = ON::LengthUnitSystem::Unset;
    break;
  }


  ON_LengthUnitName length_unit_name;
  length_unit_name.m_locale_id = locale_id;
  length_unit_name.m_length_unit_system = length_unit_system;
  length_unit_name.m_bNameIsSingular = bPlural ? false : true;
  length_unit_name.m_bNameIsPlural = bPlural ? true : false;

  // attempt to get localized name
  length_unit_name.m_name = ON_Internal_GetUnitsName(
    locale_id,
    static_cast<unsigned int>(length_unit_system),
    bPlural,
    count,
    names
  );

  if (nullptr == length_unit_name.m_name || 0 == length_unit_name.m_name[0])
  {
    switch (length_unit_system)
    {
    case ON::LengthUnitSystem::None:
      break;

    case ON::LengthUnitSystem::Angstroms:
      length_unit_name.m_name = bPlural ? L"angstroms" : L"angstrom";
      break;
    case ON::LengthUnitSystem::Nanometers:
      length_unit_name.m_name = bPlural ? L"nanometers" : L"nanometer";
      break;
    case ON::LengthUnitSystem::Microns:
      length_unit_name.m_name = bPlural ? L"microns" : L"micron";
      break;
    case ON::LengthUnitSystem::Millimeters:
      length_unit_name.m_name = bPlural ? L"millimeters" : L"millimeter";
      break;
    case ON::LengthUnitSystem::Centimeters:
      length_unit_name.m_name = bPlural ? L"centimeters" : L"centimeter";
      break;
    case ON::LengthUnitSystem::Decimeters:
      length_unit_name.m_name = bPlural ? L"decimeters" : L"decimeter";
      break;
    case ON::LengthUnitSystem::Meters:
      length_unit_name.m_name = bPlural ? L"meters" : L"meter";
      break;
    case ON::LengthUnitSystem::Dekameters:
      length_unit_name.m_name = bPlural ? L"dekameters" : L"dekameter";
      break;
    case ON::LengthUnitSystem::Hectometers:
      length_unit_name.m_name = bPlural ? L"hectometers" : L"hectometer";
      break;
    case ON::LengthUnitSystem::Kilometers:
      length_unit_name.m_name = bPlural ? L"kilometers" : L"kilometer";
      break;
    case ON::LengthUnitSystem::Megameters:
      length_unit_name.m_name = bPlural ? L"megameters" : L"megameter";
      break;
    case ON::LengthUnitSystem::Gigameters:
      length_unit_name.m_name = bPlural ? L"gigameters" : L"gigameter";
      break;
    case ON::LengthUnitSystem::Microinches:
      length_unit_name.m_name = bPlural ? L"microinches" : L"microinche";
      break;
    case ON::LengthUnitSystem::Mils:
      length_unit_name.m_name = bPlural ? L"mils" : L"mil";
      break;
    case ON::LengthUnitSystem::Inches:
      length_unit_name.m_name = bPlural ? L"inches" : L"inch";
      break;
    case ON::LengthUnitSystem::Feet:
      length_unit_name.m_name = bPlural ? L"feet" : L"foot";
      break;
    case ON::LengthUnitSystem::Yards:
      length_unit_name.m_name = bPlural ? L"yards" : L"yard";
      break;
    case ON::LengthUnitSystem::Miles:
      length_unit_name.m_name = bPlural ? L"miles" : L"mile";
      break;
    case ON::LengthUnitSystem::PrinterPoints:
      length_unit_name.m_name = bPlural ? L"points" : L"point";
      break;
    case ON::LengthUnitSystem::PrinterPicas:
      length_unit_name.m_name = bPlural ? L"picas" : L"pica";
      break;
    case ON::LengthUnitSystem::NauticalMiles:
      length_unit_name.m_name = bPlural ? L"nauticalmiles" : L"nauticalmile"; // no spaces!
      break;
    case ON::LengthUnitSystem::AstronomicalUnits:
      length_unit_name.m_name = bPlural ? L"AUs" : L"AU"; // no spaces!
      break;
    case ON::LengthUnitSystem::LightYears:
      length_unit_name.m_name = bPlural ? L"lightyears" : L"lightyear"; // no spaces!
      break;
    case ON::LengthUnitSystem::Parsecs:
      length_unit_name.m_name = bPlural ? L"parsecs" : L"parsec";
      break;
    case ON::LengthUnitSystem::CustomUnits:
      break;
    case ON::LengthUnitSystem::Unset:
      break;
    }
  }



  return length_unit_name;
}

int ON_LengthUnitName::Internal_Compare(
  unsigned int order_selector,
  const ON_LengthUnitName& a,
  const ON_LengthUnitName& b
)
{
  if (1 == order_selector)
  {
    // locale id is first
    if (a.m_locale_id < b.m_locale_id)
      return -1;
    if (a.m_locale_id > b.m_locale_id)
      return 1;
  }

  unsigned int i = static_cast<unsigned int>(a.m_length_unit_system);
  unsigned int j = static_cast<unsigned int>(b.m_length_unit_system);
    if (i < j)
    return -1;
  if (i > j)
    return 1;

  if (1 != order_selector)
  {
    // locale id is second
    if (a.m_locale_id < b.m_locale_id)
      return -1;
    if (a.m_locale_id > b.m_locale_id)
      return 1;
  }

  i =  ON_wString::CompareOrdinal(a.m_name,b.m_name,false);
  if (i != 0)
  {
    // ignore case order is prefered
    j = ON_wString::CompareOrdinal(a.m_name, b.m_name, true);
    return (0 != j) ? j : i;
  }

  i = a.m_bNameIsSingular ? 1 : 0;
  j = b.m_bNameIsSingular ? 1 : 0;
  if (i < j)
    return -1;
  if (i > j)
    return 1;

  i = a.m_bNameIsPlural ? 1 : 0;
  j = b.m_bNameIsPlural ? 1 : 0;
  if (i < j)
    return -1;
  if (i > j)
    return 1;

  return 0;
}

int ON_LengthUnitName::CompareUnitSystemLocaleIdName(
  const ON_LengthUnitName& a,
  const ON_LengthUnitName& b
)
{
  return Internal_Compare(0, a, b);
}

int ON_LengthUnitName::CompareLocaleIdUnitSystemName(
  const ON_LengthUnitName& a,
  const ON_LengthUnitName& b
)
{
  return Internal_Compare(1, a, b);
}


unsigned int ON_LengthUnitName::LocaleId() const
{
  return m_locale_id;
}

ON::LengthUnitSystem ON_LengthUnitName::LengthUnit() const
{
  return m_length_unit_system;
}

bool ON_LengthUnitName::LengthUnitAndNameAreSet() const
{
  return LengthUnitIsSet() && LengthUnitNameIsNotEmpty();
}

bool ON_LengthUnitName::LengthUnitIsSet() const
{
  return (ON::LengthUnitSystem::Unset != m_length_unit_system && ON::LengthUnitSystem::None != m_length_unit_system);
}

bool ON_LengthUnitName::LengthUnitIsSetOrNone() const
{
  return (ON::LengthUnitSystem::Unset != m_length_unit_system);
}

const wchar_t* ON_LengthUnitName::LengthUnitName() const
{
  return (nullptr == m_name) ? static_cast<const wchar_t*>(ON_wString::EmptyString) : m_name;
}

bool ON_LengthUnitName::LengthUnitNameIsSingular() const
{
  return m_bNameIsSingular;
}

bool ON_LengthUnitName::LengthUnitNameIsPlural() const
{
  return m_bNameIsPlural;
}

bool ON_LengthUnitName::LengthUnitNameIsEmpty() const
{
  return (nullptr == m_name || 0 == m_name[0]);
}

bool ON_LengthUnitName::LengthUnitNameIsNotEmpty() const
{
  return (nullptr != m_name && 0 != m_name[0]);
}

bool operator==(
  const ON_LengthUnitName& a,
  const ON_LengthUnitName& b
  )
{
  return (0 == ON_LengthUnitName::CompareUnitSystemLocaleIdName(a, b));
}

bool operator!=(
  const ON_LengthUnitName& a,
  const ON_LengthUnitName& b
  )
{
  return (0 != ON_LengthUnitName::CompareUnitSystemLocaleIdName(a, b));
}

ON_AngleUnitName ON_AngleUnitName::Create(
  unsigned int locale_id,
  ON::AngleUnitSystem angle_unit_system,
  bool bPlural
)
{
  angle_unit_system = ON::AngleUnitSystemFromUnsigned(static_cast<unsigned int>(angle_unit_system));
  
  size_t count = 0;
  const ON_UnitName* names = nullptr;

  switch (angle_unit_system)
  {
  case ON::AngleUnitSystem::None:
    break;
  case ON::AngleUnitSystem::Turns:
    names = angle_turn_units;
    count = sizeof(angle_turn_units)/sizeof(angle_turn_units[0]);
    break;
  case ON::AngleUnitSystem::Radians:
    names = angle_radian_units;
    count = sizeof(angle_radian_units)/sizeof(angle_radian_units[0]);
    break;
  case ON::AngleUnitSystem::Degrees:
    names = angle_degree_units;
    count = sizeof(angle_degree_units)/sizeof(angle_degree_units[0]);
    break;
  case ON::AngleUnitSystem::Minutes:
    names = angle_minute_units;
    count = sizeof(angle_minute_units)/sizeof(angle_minute_units[0]);
    break;
  case ON::AngleUnitSystem::Seconds:
    names = angle_second_units;
    count = sizeof(angle_second_units)/sizeof(angle_second_units[0]);
    break;
  case ON::AngleUnitSystem::Gradians:
    names = angle_gradian_units;
    count = sizeof(angle_gradian_units)/sizeof(angle_gradian_units[0]);
    break;
  case ON::AngleUnitSystem::Unset:
    break;
  default:
    ON_ERROR("Invalid angle_unit_system parameter.");
    angle_unit_system = ON::AngleUnitSystem::Unset;
    break;
  }

  ON_AngleUnitName angle_unit_name;
  angle_unit_name.m_locale_id = locale_id;
  angle_unit_name.m_angle_unit_system = angle_unit_system;
  angle_unit_name.m_bNameIsSingular = bPlural ? false : true;
  angle_unit_name.m_bNameIsPlural = bPlural ? true : false;

  // attempt to get localized name
  angle_unit_name.m_name = ON_Internal_GetUnitsName(
    locale_id,
    static_cast<unsigned int>(angle_unit_system),
    bPlural,
    count,
    names
  );

  if (nullptr == angle_unit_name.m_name || 0 == angle_unit_name.m_name[0])
  {
    switch (angle_unit_system)
    {
    case ON::AngleUnitSystem::None:
      break;
    case ON::AngleUnitSystem::Turns:
      angle_unit_name.m_name = bPlural ? L"turns" : L"turn";
      break;
    case ON::AngleUnitSystem::Radians:
      angle_unit_name.m_name = bPlural ? L"radians" : L"radian";
      break;
    case ON::AngleUnitSystem::Degrees:
      angle_unit_name.m_name = bPlural ? L"degrees" : L"degree";
      break;
    case ON::AngleUnitSystem::Minutes:
      angle_unit_name.m_name = bPlural ? L"minutes" : L"minute";
      break;
    case ON::AngleUnitSystem::Seconds:
      angle_unit_name.m_name = bPlural ? L"seconds" : L"second";
      break;
    case ON::AngleUnitSystem::Gradians:
      angle_unit_name.m_name = bPlural ? L"gradians" : L"gradian";
      break;
    case ON::AngleUnitSystem::Unset:
      break;
    }
  }
   
  return angle_unit_name;
}

int ON_AngleUnitName::Internal_Compare(
  unsigned int order_selector,
  const ON_AngleUnitName& a,
  const ON_AngleUnitName& b
)
{
  if (1 == order_selector)
  {
    // locale id is first
    if (a.m_locale_id < b.m_locale_id)
      return -1;
    if (a.m_locale_id > b.m_locale_id)
      return 1;
  }

  unsigned int i = static_cast<unsigned int>(a.m_angle_unit_system);
  unsigned int j = static_cast<unsigned int>(b.m_angle_unit_system);
  if (i < j)
    return -1;
  if (i > j)
    return 1;

  if (1 != order_selector)
  {
    // localed id is second
    if (a.m_locale_id < b.m_locale_id)
      return -1;
    if (a.m_locale_id > b.m_locale_id)
      return 1;
  }

  i =  ON_wString::CompareOrdinal(a.m_name,b.m_name,false);
  if (i != 0)
  {
    // ignore case order is prefered
    j = ON_wString::CompareOrdinal(a.m_name, b.m_name, true);
    return (0 != j) ? j : i;
  }

  i = a.m_bNameIsSingular ? 1 : 0;
  j = b.m_bNameIsSingular ? 1 : 0;
  if (i < j)
    return -1;
  if (i > j)
    return 1;

  i = a.m_bNameIsPlural ? 1 : 0;
  j = b.m_bNameIsPlural ? 1 : 0;
  if (i < j)
    return -1;
  if (i > j)
    return 1;

  return 0;
}

int ON_AngleUnitName::CompareUnitSystemLocaleIdName(
  const ON_AngleUnitName& a,
  const ON_AngleUnitName& b
)
{
  return Internal_Compare(0, a, b);
}

int ON_AngleUnitName::CompareLocaleIdUnitSystemName(
  const ON_AngleUnitName& a,
  const ON_AngleUnitName& b
)
{
  return Internal_Compare(1, a, b);
}


unsigned int ON_AngleUnitName::LocaleId() const
{
  return m_locale_id;
}

ON::AngleUnitSystem ON_AngleUnitName::AngleUnit() const
{
  return m_angle_unit_system;
}

bool ON_AngleUnitName::AngleUnitAndNameAreSet() const
{
  return AngleUnitIsSet() && AngleUnitNameIsNotEmpty();
}

bool ON_AngleUnitName::AngleUnitIsSet() const
{
  return (ON::AngleUnitSystem::Unset != m_angle_unit_system && ON::AngleUnitSystem::None != m_angle_unit_system);
}

bool ON_AngleUnitName::AngleUnitIsSetOrNone() const
{
  return (ON::AngleUnitSystem::Unset != m_angle_unit_system);
}

const wchar_t* ON_AngleUnitName::AngleUnitName() const
{
  return (nullptr == m_name) ? static_cast<const wchar_t*>(ON_wString::EmptyString) : m_name;
}

bool ON_AngleUnitName::AngleUnitNameIsSingular() const
{
  return m_bNameIsSingular;
}

bool ON_AngleUnitName::AngleUnitNameIsPlural() const
{
  return m_bNameIsPlural;
}

bool ON_AngleUnitName::AngleUnitNameIsEmpty() const
{
  return (nullptr == m_name || 0 == m_name[0]);
}

bool ON_AngleUnitName::AngleUnitNameIsNotEmpty() const
{
  return (nullptr != m_name && 0 != m_name[0]);
}

bool operator==(
  const ON_AngleUnitName& a,
  const ON_AngleUnitName& b
  )
{
  return (0 == ON_AngleUnitName::CompareUnitSystemLocaleIdName(a, b));
}

bool operator!=(
  const ON_AngleUnitName& a,
  const ON_AngleUnitName& b
  )
{
  return (0 != ON_AngleUnitName::CompareUnitSystemLocaleIdName(a, b));
}
