internal void Load(Stream stream) { var data = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); var readerSettings = new XmlReaderSettings() { DtdProcessing = DtdProcessing.Prohibit, IgnoreComments = true, IgnoreWhitespace = true }; using (var reader = XmlReader.Create(stream, readerSettings)) { var prefixStack = new Stack <string>(); SkipUntilRootElement(reader); // We process the root element individually since it doesn't contribute to prefix ProcessAttributes(reader, prefixStack, data, AddNamePrefix); ProcessAttributes(reader, prefixStack, data, AddAttributePair); var preNodeType = reader.NodeType; while (reader.Read()) { switch (reader.NodeType) { case XmlNodeType.Element: prefixStack.Push(reader.LocalName); ProcessAttributes(reader, prefixStack, data, AddNamePrefix); ProcessAttributes(reader, prefixStack, data, AddAttributePair); // If current element is self-closing if (reader.IsEmptyElement) { 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()); data[key] = string.Empty; } prefixStack.Pop(); } break; case XmlNodeType.CDATA: case XmlNodeType.Text: { var key = string.Join(Constants.KeyDelimiter, prefixStack.Reverse()); if (data.ContainsKey(key)) { throw new FormatException(Resources.FormatError_KeyIsDuplicated(key, GetLineInfo(reader))); } data[key] = reader.Value; break; } case XmlNodeType.XmlDeclaration: case XmlNodeType.ProcessingInstruction: case XmlNodeType.Comment: case XmlNodeType.Whitespace: // Ignore certain types of nodes break; default: throw new FormatException(Resources.FormatError_UnsupportedNodeType(reader.NodeType, GetLineInfo(reader))); } preNodeType = reader.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 && reader.IsEmptyElement) { preNodeType = XmlNodeType.EndElement; } } } ReplaceData(data); }
// 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)); } }