using System; using System.Data; using System.Data.SqlClient; using System.Configuration; using System.Collections; using System.Xml; using System.IO; namespace Blend.Ektron.Context { /// /// Contains information about a folder's position in the Ektron folder tree. This the only class with which you'll interact. All other classes contained in this file are used internally by the EktronContext class. /// public class EktronContext { /// /// The folder ID of the source folder -- the folder from which you want to retrieve context information /// public int FolderId; /// /// An instance of the utility EktronDatabase class. This class just provides a simple entrance point to the Ektron database. /// private EktronDatabase Database; /// /// An XML writer used to create the XML document /// private XmlWriter Xml; /// /// This is the name of the settings folders. It can be changed to whatever you like. /// public string SettingsFolderName = "_settings"; /// /// Contains an internal list of EktronFolders from the source folder back to the root folder /// public ArrayList SettingsFolders = new ArrayList(); /// /// The XML document which will contain the context information /// public XmlDocument XmlDoc = new XmlDocument(); /// /// Constructor. Passing a folder ID into the constructor retieves all the context information for that folder. /// /// public EktronContext(int FolderId) { //Store the passed in FolderId this.FolderId = FolderId; //Create an instance of the Ektron database. Use the connection string from the Web.config this.Database = new EktronDatabase(ConfigurationManager.ConnectionStrings["Ektron.DbConnection"].ToString()); //Intialize the list of folders. This will contain the EktronFolder objects from our source folder back to root. this.SettingsFolders = new ArrayList(); //Add this folder EktronFolder ThisFolder = new EktronFolder(FolderId, SettingsFolderName, Database); SettingsFolders.Add(ThisFolder); //Keep adding folders until you hit root while (ThisFolder.IsRootFolder() == false) { ThisFolder = new EktronFolder(ThisFolder.ParentId, SettingsFolderName, Database); SettingsFolders.Add(ThisFolder); } //Now that we have our list of folders, create the XML documenbt this.FillXml(); } /// /// Creates the XML context information from the list of EktronFolder objects /// private void FillXml() { //Initialize the Xml StringWriter XmlString = new StringWriter(); this.Xml = new XmlTextWriter(XmlString); //Write the root element this.Xml.WriteStartElement("EktronContext"); //Spin through the list of EktronFolder objects foreach (EktronFolder ThisFolder in this.SettingsFolders) { //Start the element and write all the folder meta this.Xml.WriteStartElement("Folder"); this.Xml.WriteAttributeString("Id", ThisFolder.Id.ToString()); this.Xml.WriteAttributeString("ObjectCount", ThisFolder.SettingsObjects.Count.ToString()); this.Xml.WriteStartElement("Name"); this.Xml.WriteString(ThisFolder.Name.ToString()); this.Xml.WriteEndElement(); this.Xml.WriteStartElement("Description"); this.Xml.WriteString(ThisFolder.Description.ToString()); this.Xml.WriteEndElement(); //Sping through the settings objects in this folder foreach (EktronSettingsObject ThisObject in ThisFolder.SettingsObjects) { //Write the Object tag this.Xml.WriteStartElement("Object"); this.Xml.WriteAttributeString("Id", ThisObject.Id.ToString()); this.Xml.WriteAttributeString("SmartFormId", ThisObject.SmartFormId.ToString()); //Have the object write its own XML this.Xml.WriteRaw(ThisObject.Content); this.Xml.WriteEndElement(); } this.Xml.WriteEndElement(); } this.Xml.WriteEndElement(); //Close out the XML and load it into the XmlDocument this.Xml.Close(); this.XmlDoc.LoadXml(XmlString.ToString()); } /// /// A convenience method to get the raw XML as a string /// /// public string GetXml() { return this.XmlDoc.OuterXml; } /// /// This is the workhorse of the entire system. This will return the requested information nearest the source folder. /// /// An abbreviatwed XPath string. This string will have "/EktronContext/Folder/Object/" prepending to it, before being used to search the XML of the object /// A string containing the nearest information it finds. If it finds nothing, it returns an empty string. public string GetNearest(string XPath) { //Prepend the standard XPath string FullXPath = "/EktronContext/Folder/Object/" + XPath; //Get all nodes that match XmlNodeList MatchingNodes = this.XmlDoc.SelectNodes(FullXPath); //Spin through those, and return the first one that has a value //TODO: There has to be a more intelligent way of doing this... string ReturnValue = ""; if (MatchingNodes.Count > 0) { foreach (XmlNode MatchingNode in MatchingNodes) { if (MatchingNode.InnerXml.Length > 0) { ReturnValue = MatchingNode.InnerXml; break; } } } return ReturnValue; } /// /// Does the same thing as GetNearest, except it returns the entire XML node, rather than its contents as a string /// /// An abbreviatwed XPath string. This string will have "/EktronContext/Folder/Object/" prepending to it, before being used to search the XML of the object /// An XML node containing the nearest information it finds. If it finds nothing, it returns an empty node. public XmlNode GetNearestNode(string XPath) { XmlNodeList ThisNodeList = this.XmlDoc.SelectNodes("/EktronContext/Folder/Object/" + XPath); if (ThisNodeList.Count > 0) { return ThisNodeList[0]; } else { return this.XmlDoc.CreateNode(XmlNodeType.Element, "Empty", ""); } } /// /// Wrapper for DistanceFromFolderByXPath /// /// The folder ID of the target folder /// The number of "hops" fro the target folder public int DistanceFromFolder(int FolderId) { string XPath = "//Folder[@Id='" + FolderId.ToString() + "']"; return this.DistanceFromFolderByXPath(XPath); } /// /// Wrapper for DistanceFromFolderByXPath /// /// The folder name of the target folder /// The number of "hops" fro the target folder public int DistanceFromFolder(string FolderName) { string XPath = "//Folder[Name='" + FolderName + "']"; return this.DistanceFromFolderByXPath(XPath); } /// /// This is the internal method that the two "DistanceFromFolder" methods use /// /// The XPath of the target folder /// An integer representing the nunber of "folder hops" from source to target. -1 means the source folder is not under the target folder. 0 means that the source folder *is* the target folder. 1 means the source folder ia a direct subfolder of the target folder. And so on. private int DistanceFromFolderByXPath(string XPath) { //Does this folder exist at all? if (this.XmlDoc.SelectSingleNode(XPath) == null) { return -1; } else { //Find it, and return all the folder nodes preceding it XmlNodeList PrecedingNodes = this.XmlDoc.SelectNodes(XPath + "/preceding-sibling::*"); return PrecedingNodes.Count; } } /// /// Used to determine if the source folder is anywhere under the target folder. /// /// The ID of the target folder. /// True if the source folder is under the target folder, false otherwise. (Note that this method returns false if the source folder *is* the target folder.) public bool IsUnderFolder(int FolderId) { //If the distance from folder is 0 or -1, then we're not under it if (this.DistanceFromFolder(FolderId) < 1) { return false; } else { return true; } } /// /// Used to determine if the source folder is anywhere under the target folder. /// /// The Iname of the target folder. /// True if the source folder is under the target folder, false otherwise. (Note that this method returns false if the source folder *is* the target folder.) public bool IsUnderFolder(string FolderName) { //If the distance from folder is 0 or -1, then we're not under it if (this.DistanceFromFolder(FolderName) < 1) { return false; } else { return true; } } } /// /// Represents a single Ektron folder. /// public class EktronFolder { /// /// The ID of the folder /// public int Id; /// /// The name of the folder /// public string Name; /// /// The description of the folder /// public string Description; /// /// The ID of the folder's parent /// public int ParentId; /// /// A list of objects inside this folder's settings folder /// public ArrayList SettingsObjects = new ArrayList(); /// /// Constuctor. Pass in the folder's ID to create an object representing this folder. /// /// The ID of the desired folder /// The name of the settings folder (this is set in the EktronContext class. /// An EktronDatabase object public EktronFolder(int FolderId, string SettingsFolderName, EktronDatabase Database) { //Get data on this folder SqlDataReader ThisFolder = Database.GetDataReader("SELECT * FROM content_folder_tbl WHERE folder_id = " + FolderId.ToString()); ThisFolder.Read(); //Set the available properties this.Id = FolderId; this.Name = ThisFolder["folder_name"].ToString(); this.Description = ThisFolder["folder_description"].ToString(); this.ParentId = int.Parse(ThisFolder["parent_id"].ToString()); ThisFolder.Close(); //Look for a settings folder in this folder SqlDataReader SettingsFolder = Database.GetDataReader("SELECT * FROM content_folder_tbl f INNER JOIN content c ON f.folder_id = c.folder_id WHERE f.folder_name = '" + SettingsFolderName + "' AND f.parent_id = " + FolderId.ToString()); //Do we have one? if (SettingsFolder.HasRows) { //Yes... while (SettingsFolder.Read()) { //Create an EktronSettingsObject from the data and store in SettingsObjects EktronSettingsObject ThisObject = new EktronSettingsObject(SettingsFolder); this.SettingsObjects.Add(ThisObject); } } SettingsFolder.Close(); } /// /// A handy was of determining whether or not this is Ektron's root folder. Used to deteremine when to stop recursing up the tree. /// /// True if this is the root folder, false otherwise. public bool IsRootFolder() { if (this.Id == 0) { return true; } else { return false; } } } /// /// Represents a single settings object -- a single content object contained in a settings folder. /// public class EktronSettingsObject { /// /// The Ektron ID of this content object /// public int Id; /// /// The name of this content object /// Note: I don't think this is used anywhere... /// public string Name; /// /// An abbreviation of the XML contained in this content object. It consists of the InnerXML of the EktronXML (so, everything inside the "root" element) /// public string Content; /// /// The ID of the SmartForm on which the content is based /// public string SmartFormId; /// /// Constructor. Takes in a SqlDataReader, set to the first (and only) row, and uses columns in it to populate the properties /// /// public EktronSettingsObject(SqlDataReader Data) { this.Id = int.Parse(Data["content_id"].ToString()); this.Name = Data["content_title"].ToString(); this.SmartFormId = Data["xml_config_id"].ToString(); //Note: I don't know why I don't use the InnerXml of the DocumentElement here...but there must be a reason so I'm leaving it for now (I'm documenting this some time after I wrote it...) Content = Data["content_html"].ToString().Replace("", "").Replace("", ""); } } /// /// A simple interface to the Ektron database /// public class EktronDatabase { /// /// The database connection object /// SqlConnection Connection; /// /// Constructor. Opens a database connection /// /// The database connection string public EktronDatabase(string ConnectionString) { //Open a database connection... SqlConnection ThisConnection = new SqlConnection(ConnectionString); ThisConnection.Open(); //...and set it as an object property. this.Connection = ThisConnection; } /// /// Returns a SqlDataReader from specified SQL /// /// A SQL string /// The results of the SQL string run against the database public SqlDataReader GetDataReader(string Sql) { SqlCommand Command = new SqlCommand(Sql, this.Connection); return Command.ExecuteReader(); } } }