Private/Objects/Read-NavObjectHeader.ps1
|
function Read-NavObjectHeader { <# .SYNOPSIS Reads the header of a C/AL text object file without NAV Model Tools: OBJECT <Type> <Id> <Name>, the OBJECT-PROPERTIES (Date, Time, Modified, Version List) and Subtype from PROPERTIES. Returns [pscustomobject]@{ Type; TypeId; Id; Name; Date; Time; Modified; VersionList; Subtype; Path }. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Path, # C/AL files are written in the OEM code page of the exporting machine. [System.Text.Encoding] $Encoding = [System.Text.Encoding]::GetEncoding([System.Globalization.CultureInfo]::CurrentCulture.TextInfo.OEMCodePage) ) $header = [ordered]@{ Type = $null TypeId = $null Id = $null Name = $null Date = '' Time = '' Modified = $false VersionList = '' Subtype = '' Path = $Path } $reader = New-Object System.IO.StreamReader($Path, $Encoding, $true) try { $first = $reader.ReadLine() # Names with leading/trailing spaces are written in brackets: OBJECT [Table 50376 Required Sales Doc. Document ] $declaration = $null if ($first -match '^OBJECT\s+\[(?<inner>.*)\]\s*$') { $declaration = $Matches.inner } elseif ($first -match '^OBJECT\s+(?<inner>.*?)\s*$') { $declaration = $Matches.inner } if ($null -eq $declaration -or $declaration -notmatch '^(?<type>[A-Za-z]+)\s+(?<id>\d+)\s(?<name>.*)$') { throw "'$Path' is not a C/AL object file (first line: '$first')." } $header.Type = Get-NAVGitObjectTypeName -Name $Matches.type $header.TypeId = Get-NAVGitObjectTypeId -Type $header.Type $header.Id = [int]$Matches.id $header.Name = $Matches.name $section = '' $lineNumber = 1 while ($null -ne ($line = $reader.ReadLine())) { $lineNumber++ if ($line -match '^ (OBJECT-PROPERTIES|PROPERTIES)\s*$') { $section = $Matches[1] continue } if ($line -match '^ }\s*$') { if ($section -eq 'PROPERTIES') { break } $section = '' continue } # Anything after the properties (FIELDS, CONTROLS, CODE, ...) is not needed. if ($line -match '^ [A-Z]' -and $section -eq '') { break } if ($section -eq 'OBJECT-PROPERTIES' -and $line -match '^\s+(?<key>Date|Time|Modified|Version List)=(?<value>.*);\s*$') { $value = ConvertFrom-NAVGitPropertyValue $Matches.value switch ($Matches.key) { 'Date' { $header.Date = $value } 'Time' { $header.Time = $value } 'Modified' { $header.Modified = ($value -eq 'Yes') } 'Version List' { $header.VersionList = $value } } } elseif ($section -eq 'PROPERTIES' -and $line -match '^\s+Subtype=(?<value>[^;]*);') { $header.Subtype = ConvertFrom-NAVGitPropertyValue $Matches.value } if ($lineNumber -gt 500) { break } } } finally { $reader.Dispose() } return [pscustomobject]$header } function ConvertFrom-NAVGitPropertyValue { # C/AL wraps values with special characters or leading spaces in [ ]: Time=[ 9:00:00]; Version List=[A;B]; param([string] $Value) $Value = $Value.Trim() if ($Value.Length -ge 2 -and $Value.StartsWith('[') -and $Value.EndsWith(']')) { $Value = $Value.Substring(1, $Value.Length - 2) } return $Value.Trim() } function Get-NAVGitObjectTypeId { <# .SYNOPSIS Object type name -> [Type] value in the [Object] table. #> [CmdletBinding()] [OutputType([int])] param( [Parameter(Mandatory)] [string] $Type ) $map = @{ TableData = 0; Table = 1; Form = 2; Report = 3; Dataport = 4; Codeunit = 5; XMLport = 6; MenuSuite = 7; Page = 8; Query = 9 } if (-not $map.ContainsKey($Type)) { throw "Unknown object type '$Type'." } return $map[$Type] } function Get-NAVGitObjectTypeName { <# .SYNOPSIS Normalises an object type name or [Object].[Type] number to the C/AL spelling (Table, Codeunit, XMLport, ...). #> [CmdletBinding()] [OutputType([string])] param( [string] $Name, [int] $TypeId = -1 ) $names = @('TableData', 'Table', 'Form', 'Report', 'Dataport', 'Codeunit', 'XMLport', 'MenuSuite', 'Page', 'Query') if ($TypeId -ge 0) { return $names[$TypeId] } $match = $names | Where-Object { $_ -eq $Name } | Select-Object -First 1 if (-not $match) { throw "Unknown object type '$Name'." } return $match } |