首页 > 解决方案 > 验证带有错误行号的 XML

问题描述

我正在尝试使用行号获取 XML 验证错误,并且存在 的LineNumberOffset属性这一事实[xml.xmlReaderSettings]表明这是可能的。但我似乎无法找到如何启用行号或在结果错误中访问行号。谈到在 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 文件进行验证,该文件会产生不是特别有用的错误。

标签: powershellxsd-validation

解决方案


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

如果你看一下System.Management.Automation.MethodInvocationException你捕获的,你会看到它有一个InnerException属性,其中包含实际抛出的System.Xml.Schema.XmlSchemaValidationException实例,并且有你想要的and属性。XmlReaderLineNumberLinePosition

但是,一种更简洁的方法是首先只捕获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)否则它将被写入函数的输出流,并会给出一些奇怪的结果......

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

推荐阅读