Пример #1
0
		private IcuRulesCollationDefinition ReadCollationRulesForCustomIcu(XElement collationElem, WritingSystemDefinition ws, string collationType)
		{
			var icd = new IcuRulesCollationDefinition(collationType) {WritingSystemFactory = _writingSystemFactory, OwningWritingSystemDefinition = ws};
			icd.Imports.AddRange(collationElem.NonAltElements("import").Select(ie => new IcuCollationImport((string) ie.Attribute("source"), (string) ie.Attribute("type"))));
			icd.IcuRules = LdmlCollationParser.GetIcuRulesFromCollationNode(collationElem);
			return icd;
		}
Пример #2
0
		/// <summary>
		/// Update element based on the writing system model.  At the end, write the contents to LDML
		/// </summary>
		/// <param name="writer"></param>
		/// <param name="element"></param>
		/// <param name="ws"></param>
		private void WriteLdml(XmlWriter writer, XElement element, WritingSystemDefinition ws)
		{
			Debug.Assert(element != null);
			Debug.Assert(ws != null);

			XElement identityElem = element.GetOrCreateElement("identity");
			WriteIdentityElement(identityElem, ws);
			RemoveIfEmpty(ref identityElem);

			XElement charactersElem = element.GetOrCreateElement("characters");
			WriteCharactersElement(charactersElem, ws);
			RemoveIfEmpty(ref charactersElem);

			XElement delimitersElem = element.GetOrCreateElement("delimiters");
			WriteDelimitersElement(delimitersElem, ws);
			RemoveIfEmpty(ref delimitersElem);

			XElement layoutElem = element.GetOrCreateElement("layout");
			WriteLayoutElement(layoutElem, ws);
			RemoveIfEmpty(ref layoutElem);

			XElement numbersElem = element.GetOrCreateElement("numbers");
			WriteNumbersElement(numbersElem, ws);
			RemoveIfEmpty(ref numbersElem);

			XElement collationsElem = element.GetOrCreateElement("collations");
			WriteCollationsElement(collationsElem, ws);
			RemoveIfEmpty(ref collationsElem);

			// Can have multiple specials.  Find the one with SIL namespace and external-resources.
			// Also handle case where we create special because writingsystem has entries to write
			XElement specialElem = element.NonAltElements("special").FirstOrDefault(
				e => !string.IsNullOrEmpty((string) e.Attribute(XNamespace.Xmlns+"sil")) && e.NonAltElement(Sil + "external-resources") != null);
			if (specialElem == null && (ws.Fonts.Count > 0 || ws.KnownKeyboards.Count > 0 || ws.SpellCheckDictionaries.Count > 0))
			{
				// Create special element
				specialElem = GetOrCreateSpecialElement(element);
			}
			if (specialElem != null)
			{
				WriteTopLevelSpecialElements(specialElem, ws);
				RemoveIfEmpty(ref specialElem);
			}

			element.WriteTo(writer);
		}
Пример #3
0
		// Numbering system gets added to the character set definition
		private void ReadNumbersElement(XElement numbersElem, WritingSystemDefinition ws)
		{
			XElement defaultNumberingSystemElem = numbersElem.NonAltElement("defaultNumberingSystem");
			if (defaultNumberingSystemElem != null)
			{
				var id = (string) defaultNumberingSystemElem;
				XElement numberingSystemsElem = numbersElem.NonAltElements("numberingSystem")
					.FirstOrDefault(e => id == (string) e.Attribute("id") && (string) e.Attribute("type") == "numeric");
				if (numberingSystemsElem != null)
				{
					var csd = new CharacterSetDefinition("numeric");
					// Only handle numeric types
					var digits = (string) numberingSystemsElem.Attribute("digits");
					foreach (char charItem in digits)
						csd.Characters.Add(charItem.ToString(CultureInfo.InvariantCulture));
					ws.CharacterSets.Add(csd);
				}
			}
		}
Пример #4
0
		private void ReadCollationsElement(XElement collationsElem, WritingSystemDefinition ws)
		{
			XElement defaultCollationElem = collationsElem.Element("defaultCollation");
			ws.DefaultCollationType = (string) defaultCollationElem;
			foreach (XElement collationElem in collationsElem.NonAltElements("collation"))
				ReadCollationElement(collationElem, ws);
		}
Пример #5
0
		private void ReadKeyboardElement(XElement externalResourcesElem, WritingSystemDefinition ws)
		{
			foreach (XElement kbdElem in externalResourcesElem.NonAltElements(Sil + "kbd"))
			{
				var id = (string) kbdElem.Attribute("id");
				if (!string.IsNullOrEmpty(id))
				{
					KeyboardFormat format = KeyboardToKeyboardFormat[(string) kbdElem.Attribute("type") ?? string.Empty];
					IKeyboardDefinition keyboard = Keyboard.Controller.CreateKeyboard(id, format, kbdElem.NonAltElements(Sil + "url").Select(u => (string) u));
					ws.KnownKeyboards.Add(keyboard);
				}
			}
		}
Пример #6
0
		private void ReadCharactersElement(XElement charactersElem, WritingSystemDefinition ws)
		{
			foreach (XElement exemplarCharactersElem in charactersElem.NonAltElements("exemplarCharacters"))
				ReadExemplarCharactersElem(exemplarCharactersElem, ws);

			XElement specialElem = charactersElem.NonAltElement("special");
			if (specialElem != null)
			{
				foreach (XElement exemplarCharactersElem in specialElem.NonAltElements(Sil + "exemplarCharacters"))
				{
					// Sil:exemplarCharacters are required to have a type
					if (!string.IsNullOrEmpty((string) exemplarCharactersElem.Attribute("type")))
						ReadExemplarCharactersElem(exemplarCharactersElem, ws);
				}
			}
		}
Пример #7
0
		private void ReadFontElement(XElement externalResourcesElem, WritingSystemDefinition ws)
		{
			foreach (XElement fontElem in externalResourcesElem.NonAltElements(Sil + "font"))
			{
				var fontName = (string) fontElem.Attribute("name");
				if (!string.IsNullOrEmpty(fontName))
				{
					var fd = new FontDefinition(fontName);

					// Types (space separate list)
					var roles = (string) fontElem.Attribute("types");
					if (!String.IsNullOrEmpty(roles))
					{
						IEnumerable<string> roleList = roles.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
						foreach (string roleEntry in roleList)
						{
							fd.Roles |= RoleToFontRoles[roleEntry];
						}
					}
					else
					{
						fd.Roles = FontRoles.Default;
					}

					// Relative Size
					fd.RelativeSize = (float?) fontElem.Attribute("size") ?? 1.0f;

					// Minversion
					fd.MinVersion = (string) fontElem.Attribute("minversion");

					// Features (space separated list of key=value pairs)
					fd.Features = (string) fontElem.Attribute("features");

					// Language
					fd.Language = (string) fontElem.Attribute("lang");

					// OpenType language
					fd.OpenTypeLanguage = (string) fontElem.Attribute("otlang");

					// Font Engine (space separated list) supercedes legacy isGraphite flag
					// If attribute is missing it is assumed to be "gr ot"
					var engines = (string) fontElem.Attribute("engines") ?? "gr ot";
					IEnumerable<string> engineList = engines.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
					foreach (string engineEntry in engineList)
						fd.Engines |= (EngineToFontEngines[engineEntry]);

					// Subset
					fd.Subset = (string) fontElem.Attribute("subset");

					// URL elements
					foreach (XElement urlElem in fontElem.NonAltElements(Sil + "url"))
						fd.Urls.Add(urlElem.Value);

					ws.Fonts.Add(fd);
				}
			}
		}
Пример #8
0
		private void WriteNumbersElement(XElement numbersElem, WritingSystemDefinition ws)
		{
			Debug.Assert(numbersElem != null);
			Debug.Assert(ws != null);

			// Save off defaultNumberingSystem if it exists.
			string defaultNumberingSystem = "standard";
			XElement defaultNumberingSystemElem = numbersElem.NonAltElement("defaultNumberingSystem");
			if (defaultNumberingSystemElem != null && !string.IsNullOrEmpty((string) defaultNumberingSystemElem))
				defaultNumberingSystem = (string) defaultNumberingSystemElem;
			// Remove defaultNumberingSystem and numberingSystems elements of type "numeric" to repopulate later
			numbersElem.NonAltElements("defaultNumberingSystem").Remove();
			numbersElem.NonAltElements("numberingSystem").Where(e => (string) e.Attribute("id") == defaultNumberingSystem && (string) e.Attribute("type") == "numeric" && e.Attribute("alt") == null).Remove();

			CharacterSetDefinition csd;
			if (ws.CharacterSets.TryGet("numeric", out csd))
			{
				// Create defaultNumberingSystem element and add as the first child
				if (defaultNumberingSystemElem == null)
				{
					defaultNumberingSystemElem = new XElement("defaultNumberingSystem", defaultNumberingSystem);
					numbersElem.AddFirst(defaultNumberingSystemElem);
				}

				// Populate numbering system element
				var numberingSystemsElem = new XElement("numberingSystem");
				numberingSystemsElem.SetAttributeValue("id", defaultNumberingSystem);
				numberingSystemsElem.SetAttributeValue("type", csd.Type);
				string digits = string.Join("", csd.Characters);
				numberingSystemsElem.SetAttributeValue("digits", digits);
				numbersElem.Add(numberingSystemsElem);
			}
		}
Пример #9
0
		private void WriteKeyboardElement(XElement externalResourcesElem, WritingSystemDefinition ws)
		{
			Debug.Assert(externalResourcesElem != null);
			Debug.Assert(ws != null);
			
			// Remove sil:kbd elements to repopulate later
			externalResourcesElem.NonAltElements(Sil + "kbd").Remove();

			// Don't include unknown system keyboard definitions
			foreach (IKeyboardDefinition keyboard in ws.KnownKeyboards.Where(kbd=>kbd.Format != KeyboardFormat.Unknown))
			{
				var kbdElem = new XElement(Sil + "kbd");
				// id required
				kbdElem.SetAttributeValue("id", keyboard.Id);
				if (!string.IsNullOrEmpty(keyboard.Id))
				{
					kbdElem.SetAttributeValue("type", KeyboardFormatToKeyboard[keyboard.Format]);
					foreach (var url in keyboard.Urls)
					{
						var urlElem = new XElement(Sil + "url", url);
						kbdElem.Add(urlElem);
					}
				}
				externalResourcesElem.Add(kbdElem);
			}
		}
Пример #10
0
		/// <summary>
		/// Read the LDML file at the root element and populate the writing system.
		/// </summary>
		/// <param name="element">Root element</param>
		/// <param name="ws">Writing System to populate</param>
		/// <remarks>
		/// For elements that have * annotation in the LDML spec, we use the extensions NonAltElement
		/// and NonAltElements to ignore the elements that have the alt attribute.
		/// </remarks>
		private void ReadLdml(XElement element, WritingSystemDefinition ws)
		{
			Debug.Assert(element != null);
			Debug.Assert(ws != null);
			if (element.Name != "ldml")
				throw new ApplicationException("Unable to load writing system definition: Missing <ldml> tag.");

			ResetWritingSystemDefinition(ws);

			XElement identityElem = element.Element("identity");
			if (identityElem != null)
				ReadIdentityElement(identityElem, ws);

			// Check for the proper LDML version after reading identity element so that we have the proper language tag if an error occurs.
			foreach (XElement specialElem in element.NonAltElements("special"))
				CheckVersion(specialElem, ws);

			XElement charactersElem = element.Element("characters");
			if (charactersElem != null)
				ReadCharactersElement(charactersElem, ws);

			XElement delimitersElem = element.Element("delimiters");
			if (delimitersElem != null)
				ReadDelimitersElement(delimitersElem, ws);

			XElement layoutElem = element.Element("layout");
			if (layoutElem != null)
				ReadLayoutElement(layoutElem, ws);

			XElement numbersElem = element.Element("numbers");
			if (numbersElem != null)
				ReadNumbersElement(numbersElem, ws);

			XElement collationsElem = element.Element("collations");
			if (collationsElem != null)
				ReadCollationsElement(collationsElem, ws);

			foreach (XElement specialElem in element.NonAltElements("special"))
				ReadTopLevelSpecialElement(specialElem, ws);

			// Validate collations after all of them have been read in (for self-referencing imports)
			foreach (CollationDefinition cd in ws.Collations)
			{
				string message;
				cd.Validate(out message);
			}
			ws.Id = string.Empty;
			ws.AcceptChanges();
		}
Пример #11
0
		private void WriteSpellcheckElement(XElement externalResourcesElem, WritingSystemDefinition ws)
		{
			Debug.Assert(externalResourcesElem != null);
			Debug.Assert(ws != null);

			// Remove sil:spellcheck elements to repopulate later
			externalResourcesElem.NonAltElements(Sil + "spellcheck").Remove();
			foreach (SpellCheckDictionaryDefinition scd in ws.SpellCheckDictionaries)
			{
				var scElem = new XElement(Sil + "spellcheck");
				scElem.SetAttributeValue("type", SpellCheckDictionaryFormatsToSpellCheck[scd.Format]);

				// URL elements
				foreach (var url in scd.Urls)
				{
					var urlElem  = new XElement(Sil + "url", url);
					scElem.Add(urlElem);
				}
				externalResourcesElem.Add(scElem);
			}
		}
Пример #12
0
		private void WriteFontElement(XElement externalResourcesElem, WritingSystemDefinition ws)
		{
			Debug.Assert(externalResourcesElem != null);
			Debug.Assert(ws != null);

			// Remove sil:font elements to repopulate later
			externalResourcesElem.NonAltElements(Sil + "font").Remove();
			foreach (FontDefinition font in ws.Fonts)
			{
				var fontElem = new XElement(Sil + "font");
				fontElem.SetAttributeValue("name", font.Name);

				// Generate space-separated list of font roles
				if (font.Roles != FontRoles.Default)
				{
					var fontRoleList = new List<string>();
					foreach (FontRoles fontRole in Enum.GetValues(typeof(FontRoles)))
					{
						if ((font.Roles & fontRole) != 0)
							fontRoleList.Add(FontRolesToRole[fontRole]);
					}
					fontElem.SetAttributeValue("types", string.Join(" ", fontRoleList));
				}

				if (font.RelativeSize != 1.0f)
					fontElem.SetAttributeValue("size", font.RelativeSize);

				fontElem.SetOptionalAttributeValue("minversion", font.MinVersion);
				fontElem.SetOptionalAttributeValue("features", font.Features);
				fontElem.SetOptionalAttributeValue("lang", font.Language);
				fontElem.SetOptionalAttributeValue("otlang", font.OpenTypeLanguage);
				fontElem.SetOptionalAttributeValue("subset", font.Subset);

				// Generate space-separated list of font engines
				if (font.Engines != (FontEngines.Graphite | FontEngines.OpenType))
				{
					var fontEngineList = new List<string>();
					foreach (FontEngines fontEngine in Enum.GetValues(typeof (FontEngines)))
					{
						if ((font.Engines & fontEngine) != 0)
							fontEngineList.Add(FontEnginesToEngine[fontEngine]);
					}
					fontElem.SetAttributeValue("engines", fontEngineList.Count == 0 ? null : string.Join(" ", fontEngineList));
				}
				foreach (var url in font.Urls)
					fontElem.Add(new XElement(Sil + "url", url));

				externalResourcesElem.Add(fontElem);
			}
		}
Пример #13
0
		private void WriteCollationsElement(XElement collationsElem, WritingSystemDefinition ws)
		{
			// Preserve exisiting collations since we don't process them all
			// Remove only the collations we can repopulate from the writing system
			collationsElem.NonAltElements("collation").Where(ce => ce.NonAltElements("special").Elements().All(se => se.Name != (Sil + "reordered"))).Remove();

			// if there will be no collation elements, don't write out defaultCollation element
			if (!collationsElem.Elements("collation").Any() && ws.Collations.All(c => c is SystemCollationDefinition))
				return;

			XElement defaultCollationElem = collationsElem.GetOrCreateElement("defaultCollation");
			defaultCollationElem.SetValue(ws.DefaultCollationType);
			
			foreach (CollationDefinition collation in ws.Collations)
				WriteCollationElement(collationsElem, collation);
		}
Пример #14
0
		private void WriteIdentityElement(XElement identityElem, WritingSystemDefinition ws)
		{
			Debug.Assert(identityElem != null);
			Debug.Assert(ws != null);

			// Remove non-special elements to repopulate later
			// Preserve special because we don't recreate all its contents
			identityElem.NonAltElements().Where(e => e.Name != "special").Remove();

			// Version is required.  If VersionNumber is blank, the empty attribute is still written
			XElement versionElem = identityElem.GetOrCreateElement("version");
			versionElem.SetAttributeValue("number", ws.VersionNumber);
			if (!string.IsNullOrEmpty(ws.VersionDescription))
				versionElem.SetValue(ws.VersionDescription);

			// Write generation date with UTC so no more ambiguity on timezone
			identityElem.SetAttributeValue("generation", "date", ws.DateModified.ToISO8601TimeFormatWithUTCString());
			WriteLanguageTagElements(identityElem, ws.LanguageTag);

			// Create special element if data needs to be written
			int index = IetfLanguageTag.GetIndexOfFirstNonCommonPrivateUseVariant(ws.Variants);
			string variantName = string.Empty;
			if (index != -1)
				variantName = ws.Variants[index].Name;
			if (!string.IsNullOrEmpty(ws.WindowsLcid) || !string.IsNullOrEmpty(ws.DefaultRegion) || !string.IsNullOrEmpty(variantName))
			{
				XElement specialElem = GetOrCreateSpecialElement(identityElem);
				XElement silIdentityElem = specialElem.GetOrCreateElement(Sil + "identity");

				// uid and revid attributes are left intact

				silIdentityElem.SetOptionalAttributeValue("windowsLCID", ws.WindowsLcid);
				silIdentityElem.SetOptionalAttributeValue("defaultRegion", ws.DefaultRegion);
				silIdentityElem.SetOptionalAttributeValue("variantName", variantName);
					
				// Move special to the end of the identity block (preserving order)
				specialElem.Remove();
				identityElem.Add(specialElem);
			}
		}
Пример #15
0
		private void ReadSpellcheckElement(XElement externalResourcesElem, WritingSystemDefinition ws)
		{
			foreach (XElement scElem in externalResourcesElem.NonAltElements(Sil + "spellcheck"))
			{
				var type = (string) scElem.Attribute("type");
				if (!string.IsNullOrEmpty(type))
				{
					var scd = new SpellCheckDictionaryDefinition(SpellCheckToSpecllCheckDictionaryFormats[type]);

					// URL elements
					foreach (XElement urlElem in scElem.NonAltElements(Sil + "url"))
						scd.Urls.Add(urlElem.Value);
					ws.SpellCheckDictionaries.Add(scd);
				}
			}
		}
Пример #16
0
		private void WriteCharactersElement(XElement charactersElem, WritingSystemDefinition ws)
		{
			Debug.Assert(charactersElem != null);
			Debug.Assert(ws != null);

			// Remove exemplarCharacters and special sil:exemplarCharacters elements to repopulate later
			charactersElem.NonAltElements("exemplarCharacters").Remove();
			XElement specialElem = charactersElem.NonAltElement("special");
			if (specialElem != null)
			{
				specialElem.NonAltElements(Sil + "exemplarCharacters").Remove();
				RemoveIfEmpty(ref specialElem);
			}

			foreach (CharacterSetDefinition csd in ws.CharacterSets)
			{
				XElement exemplarCharactersElem;
				switch (csd.Type)
				{
					// These character sets go to the normal LDML exemplarCharacters space
					// http://unicode.org/reports/tr35/tr35-general.html#Exemplars
					case "main":
					case "auxiliary":
					case "index":
					case "punctuation":
						exemplarCharactersElem = new XElement("exemplarCharacters", UnicodeSet.ToPattern(csd.Characters));
						// Assume main set doesn't have an attribute type
						if (csd.Type != "main")
							exemplarCharactersElem.SetAttributeValue("type", csd.Type);
						charactersElem.Add(exemplarCharactersElem);
						break;
					// Numeric characters will be written in the numbers element
					case "numeric":
						break;
					// All others go to special Sil:exemplarCharacters
					default:
						string unicodeSet = csd.Type == "footnotes" ? string.Format("[{0}]", string.Join(" ", csd.Characters.Select(c => c.Length > 1 ? string.Format("{{{0}}}", c) : c)))
							: UnicodeSet.ToPattern(csd.Characters);
						exemplarCharactersElem = new XElement(Sil + "exemplarCharacters", unicodeSet);
						exemplarCharactersElem.SetAttributeValue("type", csd.Type);
						specialElem = GetOrCreateSpecialElement(charactersElem);
						specialElem.Add(exemplarCharactersElem);
						break;
				}
			}
		}
Пример #17
0
		private void WriteDelimitersElement(XElement delimitersElem, WritingSystemDefinition ws)
		{
			Debug.Assert(delimitersElem != null);
			Debug.Assert(ws != null);

			// Remove non-special elements to repopulate later
			delimitersElem.NonAltElements().Where(e => e.Name != "special").Remove();

			// Level 1 normal => quotationStart and quotationEnd
			QuotationMark qm1 = ws.QuotationMarks.FirstOrDefault(q => q.Level == 1 && q.Type == QuotationMarkingSystemType.Normal);
			if (qm1 != null)
			{
				var quotationStartElem = new XElement("quotationStart", qm1.Open);
				var quotationEndElem = new XElement("quotationEnd", qm1.Close);
				delimitersElem.Add(quotationStartElem);
				delimitersElem.Add(quotationEndElem);
			}
			// Level 2 normal => alternateQuotationStart and alternateQuotationEnd
			QuotationMark qm2 = ws.QuotationMarks.FirstOrDefault(q => q.Level == 2 && q.Type == QuotationMarkingSystemType.Normal);
			if (qm2 != null)
			{
				var alternateQuotationStartElem = new XElement("alternateQuotationStart", qm2.Open);
				var alternateQuotationEndElem = new XElement("alternateQuotationEnd", qm2.Close);
				delimitersElem.Add(alternateQuotationStartElem);
				delimitersElem.Add(alternateQuotationEndElem);
			}

			// Remove special Sil:matched-pairs elements to repopulate later
			XElement specialElem = delimitersElem.NonAltElement("special");
			XElement matchedPairsElem;
			if (specialElem != null)
			{
				matchedPairsElem = specialElem.NonAltElement(Sil + "matched-pairs");
				if (matchedPairsElem != null)
				{
					matchedPairsElem.NonAltElements(Sil + "matched-pair").Remove();
					RemoveIfEmpty(ref matchedPairsElem);
				}
				RemoveIfEmpty(ref specialElem);
			}
			foreach (var mp in ws.MatchedPairs)
			{
				var matchedPairElem = new XElement(Sil + "matched-pair");
				// open and close are required
				matchedPairElem.SetAttributeValue("open", mp.Open);
				matchedPairElem.SetAttributeValue("close", mp.Close);
				matchedPairElem.SetAttributeValue("paraClose", mp.ParagraphClose); // optional, default to false?
				specialElem = GetOrCreateSpecialElement(delimitersElem);
				matchedPairsElem = specialElem.GetOrCreateElement(Sil + "matched-pairs");
				matchedPairsElem.Add(matchedPairElem);
			}

			// Remove special Sil:punctuation-patterns elements to repopulate later
			XElement punctuationPatternsElem;
			if (specialElem != null)
			{
				punctuationPatternsElem = specialElem.NonAltElement(Sil + "punctuation-patterns");
				if (punctuationPatternsElem != null)
				{
					punctuationPatternsElem.NonAltElements(Sil + "punctuation-pattern").Remove();
					RemoveIfEmpty(ref punctuationPatternsElem);
				}
				RemoveIfEmpty(ref specialElem);
			}
			foreach (var pp in ws.PunctuationPatterns)
			{
				var punctuationPatternElem = new XElement(Sil + "punctuation-pattern");
				// text is required
				punctuationPatternElem.SetAttributeValue("pattern", pp.Pattern);
				punctuationPatternElem.SetAttributeValue("context", PunctuationPatternContextToContext[pp.Context]);
				specialElem = GetOrCreateSpecialElement(delimitersElem);
				punctuationPatternsElem = specialElem.GetOrCreateElement(Sil + "punctuation-patterns");
				punctuationPatternsElem.Add(punctuationPatternElem);
			}

			// Remove sil:quotation elements where type is blank or narrative. Also remove quotation continue elements.
			// These will be repopulated later
			XElement quotationmarksElem = null;
			if (specialElem != null)
			{
				quotationmarksElem = specialElem.NonAltElement(Sil + "quotation-marks");
				if (quotationmarksElem != null)
				{
					quotationmarksElem.NonAltElements(Sil + "quotation").Where(e => string.IsNullOrEmpty((string) e.Attribute("type"))).Remove();
					quotationmarksElem.NonAltElements(Sil + "quotation").Where(e => (string) e.Attribute("type") == "narrative").Remove();
					quotationmarksElem.NonAltElements(Sil + "quotationContinue").Remove();
					quotationmarksElem.NonAltElements(Sil + "alternateQuotationContinue").Remove();
					RemoveIfEmpty(ref quotationmarksElem);
				}
				RemoveIfEmpty(ref specialElem);
			}

			if (qm1 != null)
			{
				var level1ContinuerElem = new XElement(Sil + "quotationContinue", qm1.Continue);
				specialElem = GetOrCreateSpecialElement(delimitersElem);
				quotationmarksElem = specialElem.GetOrCreateElement(Sil + "quotation-marks");
				quotationmarksElem.Add(level1ContinuerElem);
			}
			if (qm2 != null && !string.IsNullOrEmpty(qm2.Continue))
			{
				var level2ContinuerElem = new XElement(Sil + "alternateQuotationContinue", qm2.Continue);
				specialElem = GetOrCreateSpecialElement(delimitersElem);
				quotationmarksElem = specialElem.GetOrCreateElement(Sil + "quotation-marks");
				quotationmarksElem.Add(level2ContinuerElem);
			}

			foreach (QuotationMark qm in ws.QuotationMarks)
			{
				// Level 1 and 2 normal have already been written
				if (!((qm.Level == 1 || qm.Level == 2) && qm.Type == QuotationMarkingSystemType.Normal))
				{
					var quotationElem = new XElement(Sil + "quotation");
					// open and level required
					quotationElem.SetAttributeValue("open", qm.Open);
					quotationElem.SetOptionalAttributeValue("close", qm.Close);
					quotationElem.SetOptionalAttributeValue("continue", qm.Continue);
					quotationElem.SetAttributeValue("level", qm.Level);
					// normal quotation mark can have no attribute defined.  Narrative --> "narrative"
					quotationElem.SetOptionalAttributeValue("type", QuotationMarkingSystemTypesToQuotation[qm.Type]);

					specialElem = GetOrCreateSpecialElement(delimitersElem);
					quotationmarksElem = specialElem.GetOrCreateElement(Sil + "quotation-marks");
					quotationmarksElem.Add(quotationElem);
				}
			}
			if ((ws.QuotationParagraphContinueType != QuotationParagraphContinueType.None) && (quotationmarksElem != null))
			{
				quotationmarksElem.SetAttributeValue("paraContinueType",
					QuotationParagraphContinueTypesToQuotation[ws.QuotationParagraphContinueType]);
			}
		}