将OpenStruct / Hash转换为XML

时间:2012-06-25 13:09:46

标签: ruby xml nokogiri openstruct

我有一组OpenStruct元素,我需要在Nokogiri的帮助下构建XML。

collection = [
OpenStruct.new(:catalogStoreNumber => '657758',
:catalogStoreId => 'CTH6536',
:catalogStoreLocation => 'UnitedStates', 
:catalogOwnerId => 'TYCT11190',
:catalogOwner => 'McGrawHill Pub.',
:catalogList => OpenStruct.new(
  :catalogProductInfo => OpenStruct.new(
  :productType => 'Book',
  :productName => 'The Client', 
  :productAuthorized => 'Y', 
  :productId => 'BKSUS113246A',
  :productVerificationCode => '4546747', 
  :productPurcTransactionTime => '2012-05-21T13:36:38+05:30',
  :productAuditDetails => OpenStruct.new(
    :productAuditNo => '1',
    :prodHandledByUser => 'StoreUserS14',
    :productAuditTime => '2012-05-21T13:36:38+05:30',
    :productAuditAdminId => 'McGr1132', 
    :productPurchaseRate => '50.14 Prcnt',
    :productSystemLoggerId => 'UNX-NETW4536'
    ), 
  :productAuditDetails => OpenStruct.new(
    :productAuditNo => '2',
    :prodHandledByUser => 'OnlineUserOn008',
    :productAuditTime => '2012-05-23T16:16:08+05:30',
    :productAuditAdminId => 'McGr1132', 
    :productPurchaseRate => '84.86 Prcnt',
    :productSystemLoggerId => 'UNX-NETW4536'
    )
  ),
:catalogProductInfo => OpenStruct.new(
  :productType => 'Pen',
  :productName => 'Reynolds'
  :productAuthorized => 'N', 
  :productId => 'PNSUS228886B',
  :productVerificationCode => '2330076', 
  :productPurcTransactionTime => '2012-04-22T15:06:18+04:30',
  :productAuditDetails => OpenStruct.new(
    :productAuditNo => '1',
    :prodHandledByUser => 'CCUserA14',
    :productAuditTime => '2012-04-26T13:36:38+05:30',
    :productAuditAdminId => 'ReyGr1132', 
    :productPurchaseRate => '20.19 Prcnt',
    :productSystemLoggerId => 'WIN-NETW4536'
    )
  )
 )
)] 

我尝试使用以下代码..根据您的回答(精选元素)

builder = Nokogiri::XML::Builder.new do |xml|
    xml.CatalogOrder do 
    collection.each do |ctlg|
     xml.CatalogStoreNumber ctlg.catalogStoreNumber
     xml CatalogStoreId ctlg.catalogStoreId
     xml.CatalogOwnerId ctlg.catalogOwnerid
     xml.CatalogOwner ctlg.catalogOwner
      xml.CatalogList do
        prod_count = 0
        aud_list_count = 0 
        collection.each do |prod|
          info = prod.catalogList[0].catalogProductInfo
          xml.ProductInfo do
            xml.ProductType info.productType
            xml.ProductName info.productName
            xml.ProductId   info.productId
            xml.ProductVerificationCode info.ProductVerificationCode
            xml.ProductPurcTransactionTime info.productPurcTransactionTime
            xml.ProductAuditDetails do
            collection.each do |aud_dtl|
             aud_info = aud_dtl.catalogList[0].catalogProductinfo[0].productAuditDetails
             xml.ProductAuditNo aud_info.productAuditNo
             xml.ProdHandledByUser aud_info.prodHandledByUser
             xml.ProductAuditTime aud_info.productAuditTime
             xml.ProductAuditAdminId aud_info.productAuditAdminId
             xml.ProductPurchaseRate aud_info.productPurchaseRate
             xml.ProductSystemLoggerId aud_info.productSystemLoggerId
            # Do whatever you must above to concoct your ProductId
          end
         aud_list_count = aud_list_count + 1
        end
        prod_count = prod_count + 1
      end
    end
  end

  puts builder.to_xml

我需要输出如下......

<CatalogOrder>
 <CatalogStoreNumber>657758</CatalogStoreNumber>
 <CatalogStoreId>CTH6536</CatalogStoreId>
 <CatalogStoreLocation>UnitedStates</CatalogStoreLocation>
 <CatalogOwnerId>TYCT11190</CatalogOwnerId>
 <CatalogOwner>McGrawHill Pub.</CatalogOwner>
 <CatalogList>
   <CatalogProductInfo>
     <ProductType>Book</ProductType>
     <ProductName>The Client</ProductName>
     <ProductAuthorized>Y</ProductAuthorized>
     <ProductId>BKSUS113246A</ProductId>
     <ProductVerificationCode>4546747</ProductVerificationCode>
     <ProductPurcTransactionTime>2012-05-21T13:36:38+05:30</ProductPurcTransactionTime>
     <ProductAuditDetails>
       <ProductAuditNo>1</ProductAuditNo>
       <ProdHandledByUser>StoreUserS14</ProdHandledByUser>
       <ProductAuditTime>2012-05-21T13:36:38+05:30</ProductAuditTime>
       <ProductAuditAdminId>McGr1132</ProductAuditAdminId>
       <ProductPurchaseRate>50.14 Prcnt</ProductPurchaseRate>
       <ProductSystemLoggerId>WIN-NETW4536</ProductSystemLoggerId>
    </ProductAuditDetails>
    <ProductAuditDetails>
       <ProductAuditNo>2</ProductAuditNo>
       <ProdHandledByUser>OnlineUserOn008</ProdHandledByUser>
       <ProductAuditTime>2012-05-23T16:16:08+05:30</ProductAuditTime>
       <ProductAuditAdminId>McGr1132</ProductAuditAdminId>
       <ProductPurchaseRate>84.86 Prcnt</ProductPurchaseRate>
       <ProductSystemLoggerId>UNX-NETW4536</ProductSystemLoggerId>
    </ProductAuditDetails>
   </CatalogProductInfo>
   <CatalogProductInfo>
     <ProductType>Pen</ProductType>
     <ProductName>Reynolds</ProductName> 
     <ProductAuthorized>N</ProductAuthorized>        
     <ProductId>PNSUS228886B</ProductId>
     <ProductVerificationCode>2330076</ProductVerificationCode>
     <ProductPurcTransactionTime>2012-04-22T15:06:18+04:30</ProductPurcTransactionTime>
     <ProductAuditDetails>
       <ProductAuditNo>1</ProductAuditNo>
       <ProdHandledByUser>CCUserA14</ProdHandledByUser>
       <ProductAuditTime>2012-04-26T13:36:38+05:30</ProductAuditTime>
       <ProductAuditAdminId>ReyGr1132</ProductAuditAdminId>
       <ProductPurchaseRate>20.19 Prcnt</ProductPurchaseRate>
       <ProductSystemLoggerId>WIN-NETW4536</ProductSystemLoggerId>
     </ProductAuditDetails>
   </CatalogProductInfo>
 </CatalogList>
</CatalogOrder> 

我试图在嵌套的OpenStruct数组中循环,但无法找到合适的逻辑。

参考.. How to add child nodes in NodeSet using Nokogiri

2 个答案:

答案 0 :(得分:3)

设置代码

require 'ostruct'
require 'nokogiri'

collection = [
  OpenStruct.new(
    :catalogStoreNumber => '657758',
    :catalogStoreId => 'CTH6536',
    :catalogStoreLocation => 'UnitedStates', 
    :catalogOwnerId => 'TYCT11190',
    :catalogOwner => 'McGrawHill Pub.',
    :catalogList => OpenStruct.new(
      :catalogProductInfo => OpenStruct.new(
        :productType => 'Book',
        :productName => 'The Client'
      )
    )
  )
]

如果您想亲自挑选元素和数据

builder = Nokogiri::XML::Builder.new do |xml|
  xml.CatalogOrder do
    xml.CatalogList do
      collection.each do |prod|
        info = prod.catalogList.catalogProductInfo
        xml.ProductInfo do
          xml.ProductType info.productType
          xml.ProductName info.productName
          xml.ProductId   "#{prod.catalogOwnerId}-#{prod.catalogStoreNumber}"
          # Do whatever you must above to concoct your ProductId
        end
      end
    end
  end
end

puts builder.to_xml

输出

<?xml version="1.0"?>
<CatalogOrder>
  <CatalogList>
    <ProductInfo>
      <ProductType>Book</ProductType>
      <ProductName>The Client</ProductName>
      <ProductId>TYCT11190-657758</ProductId>
    </ProductInfo>
  </CatalogList>
</CatalogOrder>

如果您想要更通用的转换(OpenStruct层次结构的XML表示),请参阅以下两种解决方案之一:

执行通用转换的一种方法

# Add all entries of an OpenStruct to an XML builder
# Recursively creates sub-nodes for OpenStruct instances
def ostruct_each(ostruct,xml)
  ostruct.instance_variable_get(:@table).each do |field,value|
    if value.is_a?(OpenStruct)
      xml.send(field) do
        ostruct_each(value,xml)
      end
    else
      xml.send(field,value)
    end
  end
end

builder = Nokogiri::XML::Builder.new do |xml|
  xml.CatalogOrder do
    xml.CatalogList do
      collection.each do |prod_info|
        xml.ProductInfo do
          ostruct_each(prod_info,xml)
        end
      end
    end
  end
end

puts builder.to_xml

输出

<?xml version="1.0"?>
<CatalogOrder>
  <CatalogList>
    <ProductInfo>
      <catalogStoreNumber>657758</catalogStoreNumber>
      <catalogStoreId>CTH6536</catalogStoreId>
      <catalogStoreLocation>UnitedStates</catalogStoreLocation>
      <catalogOwnerId>TYCT11190</catalogOwnerId>
      <catalogOwner>McGrawHill Pub.</catalogOwner>
      <catalogList>
        <catalogProductInfo>
          <productType>Book</productType>
          <productName>The Client</productName>
        </catalogProductInfo>
      </catalogList>
    </ProductInfo>
  </CatalogList>
</CatalogOrder>

通用转换它的另一种方式

# Create a NodeSet of elements for all attributes in an OpenStruct
# Recursively creates child elements for any value that is an OpenStruct
def ostruct_to_elements(xml_doc,ostruct)
  Nokogiri::XML::NodeSet.new(
    xml_doc,
    ostruct.instance_variable_get(:@table).map do |name,val|
      xml_doc.create_element(name.to_s).tap do |el|
        el << (val.is_a?(OpenStruct) ? ostruct_to_elements(xml_doc,val) : val)
      end
    end
  )
end

builder = Nokogiri::XML::Builder.new do |xml|
  xml.CatalogOrder do
    xml.CatalogList do
      collection.each do |prod_info|
        xml.ProductInfo do
          xml.parent << ostruct_to_elements(xml.doc,prod_info)
        end
      end
    end
  end
end

puts builder.to_xml

答案 1 :(得分:0)

回答您更改的问题和数据。

这是您的源数据,总结如下:

collection = [
  OpenStruct.new(
    :foo => 'bar',
    :list => OpenStruct.new(
      :catalogProductInfo => OpenStruct.new(...)
      :catalogProductInfo => OpenStruct.new(...)
    )
  )
]

我们注意到的第一件事是collection是一个数组,但它只有一个项目。这看起来不太有用。

要注意的第二个更重要的事情是你试图将OpenStruct(内部的)用作数组。这完全失败了,因为第二个值完全覆盖了第一个:

require 'ostruct'
p OpenStruct.new( a:1, a:2 )
#=> #<OpenStruct a=2>

结构中的同一个键不能有两个值。相反,您可能想要在对象内部使用数组值。例如:

root = OpenStruct.new(
  :foo => 'bar',
  :list => [  # this is an array of two distinct objects
    OpenStruct.new( :catalogProductInfo => OpenStruct.new(...) ),
    OpenStruct.new( :catalogProductInfo => OpenStruct.new(...) )
  ]
)

第三,正如我前面提到的,我认为你没有充分的理由在这里使用OpenStruct。相反,只需使用哈希文字。结合我在其他答案中提供的代码,这就是您的数据(简化)和工作解决方案的样子:

源数据

MyOrder = {
  catalogStoreNumber: '657758',  # Ruby 1.9 Hash syntax;
  catalogStoreId: 'CTH6536',     # same as :catalogStoreId => 'CTH536'
  catalogList: {
    catalogProductInfo: [
      {
        productType: 'Book',
        productName: 'The Client', 
        productAuditDetails: [
          { productAuditNo: '1', prodHandledByUser: 'StoreUserS14'    },
          { productAuditNo: '2', prodHandledByUser: 'OnlineUserOn008' }
        ]
      },
      {
        productType: 'Pen',
        productName: 'Reynolds',
        productAuditDetails: [
          { productAuditNo: '1', prodHandledByUser: 'CCUserA14' }
        ]
      }
    ]
  }
}

通用转换

# Adds key/value pairs from a Hash to a Nokogiri::XML::Builder
def hash2xml(hash,xml)
  hash.each do |field,value|
    name = field.to_s.sub(/^./,&:upcase) # convert "fooBar" to "FooBar"
    case value
      when Hash  then xml.send(name){ hash2xml(value,xml) }
      when Array then value.each{ |o| xml.send(name){ hash2xml(o,xml) } }
      else            xml.send(name,value)
    end
  end
end

工作代码

builder = Nokogiri::XML::Builder.new do |xml|
  xml.CatalogOrder do
    hash2xml(MyOrder,xml)
  end
end
puts builder.to_xml

输出

<?xml version="1.0"?>
<CatalogOrder>
  <CatalogStoreNumber>657758</CatalogStoreNumber>
  <CatalogStoreId>CTH6536</CatalogStoreId>
  <CatalogList>
    <CatalogProductInfo>
      <ProductType>Book</ProductType>
      <ProductName>The Client</ProductName>
      <ProductAuditDetails>
        <ProductAuditNo>1</ProductAuditNo>
        <ProdHandledByUser>StoreUserS14</ProdHandledByUser>
      </ProductAuditDetails>
      <ProductAuditDetails>
        <ProductAuditNo>2</ProductAuditNo>
        <ProdHandledByUser>OnlineUserOn008</ProdHandledByUser>
      </ProductAuditDetails>
    </CatalogProductInfo>
    <CatalogProductInfo>
      <ProductType>Pen</ProductType>
      <ProductName>Reynolds</ProductName>
      <ProductAuditDetails>
        <ProductAuditNo>1</ProductAuditNo>
        <ProdHandledByUser>CCUserA14</ProdHandledByUser>
      </ProductAuditDetails>
    </CatalogProductInfo>
  </CatalogList>
</CatalogOrder>