使用错误的行号验证 XML

Validate XML with line numbers in errors

我正在尝试使用行号获取 XML 验证错误,并且存在 [xml.xmlReaderSettings]LineNumberOffset 属性 这一事实表明这是可能的。但我似乎无法找到如何在结果错误中启用行号或访问行号。 This 谈到在 C# 中用 LoadOptions.SetLineInfo; 做这件事但是当我尝试 $xmlReaderSettings.SetLineInfo = $true.

时那不是有效的 属性
function readXMLFile ([string]$path) {
    $readXMLFile = [psCustomObject]@{    
        xml    = [xml.xmlDocument]::New()
        error = $null
    }
        
    $fileStream = $null
    $xmlreader = $null
    $importFile = [xml.xmlDocument]::New()
    $xmlReaderSettings = [xml.xmlReaderSettings]::New()
    #$xmlReaderSettings.ignoreComments = $true
    $xmlReaderSettings.closeInput = $true
    $xmlReaderSettings.prohibitDtd = $false
    $xmlReaderSettings.ValidationType = [System.Xml.ValidationType]::Schema
    $xmlReaderSettings.ValidationFlags = [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessInlineSchema -bor
                                         [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessSchemaLocation -bor 
                                         [System.Xml.Schema.XmlSchemaValidationFlags]::ReportValidationWarnings
    $xmlReaderSettings.Schemas.Add($Null, $SchemaFile)


    try {
        $fileStream = [io.fileStream]::New($path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
        $xmlreader = [xml.xmlreader]::Create($fileStream, $xmlReaderSettings)
        $importFile.Load($xmlreader)
    } catch {
        $exceptionName = $_.exception.GetType().name
        $exceptionMessage = $_.exception.message
        switch ($exceptionName) {
            MethodInvocationException {
                if ($exceptionMessage -match ': "(?<string>.*)"$') {
                    $readXMLFile.error = "Error loading XML; $($matches['string'])"
                } else {
                    $readXMLFile.error = "Error loading XML; $exceptionMessage"
                }
            }
            Default {
                $readXMLFile.error = "Error loading XML; $($exceptionName) - $exceptionMessage" # Or just the message?
            }
        }
    } finally {
        if ($xmlreader) {
            $xmlreader.Dispose()
        }
        if ($readXMLFile.error) {
            $readXMLFile.xml = $null
        } else {
            $readXMLFile.xml = $importFile
        }
    }
        
    return $readXMLFile
}

编辑:我一直在研究的模式是

<?xml version = "1.0"?>
<xs:schema xmlns:xs = "http://www.w3.org/2001/XMLSchema">
    <xs:element name = 'Definitions'>
        <xs:complexType>
         <xs:sequence>
            <xs:element name = 'Sets' type = 'Sets' minOccurs = '0'  maxOccurs = '1' />
            <xs:element name = 'Packages' type = 'Packages' minOccurs = '0'  maxOccurs = '1' />
         </xs:sequence>
      </xs:complexType>
    </xs:element>
    
    <xs:complexType name = 'Sets'>
        <xs:sequence>
            <xs:element name = "Set" type = 'Set' minOccurs = '0' maxOccurs='unbounded' />
        </xs:sequence>
    </xs:complexType>
    
    <xs:complexType name = 'Set'>
        <xs:sequence>
            <xs:element name = 'Set' type='xs:string' minOccurs = '0' maxOccurs='unbounded' />
            <xs:element name = 'Package' type='xs:string' minOccurs = '0' maxOccurs='unbounded' />
            <xs:element name = 'Rollout' type='xs:string' minOccurs = '0' maxOccurs='unbounded' />
            <xs:element name = 'Remove' type='xs:string' minOccurs = '0' maxOccurs='unbounded' />
        </xs:sequence>
        <!--<xs:attribute name = 'id' type = 'xs:string'/>-->
    </xs:complexType>
    
    <xs:complexType name = 'Packages'>
        <xs:sequence>
            <xs:element name = 'Package' type = 'Package' minOccurs = '0' maxOccurs='unbounded' />
        </xs:sequence>
        <xs:attribute name = 'id' type = 'xs:string'/>
    </xs:complexType>
    
    <xs:complexType name = 'Package'>
        <xs:sequence>
            <xs:element name = 'Package' type='xs:string' minOccurs = '0' maxOccurs='unbounded' />
            <xs:element name = 'Task' type='Task' minOccurs = '0' maxOccurs='unbounded' />
        </xs:sequence>
    </xs:complexType>
    
    
    
    <xs:complexType name = 'Task'>
        <xs:sequence>
            <xs:element name = 'PreProcess' type='TaskPrePostProcess' minOccurs = '0' maxOccurs='1' />
            <xs:element name = 'Process' type='TaskProcess' minOccurs = '1' maxOccurs='1' />
            <xs:element name = 'PostProcess' type='TaskPrePostProcess' minOccurs = '0' maxOccurs='1' />
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name = 'TaskPrePostProcess'>
        <xs:sequence>
            <xs:element name = 'Task' type='Task' minOccurs = '0' maxOccurs='unbounded' />
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name = 'TaskProcess'>
    </xs:complexType>
</xs:schema>

一些简单的样本数据是

<?xml version="1.0" encoding="utf-8" ?>
<Definitions>
    <Sets>
        <Set id="Arch">
            <Package>DTV_2017</Package>
        </Set>
        <Set id="Px_Arch">
            <Package>RVT_2017</Package>
            <Package>RVT_2018</Package>
        </Set>
    </Sets> 

    <Packages>
    </Packages>
</Definitions>

编辑:有趣的是,当我删除验证并捕获格式错误的 XML 错误时,我确实得到了行号。它仅使用 XSD 文件进行验证,该文件产生的错误不是特别有用。

您正在与 PowerShell 的一些黑魔法作斗争,因为它有时会用自己的类型包装对象 :-(

如果您查看捕获的 System.Management.Automation.MethodInvocationException,您会发现它有一个 InnerException 属性,其中包含 System.Xml.Schema.XmlSchemaValidationException 实例,XmlReader 实际上扔了,并且 that 已经得到了你想要的 LineNumberLinePosition 属性。

然而,更简洁的方法是首先只捕获 XmlSchemaValidationException 异常,让其他一切抛出。这样,PowerShell 会为您提供原始异常而不是其包装器:

catch [System.Xml.Schema.XmlSchemaValidationException]
{
    $ex = $_.Exception;
    $type = $ex.GetType().FullName;
    $lineNumber = $ex.LineNumber;
    $linePosition = $ex.LinePosition;
    $message = $ex.Message;
    write-host "type = $type";
    write-host "line = $lineNumber";
    write-host "position = $linePosition";
    write-host "message = $message";
    ...
}

输出:

type = System.Xml.Schema.XmlSchemaValidationException
line = 4
position = 14
message = The 'id' attribute is not declared.

顺便说一句,您可能还想从 $xmlReaderSettings.Schemas.Add($Null, $SchemaFile) 中捕获 return 值,否则它将从您的函数写入输出流并给出一些奇怪的结果...

$null = $xmlReaderSettings.Schemas.Add($Null, $SchemaFile)