2008-09-17 8 views
7

Dada una estructura XML moderadamente compleja (docenas de elementos, cientos de atributos) sin XSD y un deseo de crear un modelo de objetos, ¿cuál es una manera elegante de evitar escribir los métodos repetitivos from_xml() y to_xml()?¿Código de Ruby para la serialización de XML rápida y sucia?

Por ejemplo, dada:

<Foo bar="1"><Bat baz="blah"/></Foo> 

¿Cómo puedo evitar escribir secuencias sin fin de:

class Foo 
    attr_reader :bar, :bat 

    def from_xml(el) 
    @bar = el.attributes['bar'] 
    @bat = Bat.new() 
    @bat.from_xml(XPath.first(el, "./bat") 
    end 
etc... 

no me importa la creación de la estructura del objeto de forma explícita; que es la serialización que sólo soy seguro que puede ser atendido con un poco de programación de alto nivel ...


No estoy tratando de salvar una o dos líneas por clase (moviendo el comportamiento from_xml en inicializador o método de clase, etc.). Estoy buscando la solución "meta" que duplica mi proceso mental:

"Sé que cada elemento se convertirá en un nombre de clase. Sé que cada atributo XML va a ser un nombre de campo. Sé que el código que se debe asignar es simplemente @ # {attribute_name} = el. [# {attribute_name}] y luego recurse en subelementos. Y viceversa en to_xml. "


estoy de acuerdo con la sugerencia de que una clase "constructor", además de XmlSimple parece el camino correcto. XML -> Hash ->? -> Object Model (! Y Ganancias)


actualización 2008-09-18 AM: Excelentes sugerencias de @Roman, @fatgeekuk y @ScottKoon parecen haber roto el problema abierto. Descargué la fuente de HPricot para ver cómo resolvió el problema; Los métodos clave son claramente instance_variable_set y class_eval. trabajo IRB es muy alentador, estoy avanzando en la aplicación .... Muy emocionados

Respuesta

0

Podría definir un método que falta que le permite hacer:

@bar = el.bar? Eso eliminaría algunos repetidores. Si Bat siempre va a ser definido de esa manera, se puede empujar el XPath en el método initialize,

class Bar 
    def initialize(el) 
    self.from_xml(XPath.first(el, "./bat")) 
    end 
end 

hpricot o REXML puede ayudar también.

1

Puede usar Builder en lugar de crear su método to_xml, y puede usar XMLSimple para extraer su archivo xml en Hash en lugar de usar el método _xml. Desafortunadamente, no estoy seguro de que ganes mucho con el uso de estas técnicas.

0

Subclase attr_accessor para compilar su to_xml y from_xml para usted.

Algo como esto (nota, esto no es totalmente funcional, sólo un esbozo)

class XmlFoo 
    def self.attr_accessor attributes = {} 
    # need to add code here to maintain a list of the fields for the subclass, to be used in to_xml and from_xml 
    attributes.each do |name, value| 
     super name 
    end 
    end 

    def to_xml options={} 
    # need to use the hash of elements, and determine how to handle them by whether they are .kind_of?(XmlFoo) 
    end 

    def from_xml el 
    end 
end 

que luego se podría utilizar como ....

class Second < XmlFoo 
    attr_accessor :first_attr => String, :second_attr => Float 
end 

class First < XmlFoo 
    attr_accessor :normal_attribute => String, :sub_element => Second 
end 

Hope esto le da una idea general.

1

Sugiero usar un XmlSimple para empezar. Después de ejecutar XmlSimple # xml_in en su archivo de entrada, obtiene un hash. A continuación, puede recursivo en él (obj.instance_variables) y girar todos los hashes internos (element.is_a (hash)?) A los objetos del mismo nombre, por ejemplo:

obj.instance_variables.find {|v| obj.send(v.gsub(/^@/,'').to_sym).is_a?(Hash)}.each do |h| 
    klass= eval(h.sub(/^@(.)/) { $1.upcase }) 

Tal vez una forma más limpia se puede encontrar para hacer esto. Después, si desea crear un xml de este nuevo objeto, probablemente necesite cambiar XmlSimple # xml_out para aceptar otra opción, que distingue su objeto del hash usual que se usa para recibir como argumento, y luego Tendrá que escribir su versión del método XmlSimple # value_to_xml, por lo que llamará al método de acceso en lugar de intentar acceder a una estructura de hash. Otra opción es hacer que todas sus clases sean compatibles con el operador [] devolviendo la variable de instancia deseada.

Cuestiones relacionadas