【问题标题】:C# Pointer in a HTML fileHTML 文件中的 C# 指针
【发布时间】:2020-02-25 18:51:58
【问题描述】:

目标:

在 HTML 文件中为 C# 中文件的“结束”创建一个标记

我在 C# 中遍历一个文件并获取表值并将它们存储在数据库中。文件每天都在增长,添加的结果只是被附加到报告中。

在 C# 中,我有一个 StreamReader,它遍历每个文件并将值放在一个列表中以插入 SQL 数据库。

string file_path = @"C:\This\Is\The\Way";
string[] files = Directory.GetFiles(file_path, "*.html", SearchOption.TopDirectoryOnly);
List<string> file_contents = new List<string>();
int* file_pointer; // Create my pointer
foreach(string file in files)
{
    using (StreamReader reader = new StreamReader(file))
    {
        string line = string.Empty;
        while ((line = reader.ReadLine()) != null)
        {
            // Remove <td> and </td>
            line = line.Substring(4);
            line = line.Remove(4);
            // store in a list
            file_contents.Add(line);

            // Would I leave a pointer here ? then just keep moving it after every line until the end?
            // The thought for line.Contains("</td>") is that the last line, 
            // </html> will just be moved to the end and the pointer wont show new rows. 
            // this way it goes to last </td>
            if(line.Contains("</td>")
                file_pointer += 1;
         }
    }
    foreach (string item in file_contents)
        // SQL insert, not implemented yet
    file_contents.Clear();
}

“测试”数据

<table>
<tr>
<th>Test Unique to File</th>
<th>Data Point A Unique to Test</th>
<th>Data Point B Unique to Test</th>
<th>Time Taken</th>
<th>Pass/Fail</th>
</tr>
<tr>
<td>Test A Run</td>
<td>Data Point A</td>
<td>Data Point B</td>
<td>213 seconds</td>
<td>Pass</td>
</tr>
<tr>
<td>Test B Run</td>
<td>Data Point A</td>
<td>Data Point B</td>
<td>3333 seconds</td>
<td>Fail</td>
</tr>
<tr>
<td>Test C Run</td>
<td>Data Point A</td>
<td>Data Point B</td>
<td>12 seconds</td>
<td>Pass</td>
</tr>
</table>

目标是一旦 html 报告被解析并插入到数据库中,就会在该 html 文件中标记一个位置。 然后说明天,又增加了 6 行。我们无需遍历整个文件并验证要插入的所有内容都不是重复的,而是可以从中断的地方继续。

我不确定解决此问题的最佳方法,希望得到一些指导。

问题:在每个末尾创建一个指针是最好的选择吗?

【问题讨论】:

  • 如果你提供一个表格行的例子会有所帮助,也许有更好的方法来做到这一点
  • 这是一个非常奇怪和低效的存储系统。为什么要将 HTML 重复附加到同一个文件而不是将它们存储为单独的文件?
  • 此代码无法编译。您将“内存”指针(int* 指针)与行指针(或索引器,int currentLine)混淆了。内存指针只能在不安全的上下文中工作,并且指针算术非常危险。您可能想要使用 int 指针(不带星号)。
  • @Travis 如果您无法进入 html 文件生成过程,这意味着如果您将其保存在同一个文件中,您无法确定它不会覆盖您的行标记。你应该把它保存在别的地方。提议:将当前行标记保存在 {filename}.llm 中。 llm 代表最后一行标记。在您的代码中,当加载 {filename}.html 文件时,请尝试读取 {filename}.llm。如果找到它,请将内容转换为 int 并从 {filename}.html 中跳过相同数量的行。
  • 我想我会建议始终解析整个文件,并将数据与数据库进行比较,只添加新行。这将反复重新处理数据,但可以毫无问题地处理重置和意外重新排序。

标签: c# html


【解决方案1】:

除非您想创建您的 html 解析器(相信我,这对 a** 来说很痛苦),否则我认为这可能是一个不需要行标记的解决方案。 我正在使用 HtmlAgilityPack nuget 包:

Install-Package HtmlAgilityPack

那么我的代码将如下所示:

private static void Run(string[] files)
{
    var sql = $@"
        MERGE TestsResults AS target  
        USING (SELECT @TestId, @DataPointA, @DataPointB, @Duration, @Result) AS source (TestID, DataPointA, DataPointB, Duration, Result)  
        ON (target.TestID = source.TestID)  
        WHEN MATCHED THEN
            UPDATE SET DataPointA = source.DataPointA, DataPointB = source.DataPointB, Duration = source.Duration, Result = source.Result
        WHEN NOT MATCHED THEN  
            INSERT (TestID, DataPointA, DataPointB, Duration, Result)  
            VALUES (source.TestID, source.DataPointA, source.DataPointB, source.Duration, source.Result);";
    // Get database connection
    var dbConnection = GetDbConnection();
    // Create base command from sql
    var cmd = dbConnection.CreateCommand();
    cmd.CommandType = CommandType.Text;
    cmd.CommandText = sql;
    var paramsNames = new[] { "@TestId", "@DataPointA", "@DataPointB", "@Duration", "@Result" };

    foreach (var file in files)
    {
        // Using HtmlAgilityPack to load the document
        var doc = new HtmlAgilityPack.HtmlDocument();
        doc.Load(file);
        // Getting all TR from document, skipping first with header
        var rowsCollection = doc.DocumentNode.SelectNodes("//tr").Skip(1);
        foreach (var row in rowsCollection)
        {
            // Getting all TDs in current row and converting contents to string[]
            var values = row.SelectNodes("td").Select(el => el.InnerText).ToArray();

            // insert or update in database: each row is a new DbParameter set for our DbCommand
            cmd.Parameters.Clear();
            for (int i = 0; i < paramsNames.Length; i++)
            {
                var param = cmd.CreateParameter();
                param.ParameterName = paramsNames[i];
                param.DbType = DbType.String;
                param.Value = values[i];
                cmd.Parameters.Add(param);
            }
            // Run the command, will update existing tests and adding new ones
            cmd.ExecuteNonQuery();
        }
    }
}

我正在为 SQL Server 使用 MERGE 语句的强大功能:https://docs.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql

基本上,MERGE 语句将更新匹配记录并插入新记录。

请注意,您可能需要对数据进行一些额外的清理(例如,Duration 可以转换为 int,通过/失败转换为布尔值,...)。

如果您超级确定文件生成器将始终追加行(并且不会删除或修改任何先前工作的行),并且不想通过已经解析的行,这个是解决方案:

private static void Run(string[] files)
{
    var sql = $@"
        MERGE TestsResults AS target  
        USING (SELECT @TestId, @DataPointA, @DataPointB, @Duration, @Result) AS source (TestID, DataPointA, DataPointB, Duration, Result)  
        ON (target.TestID = source.TestID)  
        WHEN MATCHED THEN
            UPDATE SET DataPointA = source.DataPointA, DataPointB = source.DataPointB, Duration = source.Duration, Result = source.Result
        WHEN NOT MATCHED THEN  
            INSERT (TestID, DataPointA, DataPointB, Duration, Result)  
            VALUES (source.TestID, source.DataPointA, source.DataPointB, source.Duration, source.Result);";
    // Get database connection
    var dbConnection = GetDbConnection();
    // Create base command from sql
    var cmd = dbConnection.CreateCommand();
    cmd.CommandType = CommandType.Text;
    cmd.CommandText = sql;
    var paramsNames = new[] { "@TestId", "@DataPointA", "@DataPointB", "@Duration", "@Result" };

    foreach (var file in files)
    {
        // Using HtmlAgilityPack to load the document
        var doc = new HtmlAgilityPack.HtmlDocument();
        doc.Load(file);
        int skipLines = 1;

        // Try to load last line marker file
        var lastLineMarkerPath = Path.ChangeExtension(file, ".llm");
        int lastWorkedLine = 0;
        if (File.Exists(lastLineMarkerPath))
        {
            var text = File.ReadAllText(lastLineMarkerPath);
            if (int.TryParse(text, out lastWorkedLine))
            {
                // if file exists and content is convertible to int, add found value to the numer of line to skip
                skipLines += lastWorkedLine;
            }
        }
        // Getting all TR from document, skipping first with header
        var rowsCollection = doc.DocumentNode.SelectNodes("//tr").Skip(skipLines);
        foreach (var row in rowsCollection)
        {
            // Getting all TDs in current row and converting contents to string[]
            var values = row.SelectNodes("td").Select(el => el.InnerText).ToArray();

            // insert or update in database: each row is a new DbParameter set for our DbCommand
            cmd.Parameters.Clear();
            for (int i = 0; i < paramsNames.Length; i++)
            {
                var param = cmd.CreateParameter();
                param.ParameterName = paramsNames[i];
                param.DbType = DbType.String;
                param.Value = values[i];
                cmd.Parameters.Add(param);
            }
            // Run the command, will update existing tests and adding new ones
            cmd.ExecuteNonQuery();
        }

        // write total row count to last line marker file
        File.WriteAllText(lastLineMarkerPath, (rowsCollection.Count() + lastWorkedLine).ToString());
    }
}

虽然我建议您始终使用整个文件。

【讨论】:

  • 那么在第一个例子中,我的表名应该是MERGE my_table AS target,那么就保持原样?
  • 是的,用您的实际表名替换“TestsResults”。根据您的情况修复其他可能的命名差异。
猜你喜欢
  • 2021-11-06
  • 1970-01-01
  • 2023-03-11
  • 1970-01-01
  • 2016-03-04
  • 1970-01-01
  • 2016-03-05
  • 1970-01-01
  • 2021-08-14
相关资源
最近更新 更多