Преобразование OpenStruct/Hash в XML

У меня есть коллекция элементов OpenStruct, с помощью которых мне нужно создать XML с помощью 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', 
  :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, но не смог найти правильную логику для этого.

Ссылка.. Как добавить дочерние узлы в NodeSet с помощью Nokogiri


person user1023627    schedule 25.06.2012    source источник
comment
Почему вы используете OpenStruct вместо Hash из любопытства?   -  person Phrogz    schedule 25.06.2012
comment
Ошибка: вы просите, чтобы вывод включал CRSUS113246A, но он нигде не отображается во входных данных. Как рассчитывается ProductId?   -  person Phrogz    schedule 25.06.2012
comment
-1 за плохо сформулированный вопрос. Вы не дали полного представления ни о вводе образца, ни о желаемом выходе, и поэтому трудно точно знать, чего вы хотите.   -  person Phrogz    schedule 25.06.2012
comment
Я хотел, чтобы данные были организованы таким образом, чтобы было легко создать XML для нескольких сценариев. Я не вижу смысла использовать YAML в этом случае, поскольку он занимает мое рабочее пространство (т. е. несколько .yml для нескольких сценариев). Итак, я ищу альтернативу YML. Я не вижу масштабируемости OpenStruct для больших наборов данных, но сейчас мне этого достаточно. Можно ли этого добиться, используя Json для организации нескольких наборов данных?   -  person user1023627    schedule 25.06.2012
comment
Я бы просто использовал литералы Hash.   -  person Phrogz    schedule 26.06.2012


Ответы (2)


Код установки

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>

Если вам нужно более общее преобразование (представление XML вашей иерархии OpenStruct), см. одно из двух решений ниже:

Один из способов выполнить универсальное преобразование

# 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
person Phrogz    schedule 25.06.2012
comment
Обновил вопрос. У меня есть два продукта в CatalogList, и если я добавлю второй продукт. Как мне нужно закодировать это ..collection[0].catalogList.catalogProductInfo[0].productType" .. не работает хорошо .. - person user1023627; 08.07.2012
comment
Я получил эту ошибку .. undefined method []' for #‹OpenStruct productType=Pen, productName=Reynolds› (NoMethodError)` Не могли бы вы сказать мне, как изменить приведенный выше код для обработки нескольких продуктов .. Спасибо - person user1023627; 08.07.2012
comment
Я застрял здесь, пожалуйста, помогите мне - person user1023627; 08.07.2012
comment
Отредактируйте свой вопрос, указав фактические входные данные теста, фактический текущий код и фактический вывод XML, который вы хотите получить для этого входного сигнала теста. - person Phrogz; 08.07.2012
comment
Я обновил свой вопрос, код, который я пробовал, а также вывод, который я хотел в соответствии с вашим предложением, я не мог зациклиться на вложенном массиве элементов OpenStruct, я застрял здесь и не мог продолжить. Пожалуйста, помогите мне. - person user1023627; 09.07.2012
comment
Не могли бы вы помочь. Я застрял здесь. - person user1023627; 09.07.2012
comment
Я не мог пройти через вложенный массив элементов OpenStruct - person user1023627; 09.07.2012
comment
Привет @Phrogz! Я попытался изменить код, но не смог. - person user1023627; 09.07.2012
comment
!Поддерживает ли OpenStruct вложенные массивы? Не могли бы вы мне помочь? - person user1023627; 10.07.2012
comment
Привет Phrogz, Пожалуйста, предложите, как зациклить вложенные массивы OpenSturct - person user1023627; 10.07.2012
comment
@user1023627 user1023627 Я получаю все отправленные вами сообщения, когда вы их отправляете. Я буду работать над этой проблемой, если и когда у меня будет время. Вам не нужно продолжать оставлять комментарии, так как это не способствует вашему делу. - person Phrogz; 10.07.2012
comment
У тебя была возможность посмотреть на это? - person user1023627; 17.07.2012
comment
@user1023627 user1023627 Я только что посмотрел и обнаружил: а) ваши исходные данные синтаксически неверны (вам не хватает запятой); б) ваш код Ruby синтаксически неверен (у вас отсутствует несколько операторов end, которые было трудно найти, потому что ваш отступ не был последовательным); в) в вашем источнике много нерелевантных данных (вы можете задать в основном тот же вопрос с гораздо меньше кода); г) ваш collection установлен в массив из одного элемента, и я не могу представить, зачем вам это нужно. - person Phrogz; 17.07.2012
comment
д) В этой строке отсутствует точка: xml CatalogStoreId ctlg.catalogStoreId - person Phrogz; 17.07.2012

Ответ на измененный вопрос и данные.

Вот ваши исходные данные, обобщенные:

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. Вместо этого просто используйте литералы Hash. В сочетании с кодом, который я предоставил в своем другом ответе, вот как выглядят ваши данные (упрощенные) и рабочее решение:

Источник данных

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>
person Phrogz    schedule 17.07.2012