/// ------------------------------------------------------------------------------------
        /// <summary>
        /// Perform one increment migration step.
        ///
        /// In this case, the migration is not related to a model change,
        /// but is a simple data change that removes the class elements in the xml.
        /// The end resujlt xml will have the top-level 'rt' element and zero, or more,
        /// property level elements.
        /// </summary>
        /// ------------------------------------------------------------------------------------
        public void PerformMigration(IDomainObjectDTORepository domainObjectDtoRepository)
        {
            DataMigrationServices.CheckVersionNumber(domainObjectDtoRepository, 7000014);

            // No. We need to convert all instances, even if they are no longer part of the model.
            // DM19, for example, removes LgWritingSystem instances and the class form the model,
            // but it tries to access the data as if it had been properly processed by DM15,
            // *but* this code would leave out LgWritingSystem instnaces form being processed here.
            //foreach (var dto in domainObjectDtoRepository.AllInstancesWithSubclasses("CmObject"))
            foreach (var dto in domainObjectDtoRepository.AllInstances())
            {
                var rtElement = XElement.Parse(dto.Xml);
                // Removes all current child nodes (class level),
                // and replaces them with the old property nodes (if any).
#if !__MonoCS__
                rtElement.ReplaceNodes(rtElement.Elements().Elements());
                dto.Xml = rtElement.ToString();
#else // FWNX-165: work around mono bug https://bugzilla.novell.com/show_bug.cgi?id=592435
                var copy = new XElement(rtElement);
                copy.ReplaceNodes(rtElement.Elements().Elements());
                dto.Xml = copy.ToString();
#endif
                domainObjectDtoRepository.Update(dto);
            }

            DataMigrationServices.IncrementVersionNumber(domainObjectDtoRepository);
        }
		/// ------------------------------------------------------------------------------------
		/// <summary>
		/// Perform one increment migration step.
		///
		/// In this case, the migration is not related to a model change,
		/// but is a simple data change that removes the class elements in the xml.
		/// The end resujlt xml will have the top-level 'rt' element and zero, or more,
		/// property level elements.
		/// </summary>
		/// ------------------------------------------------------------------------------------
		public void PerformMigration(IDomainObjectDTORepository domainObjectDtoRepository)
		{
			DataMigrationServices.CheckVersionNumber(domainObjectDtoRepository, 7000014);

			// No. We need to convert all instances, even if they are no longer part of the model.
			// DM19, for example, removes LgWritingSystem instances and the class form the model,
			// but it tries to access the data as if it had been properly processed by DM15,
			// *but* this code would leave out LgWritingSystem instnaces form being processed here.
			//foreach (var dto in domainObjectDtoRepository.AllInstancesWithSubclasses("CmObject"))
			foreach (var dto in domainObjectDtoRepository.AllInstances())
			{
				var rtElement = XElement.Parse(dto.Xml);
				// Removes all current child nodes (class level),
				// and replaces them with the old property nodes (if any).
#if !__MonoCS__
				rtElement.ReplaceNodes(rtElement.Elements().Elements());
				dto.Xml = rtElement.ToString();
#else // FWNX-165: work around mono bug https://bugzilla.novell.com/show_bug.cgi?id=592435
				var copy = new XElement(rtElement);
				copy.ReplaceNodes(rtElement.Elements().Elements());
				dto.Xml = copy.ToString();
#endif
				domainObjectDtoRepository.Update(dto);
			}

			DataMigrationServices.IncrementVersionNumber(domainObjectDtoRepository);
		}
Example #3
0
        /// ------------------------------------------------------------------------------------
        /// <summary>
        /// Cleans up legacy ChkRendering objects with null SurfaceForms.
        /// </summary>
        /// <param name="repoDto">
        /// 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 repoDto)
        {
            DataMigrationServices.CheckVersionNumber(repoDto, 7000046);

            Dictionary <Guid, DomainObjectDTO> mapOfRenderingsToChk = new Dictionary <Guid, DomainObjectDTO>();
            HashSet <DomainObjectDTO>          renderingsToDelete   = new HashSet <DomainObjectDTO>();

            foreach (DomainObjectDTO dto in repoDto.AllInstances())
            {
                XElement   data      = XElement.Parse(dto.Xml);
                XAttribute classAttr = data.Attribute("class");
                if (classAttr.Value == "ChkTerm")
                {
                    XElement renderings = data.Element("Renderings");
                    if (renderings != null)
                    {
                        foreach (XElement r in renderings.Elements())
                        {
                            mapOfRenderingsToChk[new Guid(r.Attribute("guid").Value)] = dto;
                        }
                    }
                }
                else if (classAttr.Value == "ChkRendering")
                {
                    XElement surfaceForm = data.Element("SurfaceForm");
                    if (surfaceForm == null || !surfaceForm.HasElements)
                    {
                        renderingsToDelete.Add(dto);
                    }
                }
            }

            foreach (DomainObjectDTO rendering in renderingsToDelete)
            {
                DomainObjectDTO chkTerm        = mapOfRenderingsToChk[new Guid(rendering.Guid)];
                XElement        termData       = XElement.Parse(chkTerm.Xml);
                XElement        renderings     = termData.Element("Renderings");
                XElement        bogusRendering = renderings.Elements().First(e => e.Attribute("guid").Value.Equals(rendering.Guid, StringComparison.OrdinalIgnoreCase));
                bogusRendering.Remove();
                DataMigrationServices.UpdateDTO(repoDto, chkTerm, termData.ToString());
                repoDto.Remove(rendering);
            }

            DataMigrationServices.IncrementVersionNumber(repoDto);
        }
		/// <summary>
		/// Remove all attributes from the "Uni"> element.
		/// </summary>
		/// <param name="domainObjectDtoRepository"></param>
		public void PerformMigration(IDomainObjectDTORepository domainObjectDtoRepository)
		{
			DataMigrationServices.CheckVersionNumber(domainObjectDtoRepository, 7000066);

			foreach (var dto in domainObjectDtoRepository.AllInstances())
			{
				var element = XElement.Parse(dto.Xml);
				var uniElementsWithAttrs = element.Elements().Elements("Uni").Where(uniElement => uniElement.HasAttributes).ToList();
				if (uniElementsWithAttrs.Count == 0)
					continue;
				foreach (var uniElementWithAttrs in uniElementsWithAttrs)
				{
					uniElementWithAttrs.Attributes().Remove();
					dto.Xml = element.ToString();
					domainObjectDtoRepository.Update(dto);
				}
			}

			DataMigrationServices.IncrementVersionNumber(domainObjectDtoRepository);
		}
        /// <summary>
        /// Remove all attributes from the "Uni"> element.
        /// </summary>
        /// <param name="domainObjectDtoRepository"></param>
        public void PerformMigration(IDomainObjectDTORepository domainObjectDtoRepository)
        {
            DataMigrationServices.CheckVersionNumber(domainObjectDtoRepository, 7000066);

            foreach (var dto in domainObjectDtoRepository.AllInstances())
            {
                var element = XElement.Parse(dto.Xml);
                var uniElementsWithAttrs = element.Elements().Elements("Uni").Where(uniElement => uniElement.HasAttributes).ToList();
                if (uniElementsWithAttrs.Count == 0)
                {
                    continue;
                }
                foreach (var uniElementWithAttrs in uniElementsWithAttrs)
                {
                    uniElementWithAttrs.Attributes().Remove();
                    dto.Xml = element.ToString();
                    domainObjectDtoRepository.Update(dto);
                }
            }

            DataMigrationServices.IncrementVersionNumber(domainObjectDtoRepository);
        }
		/// ------------------------------------------------------------------------------------
		/// <summary>
		/// Cleans up legacy ChkRendering objects with null SurfaceForms.
		/// </summary>
		/// <param name="repoDto">
		/// 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 repoDto)
		{
			DataMigrationServices.CheckVersionNumber(repoDto, 7000046);

			Dictionary<Guid, DomainObjectDTO> mapOfRenderingsToChk = new Dictionary<Guid, DomainObjectDTO>();
			HashSet<DomainObjectDTO> renderingsToDelete = new HashSet<DomainObjectDTO>();

			foreach (DomainObjectDTO dto in repoDto.AllInstances())
			{
				XElement data = XElement.Parse(dto.Xml);
				XAttribute classAttr = data.Attribute("class");
				if (classAttr.Value == "ChkTerm")
				{
					XElement renderings = data.Element("Renderings");
					if (renderings != null)
						foreach (XElement r in renderings.Elements())
							mapOfRenderingsToChk[new Guid(r.Attribute("guid").Value)] = dto;
				}
				else if (classAttr.Value == "ChkRendering")
				{
					XElement surfaceForm = data.Element("SurfaceForm");
					if (surfaceForm == null || !surfaceForm.HasElements)
						renderingsToDelete.Add(dto);
				}
			}

			foreach (DomainObjectDTO rendering in renderingsToDelete)
			{
				DomainObjectDTO chkTerm = mapOfRenderingsToChk[new Guid(rendering.Guid)];
				XElement termData = XElement.Parse(chkTerm.Xml);
				XElement renderings = termData.Element("Renderings");
				XElement bogusRendering = renderings.Elements().First(e => e.Attribute("guid").Value.Equals(rendering.Guid, StringComparison.OrdinalIgnoreCase));
				bogusRendering.Remove();
				DataMigrationServices.UpdateDTO(repoDto, chkTerm, termData.ToString());
				repoDto.Remove(rendering);
			}

			DataMigrationServices.IncrementVersionNumber(repoDto);
		}
		/// <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);
		}
Example #8
0
        // We update every instance of an AUni, AStr, Run, or WsProp element that has a ws attribute.
        // Also the value of every top-level WritingSystem element that has a Uni child
        // Finally several ws-list properties of langProject.
        // AUni, ASTr, and Run are very common; WsProp and WritingSystem are relatively rare. So there's some
        // inefficiency in checking for them everywhere. I'm guess it won't add all that much overhead, and
        // it simplifies the code and testing.
        public void Migrate()
        {
            foreach (DomainObjectDTO dto in m_repoDto.AllInstances())
            {
                var      changed          = false;
                XElement data             = XElement.Parse(dto.Xml);
                var      elementsToRemove = new List <XElement>();
                foreach (XElement elt in data.XPathSelectElements("//*[name()='AUni' or name()='AStr' or name()='Run' or name()='WsProp' or name()='Prop']"))
                {
                    if ((elt.Name == "AUni" || elt.Name == "AStr") && string.IsNullOrEmpty(elt.Value))
                    {
                        changed = true;
                        elementsToRemove.Add(elt);                         // don't remove right away, messes up the iteration.
                        continue;
                    }
                    XAttribute attr = elt.Attribute("ws");
                    if (attr == null)
                    {
                        continue;                         // pathological, but let's try to survive
                    }
                    string oldTag = attr.Value;
                    string newTag;
                    if (TryGetNewTag(oldTag, out newTag))
                    {
                        changed    = true;
                        attr.Value = newTag;
                    }
                }
                foreach (XElement elt in elementsToRemove)
                {
                    elt.Remove();
                }
                var wsElt = data.Element("WritingSystem");
                if (wsElt != null)
                {
                    var uniElt = wsElt.Element("Uni");
                    if (uniElt != null)
                    {
                        string newTag1;
                        if (TryGetNewTag(uniElt.Value, out newTag1))
                        {
                            changed      = true;
                            uniElt.Value = newTag1;
                        }
                    }
                }
                XElement residueElt = data.Element("LiftResidue");
                if (residueElt != null)
                {
                    bool changedResidue = false;
                    var  uniElt         = residueElt.Element("Uni");
                    if (uniElt != null)
                    {
                        // We may have more than one root element which .Parse can't handle. LT-11856, LT-11698.
                        XElement contentElt = XElement.Parse("<x>" + uniElt.Value + "</x>");
                        foreach (XElement elt in contentElt.XPathSelectElements("//*[@lang]"))
                        {
                            var attr = elt.Attribute("lang");
                            if (attr == null)
                            {
                                continue;                                 // pathological, but let's try to survive
                            }
                            var    oldTag = attr.Value;
                            string newTag;
                            if (TryGetNewTag(oldTag, out newTag))
                            {
                                changedResidue = true;
                                attr.Value     = newTag;
                            }
                        }
                        if (changedResidue)
                        {
                            changed      = true;
                            uniElt.Value = "";
                            foreach (var node in contentElt.Nodes())
                            {
                                uniElt.Value += node.ToString();
                            }
                        }
                    }
                }
                if (changed)
                {
                    DataMigrationServices.UpdateDTO(m_repoDto, dto, data.ToString());
                }
            }
            DomainObjectDTO langProjDto = m_repoDto.AllInstancesSansSubclasses("LangProject").First();
            XElement        langProj    = XElement.Parse(langProjDto.Xml);
            bool            lpChanged   = UpdateAttr(langProj, "AnalysisWss");

            lpChanged |= UpdateAttr(langProj, "CurVernWss");
            lpChanged |= UpdateAttr(langProj, "CurAnalysisWss");
            lpChanged |= UpdateAttr(langProj, "CurPronunWss");
            lpChanged |= UpdateAttr(langProj, "VernWss");
            if (lpChanged)
            {
                DataMigrationServices.UpdateDTO(m_repoDto, langProjDto, langProj.ToString());
            }
            string settingsFolder = Path.Combine(m_repoDto.ProjectFolder, LcmFileHelper.ksConfigurationSettingsDir);

            if (Directory.Exists(settingsFolder))
            {
                foreach (string layoutFile in Directory.GetFiles(settingsFolder, m_layoutFilePattern))
                {
                    XElement layout      = XElement.Parse(File.ReadAllText(layoutFile, Encoding.UTF8));
                    bool     changedFile = false;
                    foreach (XElement elt in layout.XPathSelectElements("//*[@ws]"))
                    {
                        changedFile |= FixWSAtttribute(elt.Attribute("ws"));
                    }
                    foreach (XElement elt in layout.XPathSelectElements("//*[@visibleWritingSystems]"))
                    {
                        changedFile |= FixWSAtttribute(elt.Attribute("visibleWritingSystems"));
                    }
                    if (changedFile)
                    {
                        using (var xmlWriter = XmlWriter.Create(layoutFile, new XmlWriterSettings {
                            Encoding = Encoding.UTF8
                        }))
                            layout.WriteTo(xmlWriter);
                    }
                }
                string localSettingsPath = Path.Combine(settingsFolder, "db$local$Settings.xml");
                if (File.Exists(localSettingsPath))
                {
                    XElement settings         = XElement.Parse(File.ReadAllText(localSettingsPath, Encoding.UTF8));
                    bool     changedFile      = false;
                    var      namesAndPatterns = new Dictionary <string, string>();
                    // Each item in this dictionary should be a property name that occurs in the <name> attribute of a <Property> element
                    // in the db$local$Settings.xml file, mapping to a regular expression that will pick out the writing system tag
                    // in the corresponding <value> element in the property table. Each regex must have a 'target' group which matches the
                    // writing system.
                    // Looking in a list like this, we want number followed by % followed by ws code,5062001%,5062001%x-kal,5112002%,5112002%x-kal,
                    namesAndPatterns["db$local$InterlinConfig_Edit_Interlinearizer"] = ",[0-9]+%(?'target'[^,]+)";
                    // Here we expect to find something like  ws="x-kal"
                    namesAndPatterns["db$local$LexDb.Entries_sorter"] = "ws=\"(?'target'[^\"]+)\"";
                    // The value of this one simply IS a writing system.
                    namesAndPatterns["db$local$ConcordanceWs"] = "^(?'target'.*)$";
                    foreach (XElement elt in settings.Elements("Property"))
                    {
                        XElement nameElt = elt.Element("name");
                        if (nameElt == null)
                        {
                            continue;
                        }
                        string pattern;
                        string propName = nameElt.Value;
                        if (namesAndPatterns.TryGetValue(propName, out pattern))
                        {
                            ReplaceWSIdInValue(elt, pattern, ref changedFile);
                        }
                        else if (propName.EndsWith("_sorter") || propName.EndsWith("_filter") || propName.EndsWith("_ColumnList"))
                        {
                            ReplaceWSIdInValue(elt, "ws=\"(?'target'[^\"]+)\"", ref changedFile);
                        }
                    }
                    if (changedFile)
                    {
                        using (var xmlWriter = XmlWriter.Create(localSettingsPath, new XmlWriterSettings {
                            Encoding = Encoding.UTF8
                        }))
                            settings.WriteTo(xmlWriter);
                    }
                }
            }
        }
		// We update every instance of an AUni, AStr, Run, or WsProp element that has a ws attribute.
		// Also the value of every top-level WritingSystem element that has a Uni child
		// Finally several ws-list properties of langProject.
		// AUni, ASTr, and Run are very common; WsProp and WritingSystem are relatively rare. So there's some
		// inefficiency in checking for them everywhere. I'm guess it won't add all that much overhead, and
		// it simplifies the code and testing.
		private void UpdateTags(IDomainObjectDTORepository repoDto)
		{
			foreach (var dto in repoDto.AllInstances())
			{
				var changed = false;
				XElement data = XElement.Parse(dto.Xml);
				var elementsToRemove = new List<XElement>();
				foreach (var elt in data.XPathSelectElements("//*[name()='AUni' or name()='AStr' or name()='Run' or name()='WsProp' or name()='Prop']"))
				{
					if ((elt.Name == "AUni" || elt.Name == "AStr") && string.IsNullOrEmpty(elt.Value))
					{
						changed = true;
						elementsToRemove.Add(elt); // don't remove right away, messes up the iteration.
						continue;
					}
					var attr = elt.Attribute("ws");
					if (attr == null)
						continue; // pathological, but let's try to survive
					var oldTag = attr.Value;
					string newTag;
					if (TryGetNewTag(oldTag, out newTag))
					{
						changed = true;
						attr.Value = newTag;
					}
				}
				foreach (var elt in elementsToRemove)
					elt.Remove();
				var wsElt = data.Element("WritingSystem");
				if (wsElt != null)
				{
					var uniElt = wsElt.Element("Uni");
					if (uniElt != null)
					{
						string newTag1;
						if (TryGetNewTag(uniElt.Value, out newTag1))
						{
							changed = true;
							uniElt.Value = newTag1;
						}
					}
				}
				var residueElt = data.Element("LiftResidue");
				if (residueElt != null)
				{
					bool changedResidue = false;
					var uniElt = residueElt.Element("Uni");
					if (uniElt != null)
					{
						// We may have more than one root element which .Parse can't handle. LT-11856, LT-11698.
						var contentElt = XElement.Parse("<x>" + uniElt.Value + "</x>");
						foreach (var elt in contentElt.XPathSelectElements("//*[@lang]"))
						{
							var attr = elt.Attribute("lang");
							if (attr == null)
								continue; // pathological, but let's try to survive
							var oldTag = attr.Value;
							string newTag;
							if (TryGetNewTag(oldTag, out newTag))
							{
								changedResidue = true;
								attr.Value = newTag;
							}
						}
						if (changedResidue)
						{
							changed = true;
							uniElt.Value = "";
							foreach (var node in contentElt.Nodes())
								uniElt.Value += node.ToString();
						}
					}
				}
				if (changed)
				{
					DataMigrationServices.UpdateDTO(repoDto, dto, data.ToString());
				}
			}
			var langProjDto = repoDto.AllInstancesSansSubclasses("LangProject").First();
			var langProj = XElement.Parse(langProjDto.Xml);
			bool lpChanged = UpdateAttr(langProj, "AnalysisWss");
			lpChanged |= UpdateAttr(langProj, "CurVernWss");
			lpChanged |= UpdateAttr(langProj, "CurAnalysisWss");
			lpChanged |= UpdateAttr(langProj, "CurPronunWss");
			lpChanged |= UpdateAttr(langProj, "VernWss");
			if (lpChanged)
				DataMigrationServices.UpdateDTO(repoDto, langProjDto, langProj.ToString());
			var settingsFolder = Path.Combine(repoDto.ProjectFolder, FdoFileHelper.ksConfigurationSettingsDir);
			if (Directory.Exists(settingsFolder))
			{
				m_tagMap["$wsname"] = "$wsname"; // should never be changed.
				foreach (var layoutFile in Directory.GetFiles(settingsFolder, "*_Layouts.xml"))
				{
					var layout = XElement.Parse(File.ReadAllText(layoutFile, Encoding.UTF8));
					bool changedFile = false;
					foreach (var elt in layout.XPathSelectElements("//*[@ws]"))
					{
						changedFile |= FixWsAtttribute(elt.Attribute("ws"));
					}
					foreach (var elt in layout.XPathSelectElements("//*[@visibleWritingSystems]"))
					{
						changedFile |= FixWsAtttribute(elt.Attribute("visibleWritingSystems"));
					}
					if (changedFile)
					{
						using (var xmlWriter = XmlWriter.Create(layoutFile, new XmlWriterSettings() { Encoding = Encoding.UTF8 }))
							layout.WriteTo(xmlWriter);
					}
				}
				var localSettingsPath = Path.Combine(settingsFolder, "db$local$Settings.xml");
				if (File.Exists(localSettingsPath))
				{
					var settings = XElement.Parse(File.ReadAllText(localSettingsPath, Encoding.UTF8));
					bool changedFile = false;
					var namesAndPatterns = new Dictionary<string, string>();
					// Each item in this dictionary should be a property name that occurs in the <name> attribute of a <Property> element
					// in the db$local$Settings.xml file, mapping to a regular expression that will pick out the writing system tag
					// in the corresponding <value> element in the property table. Each regex must have a 'target' group which matches the
					// writing system.
					// Looking in a list like this, we want number followed by % followed by ws code,5062001%,5062001%x-kal,5112002%,5112002%x-kal,
					namesAndPatterns["db$local$InterlinConfig_Edit_Interlinearizer"] = ",[0-9]+%(?'target'[^,]+)";
					// Here we expect to find something like  ws="x-kal"
					namesAndPatterns["db$local$LexDb.Entries_sorter"] = "ws=\"(?'target'[^\"]+)\"";
					// The value of this one simply IS a writing system.
					namesAndPatterns["db$local$ConcordanceWs"] = "^(?'target'.*)$";
					foreach (var elt in settings.Elements("Property"))
					{
						var nameElt = elt.Element("name");
						if (nameElt == null)
							continue;
						string pattern;
						var propName = nameElt.Value;
						if (namesAndPatterns.TryGetValue(propName, out pattern))
							ReplaceWsIdInValue(elt, pattern, ref changedFile);
						else if (propName.EndsWith("_sorter") || propName.EndsWith("_filter") || propName.EndsWith("_ColumnList"))
							ReplaceWsIdInValue(elt, "ws=\"(?'target'[^\"]+)\"", ref changedFile);
					}
					if (changedFile)
					{
						using (var xmlWriter = XmlWriter.Create(localSettingsPath, new XmlWriterSettings() { Encoding = Encoding.UTF8 }))
							settings.WriteTo(xmlWriter);
					}
				}
			}
		}