2009-05-05 27 views
22

Como parte de mi desarrollo, me gustaría poder validar toda una carpeta de archivos XML en un solo archivo XSD. Una función de PowerShell parece ser una buena candidata para esto, ya que puedo canalizar una lista de archivos como esta: dir * .xml | Validate-Xml -Schema. \ MySchema.xsd¿Cómo uso PowerShell para validar archivos XML en un XSD?

He considerado portar el código C# de la pregunta Validating an Xml against Referenced XSD in C#, pero no sé cómo agregar controladores en PowerShell.

+0

¿Por qué exactamente lo necesita ser PowerShell simplemente porque está leyendo una lista de archivos de stdin? –

+2

Me gustaría poder integrarlo fácilmente en scripts de compilación automatizados. No quería tener que compilar una aplicación solo para hacer esto. Un script de PowerShell parecía una forma natural para este tipo de cosas. –

Respuesta

11

escribí una función PowerShell para hacer esto:

Uso:

dir * .xml | Prueba en XML -schema "\ MySchemaFile.xsd" -nameSpace "http://tempuri.org"

Código:

function Test-Xml { 
param(
    $InputObject = $null, 
    $Namespace = $null, 
    $SchemaFile = $null 
) 

BEGIN { 
    $failCount = 0 
    $failureMessages = "" 
    $fileName = "" 
} 

PROCESS { 
    if ($InputObject -and $_) { 
     throw 'ParameterBinderStrings\AmbiguousParameterSet' 
     break 
    } elseif ($InputObject) { 
     $InputObject 
    } elseif ($_) { 
     $fileName = $_.FullName 
     $readerSettings = New-Object -TypeName System.Xml.XmlReaderSettings 
     $readerSettings.ValidationType = [System.Xml.ValidationType]::Schema 
     $readerSettings.ValidationFlags = [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessInlineSchema -bor 
      [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessSchemaLocation -bor 
      [System.Xml.Schema.XmlSchemaValidationFlags]::ReportValidationWarnings 
     $readerSettings.Schemas.Add($Namespace, $SchemaFile) | Out-Null 
     $readerSettings.add_ValidationEventHandler(
     { 
      $failureMessages = $failureMessages + [System.Environment]::NewLine + $fileName + " - " + $_.Message 
      $failCount = $failCount + 1 
     }); 
     $reader = [System.Xml.XmlReader]::Create($_, $readerSettings) 
     while ($reader.Read()) { } 
     $reader.Close() 
    } else { 
     throw 'ParameterBinderStrings\InputObjectNotBound' 
    } 
} 

END { 
    $failureMessages 
    "$failCount validation errors were found" 
} 
} 
+0

El script tiene un error. No tiene abrazadera de cierre para la función. – OnesimusUnbound

+0

El '$ reader' se debe cerrar después del' while'-loop. De lo contrario, no podrá editar el archivo hasta que aparezca Finalizer-saftey-net. –

+0

Esto no parece funcionar: PS D: \ projects \ svcs> dir * .xml | Test-Xml El término 'Test-Xml' no se reconoce como el nombre de un cmdlet, función, archivo de script o programa operable. – Chloe

13

El PowerShell Community Extensions tiene un cmdlet Test-Xml. El único inconveniente es que las extensiones no se han actualizado durante un tiempo, pero la mayoría funcionan en la última versión de powershell (incluido Test-Xml). Solo haga un Get-Childitem's y pase la lista a un foreach, llamando a Test-Xml en cada uno.

+1

v1.2 de las extensiones se lanzaron para admitir v2 de PowerShell. Todos parecen funcionar bien, así que no estoy seguro de ningún inconveniente. –

8

Quiero comentar que la secuencia de comandos en la respuesta aceptada actual no valida errores sobre pedidos incorrectos de elementos de xs:sequence. Por ejemplo: test.xml

<addresses xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:noNamespaceSchemaLocation='test.xsd'> 
    <address> 
    <street>Baker street 5</street> 
    <name>Joe Tester</name> 
    </address> 
</addresses> 

Test.xsd

<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>  
<xs:element name="addresses"> 
     <xs:complexType> 
     <xs:sequence> 
     <xs:element ref="address" minOccurs='1' maxOccurs='unbounded'/> 
     </xs:sequence> 
    </xs:complexType> 
    </xs:element> 

    <xs:element name="address"> 
     <xs:complexType> 
     <xs:sequence> 
     <xs:element ref="name" minOccurs='0' maxOccurs='1'/> 
     <xs:element ref="street" minOccurs='0' maxOccurs='1'/> 
     </xs:sequence> 
     </xs:complexType> 
    </xs:element> 

    <xs:element name="name" type='xs:string'/> 
    <xs:element name="street" type='xs:string'/> 
    </xs:schema> 

escribí otra versión que se puede producir este error:

function Test-XmlFile 
{ 
    <# 
    .Synopsis 
     Validates an xml file against an xml schema file. 
    .Example 
     PS> dir *.xml | Test-XmlFile schema.xsd 
    #> 
    [CmdletBinding()] 
    param (  
     [Parameter(Mandatory=$true)] 
     [string] $SchemaFile, 

     [Parameter(ValueFromPipeline=$true, Mandatory=$true, ValueFromPipelineByPropertyName=$true)] 
     [alias('Fullname')] 
     [string] $XmlFile, 

     [scriptblock] $ValidationEventHandler = { Write-Error $args[1].Exception } 
    ) 

    begin { 
     $schemaReader = New-Object System.Xml.XmlTextReader $SchemaFile 
     $schema = [System.Xml.Schema.XmlSchema]::Read($schemaReader, $ValidationEventHandler) 
    } 

    process { 
     $ret = $true 
     try { 
      $xml = New-Object System.Xml.XmlDocument 
      $xml.Schemas.Add($schema) | Out-Null 
      $xml.Load($XmlFile) 
      $xml.Validate({ 
        throw ([PsCustomObject] @{ 
         SchemaFile = $SchemaFile 
         XmlFile = $XmlFile 
         Exception = $args[1].Exception 
        }) 
       }) 
     } catch { 
      Write-Error $_ 
      $ret = $false 
     } 
     $ret 
    } 

    end { 
     $schemaReader.Close() 
    } 
} 

PS C: \ temp \ lab-xml -validación> dir test.xml | Test-XmlFile test.xsd

System.Xml.Schema.XmlSchemaValidationException: The element 'address' has invalid child element 'name'. 
... 
+1

Tu respuesta es genial, corta y efectiva :) simplemente te pierdes '$ schemaReader.Dispose()' que causa el archivo de esquema cerradura – Adassko

+0

Gracias; He actualizado mi respuesta con una versión actualizada que está escrita como una función que se puede incluir en un módulo y admite la canalización. – wangzq

2

Estoy usando este simple fragmento, siempre funciona y no necesita funciones complicadas. Es este ejemplo Estoy cargando xml de configuración con los datos que se utilizan posteriormente para la configuración y el despliegue del servidor:

# You probably don't need this, it's just my way 
$script:Context = New-Object -TypeName System.Management.Automation.PSObject 
Add-Member -InputObject $Context -MemberType NoteProperty -Name Configuration -Value "" 
$ConfigurationPath = $(Join-Path -Path $PWD -ChildPath "Configuration") 

# Load xml and its schema 
$Context.Configuration = [xml](Get-Content -LiteralPath $(Join-Path -Path $ConfigurationPath -ChildPath "Configuration.xml")) 
$Context.Configuration.Schemas.Add($null, $(Join-Path -Path $ConfigurationPath -ChildPath "Configuration.xsd")) | Out-Null 

# Validate xml against schema 
$Context.Configuration.Validate(
    { 
     Write-Host "ERROR: The Configuration-File Configuration.xml is not valid. $($_.Message)" -ForegroundColor Red 

     exit 1 
    }) 
+0

Esta es la solución más simple (y, por lo tanto, la mejor). El único problema es que no funciona para un esquema que tiene un espacio de nombres de destino que no sea la cadena vacía. Para manejar este caso, debe cargar el objeto XmlSchema por separado. – ssamuel

2

la solución de (Flatliner DOA) está trabajando bien en PSV2, pero no en Server 2012 PSV3.

la solución de (wangzq) está funcionando en PS2 y PS3 !!

cualquier persona que necesita una validación XML en PS3, puede utilizar este (basado en la función de wangzq)

function Test-Xml { 
    param (
    [Parameter(ValueFromPipeline=$true, Mandatory=$true)] 
     [string] $XmlFile, 

     [Parameter(Mandatory=$true)] 
     [string] $SchemaFile 
    ) 

    [string[]]$Script:XmlValidationErrorLog = @() 
    [scriptblock] $ValidationEventHandler = { 
     $Script:XmlValidationErrorLog += $args[1].Exception.Message 
    } 

    $xml = New-Object System.Xml.XmlDocument 
    $schemaReader = New-Object System.Xml.XmlTextReader $SchemaFile 
    $schema = [System.Xml.Schema.XmlSchema]::Read($schemaReader, $ValidationEventHandler) 
    $xml.Schemas.Add($schema) | Out-Null 
    $xml.Load($XmlFile) 
    $xml.Validate($ValidationEventHandler) 

    if ($Script:XmlValidationErrorLog) { 
     Write-Warning "$($Script:XmlValidationErrorLog.Count) errors found" 
     Write-Error "$Script:XmlValidationErrorLog" 
    } 
    else { 
     Write-Host "The script is valid" 
    } 
} 

Test-Xml -XmlFile $XmlFile -SchemaFile $SchemaFile 
0

que reescribió (sé mal habito), pero el guión a partir de @Flatliner_DOA era demasiado bueno para descartar por completo.

function Test-Xml { 
[cmdletbinding()] 
param(
    [parameter(mandatory=$true)]$InputFile, 
    $Namespace = $null, 
    [parameter(mandatory=$true)]$SchemaFile 
) 

BEGIN { 
    $failCount = 0 
    $failureMessages = "" 
    $fileName = "" 
} 

PROCESS { 
    if ($inputfile) 
    { 
     write-verbose "input file: $inputfile" 
     write-verbose "schemafile: $SchemaFile" 
     $fileName = (resolve-path $inputfile).path 
     if (-not (test-path $SchemaFile)) {throw "schemafile not found $schemafile"} 
     $readerSettings = New-Object -TypeName System.Xml.XmlReaderSettings 
     $readerSettings.ValidationType = [System.Xml.ValidationType]::Schema 
     $readerSettings.ValidationFlags = [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessIdentityConstraints -bor 
      [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessSchemaLocation -bor 
      [System.Xml.Schema.XmlSchemaValidationFlags]::ReportValidationWarnings 
     $readerSettings.Schemas.Add($Namespace, $SchemaFile) | Out-Null 
     $readerSettings.add_ValidationEventHandler(
     { 
      try { 
       $detail = $_.Message 
       $detail += "`n" + "On Line: $($_.exception.linenumber) Offset: $($_.exception.lineposition)" 
      } catch {} 
      $failureMessages += $detail 
      $failCount = $failCount + 1 
     }); 
     try { 
      $reader = [System.Xml.XmlReader]::Create($fileName, $readerSettings) 
      while ($reader.Read()) { } 
     } 
     #handler to ensure we always close the reader sicne it locks files 
     finally { 
      $reader.Close() 
     } 
    } else { 
     throw 'no input file' 
    } 
} 

END { 
    if ($failureMessages) 
    { $failureMessages} 
    write-verbose "$failCount validation errors were found" 

} 
} 

#example calling/useage code follows: 
$erroractionpreference = 'stop' 
Set-strictmode -version 2 

$valid = @(Test-Xml -inputfile $inputfile -schemafile $XSDPath) 
write-host "Found ($($valid.count)) errors" 
if ($valid.count) { 
    $valid |write-host -foregroundcolor red 
} 

La función ya no hay tuberías como alternativa al uso de una ruta de archivo, que es una complicación de este caso de uso no necesita. Siéntase libre de hackear los manejadores de inicio/proceso/final.

1

Me doy cuenta de que esta es una vieja pregunta, sin embargo, probé las respuestas proporcionadas y no pude conseguir que funcionen correctamente en Powershell.

He creado la siguiente función que utiliza algunas de las técnicas que se describen aquí. Lo he encontrado muy confiable.

Tuve que validar documentos XML antes en varias ocasiones, sin embargo, siempre encontré que el número de línea era 0. Parece que XmlSchemaException.LineNumber solo estará disponible durante la carga del documento.

Si lo hace después de validación usando el método Validate() en una XmlDocument continuación LineNumber/LinePosition siempre será 0.

lugar que debe hacer la validación durante la lectura utilizando un XmlReader y la adición de un controlador de eventos de validación para un bloque de secuencia de comandos .

Function Test-Xml() 
{ 
    [CmdletBinding(PositionalBinding=$false)] 
    param (
    [Parameter(ValueFromPipeline=$true, Mandatory=$true)] 
     [string] [ValidateScript({Test-Path -Path $_})] $Path, 

     [Parameter(Mandatory=$true)] 
     [string] [ValidateScript({Test-Path -Path $_})] $SchemaFilePath, 

     [Parameter(Mandatory=$false)] 
     $Namespace = $null 
    ) 

    [string[]]$Script:XmlValidationErrorLog = @() 
    [scriptblock] $ValidationEventHandler = { 
     $Script:XmlValidationErrorLog += "`n" + "Line: $($_.Exception.LineNumber) Offset: $($_.Exception.LinePosition) - $($_.Message)" 
    } 

    $readerSettings = New-Object -TypeName System.Xml.XmlReaderSettings 
    $readerSettings.ValidationType = [System.Xml.ValidationType]::Schema 
    $readerSettings.ValidationFlags = [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessIdentityConstraints -bor 
      [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessSchemaLocation -bor 
      [System.Xml.Schema.XmlSchemaValidationFlags]::ReportValidationWarnings 
    $readerSettings.Schemas.Add($Namespace, $SchemaFilePath) | Out-Null 
    $readerSettings.add_ValidationEventHandler($ValidationEventHandler) 
    try 
    { 
     $reader = [System.Xml.XmlReader]::Create($Path, $readerSettings) 
     while ($reader.Read()) { } 
    } 

    #handler to ensure we always close the reader sicne it locks files 
    finally 
    { 
     $reader.Close() 
    } 

    if ($Script:XmlValidationErrorLog) 
    { 
     [string[]]$ValidationErrors = $Script:XmlValidationErrorLog 
     Write-Warning "Xml file ""$Path"" is NOT valid according to schema ""$SchemaFilePath""" 
     Write-Warning "$($Script:XmlValidationErrorLog.Count) errors found" 
    } 
    else 
    { 
     Write-Host "Xml file ""$Path"" is valid according to schema ""$SchemaFilePath""" 
    } 

    Return ,$ValidationErrors #The comma prevents powershell from unravelling the collection http://bit.ly/1fcZovr 
} 
Cuestiones relacionadas