稼布欤 发表于 2025-6-28 09:38:20

基于HTML Help Workshop生成chm格式数据库说明文档

解决的问题

现有的工具都存在一些问题:比如smart Sql,无法一次完成多个库文档生成到一个文件中,且使用过程中存在表结构过多时生成失败问题,像视图、存储过程部分生成也存在一定局限性(无DDL信息,一般没有存储过程);故想研究一下自行代码实现。
基本思路


[*]基于ado.net读取元数据,需要啥读啥,目前只包含了表、视图、存储过程,数据库基于oracle;
[*]生成对应的html、hhp、hhc文件;
[*]调起HTML Help Workshop工具生成chm格式文件。
具体源码


[*]读取元数据
    public class OracleMetadataService : IDisposable
    {
      private readonly OracleConnection _connection;

      public OracleMetadataService(string connectionString)
      {
            _connection = new OracleConnection(connectionString);
            _connection.Open();
      }

      public List<TableInfo> GetTables()
      {
            var tables = new List<TableInfo>();

            // 优化:批量获取表基本信息和列信息
            using (var cmd = new OracleCommand(
                @"SELECT table_name, comments
            FROM user_tab_comments
            WHERE table_type = 'TABLE'
            ORDER BY table_name", _connection))
            {
                using (var reader = cmd.ExecuteReader())
                {
                  while (reader.Read())
                  {
                        var table = new TableInfo
                        {
                            TableName = reader["table_name"].ToString(),
                            Comments = reader["comments"].ToString()
                        };
                        table.Columns = GetColumns(table.TableName);
                        SetPrimaryKeys(table);
                        SetForeignKeys(table);
                        tables.Add(table);
                  }
                }
            }

            Console.WriteLine("表信息获取完成");

            return tables;
      }

      private List<ColumnInfo> GetColumns(string tableName)
      {
            var columns = new List<ColumnInfo>();

            using (var cmd = new OracleCommand(
                @"SELECT
                tc.column_name, data_type, data_length, data_precision, data_scale,
                nullable, data_default, comments
            FROM user_tab_columns tc
            LEFT JOIN user_col_comments cc ON tc.table_name = cc.table_name AND tc.column_name = cc.column_name
            WHERE tc.table_name = :tableName
            ORDER BY column_id", _connection))
            {
                cmd.Parameters.Add("tableName", OracleDbType.Varchar2).Value = tableName;

                using (var reader = cmd.ExecuteReader())
                {
                  while (reader.Read())
                  {
                        columns.Add(new ColumnInfo
                        {
                            ColumnName = reader["column_name"].ToString(),
                            DataType = reader["data_type"].ToString(),
                            DataLength = reader["data_length"] as int?,
                            DataPrecision = reader["data_precision"] as int?,
                            DataScale = reader["data_scale"] as int?,
                            Nullable = reader["nullable"].ToString(),
                            DefaultValue = reader["data_default"]?.ToString(),
                            Comments = reader["comments"]?.ToString()
                        });
                  }
                }
            }

            return columns;
      }

      private void SetPrimaryKeys(TableInfo table)
      {
            using (var cmd = new OracleCommand(
                @"SELECT cols.column_name
            FROM user_constraints cons, user_cons_columns cols
            WHERE cons.constraint_type = 'P'
            AND cons.constraint_name = cols.constraint_name
            AND cons.table_name = :tableName", _connection))
            {
                cmd.Parameters.Add("tableName", OracleDbType.Varchar2).Value = table.TableName;

                using (var reader = cmd.ExecuteReader())
                {
                  while (reader.Read())
                  {
                        var columnName = reader["column_name"].ToString();
                        var column = table.Columns.Find(c => c.ColumnName == columnName);
                        if (column != null)
                        {
                            column.IsPrimaryKey = true;
                        }
                  }
                }
            }
      }

      private void SetForeignKeys(TableInfo table)
      {
            using (var cmd = new OracleCommand(
                @"SELECT cols.column_name
            FROM user_constraints cons, user_cons_columns cols
            WHERE cons.constraint_type = 'R'
            AND cons.constraint_name = cols.constraint_name
            AND cons.table_name = :tableName", _connection))
            {
                cmd.Parameters.Add("tableName", OracleDbType.Varchar2).Value = table.TableName;

                using (var reader = cmd.ExecuteReader())
                {
                  while (reader.Read())
                  {
                        var columnName = reader["column_name"].ToString();
                        var column = table.Columns.Find(c => c.ColumnName == columnName);
                        if (column != null)
                        {
                            column.IsForeignKey = true;
                        }
                  }
                }
            }
      }

      public List<ViewInfo> GetViews(string owner)
      {
            var views = new List<ViewInfo>();

            // 获取视图基本信息
            using (var cmd = new OracleCommand(
                @"SELECT view_name, text, comments
            FROM user_views v
            LEFT JOIN user_tab_comments tc ON v.view_name = tc.table_name
            ORDER BY view_name", _connection))
            {
                using (var reader = cmd.ExecuteReader())
                {
                  while (reader.Read())
                  {
                        views.Add(new ViewInfo
                        {
                            ViewName = reader["view_name"].ToString(),
                            //Definition = reader.GetOracleClob(0)?.Value,
                            Comments = reader["comments"]?.ToString()
                        });
                  }
                }
            }

            Console.WriteLine($"视图基本信息获取完成");

            // 获取每张视图的列信息
            foreach (var view in views)
            {
                view.Definition = GetViewDDL(_connection, view.ViewName, owner);
                view.Columns = GetColumns(view.ViewName);
                Console.WriteLine($"视图[{view.ViewName}]列信息获取完成");
            }

            return views;
      }

      static string GetViewDDL(OracleConnection connection, string viewName, string schema)
      {
            string sql = @"
                SELECT DBMS_METADATA.GET_DDL('VIEW', :viewName, :schema)
                FROM DUAL";

            using (OracleCommand command = new OracleCommand(sql, connection))
            {
                command.Parameters.Add(":viewName", viewName);
                command.Parameters.Add(":schema", schema);

                command.InitialLOBFetchSize = 1000000;
                object result = command.ExecuteScalar();
                return result != null ? result.ToString() : string.Empty;
            }
      }

      public List<ProcedureInfo> GetProcedures()
      {
            var procedures = new List<ProcedureInfo>();

            // 获取存储过程和函数基本信息
            using (var cmd = new OracleCommand(
                @"SELECT object_name, object_type, status, created, last_ddl_time
            FROM user_objects
            WHERE object_type IN ('PROCEDURE', 'FUNCTION')
            ORDER BY object_type, object_name", _connection))
            {
                using (var reader = cmd.ExecuteReader())
                {
                  while (reader.Read())
                  {
                        procedures.Add(new ProcedureInfo
                        {
                            ProcedureName = reader["object_name"].ToString(),
                            ProcedureType = reader["object_type"].ToString()
                        });
                  }
                }
            }

            Console.WriteLine($"存储过程基本信息获取完成");

            // 获取每个对象的详细信息和参数
            foreach (var proc in procedures)
            {
                // 获取源代码
                using (var cmd = new OracleCommand(
                  @"SELECT text
                  FROM user_source
                  WHERE name = :procName
                  AND type = :procType
                  ORDER BY line", _connection))
                {
                  cmd.Parameters.Add("procName", OracleDbType.Varchar2).Value = proc.ProcedureName;
                  cmd.Parameters.Add("procType", OracleDbType.Varchar2).Value = proc.ProcedureType;

                  var source = new System.Text.StringBuilder();
                  using (var reader = cmd.ExecuteReader())
                  {
                        while (reader.Read())
                        {
                            source.AppendLine(reader["text"].ToString());
                        }
                  }
                  proc.SourceCode = source.ToString();
                }

                // 获取参数信息
                proc.Parameters = GetProcedureParameters(proc.ProcedureName);

                // 如果是函数,获取返回类型
                if (proc.ProcedureType == "FUNCTION")
                {
                  using (var cmd = new OracleCommand(
                        @"SELECT data_type
                      FROM user_arguments
                      WHERE object_name = :procName
                      AND argument_name IS NULL", _connection))
                  {
                        cmd.Parameters.Add("procName", OracleDbType.Varchar2).Value = proc.ProcedureName;
                        proc.ReturnType = cmd.ExecuteScalar()?.ToString();
                  }
                }

                //// 获取注释
                //using (var cmd = new OracleCommand(
                //    @"SELECT comments
                //FROM user_procedures
                //WHERE object_name = :procName
                //AND object_type = :procType", _connection))
                //{
                //    cmd.Parameters.Add("procName", OracleDbType.Varchar2).Value = proc.ProcedureName;
                //    cmd.Parameters.Add("procType", OracleDbType.Varchar2).Value = proc.ProcedureType;
                //    proc.Comments = cmd.ExecuteScalar()?.ToString();
                //}

                Console.WriteLine($"存储过程[{proc.ProcedureName}]详细信息获取完成");
            }

            return procedures;
      }

      private List<ParameterInfo> GetProcedureParameters(string procedureName)
      {
            var parameters = new List<ParameterInfo>();

            using (var cmd = new OracleCommand(
                @"SELECT argument_name, data_type, in_out, position
            FROM user_arguments
            WHERE object_name = :procName
            AND argument_name IS NOT NULL
            ORDER BY position", _connection))
            {
                cmd.Parameters.Add("procName", OracleDbType.Varchar2).Value = procedureName;

                using (var reader = cmd.ExecuteReader())
                {
                  while (reader.Read())
                  {
                        parameters.Add(new ParameterInfo
                        {
                            ParameterName = reader["argument_name"].ToString(),
                            DataType = reader["data_type"].ToString(),
                            InOut = reader["in_out"].ToString(),
                            Position = reader["position"] as int?
                        });
                  }
                }
            }

            return parameters;
      }

      public void Dispose()
      {
            _connection?.Close();
            _connection?.Dispose();
      }
    }

[*]生成html、hhp、hhc文件
    public class HtmlGeneratorService
    {
      private readonly string _outputDirectory;

      public HtmlGeneratorService(string outputDirectory)
      {
            _outputDirectory = outputDirectory;
            Directory.CreateDirectory(_outputDirectory);
      }

      public void GenerateHtmlFiles(Dictionary<string, Tuple<List<TableInfo>, List<ViewInfo>, List<ProcedureInfo>>> dic)
      {
            GenerateIndexFile(dic);
            foreach (var item in dic)
            {
                GenerateTablesFiles(item.Key, item.Value.Item1);
                GenerateViewsFiles(item.Key, item.Value.Item2);
                GenerateProceduresFiles(item.Key, item.Value.Item3);
            }
            GenerateStyleSheet();
      }

      private void GenerateIndexFile(Dictionary<string, Tuple<List<TableInfo>, List<ViewInfo>, List<ProcedureInfo>>> dic)
      {
            var sb = new StringBuilder();
            sb.AppendLine("<!DOCTYPE html>");
            sb.AppendLine("<html>");
            sb.AppendLine("<head>");
            sb.AppendLine($"<title>数据库说明文档</title>");
            sb.AppendLine("<link rel=\"stylesheet\" href=\"styles.css\">");
            sb.AppendLine("</head>");
            sb.AppendLine("<body>");
            sb.AppendLine($"<h1>数据库说明文档</h1>");

            foreach (var item in dic)
            {
                sb.AppendLine($"<h2>{item.Key}</h2>");

                // 表目录
                sb.AppendLine("<h3>Tables</h3>");
                sb.AppendLine("<ul>");
                foreach (var table in item.Value.Item1)
                {
                  sb.AppendLine($"<li>{table.TableName} - {table.Comments}</li>");
                }
                sb.AppendLine("</ul>");

                // 视图目录
                sb.AppendLine("<h3>Views</h3>");
                sb.AppendLine("<ul>");
                foreach (var view in item.Value.Item2)
                {
                  sb.AppendLine($"<li>{view.ViewName} - {view.Comments}</li>");
                }
                sb.AppendLine("</ul>");

                // 存储过程和函数目录
                sb.AppendLine("<h3>Procedures and Functions</h3>");
                sb.AppendLine("<ul>");
                foreach (var proc in item.Value.Item3)
                {
                  sb.AppendLine($"<li>{proc.ProcedureName} ({proc.ProcedureType})</li>");
                }
                sb.AppendLine("</ul>");

            }

            sb.AppendLine("</body>");
            sb.AppendLine("</html>");

            File.WriteAllText(Path.Combine(_outputDirectory, "index.html"), sb.ToString(), Encoding.UTF8);
      }

      private void GenerateTablesFiles(string owner, List<TableInfo> tables)
      {
            var tablesDir = Path.Combine(_outputDirectory, owner, "tables");
            Directory.CreateDirectory(tablesDir);

            foreach (var table in tables)
            {
                var sb = new StringBuilder();
                sb.AppendLine("<!DOCTYPE html>");
                sb.AppendLine("<html>");
                sb.AppendLine("<head>");
                sb.AppendLine($"<title>Table: {table.TableName}</title>");
                sb.AppendLine("<link rel=\"stylesheet\" href=\"../../styles.css\">");
                sb.AppendLine("</head>");
                sb.AppendLine("<body>");
                sb.AppendLine($"<h1>Table: {table.TableName}</h1>");
                sb.AppendLine($"<p>{table.Comments}</p>");

                sb.AppendLine("<h2>Columns</h2>");
                sb.AppendLine("<table class=\"gridtable\">");
                sb.AppendLine("<tr>");
                sb.AppendLine("<th>Name</th>");
                sb.AppendLine("<th>Type</th>");
                sb.AppendLine("<th>Length</th>");
                sb.AppendLine("<th>Precision</th>");
                sb.AppendLine("<th>Scale</th>");
                sb.AppendLine("<th>Nullable</th>");
                sb.AppendLine("<th>Default</th>");
                sb.AppendLine("<th>PK</th>");
                sb.AppendLine("<th>FK</th>");
                sb.AppendLine("<th>Description</th>");
                sb.AppendLine("</tr>");

                foreach (var column in table.Columns)
                {
                  sb.AppendLine("<tr>");
                  sb.AppendLine($"<td>{column.ColumnName}</td>");
                  sb.AppendLine($"<td>{column.DataType}</td>");
                  sb.AppendLine($"<td>{column.DataLength}</td>");
                  sb.AppendLine($"<td>{column.DataPrecision}</td>");
                  sb.AppendLine($"<td>{column.DataScale}</td>");
                  sb.AppendLine($"<td>{column.Nullable}</td>");
                  sb.AppendLine($"<td>{column.DefaultValue}</td>");
                  sb.AppendLine($"<td>{(column.IsPrimaryKey ? "✓" : "")}</td>");
                  sb.AppendLine($"<td>{(column.IsForeignKey ? "✓" : "")}</td>");
                  sb.AppendLine($"<td>{column.Comments}</td>");
                  sb.AppendLine("</tr>");
                }

                sb.AppendLine("</table>");
                sb.AppendLine("<p>Back to Index</p>");
                sb.AppendLine("</body>");
                sb.AppendLine("</html>");

                File.WriteAllText(Path.Combine(tablesDir, $"{table.TableName}.html"), sb.ToString(), Encoding.UTF8);
            }
      }

      private void GenerateViewsFiles(string owner, List<ViewInfo> views)
      {
            var viewsDir = Path.Combine(_outputDirectory, owner, "views");
            Directory.CreateDirectory(viewsDir);

            foreach (var view in views)
            {
                var sb = new StringBuilder();
                sb.AppendLine("<!DOCTYPE html>");
                sb.AppendLine("<html>");
                sb.AppendLine("<head>");
                sb.AppendLine($"<title>View: {view.ViewName}</title>");
                sb.AppendLine("<link rel=\"stylesheet\" href=\"../../styles.css\">");
                sb.AppendLine("</head>");
                sb.AppendLine("<body>");
                sb.AppendLine($"<h1>View: {view.ViewName}</h1>");
                sb.AppendLine($"<p>{view.Comments}</p>");

                sb.AppendLine("<h2>Columns</h2>");
                sb.AppendLine("<table class=\"gridtable\">");
                sb.AppendLine("<tr>");
                sb.AppendLine("<th>Name</th>");
                sb.AppendLine("<th>Type</th>");
                sb.AppendLine("<th>Length</th>");
                sb.AppendLine("<th>Precision</th>");
                sb.AppendLine("<th>Scale</th>");
                sb.AppendLine("<th>Nullable</th>");
                sb.AppendLine("<th>Description</th>");
                sb.AppendLine("</tr>");

                foreach (var column in view.Columns)
                {
                  sb.AppendLine("<tr>");
                  sb.AppendLine($"<td>{column.ColumnName}</td>");
                  sb.AppendLine($"<td>{column.DataType}</td>");
                  sb.AppendLine($"<td>{column.DataLength}</td>");
                  sb.AppendLine($"<td>{column.DataPrecision}</td>");
                  sb.AppendLine($"<td>{column.DataScale}</td>");
                  sb.AppendLine($"<td>{column.Nullable}</td>");
                  sb.AppendLine($"<td>{column.Comments}</td>");
                  sb.AppendLine("</tr>");
                }

                sb.AppendLine("</table>");

                sb.AppendLine("<h2>Definition</h2>");
                sb.AppendLine("<pre>");
                sb.AppendLine(view.Definition);
                sb.AppendLine("</pre>");

                sb.AppendLine("<p>Back to Index</p>");
                sb.AppendLine("</body>");
                sb.AppendLine("</html>");

                File.WriteAllText(Path.Combine(viewsDir, $"{view.ViewName}.html"), sb.ToString(), Encoding.UTF8);
            }
      }

      private void GenerateProceduresFiles(string owner, List<ProcedureInfo> procedures)
      {
            var procsDir = Path.Combine(_outputDirectory, owner, "procedures");
            Directory.CreateDirectory(procsDir);

            foreach (var proc in procedures)
            {
                var sb = new StringBuilder();
                sb.AppendLine("<!DOCTYPE html>");
                sb.AppendLine("<html>");
                sb.AppendLine("<head>");
                sb.AppendLine($"<title>{proc.ProcedureType}: {proc.ProcedureName}</title>");
                sb.AppendLine("<link rel=\"stylesheet\" href=\"../../styles.css\">");
                sb.AppendLine("</head>");
                sb.AppendLine("<body>");
                sb.AppendLine($"<h1>{proc.ProcedureType}: {proc.ProcedureName}</h1>");
                sb.AppendLine($"<p>{proc.Comments}</p>");

                if (proc.ProcedureType == "FUNCTION" && !string.IsNullOrEmpty(proc.ReturnType))
                {
                  sb.AppendLine($"<p>Return Type: {proc.ReturnType}</p>");
                }

                if (proc.Parameters.Count > 0)
                {
                  sb.AppendLine("<h2>Parameters</h2>");
                  sb.AppendLine("<table class=\"gridtable\">");
                  sb.AppendLine("<tr>");
                  sb.AppendLine("<th>Name</th>");
                  sb.AppendLine("<th>Type</th>");
                  sb.AppendLine("<th>In/Out</th>");
                  sb.AppendLine("<th>Position</th>");
                  sb.AppendLine("</tr>");

                  foreach (var param in proc.Parameters)
                  {
                        sb.AppendLine("<tr>");
                        sb.AppendLine($"<td>{param.ParameterName}</td>");
                        sb.AppendLine($"<td>{param.DataType}</td>");
                        sb.AppendLine($"<td>{param.InOut}</td>");
                        sb.AppendLine($"<td>{param.Position}</td>");
                        sb.AppendLine("</tr>");
                  }

                  sb.AppendLine("</table>");
                }

                sb.AppendLine("<h2>Source Code</h2>");
                sb.AppendLine("<pre>");
                sb.AppendLine(proc.SourceCode);
                sb.AppendLine("</pre>");

                sb.AppendLine("<p>Back to Index</p>");
                sb.AppendLine("</body>");
                sb.AppendLine("</html>");

                File.WriteAllText(Path.Combine(procsDir, $"{proc.ProcedureName}.html"), sb.ToString(), Encoding.UTF8);
            }
      }

      private void GenerateStyleSheet()
      {
            var css = @"
body {
    font-family: Consolas, monospace;
    line -height: 1.6;
    margin: 20px;
    color: #808080;
}

h1, h2, h3 {
    color: #0066cc;
}

table.gridtable {
    width: 100%;
    border-collapse: collapse;
    margin: 15px 0;
}

table.gridtable th {
    background-color: #006400;
    color: white;
    text-align: center;
    padding: 3px;
}

table.gridtable td {
    border:#ddd;
    padding: 3px;
}

table.gridtable tr:nth-child(even) {
    background-color: #f2f2f2;
}

pre {
    background-color: #f5f5f5;
    padding: 10px;
    border-radius: 5px;
    overflow-x: auto;
}

a {
    color: #A52A2A;
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

";

            File.WriteAllText(Path.Combine(_outputDirectory, "styles.css"), css);
      }
    }

[*]生成chm文件
    public class ChmCompiler
    {
      private readonly string _htmlHelpCompilerPath;

      public ChmCompiler(string htmlHelpCompilerPath)
      {
            _htmlHelpCompilerPath = htmlHelpCompilerPath;
      }

      public bool Compile(string projectFilePath, string outputChmPath)
      {
            if (!File.Exists(_htmlHelpCompilerPath))
            {
                throw new FileNotFoundException("HTML Help Compiler (hhc.exe) not found at the specified path.");
            }

            if (!File.Exists(projectFilePath))
            {
                throw new FileNotFoundException("HHP project file not found.");
            }

            try
            {
                var process = new Process
                {
                  StartInfo = new ProcessStartInfo
                  {
                        FileName = _htmlHelpCompilerPath,
                        Arguments = $"\"{projectFilePath}\"",
                        UseShellExecute = false,
                        CreateNoWindow = true,
                        RedirectStandardOutput = true,
                        RedirectStandardError = true
                  }
                };

                process.Start();
                process.WaitForExit(60 * 1000);

                //if (process.ExitCode != 0)
                //{
                //    var error = process.StandardError.ReadToEnd();
                //    throw new Exception($"CHM compilation failed with error: {error}");
                //}

                // 检查生成的CHM文件是否存在
                return File.Exists(outputChmPath);
            }
            catch (Exception ex)
            {
                throw new Exception("Error during CHM compilation: " + ex.Message, ex);
            }
      }

      public void CreateHhpFile(string outputDirectory, string projectName)
      {
            var hhpContent = $@"
Compatibility=1.1 or later
Compiled file={projectName}.chm
Contents file={projectName}.hhc
Default topic=index.html
Display compile progress=No
Full-text search=Yes
Language=0x409 English (United States)
Title={projectName} Database Documentation


";

            // 添加所有HTML文件
            var htmlFiles = Directory.GetFiles(outputDirectory, "*.html", SearchOption.AllDirectories);
            foreach (var file in htmlFiles)
            {
                var relativePath = file.Substring(outputDirectory.Length + 1);
                hhpContent += relativePath + "\n";
            }

            // 添加CSS文件
            hhpContent += "styles.css\n";

            File.WriteAllText(Path.Combine(outputDirectory, $"{projectName}.hhp"), hhpContent);

            // 创建目录文件 (.hhc)

            CreateHhcFile(outputDirectory, projectName);
      }

      private void CreateHhcFile(string outputDirectory, string projectName)
      {
            var hhcContent = new StringBuilder(@"<!DOCTYPE HTML PUBLIC ""-//IETF//DTD HTML//EN"">
<HTML>
<HEAD>
<meta name=""GENERATOR"" content=""Microsoft® HTML Help Workshop 4.1"">

</HEAD>
<BODY>
<OBJECT type=""text/site properties"">
    <param name=""Window Styles"" value=""0x800025"">
</OBJECT>
<UL>
    <LI> <OBJECT type=""text/sitemap"">
      <param name=""Name"" value=""Database Documentation"">
      <param name=""Local"" value=""index.html"">
      </OBJECT>
    <UL>");

            // 获取所有Owner
            var owners = GetAllOwners(outputDirectory);

            foreach (var owner in owners)
            {
                hhcContent.AppendLine($@"
      <LI> <OBJECT type=""text/sitemap"">
            <param name=""Name"" value=""{owner}"">
            </OBJECT>
      <UL>");

                // 添加Tables分组
                hhcContent.AppendLine($@"
            <LI> <OBJECT type=""text/sitemap"">
                <param name=""Name"" value=""Tables"">
                </OBJECT>
            <UL>");

                AddFilesToContent(hhcContent, Path.Combine(outputDirectory, owner, "tables"), outputDirectory);

                hhcContent.AppendLine($@"
            </UL>");

                // 添加Views分组
                hhcContent.AppendLine($@"
            <LI> <OBJECT type=""text/sitemap"">
                <param name=""Name"" value=""Views"">
                </OBJECT>
            <UL>");

                AddFilesToContent(hhcContent, Path.Combine(outputDirectory, owner, "views"), outputDirectory);

                hhcContent.AppendLine($@"
            </UL>");

                // 添加Procedures分组
                hhcContent.AppendLine($@"
            <LI> <OBJECT type=""text/sitemap"">
                <param name=""Name"" value=""Procedures and Functions"">
                </OBJECT>
            <UL>");

                AddFilesToContent(hhcContent, Path.Combine(outputDirectory, owner, "procedures"), outputDirectory);

                hhcContent.AppendLine($@"
            </UL>");

                hhcContent.AppendLine($@"
      </UL>");
            }

            hhcContent.AppendLine($@"
    </UL>
</UL>
</BODY>
</HTML>");

            File.WriteAllText(Path.Combine(outputDirectory, $"{projectName}.hhc"), hhcContent.ToString());
      }

      // 获取所有Owner的辅助方法
      private List<string> GetAllOwners(string outputDirectory)
      {
            // 方法1:从目录结构中获取
            return Directory.GetDirectories(outputDirectory)
                .Select(d => new DirectoryInfo(d).Name)
                .ToList();

            // 方法2:从数据库中获取(需要实现)
            // return GetOwnersFromDatabase();
      }

      // 之前定义的辅助方法
      private void AddFilesToContent(StringBuilder content, string directory, string outputDirectory)
      {
            if (!Directory.Exists(directory))
            {
                return;
            }

            foreach (var file in Directory.GetFiles(directory, "*.html"))
            {
                var itemName = Path.GetFileNameWithoutExtension(file);
                var relativePath = file.Substring(outputDirectory.Length + 1);
                content.AppendLine($@"
                <LI> <OBJECT type=""text/sitemap"">
                  <param name=""Name"" value=""{itemName}"">
                  <param name=""Local"" value=""{relativePath}"">
                  </OBJECT>");
            }
      }
    }

[*]调用
internal class Program
{

    // 查找HHC编译器
    private static string FindHhcCompiler()
    {
      // 检查常见位置
      string[] possiblePaths = new[]
      {
                @"C:\Program Files (x86)\HTML Help Workshop\hhc.exe",
                @"C:\Program Files\HTML Help Workshop\hhc.exe"
            };

      foreach (string path in possiblePaths)
      {
            if (File.Exists(path))
            {
                return path;
            }
      }

      // 检查PATH环境变量
      string pathEnv = Environment.GetEnvironmentVariable("PATH");
      if (!string.IsNullOrEmpty(pathEnv))
      {
            string[] paths = pathEnv.Split(Path.PathSeparator);
            foreach (string path in paths)
            {
                string fullPath = Path.Combine(path.Trim(), "hhc.exe");
                if (File.Exists(fullPath))
                {
                  return fullPath;
                }
            }
      }

      return null;
    }

    private static void Main(string[] args)
    {
      Console.WriteLine("Oracle Database CHM Documentation Generator");
      Console.WriteLine("==========================================");

      try
      {
            //数据库列表,自行添加
            List<string> schemaList = new List<string>
         {
         };

            // 配置参数         
            var baseDir = Path.Combine(Directory.GetCurrentDirectory(), "OracleDocs");


            var hhcPath = FindHhcCompiler();
            var outputDir = Path.Combine(baseDir, "OracleDocs");
            var dic = new Dictionary<string, Tuple<List<TableInfo>, List<ViewInfo>, List<ProcedureInfo>>>();
            foreach (var item in schemaList)
            {
                var projectName = $"TH_{item}";
                var connectionString = $"";//自行添加
                // 创建服务实例
                using (var metadataService = new OracleMetadataService(connectionString))
                {
                  Console.WriteLine("Fetching database metadata...");

                  // 获取元数据
                  var tables = metadataService.GetTables();
                  var views = metadataService.GetViews(projectName);
                  var procedures = metadataService.GetProcedures();
                  dic.Add(projectName, Tuple.Create(tables, views, procedures));

                  Console.WriteLine($"Found {tables.Count} tables, {views.Count} views, {procedures.Count} procedures/functions");
                }
            }

            // 生成HTML文档
            var htmlGenerator = new HtmlGeneratorService(outputDir);
            Console.WriteLine("Generating HTML files...");
            htmlGenerator.GenerateHtmlFiles(dic);

            // 编译CHM
            var chmCompiler = new ChmCompiler(hhcPath);
            Console.WriteLine("Creating CHM project files...");
            chmCompiler.CreateHhpFile(outputDir, "OracleDocs");

            Console.WriteLine("Compiling CHM file...");
            var hhpFilePath = Path.Combine(outputDir, $"TH.hhp");
            var chmFilePath = Path.Combine(outputDir, $"TH.chm");

            //var result = Task.Run(async () => await chmCompiler.RunProcessAsync(hhpFilePath)).Result;
            if (chmCompiler.Compile(hhpFilePath, chmFilePath))
            //if (result == 0)
            {
                Console.WriteLine($"Successfully generated CHM file: {chmFilePath}");
            }
            else
            {
                Console.WriteLine("CHM compilation failed.");
            }
      }
      catch (Exception ex)
      {
            Console.WriteLine($"Error: {ex.Message}");
            if (ex.InnerException != null)
            {
                Console.WriteLine($"Inner exception: {ex.InnerException.Message}");
            }
      }

      Console.WriteLine("Press any key to exit...");
      Console.ReadKey();
    }
}
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 基于HTML Help Workshop生成chm格式数据库说明文档