public void CommitOperationThrowsExceptionWhenKeysAreMissingInConfigFile() { var xml = @"<?xml version=""1.0"" encoding=""UTF-8""?> <?xml-stylesheet type=""text/xsl"" href=""style1.xsl""?> <settings> <?xml-stylesheet type=""text/xsl"" href=""style2.xsl""?> <Data> <DefaultConnection> <ConnectionString>TestConnectionString</ConnectionString> <Provider>SqlClient</Provider> </DefaultConnection> <Inventory> <ConnectionString>AnotherTestConnectionString</ConnectionString> <Provider>MySql</Provider> </Inventory> </Data> </settings>"; var modifiedXml = @"<?xml version=""1.0"" encoding=""UTF-8""?> <?xml-stylesheet type=""text/xsl"" href=""style1.xsl""?> <settings> <?xml-stylesheet type=""text/xsl"" href=""style2.xsl""?> <Data> <DefaultConnection> <ConnectionString>TestConnectionString</ConnectionString> </DefaultConnection> <Inventory> <Provider>MySql</Provider> </Inventory> </Data> </settings>"; var xmlConfigSrc = new XmlConfigurationSource(ArbitraryFilePath); var outputCacheStream = new MemoryStream(); xmlConfigSrc.Load(StringToStream(xml)); var exception = Assert.Throws <InvalidOperationException>( () => xmlConfigSrc.Commit(StringToStream(modifiedXml), outputCacheStream)); Assert.Equal( Resources. FormatError_CommitWhenKeyMissing("Data:DefaultConnection:Provider, Data:Inventory:ConnectionString"), exception.Message); }
// Use the original file as a template while generating new file contents // to make sure the format is consistent and comments are not lost internal void Commit(Stream inputStream, Stream outputStream) { var dataCopy = new Dictionary <string, string>(Data, StringComparer.OrdinalIgnoreCase); var writerSettings = new XmlWriterSettings() { Indent = false, ConformanceLevel = ConformanceLevel.Auto }; var outputWriter = XmlWriter.Create(outputStream, writerSettings); var readerSettings = new XmlReaderSettings() { DtdProcessing = DtdProcessing.Prohibit, IgnoreWhitespace = false, IgnoreComments = false, IgnoreProcessingInstructions = false }; using (var inputReader = XmlReader.Create(inputStream, readerSettings)) { var prefixStack = new Stack <string>(); CopyUntilRootElement(inputReader, outputWriter); // We process the root element individually since it doesn't contribute to prefix outputWriter.WriteStartElement(inputReader.LocalName); ProcessAttributes(inputReader, prefixStack, dataCopy, AddNamePrefix); ProcessAttributes(inputReader, prefixStack, dataCopy, CommitAttributePair, outputWriter); var preNodeType = inputReader.NodeType; while (inputReader.Read()) { switch (inputReader.NodeType) { case XmlNodeType.Element: prefixStack.Push(inputReader.LocalName); outputWriter.WriteStartElement(inputReader.LocalName); ProcessAttributes(inputReader, prefixStack, dataCopy, AddNamePrefix); ProcessAttributes(inputReader, prefixStack, dataCopy, CommitAttributePair, outputWriter); // If current element is self-closing if (inputReader.IsEmptyElement) { outputWriter.WriteEndElement(); prefixStack.Pop(); } break; case XmlNodeType.EndElement: if (prefixStack.Any()) { // If this EndElement node comes right after an Element node, // it means there is no text/CDATA node in current element if (preNodeType == XmlNodeType.Element) { var key = string.Join(Constants.KeyDelimiter, prefixStack.Reverse()); if (!dataCopy.ContainsKey(key)) { throw new InvalidOperationException(Resources.FormatError_CommitWhenNewKeyFound(key)); } outputWriter.WriteValue(dataCopy[key]); dataCopy.Remove(key); } outputWriter.WriteFullEndElement(); prefixStack.Pop(); } break; case XmlNodeType.CDATA: case XmlNodeType.Text: { var key = string.Join(Constants.KeyDelimiter, prefixStack.Reverse()); if (!dataCopy.ContainsKey(key)) { throw new InvalidOperationException(Resources.FormatError_CommitWhenNewKeyFound(key)); } if (inputReader.NodeType == XmlNodeType.CDATA) { outputWriter.WriteCData(dataCopy[key]); } else { outputWriter.WriteValue(dataCopy[key]); } dataCopy.Remove(key); break; } case XmlNodeType.ProcessingInstruction: outputWriter.WriteProcessingInstruction(inputReader.LocalName, inputReader.Value); break; case XmlNodeType.Comment: outputWriter.WriteComment(inputReader.Value); break; case XmlNodeType.Whitespace: outputWriter.WriteWhitespace(inputReader.Value); break; default: throw new FormatException(Resources.FormatError_UnsupportedNodeType(inputReader.NodeType, GetLineInfo(inputReader))); } preNodeType = inputReader.NodeType; // If this element is a self-closing element, // we pretend that we just processed an EndElement node // because a self-closing element contains an end within itself if (preNodeType == XmlNodeType.Element && inputReader.IsEmptyElement) { preNodeType = XmlNodeType.EndElement; } } // Close the root element outputWriter.WriteEndElement(); outputWriter.Flush(); } if (dataCopy.Any()) { var missingKeys = string.Join(", ", dataCopy.Keys); throw new InvalidOperationException(Resources.FormatError_CommitWhenKeyMissing(missingKeys)); } }