/// <summary> /// Updates this XLIFF document with the source data from the given translatable document. /// </summary> /// <returns>True if any changes were made to this document.</returns> public bool Update(TranslatableDocument sourceDocument, string sourceDocumentId) { bool changed = false; var nodesById = new Dictionary <string, TranslatableNode>(); foreach (var node in sourceDocument.Nodes) { if (nodesById.ContainsKey(node.Id)) { throw new BuildErrorException($"The document '{sourceDocumentId}' has a duplicate node '{node.Id}'."); } nodesById.Add(node.Id, node); } XElement fileElement = _document.Root.Element(File); XAttribute originalAttribute = fileElement.Attribute("original"); if (originalAttribute.Value != sourceDocumentId) { // update original path in case where user has renaned source file and corresponding xlf originalAttribute.Value = sourceDocumentId; changed = true; } XElement bodyElement = fileElement.Element(Body); XElement groupElement = bodyElement.Element(Group); if (groupElement != null && !groupElement.Elements().Any()) { // remove unnecessary empty group added by older tool. We don't want to bother keeping that unnecessary id up-to-date. groupElement.Remove(); changed = true; } foreach (XElement unitElement in bodyElement.Descendants(TransUnit).ToList()) { string id = unitElement.GetId(); string state = unitElement.GetTargetState(); string source = unitElement.GetSourceValue(); string note = unitElement.GetNoteValue(); // delete node in document that has been removed from source. if (!nodesById.TryGetValue(id, out TranslatableNode sourceNode)) { unitElement.Remove(); changed = true; continue; } // update trans-unit state if either the source text or associated note has change. if (source != sourceNode.Source || (sourceNode.Note != null && note != sourceNode.Note)) { unitElement.SetSourceValue(sourceNode.Source); // if sourceNode.Note is null, it indicates that the source format can't have notes, in which case // they may be applied directly to the xlf by the user and we should not revert that on update if (sourceNode.Note != null) { unitElement.SetNoteValue(sourceNode.Note); } switch (state) { case "new": // when a new string gets modified before it has been translated, // update untranslated target to match the new source unitElement.SetTargetValue(sourceNode.Source); break; case "translated": // flag strings that have been modified after translation for review/re-translation unitElement.SetTargetState("needs-review-translation"); break; } changed = true; } // If the source and target require different numbers of formatting items then reset // the target string completely. This avoids problems when the source has been updated // to remove formatting items--when formatting the target string we won't have as many // replacement items as it calls for, leading to an exception. // And if the source string is updated to use _more_ items then formatting with the // target string is likely to produce misleading (or outright meaningless) text. In // either case we lose nothing by just reverting the string until it can be localized // again. // Note we don't limit this check to when the source has changed in the original // document because we also want to catch errors introduced during translation. var sourceReplacementCount = unitElement.GetSourceValue().GetReplacementCount(); var targetReplacementCount = unitElement.GetTargetValue().GetReplacementCount(); if (targetReplacementCount != sourceReplacementCount) { unitElement.SetTargetValue(sourceNode.Source); unitElement.SetTargetState("new"); changed = true; } // signal to loop below that this node is not new nodesById.Remove(id); } // Add new trans-units foreach (TranslatableNode sourceNode in sourceDocument.Nodes) { // Nodes that have been removed from nodesById table are not new and have already been handled. // Do not refactor this check away by iterating over dictionary values as the document order must be maintained deterministically. if (!nodesById.ContainsKey(sourceNode.Id)) { continue; } XElement newTransUnit = new XElement(TransUnit, new XAttribute("id", sourceNode.Id), new XElement(Source, sourceNode.Source), new XElement(Target, new XAttribute("state", "new"), sourceNode.Source), new XElement(Note, sourceNode.Note == "" ? null : sourceNode.Note)); bool inserted = false; foreach (var transUnit in bodyElement.Elements(TransUnit)) { if (StringComparer.Ordinal.Compare(newTransUnit.GetId(), transUnit.GetId()) < 0) { transUnit.AddBeforeSelf(newTransUnit); inserted = true; break; } } if (!inserted) { bodyElement.Add(newTransUnit); } changed = true; } return(changed); }
/// <summary> /// Updates this XLIFF document with the source data from the given translatable document. /// </summary> /// <returns>True if any changes were made to this document.</returns> public bool Update(TranslatableDocument sourceDocument, string sourceDocumentId) { bool changed = false; Dictionary <string, TranslatableNode> nodesById = sourceDocument.Nodes.ToDictionary(n => n.Id); XNamespace ns = _document.Root.Name.Namespace; XElement fileElement = _document.Root.Element(ns + "file"); XAttribute originalAttribute = fileElement.Attribute("original"); if (originalAttribute.Value != sourceDocumentId) { // update original path in case where user has renaned source file and corresponding xlf originalAttribute.Value = sourceDocumentId; changed = true; } XElement bodyElement = fileElement.Element(ns + "body"); XElement groupElement = bodyElement.Element(ns + "group"); if (groupElement != null && !groupElement.Elements().Any()) { // remove unnecessary empty group added by older tool. We don't want to bother keeping that unnecessary id up-to-date. groupElement.Remove(); changed = true; } foreach (XElement unitElement in bodyElement.Descendants(ns + "trans-unit").ToList()) { XElement sourceElement = unitElement.Element(ns + "source"); XElement targetElement = unitElement.Element(ns + "target"); XElement noteElement = unitElement.Element(ns + "note"); XAttribute idAttribute = unitElement.Attribute("id"); XAttribute stateAttribute = targetElement.Attribute("state"); string id = idAttribute.Value; string state = stateAttribute.Value; string source = sourceElement.Value; string note = noteElement.Value; // delete node in document that has been removed from source. if (!nodesById.TryGetValue(id, out TranslatableNode sourceNode)) { unitElement.Remove(); changed = true; continue; } // update trans-unit state if either the source text or associated note has change. if (source != sourceNode.Source || (sourceNode.Note != null && note != sourceNode.Note)) { sourceElement.Value = sourceNode.Source; // if sourceNode.Note is null, it indicates that the source format can't have notes, in which case // they may be applied directly to the xlf by the user and we should not revert that on update if (sourceNode.Note != null) { noteElement.Value = sourceNode.Note; noteElement.SelfCloseIfPossible(); } switch (state) { case "new": // when a new string gets modified before it has been translated, // update untranslated target to match the new source targetElement.Value = sourceNode.Source; break; case "translated": // flag strings that have been modified after translation for review/re-translation stateAttribute.Value = "needs-review-translation"; break; } changed = true; } // signal to loop below that this node is not new nodesById.Remove(id); } // Add new trans-units foreach (TranslatableNode sourceNode in sourceDocument.Nodes) { // Nodes that have been removed from nodesById table are not new and have already been handled. // Do not refactor this check away by iterating over dictionary values as the document order must be maintained deterministically. if (!nodesById.ContainsKey(sourceNode.Id)) { continue; } bodyElement.Add( new XElement(ns + "trans-unit", new XAttribute("id", sourceNode.Id), new XElement(ns + "source", sourceNode.Source), new XElement(ns + "target", new XAttribute("state", "new"), sourceNode.Source), new XElement(ns + "note", sourceNode.Note == "" ? null : sourceNode.Note))); changed = true; } return(changed); }