Extracting PartAtom XML from a Revit Family

The first time I was introduced to UNIFI labs, the family parameter extraction and category classification feature caught my attention. Just recently, I saw a post that suggested that we can unzip a Revit file (family or model) with 7-zip. See image below. Then, it was easier to explore and understand how Revit files are packaged. Later, as I kept reading about the topic, I found out that The Building Coder had discussed this topic 10 years ago! There are a couple of examples on Github for Revit plugins to read informational data from Revit content.

Extracted Revit Family

The example below is console app in C# that does not run on top of Revit. It reads a Revit family line by line until if finds the beginning and the end of the PartAtom XML that contains the family information. There are other ways to extract that XML string, but this was the most efficient I found. It finally console prints family information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Xml;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Program
{

    class Program
    {  

        static void Main(string[] args)
        {
            //excecution time start
            var watch = new System.Diagnostics.Stopwatch();
            watch.Start();

            string path = @"C:\familyName.rfa";
            //Extract AtomXml from Rfa
            string atomXml = ExtractAtomXmlFromRfa(path);
            //Console.WriteLine(atomXml);
            
            //initialize XML document class from string
            XmlDocument xmlDoc = new XmlDocument();
            //load XML
            xmlDoc.LoadXml(atomXml);
            //rfa <entry> element
            XmlNode root = xmlDoc.DocumentElement;

            //Display the contents of the child nodes.
            PrintXmlNodes(xmlDoc);

            //dynamic jsonText = JsonConvert.SerializeXmlNode(xmlDoc);
            //JObject json = JsonConvert.DeserializeObject<JObject>(jsonText);

            //foreach (JProperty property in json.Properties())
            //{
            //    property.Children();
            //    Console.WriteLine(property.Name + " - " + property.Value);
            //}

            //excecution time stop
            watch.Stop();
            Console.WriteLine($"Execution Time: {watch.ElapsedMilliseconds} ms");

            Console.ReadKey();
        }


        /// <summary>
        /// Reads line by line and Extracts AtomXML from revit family file
        /// </summary>
        /// <param name="path"></param>
        /// <returns>string</returns>
        static string ExtractAtomXmlFromRfa(string path)
        {        
            //define variables
            string startString = "<?xml";
            string endString = "</entry>";
            string chunks = "";
            string xml = "";
            bool startStringFound = false;
            bool endStringFound = false;

            StreamReader streamReader = File.OpenText(path);

            while ((!startStringFound || !endStringFound) && !streamReader.EndOfStream)
            {
                string line = streamReader.ReadLine();
                Byte[] lineByte = Encoding.ASCII.GetBytes(line);
                string lineEncoded = Encoding.ASCII.GetString(lineByte);
                //Console.WriteLine(lineEncoded);
                if (lineEncoded.Contains(startString))
                {
                    //Console.WriteLine("start string {0} found in the following line:\n{1}", startString, lineEncoded);
                    startStringFound = true;
                }
                if (startStringFound)
                {
                    chunks += lineEncoded;
                    if (lineEncoded.Contains(endString))
                    {
                        //Console.WriteLine("end string {0} found in the following line:\n{1}", endString, lineEncoded);
                        int startIndex = chunks.IndexOf(startString);
                        int endIndex = chunks.IndexOf(endString);
                        xml = chunks.Substring(startIndex, endIndex + endString.Length - startIndex);
                        //Console.WriteLine(xml);
                        endStringFound = true;
                    }
                }
            }
            //XmlDocument xmlDoc = new XmlDocument();
            //xmlDoc.LoadXml(xml);
            return xml;
        }
                
        /// <summary>
        /// Prints in console Xml elements and Inner text
        /// </summary>
        /// <param name="node"></param>
        static void PrintXmlNodes(XmlNode node)
        {            
            if (node.HasChildNodes)
            {
                for (int i = 0; i < node.ChildNodes.Count; i++)
                {
                    XmlNode inode = node.ChildNodes[i];
                    if (node.ChildNodes[i].FirstChild != null)
                    {
                        if (node.ChildNodes[i].NodeType == XmlNodeType.Element && node.ChildNodes[i].FirstChild.NodeType == XmlNodeType.Element)
                        {                            
                            int parentCount = NodeParentCount(node.ChildNodes[i], 0);
                            Console.WriteLine(new String('=',parentCount) + " " + node.ChildNodes[i].LocalName);
                            PrintXmlNodes(node.ChildNodes[i]);
                        }
                        if (node.ChildNodes[i].NodeType == XmlNodeType.Element && node.ChildNodes[i].FirstChild.NodeType == XmlNodeType.Text)
                        {
                            int parentCount = NodeParentCount(node.ChildNodes[i], 0);                           
                            Console.WriteLine(new String('=', parentCount) + " " + node.ChildNodes[i].LocalName + " : " + node.ChildNodes[i].InnerText);
                        }
                    }
                }
            }
            else
            {
                Console.WriteLine("ELSE");
            }
        }

        /// <summary>
        /// Counts the number of parents that a XML node is nested in
        /// </summary>
        /// <param name="node">XmlNode</param>
        /// <param name="count">int initial counter. Usually, 0</param>
        /// <returns>int</returns>
        static int NodeParentCount(XmlNode node, int count)
        {            
            if (node.ParentNode != null)
            {
                count++;                
                return NodeParentCount(node.ParentNode, count);                
            }
            else
            {
                return count;
            }
        }
    }
}

You will see a commented snippet where I try to convert XML to JSON. I found out the execution time was slower when doing the conversion.

I created a github repository that may evolve in the future:

summonidea/rfa-atomxml-reader
Console app that reads AtomXML file of Revit family files. - summonidea/rfa-atomxml-reader

Enjoy!

https://thebuildingcoder.typepad.com/blog/2009/11/extract-part-atoms.html