using System; using System.Text; using System.IO; using System.Collections.Generic; namespace FastReport.Utils { /// /// Represents a xml property. /// #if READONLY_STRUCTS public readonly struct XmlProperty #else public struct XmlProperty #endif { private readonly string key; private readonly string value; /// /// Represents a property key. /// public string Key { get { return key; } } /// /// Represents a property value. /// public string Value { get { return value; } } private XmlProperty(string key, string value) { this.key = key; this.value = value; } /// /// Creates new property and assigns value /// /// Property key /// Property value public static XmlProperty Create(string key, string value) { return new XmlProperty(key, value); } } /// /// Represents a xml node. /// public class XmlItem : IDisposable { private List items; private XmlItem parent; private string name; private string value; private XmlProperty[] properties; /// /// Gets a number of children in this node. /// public int Count { get { return items == null ? 0 : items.Count; } } /// /// Gets a list of children in this node. /// public List Items { get { if (items == null) items = new List(); return items; } } /// /// Gets a child node with specified index. /// /// Index of node. /// The node with specified index. public XmlItem this[int index] { get { return Items[index]; } } /// /// Gets or sets the node name. /// /// /// This property will return "Node" for a node like <Node Text="" Left="0"/> /// public string Name { get { return name; } set { name = value; } } /// /// Gets or sets a list of properties in this node. /// public XmlProperty[] Properties { get { if (properties == null) properties = new XmlProperty[0]; return properties; } set { properties = value; } } /// /// Gets or sets the parent for this node. /// public XmlItem Parent { get { return parent; } set { if (parent != value) { if (parent != null) parent.Items.Remove(this); if (value != null) value.Items.Add(this); } parent = value; } } /// /// Gets or sets the node value. /// /// /// This property will return "ABC" for a node like <Node>ABC</Node> /// public string Value { get { return value; } set { this.value = value; } } /// /// Gets the root node which owns this node. /// public XmlItem Root { get { XmlItem result = this; while (result.Parent != null) { result = result.Parent; } return result; } } /// /// Clears the child nodes of this node. /// public void Clear() { if (items != null) { items.Clear(); /* while (Items.Count > 0) { Items[0].Dispose(); } */ items = null; } } /// /// Adds a new child node to this node. /// /// The new child node. public XmlItem Add() { XmlItem result = new XmlItem(); AddItem(result); return result; } /// /// Adds a specified node to this node. /// /// The node to add. public void AddItem(XmlItem item) { item.Parent = this; } /// /// Inserts a specified node to this node. /// /// Position to insert. /// Node to insert. public void InsertItem(int index, XmlItem item) { AddItem(item); Items.RemoveAt(Count - 1); Items.Insert(index, item); } /// /// Finds the node with specified name. /// /// The name of node to find. /// The node with specified name, if found; null otherwise. public int Find(string name) { for (int i = 0; i < Count; i++) { if (String.Compare(Items[i].Name, name, true) == 0) return i; } return -1; } /// /// Finds the node with specified name. /// /// The name of node to find. /// The node with specified name, if found; the new node otherwise. /// /// This method adds the node with specified name to the child nodes if it cannot find the node. /// Do not dispose items, which has been created by this method /// public XmlItem FindItem(string name) { XmlItem result = null; int i = Find(name); if (i == -1) { result = Add(); result.Name = name; } else result = Items[i]; return result; } /// /// Gets the index of specified node in the child nodes list. /// /// The node to find. /// Zero-based index of node, if found; -1 otherwise. public int IndexOf(XmlItem item) { return Items.IndexOf(item); } /// /// Gets a property with specified name. /// /// The property name. /// The value of property, if found; empty string otherwise. /// /// This property will return "0" when you request the "Left" property for a node /// like <Node Text="" Left="0"/> /// public string GetProp(string key) { return GetProp(key, true); } internal string GetProp(string key, bool convertFromXml) { if (properties == null || properties.Length == 0) return ""; // property key should be trimmed key = key.Trim(); foreach (XmlProperty kv in properties) if (kv.Key == key) return kv.Value; return ""; } internal void WriteProps(FastString sb) { if (properties == null || properties.Length == 0) return; sb.Append(" "); foreach (XmlProperty kv in properties) { //if (string.IsNullOrWhiteSpace(kv.Key)) if (String.IsNullOrEmpty(kv.Key) || kv.Key.Trim().Length == 0) continue; sb.Append(kv.Key); sb.Append("=\""); sb.Append(Converter.ToXml(kv.Value)); sb.Append("\" "); } sb.Length--; } /// /// Removes all properties. /// public void ClearProps() { properties = null; } internal void CopyPropsTo(XmlItem item) { if (properties == null) { item.properties = null; return; } item.properties = (XmlProperty[])properties.Clone(); } internal bool IsNullOrEmptyProps() { return properties == null || properties.Length == 0; } /// /// Sets the value for a specified property. /// /// The property name. /// Value to set. /// /// For example, you have a node like <Node Text="" Left="0"/>. When you set the /// "Text" property to "test", the node will be <Node Text="test" Left="0"/>. /// If property with specified name is not exist, it will be added. /// public void SetProp(string key, string value) { // property key should be trimmed key = key.Trim(); if (properties == null) { properties = new XmlProperty[1]; properties[0] = XmlProperty.Create(key, value); return; } for (int i = 0; i < properties.Length; i++) { if (properties[i].Key == key) { properties[i] = XmlProperty.Create(key, value); return; } } Array.Resize(ref properties, properties.Length + 1); properties[properties.Length - 1] = XmlProperty.Create(key, value); } /// /// Removes a property with specified name. /// /// The property name. /// Returns true if property is removed, false otherwise. public bool RemoveProp(string key) { if (properties == null || properties.Length == 0) return false; // property key should be trimmed key = key.Trim(); if (properties.Length == 1 && properties[0].Key == key) { properties = null; return true; } if (properties[properties.Length - 1].Key == key) { Array.Resize(ref properties, properties.Length - 1); return true; } int target = -1; for (int i = 0; i < properties.Length; i++) { if (properties[i].Key == key) { target = i; break; } } if (target != -1) { for (int i = target; i < properties.Length - 1; i++) { properties[i] = properties[i + 1]; } Array.Resize(ref properties, properties.Length - 1); return true; } return false; } /// /// Disposes the node and all its children. /// public void Dispose() { Clear(); Parent = null; } /// /// Initializes a new instance of the XmlItem class with default settings. /// public XmlItem() { name = ""; value = ""; } } /// /// Represents a xml document that contains the root xml node. /// /// /// Use Load and Save methods to load/save the document. To access the root node /// of the document, use the property. /// public class XmlDocument : IDisposable { private bool autoIndent; private bool writeHeader; private XmlItem root; /// /// Gets or sets a value indicating whether is necessary to indent the document /// when saving it to a file/stream. /// public bool AutoIndent { get { return autoIndent; } set { autoIndent = value; } } /// /// Gets or sets a value indicating whether is necessary to add xml header. /// public bool WriteHeader { get { return writeHeader; } set { writeHeader = value; } } /// /// Gets the root node of the document. /// public XmlItem Root { get { return root; } } /// /// Clears the document. /// public void Clear() { root.Clear(); } /// /// Saves the document to a stream. /// /// Stream to save to. public void Save(Stream stream) { XmlWriter wr = new XmlWriter(stream); wr.AutoIndent = autoIndent; wr.IsWriteHeader = WriteHeader; wr.Write(root); } /// /// Saves the document to a string. /// /// Writer to save to. public void Save(TextWriter textWriter) { XmlWriter wr = new XmlWriter(textWriter); wr.AutoIndent = autoIndent; wr.IsWriteHeader = WriteHeader; wr.Write(root); } /// /// Loads the document from a stream. /// /// Stream to load from. public void Load(Stream stream) { XmlReader rd = new XmlReader(stream); root.Clear(); rd.Read(root); } /// /// Saves the document to a file. /// /// The name of file to save to. public void Save(string fileName) { FileStream s = new FileStream(fileName, FileMode.Create); Save(s); s.Close(); } /// /// Loads the document from a file. /// /// The name of file to load from. public void Load(string fileName) { FileStream s = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); Load(s); s.Close(); } /// /// Disposes resources used by the document. /// public void Dispose() { root.Dispose(); } /// /// Initializes a new instance of the XmlDocument class with default settings. /// public XmlDocument() { root = new XmlItem(); writeHeader = true; } /// public override string ToString() { using (TextWriter tw = new StringWriter()) { this.Save(tw); tw.Flush(); return tw.ToString(); } } } internal class XmlReader { private StreamReader reader; private Stream stream; private string lastName; private enum ReadState { FindLeft, FindRight, FindComment, FindCloseItem, Done } private enum ItemState { Begin, End, Complete } private int symbolInBuffer; Dictionary stringPool; private ItemState ReadItem(XmlItem item) { FastString builder; if (Config.IsStringOptimization) builder = new FastStringWithPool(stringPool); else builder = new FastString(); ReadState state = ReadState.FindLeft; int comment = 0; int i = 0; //string tempAttrName = null; //int lc = -1; int c = -1; // find < c = readNextSymbol(); while (c != -1) { if (c == '<') break; c = readNextSymbol(); } //while not end while (state != ReadState.Done && c != -1) { // find name or comment; c = readNextSymbol(); i = 0; while (c != -1) { if (i <= comment) { switch (comment) { case 0: if (c == '!') comment++; break; case 1: if (c == '-') comment++; break; case 2: if (c == '-') state = ReadState.FindComment; break; default: comment = -1; break; } if (state == ReadState.FindComment) break; } i++; switch (c) { case '>': state = ReadState.Done; break; //Found name case ' ': state = ReadState.FindRight; break; //Found name case '<': RaiseException(); break; default: builder.Append((char)c); break; } if (state != ReadState.FindLeft) break; c = readNextSymbol(); } switch (state) { case ReadState.FindComment: comment = 0; while (c != -1) { c = readNextSymbol(); if (comment > 1) { if (c == '>') { state = ReadState.FindLeft; break; } } else { if (c == '-') comment++; else comment = 0; } } comment = 0; builder.Length = 0; while (c != -1) { if (c == '<') break; c = readNextSymbol(); } break; case ReadState.Done: string result = builder.ToString(); if (result[0] == '/') { item.Name = result.Substring(1); return ItemState.End; } if (result[result.Length - 1] == '/') { item.Name = result.Substring(0, result.Length - 1); return ItemState.Complete; } item.Name = result; return ItemState.Begin; case ReadState.FindRight: if (builder[0] == '/') { builder.Remove(0, 1); item.Name = builder.ToString(); return ItemState.End; } item.Name = builder.ToString(); builder.Length = 0; while (c != -1 && c != '>') { c = readNextSymbol(); while (c != -1) { if (c == ' ') { builder.Length = 0; c = readNextSymbol(); continue; } if (c == '=' || c == '>') break; builder.Append((char)c); c = readNextSymbol(); } if (c == '>') { if (builder.Length > 0 && builder[builder.Length - 1] == '/') return ItemState.Complete; return ItemState.Begin; } c = readNextSymbol(); if (c != '"') continue; string attrName = builder.ToString(); builder.Length = 0; while (c != -1) { c = readNextSymbol(); if (c == '"') break; builder.Append((char)c); } item.SetProp(attrName, Converter.FromXml(builder.ToString())); builder.Length = 0; } break; } } //just for errors return ItemState.Begin; } private int readNextSymbol() { if (symbolInBuffer != -1) { int temp = symbolInBuffer; symbolInBuffer = -1; return temp; } return reader.Read(); } private bool ReadValue(XmlItem item) { FastString builder; if (Config.IsStringOptimization) builder = new FastStringWithPool(stringPool); else builder = new FastString(); ReadState state = ReadState.FindLeft; string lastName = ""; int lastNameLength = lastName.Length; do { int c = reader.Read(); if (c == -1) RaiseException(); builder.Append((char)c); if (state == ReadState.FindLeft) { if (c == '<') { symbolInBuffer = '<'; return false; } else if (c != ' ' && c != '\r' && c != '\n' && c != '\t') state = ReadState.FindCloseItem; } else if (state == ReadState.FindCloseItem) { if (builder.Length >= lastNameLength) { bool match = true; for (int j = 0; j < lastNameLength; j++) { if (builder[builder.Length - lastNameLength + j] != lastName[j]) { match = false; break; } } if (match) { builder.Length -= lastNameLength; item.Value = Converter.FromXml(builder.ToString()); return true; } } } } while (true); } private bool DoRead(XmlItem rootItem) { ItemState itemState = ReadItem(rootItem); lastName = rootItem.Name; if (itemState == ItemState.End) return true; else if (itemState == ItemState.Complete) return false; if (ReadValue(rootItem)) return false; bool done = false; do { XmlItem childItem = new XmlItem(); done = DoRead(childItem); if (!done) rootItem.AddItem(childItem); else childItem.Dispose(); } while (!done); if (lastName != "" && String.Compare(lastName, rootItem.Name, true) != 0) RaiseException(); return false; } private void RaiseException() { throw new FileFormatException(); } private void ReadHeader() { using (XmlItem item = new XmlItem()) { ReadItem(item); if (item.Name.IndexOf("?xml") != 0) RaiseException(); } } public void Read(XmlItem item) { ReadHeader(); DoRead(item); } public XmlReader(Stream stream) { this.stream = stream; reader = new StreamReader(this.stream, Encoding.UTF8); if (Config.IsStringOptimization) stringPool = new Dictionary(); lastName = ""; symbolInBuffer = -1; } } internal class XmlWriter { private bool autoIndent; private bool isWriteHeader; //private Stream FStream; private TextWriter writer; public bool AutoIndent { get { return autoIndent; } set { autoIndent = value; } } public bool IsWriteHeader { get { return isWriteHeader; } set { isWriteHeader = value; } } private void WriteLn(string s) { if (!autoIndent) writer.Write(s); else writer.Write(s + "\r\n"); } private string Dup(int num) { string s = ""; return s.PadLeft(num); } private void WriteItem(XmlItem item, int level) { FastString sb = new FastString(); // start if (autoIndent) sb.Append(Dup(level)); sb.Append("<"); sb.Append(item.Name); // text item.WriteProps(sb); // end if (item.Count == 0 && item.Value == "") sb.Append("/>"); else sb.Append(">"); // value if (item.Count == 0 && item.Value != "") { sb.Append(Converter.ToXml(item.Value,false)); sb.Append(""); } WriteLn(sb.ToString()); } private void DoWrite(XmlItem rootItem, int level) { if (!autoIndent) level = 0; WriteItem(rootItem, level); for (int i = 0; i < rootItem.Count; i++) DoWrite(rootItem[i], level + 2); if (rootItem.Count > 0) { if (!autoIndent) WriteLn(""); else WriteLn(Dup(level) + ""); } } private void WriteHeader() { WriteLn(""); } public void Write(XmlItem rootItem) { if (IsWriteHeader) WriteHeader(); DoWrite(rootItem, 0); writer.Flush(); } public XmlWriter(Stream stream) { //FStream = stream; writer = new StreamWriter(stream, Encoding.UTF8); isWriteHeader = true; } public XmlWriter(TextWriter textWriter) { //FStream = null; writer = textWriter; isWriteHeader = true; } } }