// 
// Hiero
// Copyright (c) 2015  Barry Block 
// 
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at your option) any later
// version. 
// 
// This program is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE.  See the GNU General Public License for more details. 
// 
// You should have received a copy of the GNU General Public License along with
// this program.  If not, see <http://www.gnu.org/licenses/>. 
//

using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Xml;
using Gtk;
using Gdk;

namespace Hiero
{
   // Implements the Hiero document.
   public class Document
   {
      private const char documentRevision = 'a';
         // The revision level of the XML document structure/format.
      private const int charsPerIndent = 3;
         // Number of spaces to indent in an XML file. 
      internal static string escapedNewlineForCmds = "[NeWlInE]";
         // Stand in for real newlines used during CopyNode command execution.
      internal string LastFolder { get; set; }
         // The last folder visited during a file open/save operation.
      internal string Pathname { get; set; }
         // Complete pathname of this document stored on-disk.
      internal bool Modified { get; set; }
         // Is true if outline has been modified since it was last saved to disk.
      internal OutlineView Tree { get; set; }
         // A reference to the Outline widget that is used for displaying the document's 
         // data. This reference indirectly provides access to the data model.
      internal bool ReadOnly { get; set; }
         // If set to true, this document is read-only and cannot be edited. All
         // documents are created writeable by default.
      private TargetEntry[] outlineAcceptedDropTypes = new TargetEntry[] { new TargetEntry("application/x-node", 0, 0), };
         // The only media type that can be dropped onto the outline is a tree node. 
      private TargetEntry[] outlineProvidedDropTypes = new TargetEntry[] { new TargetEntry("application/x-node", 0, 0), };
         // The only media type that is provided by an outline via dragging is a tree node.
      private bool FirstSearch { get; set; } 
         // Is true if no search is in progress.
      private CellRendererText theCellRenderer;
         // The column's cell renderer.
      internal int WrapMargin { get; set; } 
         // The margin along the right edge of the main window which is a "hack" to
         // allow indenting of nodes to about 10 levels without being truncated.
      private bool pangoEnabled;
         // Whether Pango markup is enabled for this document. See PangoEnabled property also.
      private Stack<ICommand> UndoStack;
         // The command "undo" stack.
      private Stack<ICommand> RedoStack;
         // The command "redo" stack.
      internal Logger AppLog { get; set; }
         // A reference to the app's logger instance.
      internal Log CmdLog { get; set; }
         // The outline's command log.

      public Document(Logger appLog, string newOutlinesFolder)
      {
         AppLog = appLog;
         CmdLog = new Log(this, newOutlinesFolder);
         Modified = false;
         ReadOnly = false;
         ResetSearch(); 
         LastFolder = string.Empty;
         Pathname = string.Empty;

         // Create and configure the document's associated Outline widget:

         TreeStore model = new TreeStore(typeof(string), typeof(bool)); 
         Tree = new OutlineView(model);
         Tree.Visible = true;
         Tree.CanFocus = true;
         Tree.HeadersVisible = false;
         Tree.EnableTreeLines = true;
         Tree.RulesHint = true;
         Tree.EnableSearch = false;
         Tree.Selection.Mode = SelectionMode.Single; 
         Tree.EnableModelDragSource(ModifierType.Button1Mask, outlineProvidedDropTypes, DragAction.Copy | Gdk.DragAction.Move); 
         Tree.EnableModelDragDest(outlineAcceptedDropTypes, Gdk.DragAction.Copy | Gdk.DragAction.Move); 

         // Create and configure the cell renderer that will be used with the tree widget:

         theCellRenderer = new CellRendererText();
         theCellRenderer.WrapMode = Pango.WrapMode.WordChar; 
         Font = "FreeSans 10";
         theCellRenderer.Editable = false;

         pangoEnabled = true;
         WrapMargin = 200;
         Tree.AppendColumn("Text", theCellRenderer, "markup", 0);
         Tree.GetColumn(0).Sizing = TreeViewColumnSizing.Fixed;

         // Set up the command stacks:

         ResetCommandStacks();
      }

      // The outline's base font.
      internal string Font
      {
         get
         {
            return theCellRenderer.Font;
         }

         set
         {
            theCellRenderer.Font = value;
         }
      }

      // Get/set whether outline supports Pango markup.
      internal bool PangoEnabled
      {
         get
         {
            return pangoEnabled;
         }

         set
         {
            pangoEnabled = value;
            Tree.RemoveColumn(Tree.Columns[0]);

            if (value)
            {
               Tree.AppendColumn("Text", theCellRenderer, "markup", 0);
            } 
            else 
            {
               Tree.AppendColumn("Text", theCellRenderer, "text", 0);
            }

            Tree.GetColumn(0).Sizing = TreeViewColumnSizing.Fixed;
         }
      }

      // Returns the number of commands on the undo stack.
      public int UndoCount
      {
         get
         {
            return UndoStack.Count;
         }
      }

      // Returns the number of commands on the redo stack.
      public int RedoCount
      {
         get
         {
            return RedoStack.Count;
         }
      }

      // This method applies the commands that are stored in the given file to the current outline
      // (similar to how a macro recorder/player works). It is used by the crash handler logic to
      // "re-play" those commands against an outline that hadn't been saved prior to a crash. It
      // also provides a more straight-forward means of debugging command execution. When a crash
      // occurs, both the outline as well as that outline's log (which ought to exist on-disk as a
      // hidden .log file in the same folder as the outline file) are copied into Hiero's "Crash"
      // folder (and each is renamed according to the current date/time). These two files can be
      // provided to your's truly to help with debugging the error (via the logic contained in 
      // MainWindow.OnOutlineKeyPressEvent()). Note that in such a log file, a line (representing 
      // a single command) can be commented out by placing a ";" in column 1 of that line. Note 
      // also that if "logging" is set to true, the commands that are run from the given file
      // will be logged to the outline's logfile as though they were entered manually.
      public void RunCommandsFromFile(string filePath, bool logging)
      {
         string delimiter = "|";
         string contents = File.ReadAllText(filePath);
         string[] commands = contents.Split(new char[] {'\n'}, StringSplitOptions.RemoveEmptyEntries);
         for (int i=0; i < commands.Length; i++)
         {
            string command = commands[i];
            if (command[0] != ';')
            {
               // It's not a "commented" line. It must be a command. Execute it:

               string[] parts = command.Split(new string[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);
               string cmdName = parts[0];
               string cmdPath = parts[1];
               int textIndex = cmdName.Length + cmdPath.Length + (2 * delimiter.Length);
               
               TreeIter theNode;
               Tree.Model.GetIterFromString(out theNode, cmdPath);
   
               switch (cmdName)
               {
                  case "AddChildNode":
                  {
                     this.Do(new AddChildNodeCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "AddSiblingNode":
                  {
                     this.Do(new AddSiblingNodeCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "CheckmarkNode":
                  {
                     this.Do(new CheckmarkNodeCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "CopyNode":
                  {
                     string text = command.Substring(textIndex).Replace(escapedNewlineForCmds, "\n");
                     this.Do(new CopyNodeCommand(this, theNode, text), logging);
                     break;
                  }
                  
                  case "CrossmarkNode":
                  {
                     this.Do(new CrossmarkNodeCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "DeleteNode":
                  {
                     this.Do(new DeleteNodeCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "DemoteNode":
                  {
                     this.Do(new DemoteNodeCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "DoubleNewlines":
                  {
                     this.Do(new DoubleNewlinesCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "EditText":
                  {
                     string text = command.Substring(textIndex).Replace(MainClass.escapedNewline, "\n");
                     this.Do(new EditTextCommand(this, theNode, text), logging);
                     break;
                  }
                  
                  case "ExtractNode":
                  {
                     this.Do(new ExtractNodeCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "JoinNode":
                  {
                     this.Do(new JoinNodeCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "MoveNodeDown":
                  {
                     this.Do(new MoveNodeDownCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "MoveNodeUp":
                  {
                     this.Do(new MoveNodeUpCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "NewlinesToSpaces":
                  {
                     this.Do(new NewlinesToSpacesCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "NormalizeNewlines":
                  {
                     this.Do(new NormalizeNewlinesCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "PromoteNode":
                  {
                     this.Do(new PromoteNodeCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "ReduceNewlines":
                  {
                     this.Do(new ReduceNewlinesCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "ReparentNode":
                  {
                     this.Do(new ReparentNodeCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "SplitNode":
                  {
                     this.Do(new SplitNodeCommand(this, theNode), logging);
                     break;
                  }
                  
                  case "Undo":
                  {
                     this.Undo(logging);
                     break;
                  }
               }
            }
         }
      }

      // Executes the command.
      public void Do(ICommand cmd, bool logging)
      {
         // Execute the command:

         cmd.Do(logging);

         // Set the document's modified flag:

         Modified = true;

         // Push it onto the undo stack:

         UndoStack.Push(cmd); 

         // Clear the redo stack:

         RedoStack.Clear();  
      }

      // Un-does execution of the command.
      public void Undo(bool logging)
      {
         if (UndoStack.Count > 0)
         {
            // Get the command to be undone off the undo stack:

            ICommand cmd = UndoStack.Pop();

            // Undo the command:

            cmd.Undo(logging);

            // Update the document's modified flag:

            Modified = cmd.OutlineModified;

            // Push the undone command onto the redo stack:

            RedoStack.Push(cmd);
         }
      }

      // Re-does execution of the command.
      public void Redo(bool logging)
      {
         if (RedoStack.Count > 0)
         {
            // Get the undone command off the redo stack:

            ICommand cmd = RedoStack.Pop();

            // Re-execute the command:

            cmd.Do(logging);

            // Set the document's modified flag:
   
            Modified = true;

            // Push it onto the undo stack:

            UndoStack.Push(cmd);
         }
      }

      // Resets the document's command stacks.
      public void ResetCommandStacks()
      {
         UndoStack = new Stack<ICommand>();
         RedoStack = new Stack<ICommand>();
      }

      // Resets the search.
      public void ResetSearch()
      {
         FirstSearch = true;
      }

      // Returns tree iter of first/next occurrence of findStr.
      public bool Find(string searchString, out TreeIter resultNode) 
      {
         TreeStore model = (TreeStore) Tree.Model;
         TreeIter rootNode;
         model.GetIterFirst(out rootNode);         

         if (FirstSearch)
         {
            // Mark the search results:
   
            MarkSearchResults(searchString, rootNode);
            FirstSearch = false;
         }

         // Get a result:

         return GetSearchResult(rootNode, out resultNode);
      }

      // Performs search for findStr and marks nodes accordingly.
      private void MarkSearchResults(string findStr, TreeIter rootNode) 
      {
         TreeStore model = (TreeStore) Tree.Model;
         TreeIter childNode;
         
         // Process the node:

         string nodeText = (string) model.GetValue(rootNode, 0);
         bool matched = nodeText.IndexOf(findStr, StringComparison.CurrentCultureIgnoreCase) != -1; 
         model.SetValue(rootNode, 1, matched);

         // Process the child nodes:

         int NoOfChildren = model.IterNChildren(rootNode);

         for (int i = 0; i < NoOfChildren; i++)
         {
            model.IterNthChild(out childNode, rootNode, i);
            MarkSearchResults(findStr, childNode);
         }
      }

      // Returns a search result.
      private bool GetSearchResult(TreeIter rootNode, out TreeIter resultNode) 
      {
         TreeStore model = (TreeStore) Tree.Model;
         bool matched = (bool) model.GetValue(rootNode, 1);
         resultNode = TreeIter.Zero;

         if (matched)
         {
            resultNode = rootNode;
            model.SetValue(rootNode, 1, false);
         }
         else
         {
            // Process the child nodes:
   
            int NoOfChildren = model.IterNChildren(rootNode); 
   
            for (int i = 0; i < NoOfChildren; i++)
            {
               TreeIter childNode;
               model.IterNthChild(out childNode, rootNode, i);  
               matched = GetSearchResult(childNode, out resultNode);
               if (matched) break;
            }
         }

         return matched;
      }

      // Returns the subtree rooted at the given node as an ITF string.
      private static void SerializeSubtreeITF(ref string result, TreeStore model, TreeIter theNode, int level, int noOfNewlines)
      {
         TreeIter childIter;
         string Indent;

         // Output the node's text:

         Indent = new string('\t', level);
         string newlines = new string('\n', noOfNewlines);

         string nodeText = (string) model.GetValue(theNode, 0);    

         nodeText = nodeText.Replace("\n", MainClass.escapedNewline);
         result += Indent + nodeText + newlines;

         // Process the child nodes:

         int NoOfChildren = model.IterNChildren(theNode);
         for (int i = 0; i < NoOfChildren; i++)
         {
            model.IterNthChild(out childIter, theNode, i);
            SerializeSubtreeITF(ref result, model, childIter, level + 1, noOfNewlines);
         }
      }

      // Returns the text contents of the subtree rooted at the given node.
      public static string GetITF_Text(TreeIter theNode, TreeStore model)
      {
         const int noOfNewlines = 1; 
         string result = string.Empty;

         // Get the text:

         SerializeSubtreeITF(ref result, model, theNode, 0, noOfNewlines);

         // Remove that trailing newline:

         if (result.Length > (noOfNewlines-1)) result = result.Remove(result.Length - noOfNewlines, noOfNewlines);

         return result;
      }

      // Returns the subtree rooted at the given node as a "flat text" string.
      private static void SerializeSubtreeFlat(ref string result, TreeStore model, TreeIter theNode, int level, int noOfNewlines, bool includeMarkup)
      {
         TreeIter childIter;

         // Output the node's text:

         string newlines = new string('\n', noOfNewlines);
         string nodeText = (string) model.GetValue(theNode, 0);

         if (!includeMarkup)
         {
            // Scrub the Pango markup from the text:
   
            Label aLabel = new Label();
            aLabel.Markup = nodeText;
            nodeText = aLabel.Text;
         }

         result += nodeText + newlines;

         // Process the child nodes:

         int NoOfChildren = model.IterNChildren(theNode);
         for (int i = 0; i < NoOfChildren; i++)
         {
            model.IterNthChild(out childIter, theNode, i);
            SerializeSubtreeFlat(ref result, model, childIter, level + 1, noOfNewlines, includeMarkup);
         }
      }

      // Returns the "flat" text contents of the subtree rooted at the given node.
      public string GetFlatText(TreeIter theNode, bool includeMarkup) 
      {
         const int noOfNewlines = 2; 
         string result = string.Empty;

         // Get the text:

         TreeStore model = (TreeStore) this.Tree.Model;
         SerializeSubtreeFlat(ref result, model, theNode, 0, noOfNewlines, includeMarkup); 

         // Remove those trailing newlines:

         if (result.Length > (noOfNewlines-1)) result = result.Remove(result.Length - noOfNewlines, noOfNewlines);

         return result;
      }

      // Returns the statistics for the given node and its subnodes. 
      internal static void GetNodeStats(TreeStore model, TreeIter rootNode, ref int charCount, ref int wordCount)
      {
         TreeIter childNode;
         
         // Process the node:

         string nodeText = (string) model.GetValue(rootNode, 0);
         charCount += nodeText.Length;
         wordCount += WordCount(nodeText);

         // Process the child nodes:

         int NoOfChildren = model.IterNChildren(rootNode);

         for (int i = 0; i < NoOfChildren; i++)
         {
            model.IterNthChild(out childNode, rootNode, i);
            GetNodeStats(model, childNode, ref charCount, ref wordCount);
         }
      }

      // Returns the word count of the given string. A "word" is considered a sequence of
      // non-whitespace characters that are bounded by one or more whitespace characters.
      internal static int WordCount(string text)
      {
         int wordCount = 0, index = 0;

         // Scrub the Pango markup from the text:

         Label aLabel = new Label();
         aLabel.Markup = text;
         string cleanText = aLabel.Text;

         // Get the word count:

         while (index < cleanText.Length)
         {
            // Check if current char is part of a word:

            while ((index < cleanText.Length) && !System.Char.IsWhiteSpace(cleanText[index])) index++;
         
            wordCount++;
         
            // Skip whitespace until next word:

            while ((index < cleanText.Length) && System.Char.IsWhiteSpace(cleanText[index])) index++;
         }

         return wordCount;
      }

      // Saves the outline document.
      public void Save()
      {
         string docHeader = 
         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
         "<!-- Hiero outline document, rev. " + documentRevision +" -->\n\n";

         string doctype = 
         "<!DOCTYPE outline\n" +
         "[\n" +
         "   <!ELEMENT outline (config,tree)>\n" +
         "   <!ELEMENT config (font,treelines,colorbars,pango,wrapmargin)>\n" +
         "   <!ELEMENT font (#PCDATA)>\n" +
         "   <!ELEMENT treelines (#PCDATA)>\n" +
         "   <!ELEMENT colorbars (#PCDATA)>\n" +
         "   <!ELEMENT pango (#PCDATA)>\n" +
         "   <!ELEMENT wrapmargin (#PCDATA)>\n" +
         "   <!ELEMENT tree (node)>\n" +
         "   <!ELEMENT node (text,node*)>\n" +
         "   <!ELEMENT text (#PCDATA)>\n" +
         "]>\n\n";

         string outlineBegin = 
         "<outline>\n";

         string outlineConfig = 
         "   <config>\n" +
         "      <font>" + Font + "</font>\n" +
         "      <treelines>" + this.Tree.EnableTreeLines.ToString() + "</treelines>\n" +
         "      <colorbars>" + this.Tree.RulesHint.ToString() + "</colorbars>\n" +
         "      <pango>" + this.pangoEnabled.ToString() + "</pango>\n" + 
         "      <wrapmargin>" + this.WrapMargin.ToString() + "</wrapmargin>\n" + 
         "   </config>\n";

         string treeBegin = 
         "   <tree>\n";

         string treeEnd = 
         "   </tree>\n";

         string outlineEnd = 
         "</outline>\n";

         string preOutline = docHeader + doctype + outlineBegin + outlineConfig + treeBegin; 
         string postOutline = treeEnd + outlineEnd;

         TreeIter Root;

         // Open the file stream for writing:

         StreamWriter theStream = new StreamWriter(this.Pathname); 

         // Write the pre-outline text to disk:

         theStream.Write(preOutline);

         // Get the root node's iter:

         TreeStore model = (TreeStore) this.Tree.Model;
         model.GetIterFirst(out Root);

         // Get the outline's XML:

         string outlineXML = GetXML_Text(Root, model);

         // Indent the outline's XML an extra six spaces to match the overall XML's indentation:

         outlineXML = "      " + outlineXML.Replace("\n", "\n      ");

         // Strip the six spaces that were added to the end of the XML:

         outlineXML = outlineXML.Remove(outlineXML.Length-6, 6);

         // Write the outline to disk:

         theStream.Write(outlineXML);

         // Write the post-outline text to disk:

         theStream.Write(postOutline);

         // Close the file stream:

         theStream.Flush();
         theStream.Close();

         // Set the outline to unmodified, reset the undo/redo stacks and delete the outline's command log:

         Modified = false;
         ResetCommandStacks();
         CmdLog.Delete();
      }

      // Attaches the subtree provided in the form of the given text to the given node and returns 
      // the root of the new subtree. Code adapted from demo by Rod Stephens which is found here: 
      // http://csharphelper.com/blog/2014/09/load-a-treeview-from-a-tab-delimited-file-in-c/ 
      public void AttachSubtree(TreeIter theNode, string text, out TreeIter root)
      {
         root = TreeIter.Zero;
         TreeStore model = (TreeStore) this.Tree.Model;

         // Break the text into lines:

         string[] textLines = text.Split(new char[] {'\n'}, StringSplitOptions.RemoveEmptyEntries);

         // Create a "lookup" for keeping track of parent nodes per level:

         Dictionary<int, TreeIter> parents = new Dictionary<int, TreeIter>();

         // Load the text lines:

         foreach (string textLine in textLines)
         {
            // See how many tabs are at the start of the line:

            int level = textLine.Length - textLine.TrimStart('\t').Length;

            // Clean up the line of text and translate newlines:

            string cleanedLine = textLine.TrimStart('\t').Replace(MainClass.escapedNewline, "\n");

            // Add the new node:

            if (level == 0)
            {
               // It's the "root" node.

               root = model.AppendValues(theNode, cleanedLine, false);
               parents[level] = root;
            }
            else
            {
               // It's not the "root" node.

               parents[level] = model.AppendValues(parents[level - 1], cleanedLine, false);
            }
         }
      }

      // Loads the outline widget from the root node of the XML file's "tree" element. 
      public void LoadOutlineFromXML(XmlNode theNode, TreeIter parent)
      {
         TreeStore model = (TreeStore) this.Tree.Model;

         if (parent.Equals(TreeIter.Zero))
            parent = model.AppendValues(theNode.FirstChild.InnerText, false);
         else
            parent = model.AppendValues(parent, theNode.FirstChild.InnerText, false);

         bool firstChild = true;
         foreach (XmlNode childNode in theNode.ChildNodes)
         {
            if (!firstChild)
               LoadOutlineFromXML(childNode, parent);
            else
               firstChild = false;
         }
      }

      // Returns the given XML element's value.
      public string GetXML_Value(XmlElement docElem, string xpath)
      {
         return docElem.SelectSingleNode(xpath).InnerText;
      }

      // Opens the outline document.
      public void Open()
      {
         XmlDocument doc = new XmlDocument();
         doc.Load(Pathname);
         XmlElement docElem = doc.DocumentElement;
         XmlNode rootNode = docElem.SelectSingleNode("/outline/tree/node");

         // Load the outline properties:

         Font = GetXML_Value(docElem, "/outline/config/font");
         Tree.EnableTreeLines = GetXML_Value(docElem, "/outline/config/treelines").ToUpper() == "TRUE";
         Tree.RulesHint = GetXML_Value(docElem, "/outline/config/colorbars").ToUpper() == "TRUE";
         PangoEnabled = GetXML_Value(docElem, "/outline/config/pango").ToUpper() == "TRUE";

         try
         {
            string temp = GetXML_Value(docElem, "/outline/config/wrapmargin");
            WrapMargin = Convert.ToInt32(temp);
         }
         catch
         {
            WrapMargin = 200;
         }

         // Load the outline:

         TreeIter parent = TreeIter.Zero;
         LoadOutlineFromXML(rootNode, parent);
   
         // Set the document's read only flag:

         System.IO.FileInfo fileInfo = new System.IO.FileInfo(this.Pathname);
         ReadOnly = fileInfo.IsReadOnly;
         
         // Assign the document's command log:
         
         this.CmdLog.Assign(this.Pathname);

         Modified = false;
      }

      // Saves the document's updated properties.
      public void SaveProperties()
      {
         // Reload the XML document (NOTE: here, the document must be loaded again from 
         // disk as we don't want to save any current changes the user has made to the 
         // document's outline -- the user himself is in control of saving those changes):

         XmlDocument doc = new XmlDocument();
         doc.Load(Pathname);
         XmlElement docElem = doc.DocumentElement;

         // "Patch" the XML document's configuration from the current outline document:

         docElem.SelectSingleNode("/outline/config/font").InnerText = Font;
         docElem.SelectSingleNode("/outline/config/treelines").InnerText = this.Tree.EnableTreeLines.ToString();
         docElem.SelectSingleNode("/outline/config/colorbars").InnerText = this.Tree.RulesHint.ToString();
         docElem.SelectSingleNode("/outline/config/pango").InnerText = this.pangoEnabled.ToString();
         docElem.SelectSingleNode("/outline/config/wrapmargin").InnerText = this.WrapMargin.ToString();

         // Save the XML document:

         doc.Save(Pathname); 
      }

      // Imports the subtree from the given file at the given node location in the outline.
      public void ImportXML(TreeIter treeNode, string pathName)
      {
         XmlDocument doc = new XmlDocument();
         doc.Load(pathName); 
         XmlElement docElem = doc.DocumentElement;
         XmlNode xmlNode = docElem.SelectSingleNode("/node");
   
         // Load the outline:

         LoadOutlineFromXML(xmlNode, treeNode);
      }

      // Initiates a new outline document.
      public void New()
      {
         ((TreeStore) Tree.Model).AppendValues(MainClass.initRootText, false);
      }

      // Exports the outline subtree rooted at the given node.
      public void Export(TreeIter theNode, string pathName, bool toITF, bool includeMarkup)
      {
         // Open the file stream for writing:

         StreamWriter theStream = new StreamWriter(pathName); 

         // Write the outline to disk:

         if (toITF)
         {
            // Export to indented text formatted file:

            TreeStore model = (TreeStore) this.Tree.Model;
            theStream.WriteLine(GetITF_Text(theNode, model));
         }
         else
         {
            // Export to flat text file:

            theStream.WriteLine(GetFlatText(theNode, includeMarkup));
         }

         // Close the file stream:

         theStream.Flush();
         theStream.Close();
      }

      // Returns the subtree rooted at the given node as an XML string.
      private static void SerializeSubtreeXML(ref string result, TreeStore model, TreeIter theNode, int level, int noOfNewlines)
      {
         TreeIter childIter;
         string indent;

         // Output the node's text:

         indent = new string(' ', level * charsPerIndent);
         string nodeText = (string) model.GetValue(theNode, 0);

         // Escape special characters:

         nodeText = nodeText.Replace("&", "&amp;");
         nodeText = nodeText.Replace("\"", "&quot;");
         nodeText = nodeText.Replace("'", "&apos;");
         nodeText = nodeText.Replace("<", "&lt;");
         nodeText = nodeText.Replace(">", "&gt;");
         nodeText = nodeText.Replace("\n", MainClass.escapedNewline);

         if (model.IterHasChild(theNode))
         {
            // Processing a "non-leaf" node.

            result += indent + "<node><text>" + nodeText + "</text>\n";

            // Process the child nodes:
   
            int NoOfChildren = model.IterNChildren(theNode);

            for (int i = 0; i < NoOfChildren; i++)
            {
               model.IterNthChild(out childIter, theNode, i);
               SerializeSubtreeXML(ref result, model, childIter, level + 1, noOfNewlines);
            }

            // Close the node:

            result += indent + "</node>\n";
         }
         else
         {
            // Processing a "leaf" node.

            result += indent + "<node><text>" + nodeText + "</text></node>\n";
         }
      }

      // Returns the text contents of the subtree rooted at the given node.
      public static string GetXML_Text(TreeIter theNode, TreeStore model)
      {
         const int noOfNewlines = 1; 
         string result = string.Empty;

         // Get the text:

         SerializeSubtreeXML(ref result, model, theNode, 0, noOfNewlines);

         return result;
      }

      // Exports the outline subtree rooted at the given node.
      public void ExportAsXML(TreeIter theNode, string pathName)
      {
         // Open the file stream for writing:

         StreamWriter theStream = new StreamWriter(pathName); 

         // Write the outline to disk:

         TreeStore model = (TreeStore) this.Tree.Model;
         theStream.Write(GetXML_Text(theNode, model));

         // Close the file stream:

         theStream.Flush();
         theStream.Close();
      }
   }
}
