註釋文檔在線編輯及生成

     產品上市以前須要詳細的幫助文檔,每一個程序員寫各自負責的部分,爲了統一格式和減輕工做量,決定用程序實現。文檔生成方便一直很出名的就是sandcastle,但他的格式不是想要的。因而就在sandcastle的基礎上進行改造。html

    需求的最終結果是這個樣子:node

   

  1、基本原理程序員

主要針對二次開發的用戶使用,簡單明瞭。右邊最多分五個塊:概要,定義,註釋,參數和示例。web

   DocumentGenerator原理圖以下:app

  

如上圖所示,咱們在VS上編譯生成以後,一個dll就會對應一個xml文件(固然前提是你最好本身寫了一些註釋,用GhostDoc很方便),咱們將一個dll和對應的xml都加載進DocumentGenerator,DocumentGenerator有用Razor定義好的模板也就是所謂的參考主題,而根據dll和xml會解析出來summary,remarks,returns,define,example這五個部分。在編輯的地方,支持在線編輯,否則直接編輯xml文檔,很麻煩很累,在web中根據樹節點展開這樣每一個寫文檔的人只用「填空」就好了,不用去關心哪些標籤和格式。最後一鍵生成,速度很快。那具體xml文檔是怎麼對應的,以下圖:ide

 而後再將這些html,目錄文件經過hhc編譯器生成了chm文檔。函數

 2、一些細節ui

 咱們獨立出來一個MemberDocumentApplet對象,提供,onload,onsave,OnGenerateChm 等方法,並且包含了一個樹對象和MemberDocument集合,前者用來導航,後者用來呈現一個方法或者一個屬性它的註釋及示例。不管是web仍是winform,經過這個applet對象均可以進行加載、修改報錯及生成的動做。this

 1.OnLoad()lua

 加載xml文檔後,先會備份一個XXname_DBfile,

    _bakFileName = Path.GetDirectoryName(xmlFile) + Path.GetFileNameWithoutExtension(xmlFile) + "_DBFile.xml";

修改的時候是修改這個文件,這樣作的目的是爲了保留用戶修改的內容,若是一個用戶已經在DocumentGenerator中編輯了一部分忽然發現又要加一些方法,因而用vs修改源文件後生成了新的xml和dll,這樣子再加載到DocumentGenerator中的時候上次填寫的內容都還在,他只須要完善那些新的方法就能夠生成文檔,而不用所有再寫一遍。

   public bool OnLoad(string xmlFile, string dllFile)
        {
            if (!File.Exists(xmlFile)) return false;

            _xmlComment = new XmlCommentsFile(xmlFile);
            _rootNode = new AssemblyNode("N:" + _xmlComment.AssemblyName);
            _bakFileName = Path.GetDirectoryName(xmlFile) + Path.GetFileNameWithoutExtension(xmlFile) + "_DBFile.xml";

            //加載dll文件
            var assemble = Assembly.LoadFrom(dllFile);
            foreach (var type in assemble.GetTypes())
            {
                var methodRootNode = new MemberTypeNode("方法", MemberTypes.Method);//,  new MemberDocumentCollection()
                var propertyRootNode = new MemberTypeNode("屬性",MemberTypes.Property);
                var eventRootNode = new MemberTypeNode("事件",MemberTypes.Event);
                var properties = type.GetProperties();
                foreach (var memberInfo in type.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
                {
                    #region 過濾
                    //過濾get set
                    var p1 = properties.Count(t => t.Name == AdjustMethodName(memberInfo.Name));
                    if (p1 > 0) continue;

                    //過濾不包含ScriptVisible的項
                    var attribute = memberInfo.GetCustomAttributes(typeof(ScriptVisibleAttribute), false);
                    if (attribute.Length <= 0) continue;
                    #endregion
                    
                    string mKey = ConsummateKeyValue(memberInfo);
                    var paramTypeList = GetMethodParamList(memberInfo as MethodInfo);
                    var paramOptionalList = GetMethodParamOptionalStatusList(memberInfo as MethodInfo);
                    var defineStr = GetMethodDefineStr(memberInfo as MethodInfo);
                    var fullname = memberInfo.ReflectedType.FullName.Substring(memberInfo.ReflectedType.FullName.LastIndexOf(".", StringComparison.Ordinal) + 1) + "." + memberInfo.Name;

                    var memberDoc = new MemberDocument(mKey, _xmlComment[mKey], defineStr, paramOptionalList, paramTypeList, fullname);
                    _memberDocumentCollection.Add(memberDoc);//_memberDocumentCollection 初始化
                    switch (memberInfo.MemberType)
                    {
                        case MemberTypes.Method:
                            methodRootNode.MemberDocumentGroup.Add(memberDoc);
                            break;
                        case MemberTypes.Property:
                            propertyRootNode.MemberDocumentGroup.Add(memberDoc);
                            break;
                        case MemberTypes.Event:
                            eventRootNode.MemberDocumentGroup.Add(memberDoc);
                            break;
                    }
                }

                //_rootMemberDic初始化
                string classKey = "T:" + type.FullName;
                var classNode = new ClassNode(classKey, _xmlComment[classKey], "", null,null, "");
                if (methodRootNode.MemberDocumentGroup.Count > 0)
                    classNode.MemberTypeNodeList.Add(methodRootNode);
                if(propertyRootNode.MemberDocumentGroup.Count > 0)
                    classNode.MemberTypeNodeList.Add(propertyRootNode);
                if(eventRootNode.MemberDocumentGroup.Count>0)
                    classNode.MemberTypeNodeList.Add(eventRootNode);

                _rootNode.ClassNodeGroup.Add(classNode);

                _memberDocumentCollection.Add(new MemberDocument
                    {
                        Define = classNode.Define,
                        Example = classNode.Example!=null?new ExampleSection(classNode.Example.Name,classNode.Example.Code):null,
                        FullName = classNode.FullName,
                        Name = classNode.Name,
                        ParamList = new List<ParamSection>(classNode.ParamList),
                        Remarks = classNode.Remarks,
                        Returns = classNode.Returns,
                        Summary = classNode.Summary
                    });
            }
            //var assemble = Assembly.LoadFrom(dllFile);
            
            //合併
            if (File.Exists(_bakFileName))
            {
                //合併 _memberDocumentCollection 和 bakFileName文件
                var bakList = DeserializeMemberDocument(_bakFileName);

                foreach (var document in bakList)
                {
                    if (_memberDocumentCollection.ContainsKey(document.Name))
                    {
                        if (String.CompareOrdinal(_memberDocumentCollection[document.Name].Summary, document.Summary) != 0)
                            _memberDocumentCollection[document.Name].Summary = document.Summary;
                        if (String.CompareOrdinal(_memberDocumentCollection[document.Name].Remarks, document.Remarks) != 0)
                            _memberDocumentCollection[document.Name].Remarks = document.Remarks;
                        if (String.CompareOrdinal(_memberDocumentCollection[document.Name].Returns, document.Returns) != 0)
                            _memberDocumentCollection[document.Name].Returns = document.Returns;
                        if (document.Example != null && _memberDocumentCollection[document.Name].Example != null)
                        {
                            if (String.CompareOrdinal(_memberDocumentCollection[document.Name].Example.Code, document.Example.Code) != 0)
                                _memberDocumentCollection[document.Name].Example.Code = document.Example.Code;
                            if (String.CompareOrdinal(_memberDocumentCollection[document.Name].Example.Name, document.Example.Name) != 0)
                                _memberDocumentCollection[document.Name].Example.Name = document.Example.Name;
                        }
                        else
                        {
                            if (_memberDocumentCollection[document.Name].Example == null && document.Example != null)
                            {
                                _memberDocumentCollection[document.Name].Example = new ExampleSection(document.Example.Name, document.Example.Code);
                            }
                        }

                        if (document.ParamList != null)
                        {
                            for (int i = 0; i < document.ParamList.Count; i++)
                            {
                                if (String.CompareOrdinal(_memberDocumentCollection[document.Name].ParamList[i].Content,document.ParamList[i].Content) != 0)
                                    _memberDocumentCollection[document.Name].ParamList[i].Content = document.ParamList[i].Content;
                            }
                        }
                    }
                    else
                    {
                        _memberDocumentCollection.Add(new MemberDocument()
                        {
                            Define = document.Define,
                            Example = document.Example,
                            FullName = document.FullName,
                            Name = document.Name,
                            ParamList = new List<ParamSection>(document.ParamList),
                            Remarks = document.Remarks,
                            Returns = document.Remarks,
                            Summary = document.Summary
                        });
                    }
                }
            }

            return true;
        }
View Code

 另一點,須要生成文檔的對象不會全部的類和方法,咱們只須要其中的一部分,因而就加了一個ScriptVisibleAttribute標籤過濾。意即帶有這個標籤的方法纔會生成在文檔中。

   var attribute = memberInfo.GetCustomAttributes(typeof(ScriptVisibleAttribute), false);

最小可編輯的對象就是MemberDocument,包含了sumary,remarks returns,param,example等部分。

using System;
using System.Collections.Generic;
using System.Xml;

namespace HelpFileExtract
{
    [Serializable]
    public class MemberDocument
    {
        public MemberDocument()
        {
        }

        public MemberDocument(string name, XmlNode member, string definedName, List<bool> optionalList, List<string> paramTypeList, string fullName)
        {
            this.Name = name;
            this.Summary = string.Empty;
            this.Remarks = string.Empty;
            this.Example = null;
            this.Define = definedName;
            this.Returns = string.Empty;
            this.FullName = fullName;

            if (member == null) return;
            //從xml文件中得到參數個數和參數名
            var paramNodes = member.SelectNodes("param");
            //合併
            if (paramNodes != null && (paramNodes.Count > 0 && paramTypeList != null && paramNodes.Count == paramTypeList.Count))
            {
                int i = 0;
                foreach (XmlNode param in paramNodes)
                {
                    if (param.Attributes != null)
                        this.ParamList.Add(new ParamSection(param.Attributes["name"].Value, optionalList[i], paramTypeList[i], param.InnerText));
                    i++;
                }
            }

            if (member["summary"] != null) this.Summary = member["summary"].InnerText;
            if (member["remarks"] != null) this.Remarks = member["remarks"].InnerText;
            if (member["returns"] != null) this.Returns = member["returns"].InnerText;

            if (member["example"] == null) return;

            if (member["example"]["code"] != null)
            {
                var node = member["example"]["code"];
                var value = node.InnerText;
                member["example"].RemoveChild(node);
                Example =
                    new ExampleSection(
                        member["example"].InnerText,
                        value);
            }
        }
        public string Name { get; set; }
        public string Summary { get; set; }
        public string Define { get; set; }
        public List<ParamSection> ParamList = new List<ParamSection>();
        public string Remarks { get; set; }
        public ExampleSection Example { get; set; }
        public string Returns { get; set; }
        public string FullName { get; set; }
    }
}
View Code

全部這些對象都在集合_memberDocumentCollection中。而MemberTypes有 構造函數、事件、字段、屬性等8個部分

 // 摘要:
    //     標記每一個已定義爲 MemberInfo 的派生類的成員類型。
    [Serializable]
    [ComVisible(true)]
    [Flags]
    public enum MemberTypes
    {
        // 摘要:
        //     指定該成員是一個構造函數,表示 System.Reflection.ConstructorInfo 成員。 0x01 的十六進制值。
        Constructor = 1,
        //
        // 摘要:
        //     指定該成員是一個事件,表示 System.Reflection.EventInfo 成員。 0x02 的十六進制值。
        Event = 2,
        //
        // 摘要:
        //     指定該成員是一個字段,表示 System.Reflection.FieldInfo 成員。 0x04 的十六進制值。
        Field = 4,
        //
        // 摘要:
        //     指定該成員是一個方法,表示 System.Reflection.MethodInfo 成員。 0x08 的十六進制值。
        Method = 8,
        //
        // 摘要:
        //     指定該成員是一個屬性,表示 System.Reflection.PropertyInfo 成員。 0x10 的十六進制值。
        Property = 16,
        //
        // 摘要:
        //     指定該成員是一種類型,表示 System.Reflection.MemberTypes.TypeInfo 成員。 0x20 的十六進制值。
        TypeInfo = 32,
        //
        // 摘要:
        //     指定該成員是一個自定義成員類型。 0x40 的十六進制值。
        Custom = 64,
        //
        // 摘要:
        //     指定該成員是一個嵌套類型,可擴展 System.Reflection.MemberInfo。
        NestedType = 128,
        //
        // 摘要:
        //     指定全部成員類型。
        All = 191,
    }
View Code

2.Onsave

  public void OnSave()
        {
            //保存 _memberDocumentCollection 爲 bakFileName文件
            SerializeMemberDocument(MemberDocumentCollection.CollectionList, _bakFileName);
        }

        private void SerializeMemberDocument( List<MemberDocument> memberList,string xmlFileName )
        {
            var xd = new XmlDocument();
            using (var sw = new StringWriter())
            {
                var xz = new XmlSerializer(typeof(List<MemberDocument>));//memberList.GetType()
                xz.Serialize(sw, memberList);
                //Console.WriteLine(sw.ToString());
                xd.LoadXml(sw.ToString());
                xd.Save(xmlFileName);
            }
        }
View Code

Save的時候將集合的中的list寫入到xml中。

3.OnGenerateChm

  public string OnGenerateChm(string title,string fileName,string path="")
        {
            return DoucmentGenerator.ExtractHelpFile(this, title, fileName,path);
        }
View Code

ExtractHelpFile 須要生成的對象越多,時間越久。這裏就是參考了Sandcastle的源碼,從新組織了模板。

部分核心代碼:

  public static string ExtractHelpFile(MemberDocumentApplet memberDocumentApplet, string title, string fileName,string path = "")//string dllFullPath, string xmlFullPath
        {
            string myDocumentPath = String.IsNullOrEmpty(path)? Environment.GetFolderPath(Environment.SpecialFolder.Desktop):path;
            string outputFileName = myDocumentPath + "\\Help\\" + fileName + ".chm";//此處順序不能調整

            if (File.Exists(outputFileName)) File.Delete(outputFileName);
            htmlFolder = myDocumentPath + "\\Help\\Working\\Output\\HtmlHelp1\\html\\";
            workingFolder = myDocumentPath + "\\Help\\Working\\";
            outputFolder = myDocumentPath + "\\Help\\";
            help1Folder = myDocumentPath + "\\Help\\Working\\Output\\HtmlHelp1\\";
            helpName = fileName;
            helpTitle = title;
            
            _memberDocumentApplet = memberDocumentApplet;

            if (fieldMatchEval == null)fieldMatchEval = new MatchEvaluator(OnFieldMatch);
         
            //建立臨時工做區
            if (Directory.Exists(workingFolder))
                Directory.Delete(workingFolder, true);
            CopyDirectory(workingSourcePath, outputFolder);

            //經過反射獲得toc.xml文件
            tocFile = GenerateIntermediateToc();

            if (!File.Exists(tocFile)) return string.Empty;

            //根據toc.xml生成htm文件
            GenerateHtmFiles();

            //根據toc.xml生成hhc
            WriteHelp1XTableOfContents();
            //GenerateHHC();
            GenerateHHK();
            //根據toc.xml生成hhk
            WriteHelp1XKeywordIndex();

            //生成編譯配置文件hhp
            TransformTemplate("Help1x.hhp", templatePath, workingFolder);

            //生成編譯工程文件*.proj
            TransformTemplate("Build1xHelpFile.proj", templatePath, workingFolder);

            //用MSBuild編譯生成chm
            RunProcess(msBuildPath, "Build1xHelpFile.proj");

            //刪除臨時工做區
            if (Directory.Exists(workingFolder))
                Directory.Delete(workingFolder, true);

            if (File.Exists(outputFileName))
                return outputFileName;

            return string.Empty;
        }
View Code

3、Web部分

這個時候web就只是一種表現形式了,由於web不一樣於winform,不能直接獲取用戶電腦上的文件,也不能直接將文件生成到用戶電腦上。因此就要想上傳,編輯生成以後下載下來。

 上傳以後,點擊編輯文檔

  

   這樣每一個人均可以在線編輯本身負責的部分,而後生成文檔下載下來給用戶使用。

  PS:下載下來的chm文檔可能打不開,須要在屬性裏面解除鎖定。由於一些緣由,暫時不方便公開源碼,只能分享下實現思路。

相關文章
相關標籤/搜索