1

这个问题已经有很多不同的答案,但没有一个涉及我的情况。

我正在使用 WSMan 提取数据,然后将输出作为一种 sudo-xml 返回。我什至不会认为它是“真正的”xml,因为它有很多非标准属性。问题是我需要能够将输出作为 PHP 中的对象引用。所以目前我使用了很多 str_replace。这样做的问题是,如果非标准格式出现偏差(在某些情况下它会返回类似的东西,而<KeyID xsi:nil="true"/>在其他情况下它可能是这样的东西<CMCIP xsi:nil="true"/>),很难预见我将拥有的所有不同属性在使用 simplexml_load_string 将其作为对象导入之前考虑并退出变量。

所以,我的问题很简单:有没有办法将非标准 XML 加载到对象中?这是 xml 数据的示例,以便您知道我们在这里处理的是什么疯狂。

<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsen="http://schemas.xmlsoap.org/ws/2004/09/enumeration">
  <s:Header>
    <wsa:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>
    <wsa:Action>http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse</wsa:Action>
    <wsa:RelatesTo>uuid:3ae2d181-04f0-14f0-8002-89040b5d1500</wsa:RelatesTo>
    <wsa:MessageID>uuid:43a291ab-04f0-14f0-8073-b516f1d9bed4</wsa:MessageID>
  </s:Header>
  <s:Body>
    <wsen:EnumerateResponse>
      <wsen:EnumerationContext>439c90e9-04f0-14f0-8072-b516f1d9bed4</wsen:EnumerationContext>
    </wsen:EnumerateResponse>
  </s:Body>
</s:Envelope>
<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsen="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:n1="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_SystemView" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <s:Header>
    <wsa:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>
    <wsa:Action>http://schemas.xmlsoap.org/ws/2004/09/enumeration/PullResponse</wsa:Action>
    <wsa:RelatesTo>uuid:3af0a1eb-04f0-14f0-8003-89040b5d1500</wsa:RelatesTo>
    <wsa:MessageID>uuid:43a41fe8-04f0-14f0-8074-b516f1d9bed4</wsa:MessageID>
  </s:Header>
  <s:Body>
    <wsen:PullResponse>
      <wsen:Items>
        <n1:DCIM_SystemView>
          <n1:AssetTag/>
          <n1:BIOSReleaseDate>11/20/2013</n1:BIOSReleaseDate>
          <n1:BIOSVersionString>2.1.3</n1:BIOSVersionString>
          <n1:BaseBoardChassisSlot>NA</n1:BaseBoardChassisSlot>
          <n1:BatteryRollupStatus>1</n1:BatteryRollupStatus>
          <n1:BladeGeometry>255</n1:BladeGeometry>
          <n1:BoardPartNumber>061P35A00</n1:BoardPartNumber>
          <n1:BoardSerialNumber>CN70163231007K</n1:BoardSerialNumber>
          <n1:CMCIP xsi:nil="true"/>
          <n1:CPLDVersion>1.0.3</n1:CPLDVersion>
          <n1:CPURollupStatus>1</n1:CPURollupStatus>
          <n1:ChassisModel/>
          <n1:ChassisName>Main System Chassis</n1:ChassisName>
          <n1:ChassisServiceTag>REMOVED</n1:ChassisServiceTag>
          <n1:ChassisSystemHeight>2</n1:ChassisSystemHeight>
          <n1:DeviceDescription>System</n1:DeviceDescription>
          <n1:ExpressServiceCode>33088672189</n1:ExpressServiceCode>
          <n1:FQDD>System.Embedded.1</n1:FQDD>
          <n1:FanRollupStatus>1</n1:FanRollupStatus>
          <n1:HostName/>
          <n1:InstanceID>System.Embedded.1</n1:InstanceID>
          <n1:LastSystemInventoryTime>20140928010936.000000+000</n1:LastSystemInventoryTime>
          <n1:LastUpdateTime>20140220171215.000000+000</n1:LastUpdateTime>
          <n1:LicensingRollupStatus>1</n1:LicensingRollupStatus>
          <n1:LifecycleControllerVersion>2.1.0</n1:LifecycleControllerVersion>
          <n1:Manufacturer>Dell Inc.</n1:Manufacturer>
          <n1:MaxCPUSockets>2</n1:MaxCPUSockets>
          <n1:MaxDIMMSlots>24</n1:MaxDIMMSlots>
          <n1:MaxPCIeSlots>6</n1:MaxPCIeSlots>
          <n1:MemoryOperationMode>OptimizerMode</n1:MemoryOperationMode>
          <n1:Model>PowerEdge R720xd</n1:Model>
          <n1:NodeID>F7852V1</n1:NodeID>
          <n1:PSRollupStatus>1</n1:PSRollupStatus>
          <n1:PlatformGUID>3156324f-c0c6-3580-3810-00374c4c4544</n1:PlatformGUID>
          <n1:PopulatedCPUSockets>2</n1:PopulatedCPUSockets>
          <n1:PopulatedDIMMSlots>8</n1:PopulatedDIMMSlots>
          <n1:PopulatedPCIeSlots>2</n1:PopulatedPCIeSlots>
          <n1:PowerCap>598</n1:PowerCap>
          <n1:PowerCapEnabledState>3</n1:PowerCapEnabledState>
          <n1:PowerState>2</n1:PowerState>
          <n1:PrimaryStatus>1</n1:PrimaryStatus>
          <n1:RollupStatus>1</n1:RollupStatus>
          <n1:ServerAllocation xsi:nil="true"/>
          <n1:ServiceTag>REMOVED</n1:ServiceTag>
          <n1:StorageRollupStatus>1</n1:StorageRollupStatus>
          <n1:SysMemErrorMethodology>6</n1:SysMemErrorMethodology>
          <n1:SysMemFailOverState>NotInUse</n1:SysMemFailOverState>
          <n1:SysMemLocation>3</n1:SysMemLocation>
          <n1:SysMemMaxCapacitySize>1572864</n1:SysMemMaxCapacitySize>
          <n1:SysMemPrimaryStatus>1</n1:SysMemPrimaryStatus>
          <n1:SysMemTotalSize>65536</n1:SysMemTotalSize>
          <n1:SystemGeneration>12G Monolithic</n1:SystemGeneration>
          <n1:SystemID>1320</n1:SystemID>
          <n1:SystemRevision>0</n1:SystemRevision>
          <n1:TempRollupStatus>1</n1:TempRollupStatus>
          <n1:UUID>4c4c4544-0037-3810-8035-c6c04f325631</n1:UUID>
          <n1:VoltRollupStatus>1</n1:VoltRollupStatus>
          <n1:smbiosGUID>44454c4c-3700-1038-8035-c6c04f325631</n1:smbiosGUID>
        </n1:DCIM_SystemView>
      </wsen:Items>
      <wsen:EndOfSequence/>
    </wsen:PullResponse>
  </s:Body>
</s:Envelope>
4

2 回答 2

2

作为响应/输出返回的是多个 XML 文档的串联。在您的示例中,这是两个。

这不是有效的 XML,但也很常见。

因此,您需要做的就是拆分文档并选择您需要处理的文档(在您的示例中为第二个):

$split = preg_split('~\Q<?xml version="1.0" encoding="UTF-8"?>\E\R~u', $sequenced_xml, 2, PREG_SPLIT_NO_EMPTY);
$xml   = simplexml_load_string($split[1]);

由于您现在拥有了您感兴趣的 XML 文档,因此您可以执行所有其他答案所建议的有关如何解析 SOAP 响应的操作。不再有格式错误的 XML(实际上是一系列连接的格式良好的 XML 文档)。

剩下的就是处理命名空间。

一些指示:

...获取 SOAP 信封主体:

$soap = 'http://www.w3.org/2003/05/soap-envelope';
$body = $xml->children($soap)->Body;

...所有枚举项作为数组:

$wsen = 'http://schemas.xmlsoap.org/ws/2004/09/enumeration';
$xml->registerXPathNamespace('wsen', $wsen);
$items = $body->xpath('.//wsen:*/*[not(namespace-uri(.) = namespace-uri(..))]');

等等等等。

您在回答中给出的关于您自己的代码示例的注释:如果您要在任何命名空间中查找元素名称,您可以在 xpath 中使用local-name()

$pullitem = 'ServiceTag';
$try      = $xml->xpath(sprintf("//*[local-name(.)='%s']", $pullitem));

printf("pullitem '%s' has been foun in the following namespaces:\n", $pullitem);
foreach ($try as $element) {
    $nsURI = dom_import_simplexml($element)->namespaceURI;
    printf(" - %s\n", $nsURI);
}

这确实可以节省您五个左右的单独 xpath 调用。

如果你最终不想关心命名空间,因为你希望它是每个项目的“那个”,你可以在 DOMDocument 的帮助下通过将每个结果提取到它的新文档中来为每个结果创建一个 SimpleXMLElement自己的:

/**
 * create a new SimpleXMLElement out of an existing one
 *
 * @param SimpleXMLElement $item
 *
 * @return SimpleXMLElement
 */
function simplexml_export_element(SimpleXMLElement $item) {
    $doc  = new DOMDocument();
    $node = $doc->importNode(dom_import_simplexml($item), true);
    $node = $doc->appendChild($node);
    return simplexml_load_string($doc->saveXML($doc->documentElement), get_class($item), 0, $node->namespaceURI);
}

这样的帮助例程很有帮助,因为它将元素自己的名称空间作为新 SimpleXMLElement 的名称空间。这允许直接访问同一命名空间中的子项。进一步使用它的代码不需要特别关心这个“默认”命名空间。

例子:

$split = preg_split('~\Q<?xml version="1.0" encoding="UTF-8"?>\E\R~u', $sequenced_xml, 2, PREG_SPLIT_NO_EMPTY);

$xml  = new SimpleXMLElement($split[1]);

$soap = 'http://www.w3.org/2003/05/soap-envelope';
$body = $xml->children($soap)->Body;

$wsen = 'http://schemas.xmlsoap.org/ws/2004/09/enumeration';
$xml->registerXPathNamespace('wsen', $wsen);

$enumerated = $body->xpath('.//wsen:*/*[not(namespace-uri(.) = namespace-uri(..))]');
$enumerated = array_map('simplexml_export_element', $enumerated);

foreach ($enumerated as $item) {
    echo $item->getName(), "\n";
    foreach ($item as $key => $value) {
        printf(" - %s: %s\n", $key, $value);
    }
}

输出:

DCIM_SystemView
 - AssetTag: 
 - BIOSReleaseDate: 11/20/2013
 - BIOSVersionString: 2.1.3
 - BaseBoardChassisSlot: NA
 - BatteryRollupStatus: 1
 - BladeGeometry: 255
 - BoardPartNumber: 061P35A00
 - BoardSerialNumber: CN70163231007K
 - CMCIP: 
 - CPLDVersion: 1.0.3
 - CPURollupStatus: 1
 - ChassisModel: 
 - ChassisName: Main System Chassis
 - ChassisServiceTag: REMOVED
 - ChassisSystemHeight: 2
 - DeviceDescription: System
 - ExpressServiceCode: 33088672189
 - FQDD: System.Embedded.1
 - FanRollupStatus: 1
 - HostName: 
 - InstanceID: System.Embedded.1
 - LastSystemInventoryTime: 20140928010936.000000+000
 - LastUpdateTime: 20140220171215.000000+000
 - LicensingRollupStatus: 1
 - LifecycleControllerVersion: 2.1.0
 - Manufacturer: Dell Inc.
 - MaxCPUSockets: 2
 - MaxDIMMSlots: 24
 - MaxPCIeSlots: 6
 - MemoryOperationMode: OptimizerMode
 - Model: PowerEdge R720xd
 - NodeID: F7852V1
 - PSRollupStatus: 1
 - PlatformGUID: 3156324f-c0c6-3580-3810-00374c4c4544
 - PopulatedCPUSockets: 2
 - PopulatedDIMMSlots: 8
 - PopulatedPCIeSlots: 2
 - PowerCap: 598
 - PowerCapEnabledState: 3
 - PowerState: 2
 - PrimaryStatus: 1
 - RollupStatus: 1
 - ServerAllocation: 
 - ServiceTag: REMOVED
 - StorageRollupStatus: 1
 - SysMemErrorMethodology: 6
 - SysMemFailOverState: NotInUse
 - SysMemLocation: 3
 - SysMemMaxCapacitySize: 1572864
 - SysMemPrimaryStatus: 1
 - SysMemTotalSize: 65536
 - SystemGeneration: 12G Monolithic
 - SystemID: 1320
 - SystemRevision: 0
 - TempRollupStatus: 1
 - UUID: 4c4c4544-0037-3810-8035-c6c04f325631
 - VoltRollupStatus: 1
 - smbiosGUID: 44454c4c-3700-1038-8035-c6c04f325631

希望这对您的情况仍然有帮助。

于 2014-10-10T22:51:38.123 回答
1

感谢@Paul Crovella 的评论,这实际上让我走上了正轨。我最终学到了很多东西。无论如何,解决方案最终是多种事物的组合。这篇文章是最有帮助的,但是:

如何通过 PHP 阅读 SOAP 回复信封

对于那些好奇我是如何工作的人,我最终编写了一个函数来为我解析它。为了您的方便,它位于下方。希望这可以帮助其他正在寻找与我相同的东西的人!

function pull_wsman_idrac($run_iDRAC_IP,$user,$pass,$namespace,$pullitem,$context="NULL"){
    //Example pull_wsman("10.10.10.10","root","calvin","DCIM_SystemView","ServiceTag")
    //Possible namespaces are listed on the DCIM profile page on Dell's website. http://en.community.dell.com/techcenter/systems-management/w/wiki/1906.dcim-library-profile
    //Namspaces used (primary) : DCIM_SystemView, DCIM_CPUView, DCIM_ControllerView, DCIM_VFlashView, DCIM_MemoryView, DCIM_PCIDeviceView, DCIM_NICView, DCIM_IDRACCARDView
    $wsman_output = shell_exec("wsman enumerate http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/root/dcim/$namespace -h $run_iDRAC_IP -V -v -c dummy.cert -P 443 -u $user -p $pass -j utf-8 -y basic");
    $wsman_output = preg_replace('/\<\?xml version="1.0" encoding="UTF-8"\?\>/', '', $wsman_output);
    $wsman_output = preg_replace('/\<\?xml version="1.0" encoding="utf-8"\?\>/', '', $wsman_output);
    $wsman_output = trim($wsman_output);
    $conv = <<<XML  
    <root> $wsman_output </root> 
    XML;
    $xml = new SimpleXMLElement($conv);
    $xml->registerXPathNamespace("s", "http://www.w3.org/2003/05/soap-envelope");
    $xml->registerXPathNamespace("wsa", "http://schemas.xmlsoap.org/ws/2004/08/addressing");
    $xml->registerXPathNamespace("wsen", "http://schemas.xmlsoap.org/ws/2004/09/enumeration");
    $xml->registerXPathNamespace("n1", "http://schemas.dell.com/wbem/wscim/1/cim-schema/2/$namespace");
    $xml->registerXPathNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
    if($context == "NULL"){
        $try = $xml->xpath("//s:$pullitem");
        if(!empty($try)){ $cpath="s"; goto gotit;   }
        $try = $xml->xpath("//wsa:$pullitem");
        if(!empty($try)){ $cpath="wsa"; goto gotit; }
        $try = $xml->xpath("//wsen:$pullitem");
        if(!empty($try)){ $cpath="wsen"; goto gotit;    }
        $try = $xml->xpath("//n1:$pullitem");
        if(!empty($try)){ $cpath="n1"; goto gotit;  }
        $try = $xml->xpath("//xsi:$pullitem");
        if(!empty($try)){ $cpath="xsi"; goto gotit; }
        gotit:
        if(empty($cpath)){ return 0; }
            else{
                $a = array();
                $i = 0;
                foreach($try as $tried){
                    foreach($tried->xpath("//n1:*") as $trys) { 
                    $go = $trys->getName();
                    //$i++; echo "$i : " . $go . "\n";
                    $a[] = $go;
                }
            }
            $a = array_unique($a);
            return $a;      
        }
    }
    elseif($context !== "NULL"){
        $try = $xml->xpath("//$context:$pullitem");
        $a = array();
        foreach($try as $extract){
            $a[] = $extract;
        }
        if(!empty($a)){
            return $a;
        }
        else{ return 0; }
    }
}
于 2014-10-09T21:38:51.283 回答