【问题标题】:How to read a filename and write a specific value from it depending on what is in an xml file?如何根据 xml 文件中的内容读取文件名并从中写入特定值?
【发布时间】:2013-06-19 16:04:48
【问题描述】:

下午好,

我是 VB.net 的新手,我正在为我的 VB.Net windows 窗体项目使用 Visual Studio Express 2012。

我的代码需要一些帮助,因为我不确定如何做我想做的事。

场景是这样的:

用户在表单上选择一个目录并按下一个按钮。然后应用程序将查找一些文件并将它们移动到另一个目录。在该新目录中,它将找到包含三个字符代码的文件名。

然后,应用程序将为 xml 文件中的每个代码分配适当的 docgroup、doctype 和 docsubtype 值。然后将其输出到文本文件。

让应用程序知道要使用的 xml 文件中的哪些 docgroup、doctype 和 docsubtype 值取决于文件文件名,这让我不知所措。

我的 xml 文件结构如下。请注意,这些值不是静态的,用户可以随时在我的设置表单中更改。

<Settings>
 <ApplicationSettings>
   <code>FTO</code>
   <docgroup>Operations</docgroup>
   <doctype>Funds Transfer</doctype>
   <docsubtype>Out</docsubtype>
   <code>FTI</code>
   <docgroup>null</docgroup>
   <doctype>null</doctype>
   <docsubtype>null</docsubtype>
   <code>ACL</code>
   <docgroup>Documentation</docgroup>
   <doctype>Client Documentation</doctype>
   <docsubtype>Termination</docsubtype>
   <code>TBA</code>
   <docgroup>Operations</docgroup>
   <doctype>Funds Transfer Credit</doctype>
   <docsubtype>Reversed</docsubtype>
 </ApplicationSettings>
</Settings>

例如:

用户选择\ServerA\ITDept\files 目录,该目录中的所有文件将始终具有以下命名约定:

AccNum-YYYYMMDD-code 示例:123456-20130610-FTO

\ServerA\ITDept\文件

12345-20130610-FTO 审核扫描.pdf

54265-20130512-FTI A1.pdf

45752-20121204-TBA.pdf

因此,如果我能弄清楚如何编写此代码,这些文件到文本文件的输出将如下所示:

\\ServerA\ITDept\files\12345-20130610-FTO Reviewed and scanned.pdf|12345|_||Operations| Funds Transfer|Out|swfoi6848484|06/10/2013| 
\\ServerA\ITDept\files\54265-20130512-FTI A1.pdf|54265|_||NULL| NULL|NULL|swfoi15157|05/12/2013| 
\\ServerA\ITDept\files\45752-20121204-TBA.pdf|45752|_||Operations|Funds Transfer|Reversed|swfoi54572258|12/04/2012|

目录中会有其他文件有其他代码,如果这些代码不存在于 xml 文件中,则应忽略这些代码。

我的代码如下。除了将 xml 文件中的特定 docgroup、doctype 和 docsubtype 值合并到输出文件中的最后一步,一切都“除了”。

Imports System
Imports System.Xml
Imports System.Text.RegularExpressions

Public Class Userform

Dim xmlfile As String = "\\ServerA\ITDept\XML\Settings.xml"


Private Sub Userform_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    'Check if Setting.xml exists, if not show message box and close application.
    If IO.File.Exists(xmlfile) = False Then
        MessageBox.Show("Cannot locate Settings.xml file. Please contact IT Department for assistance.", "ERROR")
        Me.Close()

    End If

End Sub

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    If FolderBrowserDialog1.ShowDialog() = Windows.Forms.DialogResult.OK Then
        TextBox1.Text = FolderBrowserDialog1.SelectedPath
    End If
End Sub

Private Sub filebtn_Click(sender As Object, e As EventArgs) Handles filebtn.Click

    'New thread will run main tasks of program
    BackgroundWorker1.RunWorkerAsync()

End Sub

'Function used to get date in file name and use value as MM/DD/YYYY in output file

Private Function GetFormattedDateFromFileName(ByVal fileName As String) As String
    Dim parts() As String = fileName.Split("-")
    If parts.Length = 3 Then
        Dim dt As DateTime
        If DateTime.TryParseExact(parts(1), "yyyyMMdd", Nothing, Globalization.DateTimeStyles.None, dt) Then
            Return dt.ToString("MM/dd/yyyy")
        End If
    End If
    Return ""
End Function

Private Sub BackgroundWorker1_DoWork(sender As Object, e As ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

    'create directory in input folder with timestamp as the directory name.

    Dim destdir As String = [String].Format("\\ServerA\ITDept\files\{0}", DateTime.Now.ToString("MMddyyyyhhmmss"))
    System.IO.Directory.CreateDirectory(destdir)

    'read directory and look for filenames that match pattern and have code elements from xml file

    Dim regElemName As New Regex("^code")
    Dim root = XElement.Load(xmlfile)
    Dim codeElements = root.Element("ApplicationSettings").Elements().Where(Function(xe) regElemName.IsMatch(xe.Name.LocalName)).Select(Function(xe) xe.Value)
    Dim codes = String.Join("|", codeElements.ToArray())
    Dim regFileName As New Regex(String.Format("^\d+\-(?<Year>(19|20)[0-9][0-9])(?<Month>0[1-9]|12|11|10)(?<Day>[12]\d|0[1-9]|3[01])\-{0}$", codes))
    Dim files = IO.Directory.GetFiles(TextBox1.Text, "*.pdf", IO.SearchOption.TopDirectoryOnly).Where(Function(path) regFileName.IsMatch(IO.Path.GetFileName(path)))

    For Each file As String In files
        System.IO.File.Move(file, System.IO.Path.Combine(destdir, System.IO.Path.GetFileName(file)))
    Next

    'Define random numbers

    Dim randomclass As New System.Random()
    Dim randomnumber As Integer

    'create txt file from destdir of all files for output.

    Dim str As String = String.Empty
    For Each rfiles As String In System.IO.Directory.GetFiles(destdir)

        randomnumber = randomclass.Next(10000, 99999)

        Dim formattedDate As String = GetFormattedDateFromFileName(rfiles)

        str = str & rfiles & "|" & System.IO.Path.GetFileNameWithoutExtension(rfiles).Split("-")(0).Trim & "|" & "_" & "||" & "docgroup_value" & "|" & "doctype_value" & "|" & "docsubtype_value" & "|" & "swfoi" & randomnumber & "|" & formattedDate & "|" & Environment.NewLine

    Next

    Dim outputname As String = [String].Format("\\ServerA\ITDept\Index\swfoi{0}.txt", DateTime.Now.ToString("MMddyyyyhhmmss"))
    System.IO.File.WriteAllText(outputname, str)

End Sub
End Class

谁能帮我完成这段代码?

亲切的问候, 一个

【问题讨论】:

  • 不应该 docgroupdoctypedocsubtypecode 的孩子,而不是兄弟姐妹?
  • @Tim 不知道。正如我之前所说,我对 VB.net 很陌生。
  • 您已经非常接近您想要做的事情了 - 我建议您更改几件事(除了 XML 格式),并且您缺少获得docgroup、doctype 和 dosubtype 值(我认为这是您真正需要帮助的内容)。如果其他人没有先解决这个问题,我会尝试今晚(西海岸时间)回来并提出一些建议。
  • @Tim Yep 这正是我在“获取 docgroup、doctype 和 dosubtype 值”方面寻求帮助的原因。抱歉,如果我在解释中到处都是,只是不知道如何解释。感谢您的帮助或其他任何想参与其中的人。

标签: vb.net


【解决方案1】:

您几乎走在正确的轨道上 - 我建议您做几件事来完成您的代码。我的建议是一种方式——毫无疑问还有其他方式,因为编程中的大多数事情都可以通过多种方式完成。有时一种方式比另一种更好或更可取,有时归结为个人偏好或给定团队的编码标准。

首先,我将修改您的 XML 文件,使 docgroup、doctype 和 dosubtype 值成为代码元素的子项。 XML 本质上是分层的(想想 Windows 资源管理器中的文件夹和文件),以这种方式组织 XML 可以更容易地搜索。

所以:

<Settings>
  <ApplicationSettings>
    <code id="FTO">
      <docgroup>Operations</docgroup>
      <doctype>Funds Transfer</doctype>
      <docsubtype>Out</docsubtype>
    </code>
    <code id="FTI">
      <docgroup>null</docgroup>
      <doctype>null</doctype>
      <docsubtype>null</docsubtype>
    </code>
    <code id="ACL">
      <docgroup>Documentation</docgroup>
      <doctype>Client Documentation</doctype>
      <docsubtype>Termination</docsubtype>
    </code>
    <code id="TBA">
      <docgroup>Operations</docgroup>
      <doctype>Funds Transfer Credit</doctype>
      <docsubtype>Reversed</docsubtype>
    </code>
  </ApplicationSettings>
</Settings>

请注意,我将 &lt;code&gt; 元素的值移动到了属性中,因为您不能在 XML 中为给定元素混合文本和子元素(至少我不知道)。

接下来,我将解析 XML 一次,并使用 Dictionary 来保存数据,其中代码作为键,List&lt;string&gt; 保存 docgroup、doctype 和 dosubtype 值。

另外,我不会使用正则表达式来查找 XML 中的匹配项 - 正则表达式非常适合模式匹配(例如查找符合特定模式的文件名),但在许多其他情况下过于矫枉过正.顺便说一句,另一种搜索正确代码的方法是:

Dim codeElements = root.Descendants("code").Select(Function(xe) xe.Attribute("id").Value).ToArray()

上面的代码基本上是说给我所有名为“code”的节点,它们是根元素的子元素,并将它们的“id”属性的值放在一个数组中。

要从 XML 构建一个 Dictionary(Of String, List(Of String))(使用我发布的 XML 格式),您可以这样做:

Dim docTypes As Dictionary(Of String, String) = root.Descendants("code") _
                .ToDictionary(Function(xe) xe.Attribute("id").Value,
                              Function(xe) New List(Of String)(New String() _
                 { xe.Element("docgroup").Value, _
                 xe.Element("doctype").Value, _
                 xe.Element("docsubtype").Value }))

(您可能需要在 IDE 中稍微调整一下格式 - 为了便于阅读,我在此处将其分解)。这为您提供了一个字典,其中键是代码,子元素是一个列表(元素 0 = docgroup、1 = doctype 和 2 = dosubtype)。

您可以从字典中获取正确的 docgroup、doctype 和 docsubtype。

把它们放在一起你会得到这样的东西:

首先,替换这几行代码:

Dim regElemName As New Regex("^code")
Dim root = XElement.Load(xmlfile)
Dim codeElements = root.Element("ApplicationSettings").Elements().Where(Function(xe) regElemName.IsMatch(xe.Name.LocalName)).Select(Function(xe) xe.Value)
Dim codes = String.Join("|", codeElements.ToArray())

有了这个(解释如下):

Dim xml As XElement = XElement.Load(xmlfile)
Dim CodeInfo As Dictionary(Of String, List(Of String)) = _
             xml.Descendants("code") _
             .ToDictionary(Function(xe) xe.Attribute("id").Value, _
                           Function(xe) New List(Of String)(New String() _
                           { xe.Element("docgroup").Value, _
                             xe.Element("doctype").Value, _
                             xe.Element("docsubtype").Value }))
Dim codes As String = String.Join("|", CodeInfo.Keys)

简而言之,上述代码加载 XML 文件,将其解析为字典(以代码为键,以文档类型 info 的 List(Of String) 为值。然后是正则表达式中使用的代码的字符串通过在字典的Keys 属性上调用String.Join 创建。

现在,当您将信息写入文本文件时,您可以根据代码获取正确的值。这里也有一些变化。

首先,在为文件创建行之前,我会在- 上拆分文件名,因为您需要它来获取正确的代码。假设您的所有代码都是三个字符,那么您只需要拆分数组中第三个元素的前三个字符。您将传递该值作为键来获取代码的正确文档类型值。

其次,我建议使用StringBuilder(您可能需要将Imports System.Text 添加到您的程序中)。这样做的原因是字符串连接可能会变得昂贵(想象一下,如果您有 1,000 个文件要处理)。字符串是不可变的——这意味着它们不能被改变。所以当你有类似的东西时:

Dim str1 As String = "Hello"

str1 = str1 & " World"

您可能认为“世界”已添加到 str1。实际上,创建了一个新字符串,其中 str1 添加了“World” - 所以想象一下如果你这样做 1,000 次会发生什么。为了以下示例的可读性,我还使用了String.Format

替换这一行:

Dim str As String = String.Empty

以下内容:

Dim str As New StringBuilder()
Dim fileNameParts As String()
Dim docCode As String = String.Empty
Dim str As New StringBuilder()

然后我将您的str = str &amp; rfiles &amp;... 行替换为以下代码:

fileNameParts = System.IO.Path.GetFileNameWithoutExtension(rfiles).Split("-")
docCode = fileNameParts(2).Substring(0, 3)
str.Append(String.Format("{0}|{1}|_|||{2}|{3}|{4}|swfoi{5}|{6}|{7}", _
           rfiles, _
           fileNameParts(0).Trim(), _
           CodeInfo(docCode)(0),
           CodeInfo(docCode)(1),
           CodeInfo(docCode)(2),
           randomnumber,
           formattedDate,
           Environment.NewLine))

上面的代码做了以下事情:

首先,它将当前文件名拆分为“-”。然后它从拆分中获取第三个元素的前三个字符(文档代码)并将其分配给docCode 变量。

然后它使用String.FormatStringBuilder str 中添加一行。请注意我如何从字典中获取文档类型信息,使用代码作为键并引用与该键关联的 List(Of String) 值的正确元素:

CodeInfo(docCode)(0) returns the `docgroup` value for that code.

最后,你需要在str上调用ToString()来输出StringBuilder的内容:

System.IO.File.WriteAllText(outputname, str.ToString())

需要考虑的其他几个小点:

我会仔细检查您的正则表达式是否匹配文件名模式,因为日期部分似乎有点不对劲。此外,您可以删除已命名的组(例如,?&lt;Year&gt;),除非您以后使用它们。

最后,我将为 XML 文件选择一个不同于 root 的变量名称。在 LINQ to XML 中,Root 是一个返回 XML 根的属性 - 如果其他开发人员没有仔细查看代码,可能会造成混淆。

最后,您需要包含错误处理(防御性编码和/或 Try-Catch 块),以防找不到给定的键或 XML 文件中的元素丢失等。

我希望这会有所帮助 - 如果您有任何问题,请告诉我。

【讨论】:

  • 非常感谢蒂姆。特别是对于每个的解释。其他专家只会编写所需的代码而不进行解释,因此我也非常感谢您花时间解释这一点。现在有一个问题,虽然_(下划线)在您的代码的某些行末尾是什么意思?
  • @Andrea - 不客气。我几乎不是专家,但我更喜欢了解代码的为什么,而不仅仅是如何。下划线字符是 VB.NET 中的行继续符 - 它允许您在 VS 的编辑器中将单个语句(行)代码拆分为多行。编码愉快!
  • 蒂姆我需要一些帮助。上周我一直试图自己解决这个问题,但一直没能解决。我认为您为从字典中获取正确的文档类型做了错字。我已将这些行“DocInfo(code)(0)...”更改为“CodeInfo(“code”)(0)...”,因为没有声明 docinfo,所以我认为您的意思是字典变量。但是当我运行时,我得到的错误是“mscorlib.dll 中发生'System.Collections.Generic.KeyNotFoundException' 类型的异常,但未在用户代码中处理。附加信息:给定的密钥不是......跨度>
  • ...出现在字典中。我理解这意味着它无法在 xml 文件中找到值。但是不明白为什么。我尝试通过将“xml.Descendants("code")”修改为“xml.Element("ApplicationSettings").Elements("code")”来使字典更具体,但我仍然遇到相同的错误。你介意再看看这个并告诉我有什么问题吗?
  • @Andrea - 是的,这是一个错字。它应该是 CodeInfo(我已经编辑了答案来解决这个问题)。在您当前的代码中,您是否使用CodeInfo("code")(0)(注意代码周围的“)?如果是,那可能就是您收到错误的原因。代码是保存实际代码值的变量的名称,所以您' 会想在没有“ - CodeInfo(code)(0) 的情况下使用它。
猜你喜欢
  • 2014-01-29
  • 1970-01-01
  • 2016-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-24
  • 1970-01-01
  • 2023-01-10
相关资源
最近更新 更多