/// <summary> /// Serializes the <see cref="ITsString"/> to XML. /// </summary> public static string SerializeTsStringToXml(ITsString tss, ILgWritingSystemFactory lgwsf, int ws = 0, bool writeObjData = true, bool indent = false) { // We export only the NFSC form (NFC with exceptions for the parallel style information) ITsString normalizedTss = tss.get_NormalizedForm(FwNormalizationMode.knmNFSC); var xml = new StringBuilder(); var settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true, IndentChars = indent ? " " : string.Empty, NewLineChars = Environment.NewLine }; using (var writer = XmlWriter.Create(xml, settings)) { if (ws > 0) { string id = lgwsf.GetStrFromWs(ws); writer.WriteStartElement("AStr"); writer.WriteAttributeString("ws", Icu.Normalize(id, Icu.UNormalizationMode.UNORM_NFC)); } else { writer.WriteStartElement("Str"); } // Write the properties and text for each run string fieldName = null; for (int i = 0; i < normalizedTss.RunCount; i++) { TsRunInfo tri; ITsTextProps textProps = normalizedTss.FetchRunInfo(i, out tri); string objDataStr; if (textProps.TryGetStringValue(FwTextPropType.ktptObjData, out objDataStr) && !writeObjData) { var chType = (FwObjDataTypes)objDataStr[0]; if (chType == FwObjDataTypes.kodtPictEvenHot || chType == FwObjDataTypes.kodtPictOddHot || chType == FwObjDataTypes.kodtNameGuidHot || chType == FwObjDataTypes.kodtOwnNameGuidHot) { continue; } } string runFieldName; if (textProps.TryGetStringValue(FwTextPropType.ktptFieldName, out runFieldName) && fieldName != runFieldName) { if (!string.IsNullOrEmpty(fieldName)) { writer.WriteEndElement(); } if (!string.IsNullOrEmpty(runFieldName)) { writer.WriteStartElement("Field"); writer.WriteAttributeString("name", runFieldName); } fieldName = runFieldName; } bool markItem; FwTextPropVar var; int markItemValue; if (textProps.TryGetIntValue(FwTextPropType.ktptMarkItem, out var, out markItemValue) && var == FwTextPropVar.ktpvEnum && markItemValue == (int)FwTextToggleVal.kttvForceOn) { writer.WriteStartElement("Item"); writer.WriteStartElement("Run"); markItem = true; } else { writer.WriteStartElement("Run"); markItem = false; } for (int j = 0; j < textProps.IntPropCount; j++) { FwTextPropType tpt; int value = textProps.GetIntProperty(j, out tpt, out var); if (tpt != FwTextPropType.ktptMarkItem) { TsPropsSerializer.WriteIntProperty(writer, lgwsf, tpt, var, value); } } byte[] pict = null; bool hotGuid = false; for (int j = 0; j < textProps.StrPropCount; j++) { FwTextPropType tpt; string value = textProps.GetStringProperty(j, out tpt); TsPropsSerializer.WriteStringProperty(writer, tpt, value); if (tpt == FwTextPropType.ktptObjData && !string.IsNullOrEmpty(value)) { switch ((FwObjDataTypes)value[0]) { // The element data associated with a picture is the actual picture data // since it is much too large to want embedded as an XML attribute value. // (This is an antique kludge that isn't really used in practice, but some // of our test data still exercises it.) case FwObjDataTypes.kodtPictEvenHot: case FwObjDataTypes.kodtPictOddHot: pict = Encoding.Unicode.GetBytes(value.Substring(1)); break; // The generated XML contains both the link value as an attribute and the // (possibly edited) display string as the run's element data. case FwObjDataTypes.kodtExternalPathName: break; // used ONLY in the clipboard...contains XML representation of (currently) a footnote. case FwObjDataTypes.kodtEmbeddedObjectData: break; // The string data associated with this run is assumed to be a dummy magic // character that flags (redundantly for XML) that the actual data to // display is based on the ktptObjData attribute. case FwObjDataTypes.kodtNameGuidHot: case FwObjDataTypes.kodtOwnNameGuidHot: case FwObjDataTypes.kodtContextString: case FwObjDataTypes.kodtGuidMoveableObjDisp: hotGuid = true; break; } } } if (pict != null) { // Write the bytes of the picture data var sb = new StringBuilder(); for (int j = 0; j < pict.Length; j++) { sb.Append(pict[j].ToString("X2")); if (j % 32 == 31) { sb.AppendLine(); } } writer.WriteString(sb.ToString()); } else if (hotGuid) { writer.WriteString(string.Empty); } else { string runText = normalizedTss.get_RunText(i) ?? string.Empty; if (runText != string.Empty && runText.All(char.IsWhiteSpace)) { writer.WriteAttributeString("xml", "space", "", "preserve"); } // TODO: should we escape quotation marks? this is not necessary but different than the behavior of the C++ implementation writer.WriteString(Icu.Normalize(runText, Icu.UNormalizationMode.UNORM_NFC)); } writer.WriteEndElement(); if (markItem) { writer.WriteEndElement(); } } if (!string.IsNullOrEmpty(fieldName)) { writer.WriteEndElement(); } writer.WriteEndElement(); } return(xml.ToString()); }