/// <summary>
		/// Perform one increment migration step.
		/// </summary>
		/// <param name="domainObjectDtoRepository">Repository of all CmObject DTOs available for one migration step.</param>
		/// <remarks>
		/// The method must add/remove/update the DTOs to the repository,
		/// as it adds/removes objects as part of it work.
		/// Implementors of this interface should ensure the Repository's
		/// starting model version number is correct for the step.
		/// Implementors must also increment the Repository's model version number
		/// at the end of its migration work.
		/// The method also should normally modify the xml string(s)
		/// of relevant DTOs, since that string will be used by the main
		/// data migration calling client (ie. BEP).
		/// </remarks>
		public void PerformMigration(IDomainObjectDTORepository domainObjectDtoRepository)
		{
			DataMigrationServices.CheckVersionNumber(domainObjectDtoRepository, 7000018);

			// collect all writing system info
			var guidToWsInfo = new Dictionary<string, Tuple<string, DomainObjectDTO, XElement>>();
			foreach (DomainObjectDTO wsDto in domainObjectDtoRepository.AllInstancesSansSubclasses("LgWritingSystem").ToArray())
			{
				XElement wsElem = XElement.Parse(wsDto.Xml);
				XElement icuLocaleElem = wsElem.Element("ICULocale");
				var icuLocale = icuLocaleElem.Element("Uni").Value;
				string langTag = Version19LangTagUtils.ToLangTag(icuLocale);
				guidToWsInfo[wsDto.Guid.ToLowerInvariant()] = Tuple.Create(langTag, wsDto, wsElem);
			}

			// remove all CmSortSpec objects
			foreach (DomainObjectDTO sortSpecDto in domainObjectDtoRepository.AllInstancesSansSubclasses("CmSortSpec").ToArray())
				domainObjectDtoRepository.Remove(sortSpecDto);

			// remove SortSpecs property from LangProject
			DomainObjectDTO lpDto = domainObjectDtoRepository.AllInstancesSansSubclasses("LangProject").First();
			XElement lpElem = XElement.Parse(lpDto.Xml);
			XElement sortSpecsElem = lpElem.Element("SortSpecs");
			bool lpModified = false;
			if (sortSpecsElem != null)
			{
				sortSpecsElem.Remove();
				lpModified = true;
			}

			var referencedWsIds = new HashSet<string>();

			// convert all LangProject writing system references to strings
			if (ConvertRefToString(lpElem.Element("AnalysisWss"), guidToWsInfo, referencedWsIds))
				lpModified = true;
			if (ConvertRefToString(lpElem.Element("VernWss"), guidToWsInfo, referencedWsIds))
				lpModified = true;
			if (ConvertRefToString(lpElem.Element("CurAnalysisWss"), guidToWsInfo, referencedWsIds))
				lpModified = true;
			if (ConvertRefToString(lpElem.Element("CurPronunWss"), guidToWsInfo, referencedWsIds))
				lpModified = true;
			if (ConvertRefToString(lpElem.Element("CurVernWss"), guidToWsInfo, referencedWsIds))
				lpModified = true;
			if (lpModified)
				DataMigrationServices.UpdateDTO(domainObjectDtoRepository, lpDto, lpElem.ToString());

			// convert all ReversalIndex writing system references to strings
			ConvertAllRefsToStrings(domainObjectDtoRepository, "ReversalIndex", guidToWsInfo, referencedWsIds);

			// convert all WordformLookupList writing system references to strings
			ConvertAllRefsToStrings(domainObjectDtoRepository, "WordformLookupList", guidToWsInfo, referencedWsIds);

			// convert all CmPossibilityList writing system references to strings
			ConvertAllRefsToStrings(domainObjectDtoRepository, "CmPossibilityList", guidToWsInfo, referencedWsIds);

			// convert all UserViewField writing system references to strings
			ConvertAllRefsToStrings(domainObjectDtoRepository, "UserViewField", guidToWsInfo, referencedWsIds);

			// convert all CmBaseAnnotation writing system references to strings
			ConvertAllRefsToStrings(domainObjectDtoRepository, "CmBaseAnnotation", guidToWsInfo, referencedWsIds);

			// convert all FsOpenFeature writing system references to strings
			ConvertAllRefsToStrings(domainObjectDtoRepository, "FsOpenFeature", guidToWsInfo, referencedWsIds);

			// convert all ScrMarkerMapping ICU locales to lang tags
			ConvertAllIcuLocalesToLangTags(domainObjectDtoRepository, "ScrMarkerMapping", referencedWsIds);

			// convert all ScrImportSource ICU locales to lang tags
			ConvertAllIcuLocalesToLangTags(domainObjectDtoRepository, "ScrImportSource", referencedWsIds);

			// convert all ICU locales to Language Tags and remove legacy magic font names
			foreach (DomainObjectDTO dto in domainObjectDtoRepository.AllInstances())
				UpdateStringsAndProps(domainObjectDtoRepository, dto, referencedWsIds);

			var localStoreFolder = Path.Combine(domainObjectDtoRepository.ProjectFolder, FdoFileHelper.ksWritingSystemsDir);

			// If any writing systems that project needs don't already exist as LDML files,
			// create them, either by copying relevant data from a shipping LDML file, or by
			// extracting data from the obsolete writing system object's XML.
			if (!string.IsNullOrEmpty(domainObjectDtoRepository.ProjectFolder))
			{
				foreach (Tuple<string, DomainObjectDTO, XElement> wsInfo in guidToWsInfo.Values)
				{
					if (referencedWsIds.Contains(wsInfo.Item1))
					{
						var ws = new Version19WritingSystemDefn();
						var langTag = wsInfo.Item1;
						ws.LangTag = langTag;
						var ldmlFileName = Path.ChangeExtension(langTag, "ldml");
						string localPath = Path.Combine(localStoreFolder, ldmlFileName);
						if (File.Exists(localPath))
							continue; // already have one.
						string globalPath = Path.Combine(DirectoryFinder.GlobalWritingSystemStoreDirectory, ldmlFileName);
						if (File.Exists(globalPath))
							continue; // already have one.
						// Need to make one.

						// Code similar to this was in the old migrator (prior to 7000043). It does not work
						// because the XML files it is looking for are in the Languages subdirectory of the
						// FieldWorks 6 data directory, and this is looking in the FW 7 one. No one has complained
						// so we decided not to try to fix it for the new implementation of the migration.

						//string langDefPath = Path.Combine(FwDirectoryFinder.GetDataSubDirectory("Languages"),
						//    Path.ChangeExtension(langTag, "xml"));
						//if (File.Exists(langDefPath))
						//    FillWritingSystemFromLangDef(XElement.Load(langDefPath), ws);
						//else

						FillWritingSystemFromFDO(domainObjectDtoRepository, wsInfo.Item3, ws);
						ws.Save(localPath);
					}
				}
			}
			foreach (Tuple<string, DomainObjectDTO, XElement> wsInfo in guidToWsInfo.Values)
			{
				// this should also remove all LgCollations as well
				DataMigrationServices.RemoveIncludingOwnedObjects(domainObjectDtoRepository, wsInfo.Item2, false);
			}

			DataMigrationServices.IncrementVersionNumber(domainObjectDtoRepository);
		}
		// I think this is roughly what we need to reinstate if we reinstate the call to it. See comments there.
		//private static void FillWritingSystemFromLangDef(XElement langDefElem, Version19WritingSystemDefn ws)
		//{
		//    XElement wsElem = langDefElem.Element("LgWritingSystem");
		//    if (wsElem != null)
		//    {
		//        string name = GetMultiUnicode(wsElem, "Name24");
		//        if (!string.IsNullOrEmpty(name))
		//        {
		//            int parenIndex = name.IndexOf('(');
		//            if (parenIndex != -1)
		//                name = name.Substring(0, parenIndex).Trim();
		//            ws.LanguageName = name;
		//        }

		//        string abbr = GetMultiUnicode(wsElem, "Abbr24");
		//        if (!string.IsNullOrEmpty(abbr))
		//            ws.Abbreviation = abbr;
		//        XElement collsElem = wsElem.Element("Collations24");
		//        if (collsElem != null)
		//        {
		//            XElement collElem = collsElem.Element("LgCollation");
		//            if (collElem != null)
		//            {
		//                string icuRules = GetUnicode(collElem, "ICURules30");
		//                if (!string.IsNullOrEmpty(icuRules))
		//                {
		//                    ws.SortUsing = WritingSystemDefinition.SortRulesType.CustomICU;
		//                    ws.SortRules = icuRules;
		//                }
		//            }
		//        }
		//        string defFont = GetUnicode(wsElem, "DefaultSerif24");
		//        if (!string.IsNullOrEmpty(defFont))
		//            ws.DefaultFontName = defFont;
		//        string defFontFeats = GetUnicode(wsElem, "FontVariation24");
		//        if (!string.IsNullOrEmpty(defFontFeats))
		//            ws.DefaultFontFeatures = defFontFeats;
		//        string keyboard = GetUnicode(wsElem, "KeymanKeyboard24");
		//        if (!string.IsNullOrEmpty(keyboard))
		//            ws.Keyboard = keyboard;
		//        string legacyMapping = GetUnicode(wsElem, "LegacyMapping24");
		//        if (!string.IsNullOrEmpty(legacyMapping))
		//            ws.LegacyMapping = legacyMapping;
		//        XElement localeElem = wsElem.Element("Locale24");
		//        if (localeElem != null)
		//        {
		//            XElement intElem = localeElem.Element("Integer");
		//            if (intElem != null)
		//                ws.LCID = (int) intElem.Attribute("val");
		//        }
		//        string matchedPairs = GetUnicode(wsElem, "MatchedPairs24");
		//        if (!string.IsNullOrEmpty(matchedPairs))
		//            ws.MatchedPairs = matchedPairs;
		//        string punctPatterns = GetUnicode(wsElem, "PunctuationPatterns24");
		//        if (!string.IsNullOrEmpty(punctPatterns))
		//            ws.PunctuationPatterns = punctPatterns;
		//        string quotMarks = GetUnicode(wsElem, "QuotationMarks24");
		//        if (!string.IsNullOrEmpty(quotMarks))
		//            ws.QuotationMarks = quotMarks;
		//        XElement rtolElem = wsElem.Element("RightToLeft24");
		//        if (rtolElem != null)
		//        {
		//            XElement boolElem = rtolElem.Element("Boolean");
		//            if (boolElem != null)
		//                ws.RightToLeftScript = (bool) boolElem.Attribute("val");
		//        }
		//        string spellCheck = GetUnicode(wsElem, "SpellCheckDictionary24");
		//        if (!string.IsNullOrEmpty(spellCheck))
		//            ws.SpellCheckingId = spellCheck;
		//        string validChars = GetUnicode(wsElem, "ValidChars24");
		//        if (!string.IsNullOrEmpty(validChars))
		//            ws.ValidChars = Icu.Normalize(validChars, Icu.UNormalizationMode.UNORM_NFD);
		//    }

		//    var localeName = (string) langDefElem.Element("LocaleName");
		//    if (!string.IsNullOrEmpty(localeName))
		//    {
		//        ws.LanguageName = localeName;
		//    }
		//    var scriptName = (string)langDefElem.Element("LocaleScript");
		//    if (!string.IsNullOrEmpty(scriptName))
		//    {
		//        ws.ScriptName = scriptName;
		//    }
		//    var regionName = (string)langDefElem.Element("LocaleCountry");
		//    if (!string.IsNullOrEmpty(regionName))
		//    {
		//        ws.RegionName = regionName;
		//    }
		//    var variantName = (string) langDefElem.Element("LocaleVariant");
		//    if (!string.IsNullOrEmpty(variantName))
		//    {
		//            ws.VariantName = variantName;
		//    }
		//}

		private static void FillWritingSystemFromFDO(IDomainObjectDTORepository domainObjectDtoRepository,
			XElement wsElem, Version19WritingSystemDefn ws)
		{
			string name = GetMultiUnicode(wsElem, "Name");
			if (!string.IsNullOrEmpty(name))
			{
				int parenIndex = name.IndexOf('(');
				if (parenIndex != -1)
					name = name.Substring(0, parenIndex).Trim();
				ws.LanguageName = name;
			}
			string abbr = GetMultiUnicode(wsElem, "Abbr");
			if (!string.IsNullOrEmpty(abbr))
				ws.Abbreviation = abbr;
			string defFont = GetUnicode(wsElem, "DefaultSerif");
			if (!string.IsNullOrEmpty(defFont))
				ws.DefaultFontName = defFont;
			string defFontFeats = GetUnicode(wsElem, "FontVariation");
			if (!string.IsNullOrEmpty(defFontFeats))
				ws.DefaultFontFeatures = defFontFeats;
			string keyboard = GetUnicode(wsElem, "KeymanKeyboard");
			if (!string.IsNullOrEmpty(keyboard))
				ws.Keyboard = keyboard;
			string legacyMapping = GetUnicode(wsElem, "LegacyMapping");
			if (!string.IsNullOrEmpty(legacyMapping))
				ws.LegacyMapping = legacyMapping;
			XElement localeElem = wsElem.Element("Locale");
			if (localeElem != null)
				ws.LCID = (int) localeElem.Attribute("val");
			string matchedPairs = GetUnicode(wsElem, "MatchedPairs");
			if (!string.IsNullOrEmpty(matchedPairs))
				ws.MatchedPairs = matchedPairs;
			string puncPatterns = GetUnicode(wsElem, "PunctuationPatterns");
			if (!string.IsNullOrEmpty(puncPatterns))
				ws.PunctuationPatterns = puncPatterns;
			string quotMarks = GetUnicode(wsElem, "QuotationMarks");
			if (!string.IsNullOrEmpty(quotMarks))
				ws.QuotationMarks = quotMarks;
			XElement rtolElem = wsElem.Element("RightToLeft");
			if (rtolElem != null)
				ws.RightToLeftScript = (bool) rtolElem.Attribute("val");
			string spellCheck = GetUnicode(wsElem, "SpellCheckDictionary");
			if (!string.IsNullOrEmpty(spellCheck))
				ws.SpellCheckingId = spellCheck;
			string validChars = GetUnicode(wsElem, "ValidChars");
			if (!string.IsNullOrEmpty(validChars))
				ws.ValidChars = Icu.Normalize(validChars, Icu.UNormalizationMode.UNORM_NFD);

			XElement collsElem = wsElem.Element("Collations");
			if (collsElem != null)
			{
				XElement surElem = collsElem.Element("objsur");
				if (surElem != null)
				{
					var guid = (string) surElem.Attribute("guid");
					DomainObjectDTO collDto = domainObjectDtoRepository.GetDTO(guid);
					XElement collElem = XElement.Parse(collDto.Xml);
					string sortRules = GetUnicode(collElem, "ICURules");
					if (!string.IsNullOrEmpty(sortRules))
					{
						ws.SortUsing = WritingSystemDefinition.SortRulesType.CustomICU;
						ws.SortRules = sortRules;
					}
				}
			}
		}