$AppName      = 'SMJXML'
$AppVerison   = '2.66'
$AppFile      = 'Core.ps1'
$LtxXmlFolder = 'LaTeXML\script'
$ArtDtdFile   = 'A++V2.4.dtd'
$JobDtdFile   = 'A++V2.4JobSheetV2.4.1.dtd'
$JournalID    = '11202'

$StepName = @(
  'LaTeXML Pre-processing'
  'LaTeXML Post-processing'
  'XSLT Pre-processing'
  'XSLT Post-processing'
  'Formatting and validating'
)
$Tasks = @(
  '1. Process articles'
  '2. Create Springer XML package'
  '3. Create Online subfolder'
  '4. Create Proof subfolder'
  '5. Create Pleiades subfolder'
  '6. Create Abstracts subfolder'
  '7. Update Descript.ion files'
)
$DefaultTasks = '2000000'
$ConvertTask = 0; $PackageTask = 1; $OnlineTask = 2; $ProofTask = 3; $PleiadesTask = 4; $AbstractsTask = 5; $DescribeTask = 6

function Debug ([string] $message) {
  Write-Host "[Debug] $message"
  Read-Host -Prompt "Press Enter to continue"
}

try {

# Global parameters:
$P = @{
  IssueYear        = '???'
  IssueNumber      = '???'
  ArticleIDBase    = '???'
  RegistrationDate = '???'
  OnlineDate       = '???'
  Articles = @()
  Names = @{}
  Tasks = $DefaultTasks
}

$AppKey = "HKCU:\Software\$AppName"

if ("$args" -eq 'Install') {
  $s = [string](Get-Location)
  if ($s -match '\s') { throw "$AppName cannot be installed in the current folder,`r`nsince its path contains a space:`r`n`“$s`”" }
  $App = "$s\$AppFile"
  if (!(Test-Path $AppKey)) { New-Item -Path $AppKey -Force | Out-Null }
  if (!(Test-Path $AppKey)) { throw "$AppName cannot be installed:`r`nCannot create registry key $AppKey" }
  New-ItemProperty -Path $AppKey -Name 'Core' -Value $App -PropertyType String -Force | Out-Null
  $s = [string] (Get-ItemProperty $AppKey Core -ea SilentlyContinue).Core
  if ($s -ne $App) { throw "$AppName cannot be installed:`r`nCannot add registry value $AppKey\Core" }
  New-ItemProperty -Path $AppKey -Name 'Tasks' -Value $DefaultTasks -PropertyType String -Force | Out-Null
  $s = [string] (Get-ItemProperty $AppKey Tasks -ea SilentlyContinue).Tasks
  if ($s -ne $DefaultTasks) { throw "$AppName cannot be installed:`r`nCannot add registry value $AppKey\Tasks" }
  Write-Host "$AppName is successfully installed.`r`nRegistry key $AppKey is created with value`r`nCore = $App"
  exit 0
}

if ("$args" -eq 'Uninstall') {
  Remove-Item $AppKey -Force | Out-Null
  if (Test-Path $AppKey) { throw "$AppName cannot be uninstalled:`r`nCannot remove registry key $AppKey" }
  Write-Host "$AppName is successfully uninstalled."
  exit 0
}

$App = [string] (Get-ItemProperty $AppKey Core -ea SilentlyContinue).Core
if ($App -eq '') { throw "$AppName has not been installed.`r`nPlease navigate to the $AppName folder and run `“Install`”." }

$P['Tasks'] = [string] (Get-ItemProperty $AppKey Tasks -ea SilentlyContinue).Tasks
if ($P['Tasks'].length -ne $DefaultTasks.length) { $P['Tasks'] = $DefaultTasks }

$SmjXmlPath = [IO.Path]::GetDirectoryName($App)
if ($SmjXmlPath -match '\s') { throw "$AppName path contains a space: `“$SmjXmlPath`”" }

$LtxXmlPath = "$SmjXmlPath\$LtxXmlFolder"
if ($LtxXmlPath -match '\s') { throw "LaTeXML path contains a space: `“$LtxXmlPath`”" }

function s ([int] $n) { if ($n -ne 1) { return 's' }; return '' }
function z ([int] $n, [int] $m) { return "$(if($m -gt 99 -and $n -le 99){'0'})$(if($m -gt 9 -and $n -le 9){'0'})$n" }
function zz ([int] $n) { return "$(if($n -le 9){'0'})$n" }
function zzzz ([int] $n) { return "$(if($n -le 999){'0'})$(if($n -le 99){'0'})$(if($n -le 9){'0'})$n" }

function FileExists      ($path) { return      [IO.File]::Exists([IO.Path]::Combine([IO.Path]::GetFullPath((Get-Location)),$path)) }
function DirectoryExists ($path) { return [IO.Directory]::Exists([IO.Path]::Combine([IO.Path]::GetFullPath((Get-Location)),$path)) }

function Issue ([int] $year, [int] $issue, [int] $base, [string] $regDate = '') {
  $P['IssueYear'] = $year
  $P['IssueNumber'] = $issue
  $P['ArticleIDBase'] = $base
  if ($regDate) { $P['RegistrationDate'] = $regDate }
}

function OnlineDate ([string] $onlineDate = '') {
  if ($onlineDate) { $P['OnlineDate'] = $onlineDate }
}

function Article ([string] $file, [int] $firstPage, [int] $lastPage) {
  $P['Articles'] += @{
    FileName  = $file
    FirstPage = $firstPage
    LastPage  = $lastPage
    Present   = [bool] (Get-Item $file -ea SilentlyContinue) # <=> (FileExists $file) -and !(Get-Item $file -Force -ea SilentlyContinue).Attributes -band [IO.FileAttributes]::Hidden)
  }
}

function AddArticleNames {
  $M = $P['Articles'].length
  $Names = $P['Names']
  $n = 0
  for($i=0; $i -lt $M; ++$i) {
    $NN = z ($i+1) $M
    $a = $P['Articles'][$i]
    if ($a['Present']) {
      $n += 1
      $a['Number'] = $n
    } else {
      $a['Number'] = 1
    }
    $a['NN'] = $NN
    $a['NN_Name'] = $a['FileName'] -replace '(_e)?\.ams$','' # We may freely insert $NN anywhere in the NN_Name. For instance, before 2.46 it was used as a prefix: $NN+'_'+(...)
    $a['PDF'] = "simj$(zzzz $a['FirstPage'])"
    $Names[$a['NN_Name']] = $true
  }
}

function ArticleName ($number, $RegistrationYear) {
  return "$($JournalID)_$($RegistrationYear)_$(([int] $P['ArticleIDBase'])+$number)_Article"
}

$dataFound = $false
$folder = [IO.Path]::GetFullPath((Get-Location))
while ($true) {
  $file = [IO.Path]::Combine($folder,'Data.ps1')
  if ([IO.File]::Exists($file)) {
    $s = [IO.Path]::GetFullPath((Get-Location))
    Set-Location -LiteralPath $folder
    . $file
    AddArticleNames
    Set-Location -LiteralPath $s
    $dataFound = $true
    break
  }
  $folder = [IO.Path]::GetDirectoryName($folder)
  if (!$folder) { break }
}
if (!$dataFound) { throw "File `“Data.ps1`” is not found`r`nin the current folder or in its ancestors" }

function GetArticle ([string] $file) {
  for($i=0; $i -lt $P['Articles'].length; ++$i) {
    $a = $P['Articles'][$i]
    if ($a['FileName'] -eq $file){ return $a }
  }
  return $null
}

function Say ($message) { Write-Host "$message"; return "$message`r`n" }

function SayAndLog ($message, $logFile = 'Convert.log', [switch] $NoNewLine) {
  if ($NoNewLine) {
    Write-Host $message -NoNewLine
    [IO.File]::AppendAllText([IO.Path]::Combine([IO.Path]::GetFullPath((Get-Location)),$logFile), $message)
  } else {
    Write-Host $message
    Out-File $logFile -InputObject $message -Encoding UTF8 -Append
  }
}

function CleanLog ([string] $log) {
  # Return $log without superfluous information
  # In $log all linebreaks are single "\n"

  if ($log -cmatch '(?s)^(.*?\n—+\n\n)(latexml \(LaTeXML version .*?)\nprocessing started .*?\n(.*?)(\n—+\n.*?)?(?:\n)*$') {
                    #    1            2                                                        3    4
    $before  = $matches[1]
    $clean   = $matches[2]
    $toclean = $matches[3]
    $after   = $matches[4]
    $report = ''
    Select-String '(?s)\n(?:Fatal|Error|Warning(?!:\n?n\n?o\n?t\n?_\n?p\n?a\n?r\n?s\n?e\n?d\n?:)|Info):.*?(?=\n)' -input $toclean -AllMatches | Foreach {$report += $_.matches }
    if ($report) { $clean += "`n" + $report + "`n" }
    if ($toclean -cmatch '(?s)(\nConversion complete:.*?)\nprocessing finished .*$') { $clean += $matches[1] }
    $log = $before + $clean + "`n" + $after
  }
  $replace = '$1$2$3'+"`n"+'$4'
  $log = $log -replace '(?s)^(.*?\n—+\n\n)(latexmlpost \(LaTeXML version .*?)\nprocessing started .*?\n.*?(\nPostprocessing complete:.*?)\nprocessing finished .*?(\n—+\n.*?)(?:\n)*$',$replace
                        #    1            2                                                               3                                                       4
  return $log -replace '\n',"`r`n"
}

function CleanDTDLog ($logFile) {
  $log = (Get-Content -Encoding UTF8 -LiteralPath $logFile) -join "`r`n"
  $log = $log -replace '(?s)^xml.exe : (.*?\r\n)At .*?: NativeCommandError\r\n\s*', '$1'
  $log = $log -replace '(?s)(^|\r\n)\S+\.xml:(\d+\.\d+: )', '$1$1$2'
  $log = $log -replace '(?s): Element (.*?) content does not follow the DTD, expecting ', ": `"`$1`"`r`nExpected: "
  $log = $log -replace ', got ', "`r`nOccurred: "
  $log = $log -replace '\s+$', ''
  Out-File $logFile -InputObject $log -Encoding UTF8
}

function DeleteFiles ([int] $step) {
  if (1 -le $step -and $step -le 5) {
    for ($i=$step; $i -le 5; ++$i) {
      Remove-Item "XML\$i.*" -ea SilentlyContinue
      if ($i -eq 3) { Remove-Item 'XML\Refresh.txt' -ea SilentlyContinue }
      if ($i -eq 5) { Remove-Item 'XML\5f.*', 'XML\Images.*' -ea SilentlyContinue }
    }
  }
}

function ApplyXSLT ($inputFile, $xsltFile, $outputFile, $logFile, $logHead) {
  $e = ''
  try {
    & $SmjXmlPath\msxsl $inputFile $xsltFile LogFile=$logFile -o $outputFile 2>&1 | Tee-Object -Variable e | Out-Null
  } catch {
    $e += $_
  }
  $log = (Get-Content -Encoding UTF8 -LiteralPath $logFile) -join "`r`n"
  if ($e) { $log += "`r`n$e" }
  if ($log -replace '\s+','' -eq '') { $log = "OK`r`n" }
  Write-Host $log
  $log = $logHead + "`r`n" + $log
  Out-File $logFile -InputObject $log -Encoding UTF8
}

function Summary {
  $x = [int] !(FileExists 'XML\5.xml')
  if (FileExists 'Convert.log') {
    $s = (Get-Content -Encoding UTF8 -LiteralPath 'Convert.log') -join "`n"
    $b = ([regex]::Matches($s, "\n\[Bug\] " )).count
    $e = ([regex]::Matches($s, "\n\[Error\] " )).count
    $w = ([regex]::Matches($s, "\n\[Warning\] " )).count
    $r = ([regex]::Matches($s, "\n\[Remark\] " )).count
    $d = [int]($s.Contains("`nXML\5.xml - invalid"))
    if ($b+$e+$d+$x) {
      $t = ''
      if ($b) { $t += "$(if($t){', '})$b bug$(s $b)" }
      if ($e) { $t += "$(if($t){', '})$e error$(s $e)" }
      if ($w) { $t += "$(if($t){', '})$w warning$(s $w)" }
      if ($r) { $t += "$(if($t){', '})$r remark$(s $r)" }
      if ($d) { $t += "$(if($t){', '})final XML is not valid" }
      if ($x) { $t += "$(if($t){', '})no final XML file" }
      return "Error ($t)"
    } else {
      if ($w+$r) {
        $t = ''
        if ($w) { $t += "$(if($t){', '})$w warning$(s $w)" }
        if ($r) { $t += "$(if($t){', '})$r remark$(s $r)" }
        return "OK ($t)"
      } else {
        return 'OK'
      }
    }
  } else {
    return "Error (no log file$(if($x){', no final XML file'}))"
  }
}

function readLines ($fileName) {
  return [System.IO.File]::ReadAllLines($fileName, [System.Text.Encoding]::GetEncoding(1251))
}

function writeText ($fileName, $text) {
  [System.IO.File]::WriteAllText($fileName, $text, [System.Text.Encoding]::GetEncoding(1251))
}

function ConvertArticle ([int] $RedoStep = 1, [int] $TargetStep = 5) {
  SayAndLog "$AppName $AppVerison"
  Remove-Item 'Convert.log' -ea SilentlyContinue
  try {
    $files = @(Get-Item '*.ams')
    if ($files.length -eq 0) { throw 'There are no unhidden *.ams files in the current folder' }
    if ($files.length -gt 1) { throw 'There are several unhidden *.ams files in the current folder' }
    if (!(1 -le $RedoStep   -and $RedoStep   -le 5)) { throw "Unknown redo step: $RedoStep (1..5 expected)." }
    if (!(1 -le $TargetStep -and $TargetStep -le 5)) { throw "Unknown target step: $TargetStep (1..5 expected)." }
    $AmsFile = [IO.Path]::GetFileName($files)
    if ($AmsFile -match '\s') { throw "File name contains a space: `“$AmsFile`”" }
    $a = GetArticle $AmsFile
    if (!$a) { throw "There is no data for file `“$AmsFile`”" }
    $ArticleNumber    = $a['Number']
    $ArticleFirstPage = $a['FirstPage']
    $ArticleLastPage  = $a['LastPage']
    DeleteFiles $RedoStep
    $headLine = "——————————————————————————`r`n"
    for ($Y=1; $Y -le $TargetStep; ++$Y) {
      if (!(FileExists "XML\$Y.xml")) {
        $X = $Y - 1
        $head = "$headLine$($StepName[$X])`r`n$AmsFile $(if($Y -gt 1){"$X.xml "})-> $Y.xml`r`n$headLine"
        Write-Host "`r`n$head"
        $log = ''
        switch ($Y) {
        1 { try { & perl -x -S $LtxXmlPath\latexml --strict --nocomments --inputencoding=windows-1251 --preload=AmSTeX.pool --path=$SmjXmlPath --destination=XML\1.xml $AmsFile 2>&1 | Tee-Object -Variable log | Write-Host }
            catch { $log += Say $_ }
            Out-File XML\1.log -InputObject "$head`r`n$($log -join "`r`n")" -Encoding UTF8
            break
          }
        2 { try { & perl -x -S $LtxXmlPath\latexmlpost --pmml --nobibliography --noscan --nocrossref --novalidate --destination=XML\2.xml XML\1.xml 2>&1 | Tee-Object -Variable log | Write-Host }
            catch { $log += Say $_ }
            Out-File XML\2.log -InputObject "$head`r`n$($log -join "`r`n")" -Encoding UTF8
            break
          }
        3 { if (!$ArticleNumber)    { $ArticleNumber    = '???' }
            if (!$ArticleFirstPage) { $ArticleFirstPage = '???' }
            if (!$ArticleLastPage)  { $ArticleLastPage  = '???' }
            try { & $SmjXmlPath\msxsl XML\2.xml $SmjXmlPath\ArticlePre.xslt `
                    IssueYear=$($P['IssueYear']) IssueNumber=$($P['IssueNumber']) ArticleFirstPage=$ArticleFirstPage ArticleLastPage=$ArticleLastPage `
                    ArticleIDBase=$($P['ArticleIDBase']) ArticleNumber=$ArticleNumber RegistrationDate=$($P['RegistrationDate']) DefaultOnlineDate=$($P['OnlineDate']) `
                    -o XML\3.aux 2>&1 | Tee-Object -Variable log | Out-Null }
            catch { $log += $_ }
            if ($log) { SayAndLog $log XML\3.log; break }
            if (!(FileExists 'XML\3.aux')) {
              SayAndLog 'File “XML\3.aux” is not created.' XML\3.log
            } else {
              ApplyXSLT XML\3.aux $SmjXmlPath\Clean.xslt XML\3.xml XML\3.log $head
            }
            # Checking the source AMS file:
            $texError = removeTexComments $AmsFile $true
            if ($texError) { SayAndLog $texError XML\3.log }
            break
          }
        4 { try { & $SmjXmlPath\msxsl XML\3.xml $SmjXmlPath\ArticlePost.xslt -o XML\4.aux 2>&1 | Tee-Object -Variable log | Out-Null }
            catch { $log += $_ }
            if ($log) { SayAndLog $log XML\4.log; break }
            if (!(FileExists 'XML\4.aux')) {
              SayAndLog 'File “XML\4.aux” is not created.' XML\4.log
            } else {
              ApplyXSLT XML\4.aux $SmjXmlPath\Clean.xslt XML\4.xml XML\4.log $head
            }
            break
          }
        5 { # Restructuring:
            ApplyXSLT XML\4.xml $SmjXmlPath\Restructure.xslt XML\5.aux XML\5.log $head
            if (!(FileExists 'XML\5.aux')) { break }
            # Formatting:
            try { & $SmjXmlPath\msxsl XML\5.aux $SmjXmlPath\FormatMachine.xslt -o XML\5.xml 2>&1 | Tee-Object -Variable log | Out-Null }
            catch { $log += $_ }
            if ($log) { SayAndLog $log XML\5.log; break }
            try { & $SmjXmlPath\msxsl XML\5.aux $SmjXmlPath\FormatHuman.xslt -o XML\5f.xml 2>&1 | Tee-Object -Variable log | Out-Null }
            catch { $log += $_ }
            if ($log) { SayAndLog $log XML\5.log; break }
            if (!(FileExists 'XML\5.xml') -or !(FileExists 'XML\5f.xml')) { break }
            # Validating:
            $oldErrorActionPreference = $ErrorActionPreference
            $ErrorActionPreference = 'Continue'
            & $SmjXmlPath\DTD\xml val -e -d $SmjXmlPath\DTD\$ArtDtdFile XML\5.xml 2> XML\5.dtd.log | Tee-Object -Variable log | Write-Host
            $ErrorActionPreference = $oldErrorActionPreference
            $invalid = $LastExitCode
            if ($invalid) { $see = "For details, see `“XML\5.dtd.log`”"; Write-Host $see; $log += "`r`n$see" }
            Out-File XML\5.log -InputObject $log -Encoding UTF8 -Append
            if ($invalid) {
              CleanDTDLog 'XML\5.dtd.log'
            } else {
              Remove-Item 'XML\5.dtd.log' -ea SilentlyContinue
            }
          }
          default {
            throw "ConvertArticle: Unknown step: $Y (1..5 expected)."
          }
        }
        if (!(FileExists "XML\$Y.xml")) {
          SayAndLog "File `“XML\$Y.xml`” is not created." "XML\$Y.log"
          break
        }
      }
    }
    # Glue all logs into "Convert.log" (and clean):
    $log = ''
    Get-Item 'XML\?.log' | Sort-Object Name | % {$(Get-Content -Encoding UTF8 $_)} | ForEach-Object { $log += $_+"`n" }
    $log = CleanLog $log
    $log = "$AppName $AppVerison`r`n`r`n$log"
    Out-File 'Convert.log' -InputObject $log -Encoding UTF8
    SayAndLog "`r`nSummary: $(Summary)"
  } catch {
    SayAndLog $_
  }
}

function IssueArticle () {
  $logName = 'Issue.log'
  Remove-Item $logName -ea SilentlyContinue
  try {
    $files = @(Get-Item '*.ams')
    if ($files.length -eq 0) { throw 'There are no unhidden *.ams files in the current folder' }
    if ($files.length -gt 1) { throw 'There are several unhidden *.ams files in the current folder' }
    $amsFile = [IO.Path]::GetFileName($files)
    if ($amsFile -match '\s') { throw "File name contains a space: `“$amsFile`”" }
    $dataFile = '..\..\Data.ps1'
    if (!(FileExists $dataFile)) { throw "File not found: `“$dataFile`”" }
    $data = [string](Get-Content -LiteralPath $dataFile)
    if ($data -notmatch '^Issue\s+(\d+)\s+(\d+)\s') { throw "`“$dataFile`” does not start with `“Issue YYYY N`”" }
    $year  = $matches[1]
    $issue = $matches[2]
    if (!(DirectoryExists 'Issue\TeX')) { New-Item -ItemType Directory -Path 'Issue\TeX' | Out-Null }
    if (!(FileExists "Issue\TeX\$amsFile")) { cmd /c mklink "Issue\TeX\$amsFile" "..\$amsFile" | Out-Null }
    if (!(FileExists "Issue\$amsFile"))     { cmd /c mklink "Issue\$amsFile"     "..\$amsFile" | Out-Null }
    $amsFilePlus = ([Management.Automation.WildcardPattern]::Escape($amsFile) + '+*.*')
    Get-Item $amsFilePlus -Force | Where-Object {$_ -is [IO.FileInfo]} | ForEach-Object {
      if (!(FileExists "Issue\$($_.Name)"    )) { cmd /c mklink "Issue\$($_.Name)"     "..\$($_.Name)" | Out-Null }
      if (!(FileExists "Issue\TeX\$($_.Name)")) { cmd /c mklink "Issue\TeX\$($_.Name)" "..\$($_.Name)" | Out-Null }
    }
    Get-Item "Issue\$amsFilePlus" -Force | Where-Object {$_ -is [IO.FileInfo] -and [bool] ($_.Attributes -band [IO.FileAttributes]::ReparsePoint)} | ForEach-Object {
      if (!(FileExists $_.Name)) { Remove-Item -LiteralPath "Issue\$($_.Name)" -Force -ea SilentlyContinue }
    }
    Get-Item "Issue\TeX\$amsFilePlus" -Force | Where-Object {$_ -is [IO.FileInfo] -and [bool] ($_.Attributes -band [IO.FileAttributes]::ReparsePoint)} | ForEach-Object {
      if (!(FileExists $_.Name)) { Remove-Item -LiteralPath "Issue\TeX\$($_.Name)" -Force -ea SilentlyContinue }
    }
    if (!(FileExists 'Issue\Data.ps1')) { cmd /c mklink 'Issue\Data.ps1' 'TeX\Data.ps1' | Out-Null }
    writeText 'Issue\Convert.cmd' ('@PowerShell . $((Get-ItemProperty '+$AppKey+' Core).Core) ConvertAll %*')
    writeText 'Issue\Issue.ams' "\documentstyle{SibMatJ}\issueyear{$year}\issuenumber{$issue}\def\comline{amstex.exe}\def\extension{dvi}\opensplit=split.ba\overfullrule=0pt`r`n\openpages`r`n\openData{1}\pageno 1\Input $amsFile\closeData\closepages\end"
    writeText 'Issue\TeX\split.bat' "call amstex.exe \documentstyle{SibMatJ} \overfullrule=0pt \issueyear{$year}\issuenumber{$issue}\articlenumber{1} \pageno=1 \Input $amsFile\end`r`nmove sibmatj.dvi simj0001.dvi`r`nmove sibmatj.log simj0001.log`r`ndvips.exe -Ppdf -tletter simj0001.dvi"
  } catch {
    SayAndLog "$AppName $AppVerison" $logName
    SayAndLog "Cannot create the Issue folder for the current article:`r`n" $logName
    SayAndLog $_.Exception.Message $logName
  }
}

function CheckFile ([string] $file) {
  if (!(FileExists $file)) { throw "File not found: `“$file`”" }
}

function CopyFile ([string] $source, [string] $destination) {
  CheckFile $source
  if ($destination -match '^(.*)\\.*?$') { $dir = $matches[1] } else { $dir = '' }
  if ($dir -and !(DirectoryExists $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
  Copy-Item -LiteralPath $source "$destination" | Out-Null
}

function ShowDialog {
  [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
  [Reflection.Assembly]::LoadWithPartialName("System.Drawing")       | Out-Null

  $w = 270

  $c0 = New-Object System.Windows.Forms.CheckBox
  $c0.Width = $w
  $c0.TabIndex = 0
  $c0.Text = $Tasks[0]
  $c0.Checked = $true
  $c0.Enabled = $false
  $c0.Font = New-Object System.Drawing.Font $c0.Font, 'Bold'
  $CB = @($c0)

  for ($i=1; $i -lt $Tasks.length; ++$i) {
    $c = New-Object System.Windows.Forms.CheckBox
    $c.Width = $w
    $c.TabIndex = $i
    $c.Text = $Tasks[$i]
    $c.Checked = [int] $P['Tasks'].Substring($i,1)
    $CB += $c
  }

  $F   = New-Object System.Windows.Forms.Form
  $FP  = New-Object System.Windows.Forms.FlowLayoutPanel
  $RFP = New-Object System.Windows.Forms.FlowLayoutPanel
  $RP  = New-Object System.Windows.Forms.Panel
  $BP  = New-Object System.Windows.Forms.Panel
  $B   = New-Object System.Windows.Forms.Button

  $r0 = New-Object System.Windows.Forms.RadioButton; $r0.Text = 'Verify articles';  $r0.Width = 200; $r0.Checked = ($P['Tasks'].Substring(0,1) -eq '0')
  $r1 = New-Object System.Windows.Forms.RadioButton; $r1.Text = 'Refresh articles'; $r1.Width = 200; $r1.Checked = ($P['Tasks'].Substring(0,1) -eq '1')
  $r2 = New-Object System.Windows.Forms.RadioButton; $r2.Text = 'Convert articles'; $r2.Width = 200; $r2.Checked = ($P['Tasks'].Substring(0,1) -eq '2')

  $DialogResult = $false

  $Run = {
    if     ($r0.Checked) { $t = '0' }
    elseif ($r1.Checked) { $t = '1' }
    elseif ($r2.Checked) { $t = '2' }
    for ($i=1; $i -lt $Tasks.length; ++$i) { $t += if ($CB[$i].Checked) { '1' } else { '0' } }
    if ($t -ne $P['Tasks']) {
      $P['Tasks'] = $t
      Set-ItemProperty $AppKey Tasks $t
    }
    ([ref] $DialogResult).value = $true
    $F.Close()
  }

  $F_OnShown = { $F.Activate() }

  $F_OnKeyPress = { param ($sender, $e)
    $ch = "$($e.KeyChar)"
    if ($ch -match '[2-9]') {
      $i = [int] $ch
      if ($i -le $Tasks.length) {
        $e.Handled = $true
        $c = $CB[$i-1]
        $c.Focus()
        $c.Checked = !($c.Checked)
      }
    } elseif ($ch -eq '1') {
      $e.Handled = $true
      if     ($r0.Checked) { $r0.Focus() }
      elseif ($r1.Checked) { $r1.Focus() }
      elseif ($r2.Checked) { $r2.Focus() }
    } elseif ($ch -eq 'v') { $e.Handled = $true; $r0.Checked = $true; $r0.Focus()
    } elseif ($ch -eq 'r') { $e.Handled = $true; $r1.Checked = $true; $r1.Focus()
    } elseif ($ch -eq 'c') { $e.Handled = $true; $r2.Checked = $true; $r2.Focus()
    } elseif ($ch -eq [char] 13) {
      $e.Handled = $true
      & $Run
    } elseif ($ch -eq [char] 27) {
      $e.Handled = $true
      $F.Close()
    }
  }

  $F.Text = "$AppName $AppVerison"
  $F.AutoSize = $true
  $F.AutoSizeMode = 'GrowAndShrink'
  $F.Padding = New-Object System.Windows.Forms.Padding 7
  $F.StartPosition = 'CenterScreen'
  $F.MinimizeBox = $false
  $F.MaximizeBox = $false
  $F.FormBorderStyle = 'FixedSingle'
  $F.KeyPreview = $true
  $F.add_KeyPress($F_OnKeyPress)
  $F.add_Shown($F_OnShown)

  $FP.AutoSize = $true
  $FP.AutoSizeMode = 'GrowAndShrink'
  $FP.FlowDirection = 'TopDown'
  $FP.BorderStyle = 'FixedSingle'
  $FP.Padding = New-Object System.Windows.Forms.Padding 13
  $FP.Dock = 'Fill'

  $RFP.AutoSize = $true
  $RFP.AutoSizeMode = 'GrowAndShrink'
  $RFP.FlowDirection = 'TopDown'
  $RFP.Left = 40
  $RFP.Controls.Add($r2)
  $RFP.Controls.Add($r1)
  $RFP.Controls.Add($r0)

  $RP.AutoSize = $true
  $RP.AutoSizeMode = 'GrowAndShrink'
  $RP.Controls.Add($RFP)

  $BP.AutoSize = $true
  $BP.AutoSizeMode = 'GrowAndShrink'

  $B.Size = New-Object System.Drawing.Size 70,30
  $B.Left = $w - 70
  $B.Top = 20
  $B.Text = "Run"
  $B.Font = New-Object System.Drawing.Font $B.Font, 'Bold'
  $B.add_Click($Run)
  $B.TabIndex = $Tasks.length

  $FP.Controls.Add($CB[0])
  $FP.Controls.Add($RP)
  for ($i=1; $i -lt $Tasks.length; ++$i) { $FP.Controls.Add($CB[$i]) }
  $BP.Controls.Add($B)
  $FP.Controls.Add($BP)
  $F.Controls.Add($FP)

  $F.ShowDialog() | Out-Null
  return $DialogResult
}

function Checked ($task) { return [int] $P['Tasks'].Substring($task,1) }

function CheckWriteTime ($xmlFile, $amsFile) {
  if (!(FileExists $xmlFile)) { return 1 }
  if (!(FileExists $amsFile)) { return 2 }
  $xmlTime = (Get-Item -LiteralPath $xmlFile).LastWriteTime
  $amsTime = (Get-Item -LiteralPath $amsFile).LastWriteTime
  if ($amsTime -gt $xmlTime) { return 3 }
  return 0
}

function removeTexComments ($file, $justCheck=$false) {
  $lines = @(readLines $file)
  for ($i=0; $i -lt $lines.Count; ++$i) {
    $line = $lines[$i]
    if ($line -eq '') { continue }
    $len = $line.Length
    $cutPos = -1
    for ($pos=0; $pos -lt $len; ++$pos) {
      if ($line[$pos] -ne '%') { continue }
      # Count consecutive backslashes immediately preceding this '%':
      $bs = 0
      $j = $pos - 1
      while ($j -ge 0 -and $line[$j] -eq '\') { ++$bs; --$j }
      # Even number of '\' => '%' starts a comment:
      if (($bs % 2) -eq 0) { $cutPos = $pos; break }
    }
    if ($cutPos -ge 0) {
      # Keep the '%' itself, remove only the comment text after it:
      $lines[$i] = $line.Substring(0, $cutPos+1)
    }
    if ($justCheck) {
      $line = $lines[$i] -replace '^\s+',''
      if ($line -match '[\u0400-\u04FF]') {
        return "[Warning] File `“$file`” contains Russian letter `“$($matches[0])`” in the following line:`r`n" +
               "          $line`r`n"
      }
    }
  }
  if ($justCheck) { return '' }
  return ($lines -join "`n") -replace '(?m)^[^\S\n]*%(\n|$)','' # Delete all lines of the form "<spaces>%"
}

# For the "Abstracts" and "Describe" tasks:

function getTopmatter ($file) {
  $s = removeTexComments $file
  if ($s -cmatch '(?s)\\topmatter(?![a-zA-Z])(.*?)\\endtopmatter(?![a-zA-Z])') { return $Matches[1] }
  return ''
}

# For the "Abstracts" task:

function trim ($s) { return $s -replace '^(\s|\n)+','' -replace '(\s|\n)+$','' -replace '\s*%\s*$','' }

function indent ($s) { return '    ' + ($s -replace '\s*\n\s*',"`r`n    ") }

function TexBlock ($source, $name, $file) {
  if ($source -cmatch "(?s)\\$name(?![a-zA-Z])(.*?)\\end$name(?![a-zA-Z])") {
    $s = trim $Matches[1]
    $s = indent $s
    return "  \$name`r`n$s`r`n  \end$name"
  } else {
    throw "File `“$file`” does not contain \$name … \end$name"
  }
}

function TexFields ($s, $fieldRE) {
  $names = $fieldRE -split '\|'
  $F = @{}
  foreach ($name in $names) { $F[$name] = @() }
  # Find blocks of the form \\$fieldRE... (until the next \\$fieldRE or until the end of string)
  foreach ($m in [regex]::Matches($s, "(?s)\\(?<name>$fieldRE)(?![a-zA-Z])(?<val>.*?)(?=\\(?:$fieldRE)(?![a-zA-Z])|$)")) {
    $name = $m.Groups['name'].Value
    if (!$F.ContainsKey($name)) { $F[$name] = @() } # just in case
    $F[$name] += trim $m.Groups['val'].Value
  }
  return $F
}

$AuthorFieldRE = 'FamilyName|Prefix|Particle|Initial|Suffix|Gender|ORCID|Email|AffilRef|Corresponding|Print|Sign'

function TexAuthors ($authors, $file) {
  $s = ''
  $len = $authors.length
  for ($i=1; $i -le $len; ++$i) {
    if ($i -eq 2 -and $len -eq 2) { $s += ' and~' }
    elseif ($i -gt 1 -and $i -eq $len) { $s += ', and~' }
    elseif ($i -gt 1) { $s += ', ' }
    $author = $authors[$i-1]
    $a = TexFields "\FamilyName $author" $AuthorFieldRE
    $s += presentAuthor $a
  }
  return "  \authors`r`n    $s`r`n  \endauthors"
}

function presentAuthor ($a, $forSign = $false) {
  if ($forSign -and $a.Sign.length) { return $a.Sign[0] }
  if ($a.Print.length) { return $a.Print[0] }
  $s = ''
  foreach ($x in $a.Prefix)   { $s += "$x~" }
  foreach ($x in $a.Initial)  { $s += "$x~" }
  foreach ($x in $a.Particle) { $s += "$x~" }
  $s += $a.FamilyName
  foreach ($x in $a.Suffix) { $s += ",~$x" }
  return $s
}

$AffilFieldRE = 'Number|Division|Organization|Street|Postbox|Postcode|City|State|Country|URL|Print'

function TexAffils ($authors, $affils, $file) {
  $s = ''
  foreach ($author in $authors) {
    if ($s) { $s += "`r`n`r`n" }
    $s += "  \affil`r`n"
    $a = TexFields "\FamilyName $author" $AuthorFieldRE
    $t = presentAuthor $a $true
    $len = $a.AffilRef.length
    if ($len) {
      $t += "\\`r`n"
      for ($i=1; $i -le $len; ++$i) {
        if ($i -gt 1) { $t += ";\\`r`n"}
        $n = [int] $a.AffilRef[$i-1]
        $affil = $affils[$n-1]
        $f = TexFields "\Number $affil" $AffilFieldRE
        $t += presentAffil $f
      }
    }
    $s += indent $t
    $s += "`r`n  \endaffil"
  }
  return $s
}

function presentAffil ($f) {
  if ($f.Print.length) { return $f.Print[0] }
  if ($f.URL.length) { return $f.URL[0] }
  $s = ''
  foreach ($x in $f.Division    ) { $s += ", $x" }
  foreach ($x in $f.Organization) { $s += ", $x" }
  foreach ($x in $f.Street      ) { $s += ", $x" }
  foreach ($x in $f.Postbox     ) { $s += ", $x" }
  foreach ($x in $f.Postcode    ) { $s += ", $x" }
  foreach ($x in $f.City        ) { $s += ", $x" }
  foreach ($x in $f.State       ) { $s += ", $x" }
  foreach ($x in $f.Country     ) { $s += ", $x" }
  return $s -replace '^, ',''
}

# For the "Describe" task:

function getArticleStatus ($a) {
  $amsFile = [regex]::Escape($a['FileName'])
  $descrFile = 'Descript.ion'
  $d = ''
  if (Test-Path -LiteralPath $descrFile) {
    $encRead = New-Object System.Text.UTF8Encoding($true) # accepts BOM
    $d = [System.IO.File]::ReadAllText($descrFile, $encRead)
  }
  if ($d -match "(?:^|\n)`"?$amsFile`"? \d\d$Sep(?:$StartingSpace| )(.) $Sep(?:E| )$Sep") { return $matches[1]}
  return ' '
}

$Sep = [char] 9474
$StartingSpace = [char] 160

function getDescr ($x) {
  return "$StartingSpace$($x.status) $Sep$($x.lang)$Sep $($x.authors) $Sep $($x.title)"
}

function getDescrName ($line) {
  if ([string]::IsNullOrWhiteSpace($line)) { return $null }
  if ($line[0] -eq '"') {
    $q2 = $line.IndexOf('"', 1)
    if ($q2 -gt 1) { return $line.Substring(1, $q2-1) }
    return $null
  }
  $m = [regex]::Match($line, '^[^\s]+')
  if ($m.Success) { return $m.Value }
  return $null
}

function updateDescriptions ($path, $names, $descr) {
  $descrFile = [IO.Path]::Combine($path,'Descript.ion')
  $newNames = @{}
  foreach ($name in $names) { $newNames[$name.ToLowerInvariant()] = $true }
  $oldLines = @()
  if (Test-Path -LiteralPath $descrFile) {
    $encRead = New-Object System.Text.UTF8Encoding($true) # accepts BOM
    $oldLines = [System.IO.File]::ReadAllLines($descrFile, $encRead)
  }
  $keptOldLines = @(foreach ($line in $oldLines) {
    $name = getDescrName $line
    if (-not $name) { $line; continue }
    if (-not $newNames.ContainsKey($name.ToLowerInvariant())) { $line }
  })
  $newLines = @(for($i=0; $i -lt $names.count; ++$i) { $names[$i] + ' ' + $descr[$i] })
  $finalLines = @($keptOldLines + $newLines)
  $tmp = Join-Path $env:TEMP ("Descript.{0}.tmp" -f ([guid]::NewGuid().ToString('N')))
  $encWrite = New-Object System.Text.UTF8Encoding($true) # UTF-8 with BOM
  [System.IO.File]::WriteAllLines($tmp, $finalLines, $encWrite)
  Move-Item -Force -LiteralPath $tmp -Destination $descrFile
  $item = Get-Item $descrFile
  $item.Attributes = $item.Attributes -bor [System.IO.FileAttributes]::Hidden
}

# Main function:

function ConvertAll ([int] $RedoStep = 1, [int] $TargetStep = 5) {
  # Current location is assumed to be the issue folder
  Remove-Item 'Convert.log' -ea SilentlyContinue
  $processingStarted = $false
  try {
    if ($RedoStep -ne 1 -or $TargetStep -ne 5) {
      if (!(1 -le $RedoStep   -and $RedoStep   -le 5)) { $RedoStep = 1 }
      if (!(1 -le $TargetStep -and $TargetStep -le 5)) { $TargetStep = 5 }
      $doConvert   = 2
      $doPackage   = 0
      $doOnline    = 0
      $doProof     = 0
      $doPleiades  = 0
      $doAbstracts = 0
      $doDescribe  = 0
    } else {
      if (!(1 -le $RedoStep   -and $RedoStep   -le 5)) { throw "$AppName $AppVerison`r`nUnknown redo step: $RedoStep (1..5 expected)." }
      if (!(1 -le $TargetStep -and $TargetStep -le 5)) { throw "$AppName $AppVerison`r`nUnknown target step: $TargetStep (1..5 expected)." }
      if (!(ShowDialog)) { return }
      $doConvert   = Checked $ConvertTask
      $doPackage   = Checked $PackageTask
      $doOnline    = Checked $OnlineTask
      $doProof     = Checked $ProofTask
      $doPleiades  = Checked $PleiadesTask
      $doAbstracts = Checked $AbstractsTask
      $doDescribe  = Checked $DescribeTask
      if ($doConvert -eq 1) { $RedoStep = 3 }
    }
    SayAndLog "$AppName $AppVerison"
    $homePath = [IO.Path]::GetFullPath((Get-Location))
    Remove-Item 'XML\Package' -Recurse -Force -ea SilentlyContinue
    $M = $P['Articles'].length
    $H = 0; $s = ''
    for($i=0; $i -lt $M; ++$i) {
      $a = $P['Articles'][$i]
      if ($a['Present']) { $s += " $($a['NN'])" } else { $H += 1 }
    }
    $L = $M - $H
    if ($H -eq $M) { SayAndLog "All $M articles listed in `“Data.ps1`” are absent or hidden.`r`nWe have nothing to do."; return }
    SayAndLog "Processing started $((Get-Date).ToString('yyyy.MM.dd HH:mm:ss'))`r`n"
    $processingStarted = $true
    $isare = if ($H -eq 1) {'is'} else {'are'}
    if ($H) { SayAndLog ("Warning: $H of $M *.ams files listed in `“Data.ps1`” $isare absent or hidden.`r`n" +
                         "($(if($H -eq 1){'This file is'}else{'These files are'}) assumed included in the issue but $isare neither converted,`r`n" +
                         "nor presented in the XML package, Online, Proof, or Pleiades folder.)`r`n" +
                         "The following $(if($L -eq 1){'single article is'}else{"$L articles are"}) processed:$s`r`n") }
    # Check if required global parameters are defined
    $now = Get-Date # $now = $now.ToUniversalTime()
    if ($P['IssueYear']        -eq '???') { throw 'IssueYear is undefined' }
    if ($P['IssueNumber']      -eq '???') { throw 'IssueNumber is undefined' }
    if ($P['ArticleIDBase']    -eq '???') { throw 'ArticleIDBase is undefined' }
    if ($P['RegistrationDate'] -eq '???') {
      $RegistrationYear  = [string] $now.Year
      $RegistrationMonth = [string] $now.Month
      $RegistrationDay   = [string] $now.Day
    } else {
      $RegistrationYear  = $P['RegistrationDate'].Substring(0,4)
      $RegistrationMonth = $P['RegistrationDate'].Substring(5,2) -replace '^0',''
      $RegistrationDay   = $P['RegistrationDate'].Substring(8,2) -replace '^0',''
      $currentDate = $now.ToString('yyyy.MM.dd')
      if ($P['RegistrationDate'] -ne $currentDate) { SayAndLog "Warning: Registration date ($($P['RegistrationDate'])) differs from the current date ($currentDate)`r`n" }
    }
    # Create or check and update XML subfolder
    $XMLExisted = DirectoryExists 'XML'
    if ($XMLExisted) {
      SayAndLog "XML subfolder exists.`r`nChecking and updating its content..."
      $Names = $P['Names']
      $a = @(Get-ChildItem 'XML' | Where-Object PSIsContainer)
      for($i=0; $i -lt $a.length; ++$i) {
        $s = $a[$i].Name
        if (!($Names[$s])) {
          throw ("`r`n  Error: XML subfolder is malformed or obsolete:" +
                 "`r`n  subfolder `“XML\$s`” does not correspond" +
                 "`r`n  to *.ams files listed in `“Data.ps1`”.`r`n" +
                 "`r`n  Please delete (or rename) the old XML subfolder" +
                 "`r`n  and run `“Convert.cmd`” once again to recreate it.")
        }
      }
    } else {
      SayAndLog 'Creating XML subfolder...'
      New-Item -ItemType Directory -Path 'XML' | Out-Null
    }
    $XMLChanged = $false
    $ConvertCmdContent = ('@PowerShell . $((Get-ItemProperty '+$AppKey+' Core).Core) ConvertArticle %*')
    $IssueCmdContent   = ('@PowerShell . $((Get-ItemProperty '+$AppKey+' Core).Core) IssueArticle')
    SayAndLog '  Articles:' -NoNewLine
    try {
      for($i=0; $i -lt $M; ++$i) {
        $a = $P['Articles'][$i]
        SayAndLog " $($a['NN'])" -NoNewLine
        $FileName  = $a['FileName']
        $NN_Name   = $a['NN_Name']
        $PDF       = $a['PDF']
        if(!(DirectoryExists "XML\$NN_Name\XML")) { New-Item -ItemType Directory -Path "XML\$NN_Name\XML" | Out-Null; $XMLChanged = $true }
        # Remove links to obsolete companion PDF files:
        Get-Item "XML\$NN_Name\simj????.pdf" -Force | Where-Object {$_ -is [IO.FileInfo] -and [bool] ($_.Attributes -band [IO.FileAttributes]::ReparsePoint)} | ForEach-Object {
          if ($_.Name -ne "$PDF.pdf") { Remove-Item -LiteralPath "XML\$NN_Name\$($_.Name)" -Force -ea SilentlyContinue; $XMLChanged = $true }
        }
        # We always create links to the two main companion files, TeX and PDF (that are alive as soon as their targets exist):
        if (!(FileExists "XML\$NN_Name\$FileName")) { cmd /c mklink "XML\$NN_Name\$FileName" "..\..\$FileName" | Out-Null; $XMLChanged = $true }
        if (!(FileExists "XML\$NN_Name\$PDF.pdf"))  { cmd /c mklink "XML\$NN_Name\$PDF.pdf" "..\..\PDF\$PDF.pdf" | Out-Null; $XMLChanged = $true }
        if (!(FileExists "XML\$NN_Name\Convert.cmd")) { writeText "XML\$NN_Name\Convert.cmd" $ConvertCmdContent; $XMLChanged = $true }
        if (!(FileExists "XML\$NN_Name\Issue.cmd"))   { writeText "XML\$NN_Name\Issue.cmd"   $IssueCmdContent;   $XMLChanged = $true }
        $appChanged = $false
        $FileNamePlus = ([Management.Automation.WildcardPattern]::Escape($FileName) + '+*.*')
        Get-Item $FileNamePlus -Force | Where-Object {$_ -is [IO.FileInfo]} | ForEach-Object {
          if (!(FileExists "XML\$NN_Name\$($_.Name)")) { cmd /c mklink "XML\$NN_Name\$($_.Name)" "..\..\$($_.Name)" | Out-Null; $XMLChanged = $true; $appChanged = $true }
        }
        Get-Item "XML\$NN_Name\$FileNamePlus" -Force | Where-Object {$_ -is [IO.FileInfo] -and [bool] ($_.Attributes -band [IO.FileAttributes]::ReparsePoint)} | ForEach-Object {
          if (!(FileExists $_.Name)) { Remove-Item -LiteralPath "XML\$NN_Name\$($_.Name)" -Force -ea SilentlyContinue; $XMLChanged = $true; $appChanged = $true }
        }
        if ($appChanged) {
          Remove-Item "XML\$NN_Name\XML\Images.*" -ea SilentlyContinue
          Out-File "XML\$NN_Name\XML\Refresh.txt" -InputObject "The links to some appendant files are obsolete`r`nPlease run the application again and choose `“Refresh articles`”" -Encoding UTF8
        }
      }
    } finally { SayAndLog '' }
    SayAndLog "XML subfolder is $(if($XMLExisted){if($XMLChanged){'updated'}else{'left unchanged'}}else{'created'})."
    if ($XMLExisted -and (!$doConvert -or ($doConvert -and $RedoStep -ne 1))) { # If we are not completely (re)converting the articles...
      # For each article (except absent or hidden), check if "XML\NN_name\XML\1.xml" is newer than "NN_Name_e.ams"
      $ok = $true
      for($i=0; $i -lt $M; ++$i) {
        $a = $P['Articles'][$i]
        if ($a['Present']) {
          $xmlFile = "XML\$($a['NN_Name'])\XML\1.xml"
          $amsFile = $a['FileName']
          if ($result = CheckWriteTime $xmlFile $amsFile) {
            if     ($result -eq 1) { $a['State'] = "File not found: `"$xmlFile`"" }
            elseif ($result -eq 2) { $a['State'] = "File not found: `"$amsFile`"" }
            else                   { $a['State'] = "File `"$amsFile`" was changed" }
            if ($ok) { SayAndLog "`r`nWarning:`r`n" }
            $ok = $false
            SayAndLog "  [$($a['NN_Name'])] $($a['State'])"
          }
        }
      }
    }
    # Convert all the articles (except absent or hidden)
    if ($doConvert -or $XMLExisted) { SayAndLog '' }
    if ($doConvert) {
      if     ($RedoStep -eq 1) { SayAndLog 'Converting the articles...' }
      elseif ($RedoStep -eq 3) { SayAndLog 'Refreshing the articles...' }
      else                     { SayAndLog "Converting the articles from step $RedoStep..." }
      Write-Host ''
    }
    $k = 0
    for($i=0; $i -lt $M; ++$i) {
      $a = $P['Articles'][$i]
      if ($a['Present']) {
        $k += 1
        Set-Location -LiteralPath "$homePath\XML\$($a['NN_Name'])"
        if (!($a['State'])) {
          if ($doConvert) {
            Write-Host "$(z $k $L)/$L [$($a['NN_Name'])] ...`r`n"
            ConvertArticle $RedoStep $TargetStep
          }
          $a['State'] = Summary
        }
        if ($doConvert) { cls }
        if ($doConvert -and $i -lt $M-1) {
          $q = 0
          for($j=0; $j -le $i; ++$j) {
            $a = $P['Articles'][$j]
            if ($a['Present']) { $q += 1; Write-Host "$(z $q $L)/$L [$($a['NN_Name'])] $($a['State'])" }
          }
        }
      }
    }
    Set-Location -LiteralPath $homePath
    if ($doConvert -or $XMLExisted) {
      if ($doConvert) { cls }
      SayAndLog "$(if($doConvert){'Conversion results'}else{'Current state'}):`r`n"
      for($j=0; $j -lt $M; ++$j) {
        $a = $P['Articles'][$j]
        if ($a['Present']) { SayAndLog "  [$($a['NN_Name'])] $($a['State'])" }
      }
    }
    # Prepare to create Springer XML package
    if ($doPackage) {
      # Check if all articles are ready (i.e., have valid final XML)
      $articlesAreOK = $true
      for($i=0; $i -lt $M; ++$i) {
        $a = $P['Articles'][$i]
        if ($a['Present']) {
          $s = $a['State']
          if ($s.Contains('final XML') -or $s.Contains('no log file') -or $s.StartsWith('File ')) {
            SayAndLog "`r`nSome articles are not ready."
            $doPackage = $false
            break
          }
          if ($s.Contains('Error')) { $articlesAreOK = $false }
          $refreshFile = "XML\$($a['NN_Name'])\XML\Refresh.txt"
          if (FileExists $refreshFile) {
            SayAndLog "`r`n$((Get-Content -Encoding UTF8 -LiteralPath $refreshFile) -join "`r`n")"
            $doPackage = $false
            break
          }
        }
      }
    }
    if ($doPackage) {
      SayAndLog ''
      if (DirectoryExists 'Package') {
        $new = 'new '; $Nnew = 'New '
        SayAndLog 'Previous Springer XML package is found.'
      } else {
        $new = ''; $Nnew = ''
      }
      # Start creating Springer XML package
      SayAndLog "Creating $($new)Springer XML package..."
      try {
        Remove-Item 'Package', 'XML\Package' -Recurse -Force -ea SilentlyContinue
        # Check if all required files exist, and copy them into respective subfolders under required names
        $CoverIsReal = FileExists 'PDF\Cover.tif'
        $coverFile = if ($CoverIsReal) { 'PDF\Cover.tif' } else { "$SmjXmlPath\CoverStub.tif" }
        $IssuePrefix = "$($JournalID)_$(([int] $P['IssueYear'])-1959)_$($P['IssueNumber'])_"
        CopyFile $coverFile "XML\Package\$($IssuePrefix)Cover\$($IssuePrefix)CoverFigure_Print.tif"
        if (!$CoverIsReal) { SayAndLog '  Warning: A cover stub is used' }
        $pdfFolderExists = DirectoryExists 'PDF'
        $pdfsAreOK = $pdfFolderExists
        if (!$pdfFolderExists) { SayAndLog '  Warning: PDF subfolder is absent.' }
        for($i=0; $i -lt $M; ++$i) {
          $a = $P['Articles'][$i]
          if ($a['Present']) {
            $PDF = $a['PDF']
            $ArticlePrefix = "$($JournalID)_$($RegistrationYear)_$(([int] $P['ArticleIDBase'])+$a['Number'])_"
            $packagePrefix = "XML\Package\$($ArticlePrefix)Article\$($ArticlePrefix)"
            CopyFile "XML\$($a['NN_Name'])\XML\5.xml" "$($packagePrefix)Article.xml"
            if ($pdfFolderExists) {
              try { CopyFile "PDF\$PDF.pdf" "$($packagePrefix)OnlinePDF.pdf" }
              catch { SayAndLog "  Warning: $_"; $pdfsAreOK = $false }
              try {
                $destFile = "$($packagePrefix)PitStopReport_OnlinePDF.pdf"
                if (FileExists "PDF\$($PDF)_log.pdf") { CopyFile "PDF\$($PDF)_log.pdf"     $destFile }
                else                                  { CopyFile "PDF\log\$($PDF)_log.pdf" $destFile }
              }
              catch { SayAndLog "  Warning: $_"; $pdfsAreOK = $false }
            }
            $file = "XML\$($a['NN_Name'])\XML\Images.ps1"
            if (FileExists $file) { . $file }
          }
        }
        # Glue useful fragments of "5.xml" and "Images.xml" in "XML\NN_Name\XML" folders into "XML\Package\Articles.xml"
        # and, for each article, check if its ArticleSequenceNumber and RegistrationDate are actual
        $xml = '<?xml version="1.0" encoding="UTF-8"?><ARTICLES>'
        SayAndLog '  Articles:' -NoNewLine
        try {
          for($i=0; $i -lt $M; ++$i) {
            $a = $P['Articles'][$i]
            if ($a['Present']) {
              $xml += '<ARTICLE>'
              SayAndLog " $($a['NN'])" -NoNewLine
              $dir = "XML\$($a['NN_Name'])\XML"
              $file = "$dir\5.xml"
              $s = (Get-Content -Encoding UTF8 -LiteralPath $file) -join "`r`n"
              if (!($s -cmatch '(?s)^<\?xml [^>]*><!DOCTYPE [^>]*><Article [^>]*>(<ArticleInfo .*</ArticleInfo>)<ArticleHeader>(<AuthorGroup>.*</AuthorGroup>)')) {
                throw "File `“$file`” does not contain ArticleInfo and AuthorGroup elements"
              }
              $xml += $matches[1] + $matches[2]
              if (!($s -cmatch "<ArticleSequenceNumber>$($a['Number'])</ArticleSequenceNumber>" -and
                    $s -cmatch "<RegistrationDate><Year>$RegistrationYear</Year><Month>$RegistrationMonth</Month><Day>$RegistrationDay</Day></RegistrationDate>")) {
                throw "The article XML files are obsolete`r`n  Please run the application again and choose `“Refresh articles`”"
              }
              $file = "$dir\Images.xml"
              if (FileExists $file) { $xml += ((Get-Content -Encoding Unicode -LiteralPath $file) -join "`r`n") }
              $xml += '</ARTICLE>'
            }
          }
        } finally { SayAndLog '' }
        $xml += '</ARTICLES>'
        Out-File XML\Package\Articles.xml -InputObject $xml -Encoding UTF8
        # Prepare to create Springer XML package
        $TimeStamp = $now.ToString('yyyy-MM-dd_HH-mm-ss')
        $CommaDate = $now.ToString('yyyy,M,d')
        $JobSheetDate = $TimeStamp
        $packageName = "JournalID=$($JournalID)_VolumeIDStart=$(([int] $P['IssueYear'])-1959)_IssueIDStart=$($P['IssueNumber'])_$TimeStamp"
        New-Item -ItemType Directory -Path 'Package' | Out-Null
        # Transform XML\Package\Articles.xml into Package\Issue.json
        SayAndLog '  Creating JSON file...'
        $err = ''
        try { & $SmjXmlPath\msxsl XML\Package\Articles.xml $SmjXmlPath\JSON.xslt `
                IssueYear=$($P['IssueYear']) IssueNumber=$($P['IssueNumber']) ArticleIDBase=$($P['ArticleIDBase']) CommaDate=$CommaDate -o 'Package\Issue.json' 2>&1 | Tee-Object -Variable err | Out-Null
        } catch { $err += $_ }
        if ($err) { throw $err }
        # Transform XML\Package\Articles.xml into Package\DOI.csv
        SayAndLog '  Creating DOI file...'
        $err = ''
        try { & $SmjXmlPath\msxsl XML\Package\Articles.xml $SmjXmlPath\DOI.xslt `
                IssueYear=$($P['IssueYear']) IssueNumber=$($P['IssueNumber']) ArticleIDBase=$($P['ArticleIDBase']) CommaDate=$CommaDate -o 'Package\DOI.csv' 2>&1 | Tee-Object -Variable err | Out-Null
        } catch { $err += $_ }
        if ($err) { throw $err }
        # Transform XML\Package\Articles.xml into XML\Package\$JobSheetFile
        SayAndLog '  Creating JobSheet XML file...'
        $JobSheetFile = $IssuePrefix + 'JobSheet_500.xml'
        $err = ''
        try { & $SmjXmlPath\msxsl XML\Package\Articles.xml $SmjXmlPath\JobSheet.xslt `
                IssueYear=$($P['IssueYear']) IssueNumber=$($P['IssueNumber']) JobSheetDate=$JobSheetDate RegistrationYear=$RegistrationYear -o XML\Package\$JobSheetFile 2>&1 | Tee-Object -Variable err | Out-Null
        } catch { $err += $_ }
        if ($err) { throw $err }
        Remove-Item 'XML\Package\Articles.xml' -ea SilentlyContinue
        # Validate XML\Package\$JobSheetFile
        SayAndLog '  Validating JobSheet XML file...'
        $oldErrorActionPreference = $ErrorActionPreference
        $ErrorActionPreference = 'Continue'
        $logFile = "XML\Package\$JobSheetFile.log"
        & $SmjXmlPath\DTD\xml val -e -d $SmjXmlPath\DTD\$JobDtdFile XML\Package\$JobSheetFile 2> $logFile | Tee-Object -Variable log | Out-Null
        $ErrorActionPreference = $oldErrorActionPreference
        SayAndLog "  $($log -replace '^XML\\Package\\','')"
        if ($LastExitCode) {
          CleanDTDLog $logFile
          Remove-Item 'Package' -Recurse -Force -ea SilentlyContinue
          CopyFile XML\Package\$JobSheetFile Package\JobSheet.xml
          CopyFile $logFile Package\JobSheet.log
          Remove-Item 'XML\Package' -Recurse -Force -ea SilentlyContinue
          throw '  For details, see “Package\JobSheet.log”'
        } else {
          Remove-Item -LiteralPath $logFile -ea SilentlyContinue
        }
        # Create zip archive
        SayAndLog '  Creating zip archive...'
        Set-Location -LiteralPath "$homePath\XML"
        try {
          & $SmjXmlPath\7za a -tzip -r "..\Package\$packageName.zip" '.\Package\*'
          if ($LastExitCode) { throw 'The reason is unknown' }
        }
        catch { throw "Error creating zip archive:`r`n  $_" }
        finally { Set-Location -LiteralPath $homePath }
        Remove-Item 'XML\Package' -Recurse -Force -ea SilentlyContinue
        writeText "Package\$packageName.zip.delivered" "$packageName.zip"
        # Done
        SayAndLog 'Springer XML package is created in Package subfolder.'
        if (!$articlesAreOK -or !$pdfsAreOK -or !$CoverIsReal) { SayAndLog '' }
        if (!$articlesAreOK)   { SayAndLog 'Warning: Some articles contain errors.' }
        if (!$pdfsAreOK)       { SayAndLog 'Warning: XML package is incomplete (but suitable for “XMLtoPDF”).' }
        if (!$CoverIsReal)     { SayAndLog 'Reminder: Place an actual “Cover.tif” in PDF subfolder.' }
        if (!$pdfFolderExists) { SayAndLog 'Reminder: Place “simj****.pdf” and “simj****_log.pdf” in PDF subfolder.' }
      } catch {
        SayAndLog "  $_"
        SayAndLog "$($Nnew)Springer XML package is not created."
        Set-Location -LiteralPath $homePath
        if (!("$_".Contains('Package\'))) { Remove-Item 'Package', 'XML\Package' -Recurse -Force -ea SilentlyContinue }
      }
    } else {
      if (Checked $PackageTask) { SayAndLog "$($Nnew)Springer XML package is not created." }
    }
    # Create Online subfolder
    if ($doOnline) {
      SayAndLog ''
      if (DirectoryExists 'Online') {
        $new = 'new '; $Nnew = 'New '
        SayAndLog 'Previous Online subfolder is found.'
      } else {
        $new = ''; $Nnew = ''
      }
      if (!(DirectoryExists 'HTML')) {
        SayAndLog "HTML subfolder is absent.`r`n$($Nnew)Online subfolder is not created."
      } else {
        # Start creating Online subfolder
        SayAndLog "Creating $($new)Online subfolder..."
        try {
          Remove-Item 'Online' -Recurse -Force -ea SilentlyContinue
          CopyFile 'HTML\springer_epub.css' 'Online\springer_epub.css'
          CopyFile 'HTML\envelope.png' 'Online\envelope.png'
          SayAndLog '  Articles:' -NoNewLine
          try {
            for($i=0; $i -lt $M; ++$i) {
              $a = $P['Articles'][$i]
              if ($a['Present']) {
                SayAndLog " $($a['NN'])" -NoNewLine
                $NN_Name = $a['NN_Name']
                $ArticleName = ArticleName $a['Number'] $RegistrationYear
                CopyFile "HTML\$ArticleName\$ArticleName.html" "Online\HTML\$NN_Name.html"
                Copy-Item "HTML\$ArticleName\*_HTML.*" "Online\HTML\" | Out-Null
              }
            }
          } finally { SayAndLog '' }
          SayAndLog "$($Nnew)Online subfolder is created."
        } catch {
          SayAndLog "  $_"
          SayAndLog "$($Nnew)Online subfolder is not created."
          Remove-Item 'Online' -Recurse -Force -ea SilentlyContinue
        }
      }
    }
    # Create Proof subfolder
    if ($doProof) {
      SayAndLog ''
      if (DirectoryExists 'Proof') {
        $new = 'new '; $Nnew = 'New '
        SayAndLog 'Previous Proof subfolder is found.'
      } else {
        $new = ''; $Nnew = ''
      }
      if (!(DirectoryExists 'PDF')) {
        if (!(DirectoryExists 'HTML')) {
          SayAndLog "PDF and HTML subfolders are absent.`r`n$($Nnew)Proof subfolder is not created."
        } else {
          SayAndLog "PDF subfolder is absent.`r`n$($Nnew)Proof subfolder is not created."
        }
      } elseif (!(DirectoryExists 'HTML')) {
        SayAndLog "HTML subfolder is absent.`r`n$($Nnew)Proof subfolder is not created."
      } else {
        # Start creating Proof subfolder
        SayAndLog "Creating $($new)Proof subfolder..."
        try {
          Remove-Item 'Proof' -Recurse -Force -ea SilentlyContinue
          New-Item -ItemType Directory -Path 'Proof' | Out-Null
          CheckFile 'HTML\springer_epub.css'
          CheckFile 'HTML\envelope.png'
          SayAndLog '  Articles:' -NoNewLine
          try {
            for($i=0; $i -lt $M; ++$i) {
              $a = $P['Articles'][$i]
              if ($a['Present']) {
                SayAndLog " $($a['NN'])" -NoNewLine
                $NN_Name = $a['NN_Name']
                $PDF = $a['PDF']
                $ArticleName = ArticleName $a['Number'] $RegistrationYear
                CheckFile "PDF\$PDF.pdf"
                CheckFile "HTML\$ArticleName\$ArticleName.pdf"
                New-Item -ItemType Directory -Path 'Proof\Zip' | Out-Null
                Copy-Item "PDF\$PDF.pdf"                       'Proof\Zip\Print.pdf'   | Out-Null
                Copy-Item "HTML\$ArticleName\$ArticleName.pdf" 'Proof\Zip\Online.pdf'  | Out-Null
                Set-Location -LiteralPath "$homePath\Proof"
                try {
                  & $SmjXmlPath\7za a -tzip -r "$NN_Name.zip" '.\Zip\*'
                  if ($LastExitCode) { throw 'The reason is unknown' }
                }
                catch { throw "Error creating `“$NN_Name.zip`”:`r`n  $_" }
                finally { Set-Location -LiteralPath $homePath }
                Remove-Item "Proof\Zip" -Recurse -Force -ea SilentlyContinue
              }
            }
          } finally { SayAndLog '' }
          SayAndLog "$($Nnew)Proof subfolder is created."
        } catch {
          SayAndLog "  $_"
          SayAndLog "$($Nnew)Proof subfolder is not created."
          Remove-Item 'Proof' -Recurse -Force -ea SilentlyContinue
        }
      }
    }
    # Create Pleiades subfolder
    if ($doPleiades) {
      SayAndLog ''
      if (DirectoryExists 'Pleiades') {
        $new = 'new '; $Nnew = 'New '
        SayAndLog 'Previous Pleiades subfolder is found.'
      } else {
        $new = ''; $Nnew = ''
      }
      if (!(DirectoryExists 'Package')) {
        if (!(DirectoryExists 'HTML')) {
          SayAndLog "Package and HTML subfolders are absent.`r`n$($Nnew)Pleiades subfolder is not created."
        } else {
          SayAndLog "Package subfolder is absent.`r`n$($Nnew)Pleiades subfolder is not created."
        }
      } elseif (!(DirectoryExists 'HTML')) {
        SayAndLog "HTML subfolder is absent.`r`n$($Nnew)Pleiades subfolder is not created."
      } else {
        # Start creating Pleiades subfolder
        SayAndLog "Creating $($new)Pleiades subfolder..."
        try {
          Remove-Item 'Pleiades' -Recurse -Force -ea SilentlyContinue
          New-Item -ItemType Directory -Path 'Pleiades' | Out-Null
          $zips = @(Get-ChildItem -Path 'Package\*.zip' | Where-Object {$_ -is [IO.FileInfo]})
          if ($zips.length -ne 1) { throw "There are $($zips.length) *.zip files in Package subfolder" }
          $zip = $zips[0].Name
          $IssueName = $zip -replace '^(.*)\.zip$','$1'
          $PleiadesZip = 'PDF+JSON.zip'
          SayAndLog '  Articles:' -NoNewLine
          try {
            for($i=0; $i -lt $M; ++$i) {
              $a = $P['Articles'][$i]
              if ($a['Present']) {
                SayAndLog " $($a['NN'])" -NoNewLine
                $ArticleName = ArticleName $a['Number'] $RegistrationYear
                CheckFile "HTML\$ArticleName\$ArticleName.pdf"
                Set-Location -LiteralPath "HTML\$ArticleName"
                try {
                  & $SmjXmlPath\7za a -tzip "..\..\Pleiades\$PleiadesZip" "$ArticleName.pdf"
                  if ($LastExitCode) { throw 'The reason is unknown' }
                }
                catch { throw "Error creating `“$PleiadesZip`”:`r`n  $_" }
                finally { Set-Location -LiteralPath $homePath }
              }
            }
          } finally { SayAndLog '' }
          SayAndLog '  Adding XML package and JSON files...'
          CopyFile "Package\$zip" "Pleiades\$zip"
          CheckFile 'Package\Issue.json'
          Set-Location -LiteralPath "$homePath\Package"
          try {
            & $SmjXmlPath\7za a -tzip "..\Pleiades\$PleiadesZip" 'Issue.json'
            & $SmjXmlPath\7za rn "..\Pleiades\$PleiadesZip" 'Issue.json' "$IssueName.json"
          }
          catch { throw "Error creating `“$PleiadesZip`”:`r`n  $_" }
          finally { Set-Location -LiteralPath $homePath }
          # Checking and copying *.log
          $logs = @(Get-ChildItem -Path 'Package\*.log' | Where-Object {$_ -is [IO.FileInfo]})
          if ($logs.length -gt 1) {
            SayAndLog "  Error: There are $($logs.length) *.log files in Package subfolder"
          } elseif ($logs.length -eq 0) {
            SayAndLog '  Warning: There is no Submission Checker''s log in Package subfolder'
          } elseif (($logs[0].Name -replace '^(.*)\.log$','$1') -ne $IssueName) {
            SayAndLog '  Error: Submission Checker''s log in Package subfolder is obsolete'
          } else {
            CopyFile "Package\$($logs[0].Name)" "Pleiades\$($logs[0].Name)"
          }
          SayAndLog "$($Nnew)Pleiades subfolder is created."
        } catch {
          SayAndLog "  $_"
          SayAndLog "$($Nnew)Pleiades subfolder is not created."
          Remove-Item 'Pleiades' -Recurse -Force -ea SilentlyContinue
        }
      }
    }
    # Create Abstracts subfolder
    if ($doAbstracts) {
      SayAndLog ''
      if (DirectoryExists 'Abstracts') {
        $new = 'new '; $Nnew = 'New '
        SayAndLog 'Previous Abstracts subfolder is found.'
      } else {
        $new = ''; $Nnew = ''
      }
      # Start creating Abstracts subfolder
      SayAndLog "Creating $($new)Abstracts subfolder..."
      try {
        Remove-Item 'Abstracts' -Recurse -Force -ea SilentlyContinue
        New-Item -ItemType Directory -Path 'Abstracts' | Out-Null
        SayAndLog '  Articles:' -NoNewLine
        try {
          $errors = ''
          for($i=0; $i -lt $M; ++$i) {
            $a = $P['Articles'][$i]
            if ($a['Present']) {
              SayAndLog " $($a['NN'])" -NoNewLine
              $file = $a['FileName']
              CheckFile $file
              $top = getTopmatter $file
              if ($top) {
                $authors = @()
                $affils  = @()
                [regex]::Matches($top,'(?s)\\Author(?![a-zA-Z])(.*?)\\endAuthor(?![a-zA-Z])') | ForEach-Object { $authors += $_.Groups[1].Value }
                [regex]::Matches($top,'(?s)\\Affil(?![a-zA-Z])(.*?)\\endAffil(?![a-zA-Z])')   | ForEach-Object { $affils  += $_.Groups[1].Value }
                $info = ''
                try { $info += "`r`n$(TexBlock $top 'authors' $file)`r`n" } catch {
                  try { $info += "`r`n$(TexAuthors $authors $file)`r`n" } catch { $errors += "`r`n  Error: $_" }
                }
                try { $info += "`r`n$(TexAffils $authors $affils $file)`r`n" } catch { $errors += "`r`n  Error: $_" }
                try { $info += "`r`n$(TexBlock $top 'title'    $file)`r`n" } catch { $errors += "`r`n  Error: $_" }
                try { $info += "`r`n$(TexBlock $top 'keywords' $file)`r`n" } catch { $errors += "`r`n  Error: $_" }
                try { $info += "`r`n$(TexBlock $top 'abstract' $file)"     } catch { $errors += "`r`n  Error: $_" }
                writeText "Abstracts\$file" "$info`r`n"
              } else {
                $errors += "`r`n  Error: File `“$file`” does not contain \topmatter … \endtopmatter"
              }
            }
          }
        } finally { SayAndLog '' }
        if ($errors) { SayAndLog ($errors -replace '^\r\n','') }
        SayAndLog "$($Nnew)Abstracts subfolder is created."
      } catch {
        SayAndLog "  $_"
        SayAndLog "$($Nnew)Abstracts subfolder is not created."
        Remove-Item 'Abstracts' -Recurse -Force -ea SilentlyContinue
      }
    }
    # Update Descript.ion files
    if ($doDescribe) {
      SayAndLog "`r`nUpdating Descript.ion files..."
      try {
        $errors = ''
        $authorsLength = 0
        $info = @()
        $amsNames = @()
        $nnNames = @()
        $pdfNames = @()
        $psNames = @()
        $onlineNames = @()
        $htmlNames = @()
        SayAndLog '  Articles:' -NoNewLine
        for($i=0; $i -lt $M; ++$i) {
          $a = $P['Articles'][$i]
          if ($a['Present']) {
            SayAndLog " $($a['NN'])" -NoNewLine
            $x = @{
              name    = $a['NN_Name']
              volume  = zz (([int] $a['LastPage'])-([int] $a['FirstPage'])+1)
              status  = getArticleStatus $a
              lang    = ' '
              authors = ''
              title   = '???'
            }
            $info += $x
            $file = $a['FileName']
            $amsNames += $file
            $nnNames += $a['NN_Name']
            $pdfNames += $a['PDF']+'.pdf'
            $psNames += $a['PDF']+'.ps'
            $onlineNames += $a['NN_Name']+'.html'
            $htmlNames += ArticleName $a['Number'] $RegistrationYear
            CheckFile $file
            $top = getTopmatter $file
            if ($top) {
              if (!($top -cmatch '\\Origin(?![a-zA-Z])|\\opages(?![a-zA-Z])')) { $x.lang = 'E' }
              [regex]::Matches($top,"(?s)\\Author(?![a-zA-Z])(.*?)\\(?:$AuthorFieldRE|\\endAuthor)(?![a-zA-Z])") | ForEach-Object {
                if ($x.authors) { $x.authors += ', ' }
                $x.authors += trim $_.Groups[1].Value
              }
              if ($x.authors.length -gt $authorsLength) { $authorsLength = $x.authors.length }
              if ($top -cmatch '(?s)\\title(?![a-zA-Z])(.*?)\\endtitle(?![a-zA-Z])') {
                $x.title = (trim $matches[1]) -replace '\s*\r?\n\s*',' ' -replace '~',' '
              }
            } else {
              $errors += "`r`n  Error: File `“$file`” does not contain \topmatter … \endtopmatter"
            }
          }
        }
        SayAndLog ''
        foreach ($x in $info) { $x.authors = $x.authors.PadRight($authorsLength,' ') }
        $descr = @(foreach ($x in $info) { getDescr $x })
        $vDescr = @(foreach ($x in $info) { $x.volume + $Sep + (getDescr $x) })
        SayAndLog '  Describing the main folder...'
        updateDescriptions '' $amsNames $vDescr
        if (DirectoryExists 'XML') {
          SayAndLog '  Describing XML subfolder...'
          updateDescriptions 'XML' $nnNames $descr
        }
        if (DirectoryExists 'TeX' -or DirectoryExists 'PDF' -or DirectoryExists 'HTML') {
          $nnDescr = @(foreach ($x in $info) { $x.name + $Sep + (getDescr $x) })
          if (DirectoryExists 'TeX') {
            SayAndLog '  Describing TeX subfolder...'
            updateDescriptions 'TeX' @($amsNames + $pdfNames + $psNames) @($descr + $nnDescr + $nnDescr)
          }
          if (DirectoryExists 'PDF') {
            SayAndLog '  Describing PDF subfolder...'
            updateDescriptions 'PDF' $pdfNames $nnDescr
          }
          if (DirectoryExists 'HTML') {
            SayAndLog '  Describing HTML subfolder...'
            updateDescriptions 'HTML' $htmlNames $nnDescr
          }
        }
        if (DirectoryExists 'Online\HTML') {
          SayAndLog '  Describing Online subfolder...'
          updateDescriptions 'Online\HTML' $onlineNames $descr
        }
        if (DirectoryExists 'Abstracts') {
          SayAndLog '  Describing Abstracts subfolder...'
          updateDescriptions 'Abstracts' $amsNames $descr
        }
        if ($errors) { SayAndLog ($errors -replace '^\r\n','') }
        SayAndLog 'Descript.ion files are updated.'
      } catch {
        SayAndLog "  $_"
        SayAndLog 'Some Descript.ion files are not updated.'
      }
    }
  } catch {
    SayAndLog $_
  }
  if ($processingStarted) { SayAndLog "`r`nProcessing finished $((Get-Date).ToString('yyyy.MM.dd HH:mm:ss'))`r`n" -NoNewLine }
}

# Execute the called funtion
Invoke-Expression "$args" | Out-Null

} catch { Write-Host $_; exit 1 }
exit 0
