Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

RTF Document Constructor Library

, 16 Aug 2010
Create Rich Text Format documents programatically.
using System;
using System.Text;
using System.Reflection;

using System.IO;

namespace ESCommon.Rtf
{
    /// <summary>
    /// Reflects ESCommon.Rtf.RtfDocument generating an RTF string which is written to a file.
    /// </summary>
    public class RtfWriter
    {
        private const int preferredWidth = 128;

        private StringBuilder sb;
        private Encoding _encoding;
        private int lastNewLineIndex = 0;
        

        /// <summary>
        /// Writes ESCommon.Rtf.RtfDocument to a file.
        /// </summary>
        /// <param name="writer">The System.IO.TextWriter used to write RTF document.</param>
        /// <param name="document">The ESCommon.Rtf.RtfDocument to write.</param>
        public void Write(TextWriter writer, RtfDocument document)
        {
            _encoding = Encoding.GetEncoding((int)document.CodePage);
            
            sb = new StringBuilder();

            Reflect(document);

            writer.Write(sb.ToString());
        }


        private void Reflect(object instance)
        {
            Reflect(instance, -1);
        }

        private void Reflect(object instance, int arrayIndex)
        {
            Type type = instance.GetType();

            RtfAttributeInfo info = RtfDocumentInfo.GetAttributeInfo(type);

            string
                controlWord = info.ControlWordAttribute != null ? "\\" + info.ControlWordAttribute.Name : String.Empty,
                controlWordDenotingEnd = info.ControlWordDenotingEndAttribute != null ? "\\" + info.ControlWordDenotingEndAttribute.Name : String.Empty;

            bool
                isIndexed = info.ControlWordAttribute != null && info.ControlWordAttribute.IsIndexed,
                enclosedWithBraces = info.EnclosingBracesAttribute != null && info.EnclosingBracesAttribute.Braces,
                closingSemicolon = info.EnclosingBracesAttribute != null && info.EnclosingBracesAttribute.ClosingSemicolon;
            
            if (enclosedWithBraces)
            {
                Append('{');
            }

            if (!String.IsNullOrEmpty(controlWord))
            {
                Append(controlWord);

                if (isIndexed && arrayIndex >= 0)
                    Append(arrayIndex.ToString());
            }

            MemberInfo[] members = RtfDocumentInfo.GetTypeMembers(type);

            foreach (MemberInfo member in members)
            {
                ReflectMember(member, instance);
            }

            if (closingSemicolon)
                Append(';');

            if (!String.IsNullOrEmpty(controlWordDenotingEnd))
                Append(controlWordDenotingEnd);

            if (enclosedWithBraces)
                Append('}');
        }

        private void ReflectMember(MemberInfo member, object instance)
        {
            RtfAttributeInfo info = RtfDocumentInfo.GetAttributeInfo(member);

            if (!info.HasAttributes)
                return;

            if (info.IgnoreAttribute != null)
                return;

            bool include = true;

            if (info.IncludeAttribute != null)
                include = GetCondition(instance, info.IncludeAttribute);

            if (!include)
                return;

            Type memberType = GetMemberType(member);
            object value = GetMemberValue(member, instance);

            if (info.ControlWordAttribute != null)
            {
                Append(GetControlWord(member, memberType, value, info.ControlWordAttribute.Name));

                if (memberType.IsClass)
                {
                    if (TypeIsEnumerable(memberType))
                        ReflectArray(memberType, value);
                    else
                        Reflect(value);
                }

                return;
            }

            if (info.TextDataAttribute != null)
            {
                Append(' ');
                
                if (info.TextDataAttribute.Quotes)
                {
                    Append('"');
                }
                
                Append(EncodeText(value.ToString(), info.TextDataAttribute.TextDataType));

                if (info.TextDataAttribute.Quotes)
                {
                    Append('"');
                }

                return;
            }

            if (info.ControlGroupAttribute != null)
            {
                AppendLine();
                Append('{');
                Append('\\');
                Append(info.ControlGroupAttribute.Name);
                Append(' ');

                ReflectArray(memberType, value);

                Append('}');
                AppendLine();

                return;
            }

            if (info.IncludeAttribute != null && memberType.IsClass)
            {
                if (TypeIsEnumerable(memberType))
                    ReflectArray(memberType, value);
                else if (value != null)
                    Reflect(value);
            }
        }

        private void ReflectArray(Type memberType, object value)
        {
            if (memberType == null)
                throw (new ArgumentNullException("Type cannot be null"));
            
            System.Collections.IEnumerable array = value as System.Collections.IEnumerable;
            
            int index = 0;

            if (array != null)
            {
                foreach (object arrayMember in array)
                    Reflect(arrayMember, index++);
            }
        }


        private object GetAttribute(object[] attributes, Type type)
        {
            foreach (object attribute in attributes)
                if (attribute.GetType() == type)
                    return attribute;
            
            return null;
        }


        private string GetControlWord(MemberInfo member, Type memberType, object memberValue, string attributeControlWord)
        {
            if (memberType == null)
                throw (new ArgumentNullException("Type cannot be null"));

            string controlWord = String.Empty;

            if (memberType.IsEnum)
                controlWord = GetEnumControlWord(member, memberType, memberValue, attributeControlWord);
            else
                controlWord = GetMemberControlWord(member, memberType, memberValue, attributeControlWord);

            if (controlWord == "\\")
            {
                return String.Empty;
            }

            return controlWord;
        }

        private string GetMemberControlWord(MemberInfo member, Type memberType, object memberValue, string attributeControlWord)
        {
            if (memberType == null)
                throw (new ArgumentNullException("Type cannot be null"));

            if (memberType.IsEnum)
                throw (new ArgumentException("Type cannot be enum"));

            string controlWord = "\\";
            
            if (!String.IsNullOrEmpty(attributeControlWord))
                controlWord += attributeControlWord;
            else
                controlWord += member.Name.ToLower();

            if (memberType.IsPrimitive)
            {
                if (memberType == typeof(int))
                {
                    RtfAttributeInfo info = RtfDocumentInfo.GetAttributeInfo(member);

                    if (info.IndexAttribute != null)
                    {
                        if ((int)memberValue >= 0)
                            controlWord += memberValue.ToString();
                        else
                            controlWord = String.Empty;
                    }
                    else
                        controlWord += memberValue.ToString();
                }
                else if (memberType != typeof(bool))
                {
                    controlWord += memberValue.ToString();
                }
                else if (!(bool)memberValue)
                    controlWord = String.Empty;
            }

            return controlWord;
        }

        private string GetEnumControlWord(MemberInfo member, Type memberType, object memberValue, string attributeControlWord)
        {
            RtfAttributeInfo info = RtfDocumentInfo.GetAttributeInfo(memberType);
            
            if (memberType == null)
                throw (new ArgumentNullException("Type cannot be null"));

            if (!memberType.IsEnum)
                throw (new ArgumentException("Type cannot be enum"));

            if (info.EnumAsControlWordAttribute == null)
                return String.Empty;

            string controlWord = "\\";

            if (!String.IsNullOrEmpty(attributeControlWord))
                controlWord += attributeControlWord;
            else
                controlWord += info.EnumAsControlWordAttribute.Prefix;

            switch (info.EnumAsControlWordAttribute.Conversion)
            {
                case RtfEnumConversion.UseAttribute:
                    {
                        MemberInfo[] enumMember = memberType.GetMember(((Enum)memberValue).ToString());

                        if (enumMember != null && enumMember.Length > 0)
                        {
                            RtfAttributeInfo enumMemberInfo = RtfDocumentInfo.GetAttributeInfo(enumMember[0]);

                            if (enumMemberInfo.ControlWordAttribute != null)
                                controlWord += enumMemberInfo.ControlWordAttribute.Name;
                        }
                        break;
                    }
                case RtfEnumConversion.UseValue:
                    {
                        if (memberValue == null)
                            throw (new ArgumentNullException(String.Format("Type {0} cannot take null as a value", memberType.Name)));

                        controlWord += ((int)memberValue).ToString();
                        break;
                    }
                case RtfEnumConversion.UseName:
                default:
                    {
                        controlWord += ((Enum)memberValue).ToString().ToLower();
                        break;
                    }
            }

            return controlWord;
        }


        private bool GetCondition(object instance, RtfIncludeAttribute includeAttribute)
        {
            if (String.IsNullOrEmpty(includeAttribute.ConditionMember))
                return true;

            Type type = instance.GetType();
            MemberInfo[] memberInfo = type.GetMember(includeAttribute.ConditionMember);

            if (memberInfo.Length == 1)
            {
                MemberInfo member = memberInfo[0];
                
                if (member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Field)
                    return (GetMemberType(member) == typeof(bool)) && (includeAttribute.Value == (bool)GetMemberValue(member, instance));
            }

            return false;
        }

        private bool TypeIsEnumerable(Type memberType)
        {
            bool IsEnumerable = false;
            Type[] interfaceTypes = memberType.GetInterfaces();

            for (int i = 0; !IsEnumerable && i < interfaceTypes.Length; i++)
                IsEnumerable = interfaceTypes[i] == typeof(System.Collections.IEnumerable);

            return IsEnumerable;
        }


        private Type GetMemberType(MemberInfo member)
        {
            Type memberType = null;

            if (member.MemberType == MemberTypes.Field)
            {
                FieldInfo field = member as FieldInfo;
                memberType = field.FieldType;
            }
            else if (member.MemberType == MemberTypes.Property)
            {
                PropertyInfo property = member as PropertyInfo;
                memberType = property.PropertyType;
            }
            else
                throw (new ArgumentException("Method cannot take members other than fields and properties as arguments"));

            return memberType;
        }

        private object GetMemberValue(MemberInfo member, object instance)
        {
            object value = null;

            if (member.MemberType == MemberTypes.Field)
            {
                FieldInfo field = member as FieldInfo;
                value = field.GetValue(instance);
            }
            else if (member.MemberType == MemberTypes.Property)
            {
                PropertyInfo property = member as PropertyInfo;
                value = property.GetValue(instance, null);
            }
            else
                throw (new ArgumentException("Method cannot take members other than fields and properties as arguments"));

            return value;
        }


        private string EncodeText(string text, RtfTextDataType type)
        {
            if (type == RtfTextDataType.Raw)
            {
                return text;
            }

            if (type == RtfTextDataType.HyperLink)
            {
                return Uri.EscapeUriString(text);
            }

            StringBuilder encodedText = new StringBuilder();

            byte[] data = _encoding.GetBytes(text);

            for (int i = 0; i < data.Length; i++)
            {
                if (text[i] == Encoding.ASCII.GetChars(data, i, 1)[0])
                {
                    if (text[i] == '\\' || text[i] == '{' || text[i] == '}')
                        encodedText.Append('\\');

                    encodedText.Append(text[i]);
                }
                else if (text[i] == _encoding.GetChars(data, i, 1)[0])
                {
                    encodedText.Append('\\').Append('\'').Append(Convert.ToString(data[i], 16));
                }
                else
                {                    
                    byte[] unicode = Encoding.Unicode.GetBytes(text[i].ToString());
                    encodedText.Append('\\').Append('u').Append(BitConverter.ToUInt16(unicode, 0).ToString()).Append('?');
                }


                if (sb.Length + encodedText.Length - lastNewLineIndex > preferredWidth)
                {
                    encodedText.AppendLine();
                    lastNewLineIndex = sb.Length + encodedText.Length;
                }
            }

            return encodedText.ToString();
        }

        
        private void Append(char value)
        {
            if (value == '\\' && (sb.Length - lastNewLineIndex > preferredWidth))
                AppendLine();
            
            sb.Append(value);
        }

        private void Append(string value)
        {
            if (!String.IsNullOrEmpty(value))
            {
                if (value[0] == '\\' && (sb.Length - lastNewLineIndex > preferredWidth))
                    AppendLine();
                else if (sb.Length - lastNewLineIndex + value.Length > preferredWidth)
                    AppendLine();
            }

            sb.Append(value);
        }

        private void AppendLine()
        {            
            if (lastNewLineIndex != sb.Length)
            {
                sb.AppendLine();
                
                lastNewLineIndex = sb.Length;
            }
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Dima Popov
Software Developer Energoservice
Russian Federation Russian Federation
Dmitry lives in Arkhangelsk, Russia. He has developed C# applications since 2007.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 16 Aug 2010
Article Copyright 2010 by Dima Popov
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid