使用正则表达式解析 EML 文本

Parse EML text With Regular Expression

你能帮我用正则表达式解析 EML 文本吗?

我要单独领取:

1). Content-Transfer-Encoding: base64 和 --=_alternative 之间的文本,如果上面有行 Content-Type: text/html

2). Content-Transfer-Encoding: base64 和 --=_related 之间的文本,如果在 Content-Type: image/jpeg

行上方有两行

请看一下 powershell 中的代码和平:

$text = @"
--=_alternative XXXXXXXXXXXXXX_=
Content-Type: text/html; charset="KOI8-R"
Content-Transfer-Encoding: base64

111111111111111111111111111111111111111111111111111111

--=_alternative XXXXXXXXXXXXXX_=
Content-Type: text/html; charset="KOI8-R"
Content-Transfer-Encoding: base64

222222222222222222222222222222222222222222222222222222
--=_alternative XXXXXXXXXXXXXX_=--
--=_related XXXXXXXXXXXXXX_=--_=
Content-Type: image/jpeg
Content-ID: <_2_XXXXXXXXXXXXXX>
Content-Transfer-Encoding: base64

333333333333333333333333333333333333333333333333333333
--=_related XXXXXXXXXXXXXX_=
Content-Type: image/jpeg
Content-ID: <_2_XXXXXXXXXXXXXX>
Content-Transfer-Encoding: base64
444444444444444444444444444444444444444444444444444444

--=_related XXXXXXXXXXXXXX_=
Content-Type: image/jpeg
Content-ID: <_2_XXXXXXXXXXXXXX>
Content-Transfer-Encoding: base64

555555555555555555555555555555555555555555555555555555
--=_related XXXXXXXXXXXXXX_=--
"@

$regex1 = "(?ms).+?Content-Transfer-Encoding: base64(.+?)--=_alternative"
$text1 = ([regex]::Matches($text,$regex1) | foreach {$_.groups[1].value})
Write-Host "text1 : " -fore red
Write-Host  $text1

#I want to get as output elements (of array, maybe, or one after another)
#1). text between  Content-Transfer-Encoding: base64 and --=_alternative, if there is above line Content-Type: text/html
#this
#1111111111111111111111111111111111111111111111111111111
#then this
#2222222222222222222222222222222222222222222222222222222

$regex2 = "(?ms).+?Content-Transfer-Encoding: base64(.+?)--=_related"
$text2 = ([regex]::Matches($text,$regex2) | foreach {$_.groups[1].value})
#I want to get as output elements (of array, maybe, or one after another)
#2). text between  Content-Transfer-Encoding: base64 and --=_related, if there is two lines above line Content-Type: image/jpeg
#this
#3333333333333333333333333333333333333333333333333333333
#then this
#4444444444444444444444444444444444444444444444444444444
#then this
#5555555555555555555555555555555555555555555555555555555
Write-Host "text2 : " -fore red
Write-Host  $text2

感谢您的帮助。祝你有个愉快的一天。

P.S。基于 Jessie Westlake 的代码,这里是 RegEx 的一个小编辑版本,对我有用:

$files = Get-ChildItem -Path "\<SERVER_NAME>\mailroot\Drop"
Foreach ($file in $files){
    $text = Get-Content $file.FullName

    $RegexText = '(?:Content-Type: text/html.+?Content-Transfer-Encoding: base64(.+?)(?:--=_))'
    $RegexImage = '(?:Content-Type: image/jpeg.+?Content-Transfer-Encoding: base64(.+?)(?:--=_))'

    $TextMatches = [Regex]::Matches($text, $RegexText, [System.Text.RegularExpressions.RegexOptions]::Singleline)
    $ImageMatches = [Regex]::Matches($text, $RegexImage, [System.Text.RegularExpressions.RegexOptions]::Singleline)

    If ($TextMatches[0].Success)
    {
        Write-Host "Found $($TextMatches.Count) Text Matches:"
        Write-Output $TextMatches.ForEach({$_.Groups[1].Value})
    }
    If ($ImageMatches[0].Success)
    {
        Write-Host "Found $($ImageMatches.Count) Image Matches:"
        Write-Output $ImageMatches.ForEach({$_.Groups[1].Value})
    }
}

TL;DR : 直接转到底部的代码...

下面的代码比较难看,请见谅

基本上我只是创建了一个匹配以 Content-Type: text/html 开头的正则表达式。它匹配后面的任何内容,直到遇到换行符 \n、回车符 return \r 或一个接一个的组合 \r\n

您必须将它们括在括号中才能使用 or | 运算符。我们实际上不想 capture/return 任何这些组,所以我们使用 (?:text-to-match) 的非捕获组语法。如您所见,我们在其他地方使用它。您也可以将捕获组和非捕获组放在彼此内部。

无论如何,继续。匹配新行后,我们要看Content-Transfer-Encoding: base64。这似乎在您的每个示例中都是必需的。

之后我们要识别下一个换行符,就像上次一样。除了这次我们想通过使用 + 来匹配 1 个或多个。我们需要匹配多个的原因是,有时您想要保存的数据前面有一个额外的行。但由于有时它前面没有额外的行,我们需要在加号后面加上问号 +?.

来使其成为 "lazy"

之后是我们将捕获您的实际数据的部分。这将是我们第一次使用实际的捕获组,而不是非捕获组(即没有问号后跟冒号)。

我们想要捕获任何不是新行的内容,因为似乎有时您的数据后跟新行,有时却没有。通过不允许我们捕获任何新行,它还会迫使我们之前的组吞噬我们数据之前的任何额外新行。该捕获组是 ([^(?:\n|\n\r)]+)

我们在那里所做的是将正则表达式包裹在括号中以便捕获它。我们将表达式放在方括号内,因为我们想创建自己的 "class" 个字符。方括号内的任何字符都将是我们的代码要查找的内容。不过,与我们的不同之处在于,我们将克拉 ^ 作为括号内的第一个字符。这意味着不是这些字符中的任何一个。显然我们想要匹配下一行之前的所有内容,所以我们想要捕获任何不是换行符的内容,一次或多次,尽可能多次。

然后我们确保我们的正则表达式锚定到一些结束文本,所以我们不断尝试匹配。从另一个换行符开始,至少匹配一个,但要尽可能少才能使我们的捕获成功 (?:\n|\r|\r\n)+?

最后,我们确定我们可以停止寻找重要数据的地方。那就是 --=_。我不确定我们是否会偶然发现 "alternative" 词或 "related",所以我没有走那么远。现在完成了。

一切的关键

如果不添加正则表达式"SingleLine"模式,我们将无法通过换行进行匹配。为了实现这一点,我们必须使用 .NET 语言来创建我们的比赛。我们从 [System.Text.RegularExpressions.RegexOptions] 类型开始加速。选项是 "SingleLine" 和 "MultiLine".

我为 text/htmlimage/jpeg 搜索创建了一个单独的正则表达式。我们将这些匹配的结果保存到它们各自的变量中。

我们可以通过索引 0 索引来测试匹配是否成功,该索引将包含整个匹配对象并访问其 .success 属性,其中 return 是一个布尔值.可以使用 .count 属性 访问匹配计数。为了访问特定的组和捕获,我们必须在找到合适的捕获组索引后在其中添加点注释。因为我们只使用一个捕获组而其余的都是非捕获组,我们将有整个文本匹配的 [0] 索引,而 [1] 应该包含我们的捕获组的匹配。因为是一个对象,所以我们要访问值属性.

显然下面的代码需要您的 $text 变量来包含要搜索的数据。

$RegexText = '(?:Content-Type: text/html.+?(?:\n|\r|\r\n)Content-Transfer-Encoding: base64(?:\n|\r|\r\n)+?([^(?:\n|\n\r)]+)(?:\n|\r|\r\n)+?(?:\n|\r|\r\n)(?:--=_))'
$RegexImage = '(?:Content-Type: image/jpeg.+?(?:\n|\r|\r\n)Content-Transfer-Encoding: base64(?:\n|\r|\r\n)+?([^(?:\n|\n\r)]+)(?:\n|\r|\r\n)+?(?:\n|\r|\r\n)(?:--=_))'

$TextMatches = [Regex]::Matches($text, $RegexText, [System.Text.RegularExpressions.RegexOptions]::Singleline)
$ImageMatches = [Regex]::Matches($text, $RegexImage, [System.Text.RegularExpressions.RegexOptions]::Singleline)

If ($TextMatches[0].Success)
{
    Write-Host "Found $($TextMatches.Count) Text Matches:"
    Write-Output $TextMatches.ForEach({$_.Groups[1].Value})
}
If ($ImageMatches[0].Success)
{
    Write-Host "Found $($ImageMatches.Count) Image Matches:"
    Write-Output $ImageMatches.ForEach({$_.Groups[1].Value})
}

上面的代码导致屏幕输出如下:

Found 2 Text Matches:
111111111111111111111111111111111111111111111111111111
222222222222222222222222222222222222222222222222222222
Found 3 Image Matches:
333333333333333333333333333333333333333333333333333333
444444444444444444444444444444444444444444444444444444
555555555555555555555555555555555555555555555555555555