Open andrew2net opened 1 month ago
@andrew2net I've tried the following and this is currently working in lutaml/shale
I've created the following classes
class PureTextElement < Shale::Mapper
attribute :text, Shale::Type::String
attribute :em, Shale::Type::String # Em
attribute :strong, Shale::Type::String # Strong
attribute :underline, Shale::Type::String # Underline
xml do
preserve_element_order true
map_content to: :text
map_element 'em', to: :em
map_element 'strong', to: :strong
map_element 'underline', to: :underline
end
end
class Sup < Shale::Mapper
attribute :pure_text_element, PureTextElement, collection: true
xml do
root 'sup'
map_element 'PureTextElement', to: :pure_text_element
end
end
require 'shale/adapter/nokogiri'
Shale.xml_adapter = Shale::Adapter::Nokogiri
then I tried to convert the following XML from and to xml through shale
xml = <<~XML
<sup>
<PureTextElement>
Some text <strong>Bold text</strong><em>important text</em>
</PureTextElement>
<PureTextElement>
Some text <em>Bold text</em><strong>important text</strong>
</PureTextElement>
<PureTextElement>
Some text <strong>Bold text</strong><em>important text</em>
</PureTextElement>
</sup>
XML
obj = Sup.from_xml(x)
obj.to_xml(pretty: true)
and it generates the same output as input.
preserve_element_order true
is our own extension attribute to shale to preserve the order of the elements so it outputs them in the same order as they are received.
Can you explain what else is missing from shale?
@HassanAkbar it is almost good but the PureTextElement
is the definition name, not an element name. XML should look like:
<sup>
Some text <strong>Bold text</strong><em>important text</em>
Some text <em>Bold text</em><strong>important text</strong>
Some text <strong>Bold text</strong><em>important text</em>
</sup>
I've tried to map the PureTextElement
as content but Shale only recognizes text, it ignores other elements.
@HassanAkbar I found workaround but it looks not so elegant:
module Relaton
module Model
class PureTextElement
def initialize(element)
@element = element
end
def self.cast(value)
value
end
def self.of_xml(node) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/AbcSize,Metrics/MethodLength
case node.name
when "text"
text = node.text.strip
text.empty? ? nil : new(text)
when "em" then new Em.of_xml(node)
when "strong" then new Strong.of_xml(node)
when "sub" then new Sub.of_xml(node)
when "sup" then new Sup.of_xml(node)
when "tt" then new Tt.of_xml(node)
when "underline" then new Underline.of_xml(node)
when "strike" then new Strike.of_xml(node)
when "smallcap" then new Smallcap.of_xml(node)
when "br" then new Br.of_xml(node)
end
end
def add_to_xml(parent, doc)
if @element.is_a? String
doc.add_text(parent, @element)
else
parent << @element.to_xml
end
end
module Mapper
def self.included(base)
base.class_eval do
attribute :content, PureTextElement, collection: true
xml do
map_content to: :content, using: { from: :content_from_xml, to: :content_to_xml }
end
end
end
def content_from_xml(model, node)
(node.instance_variable_get(:@node) || node).children.each do |n|
next if n.text? && n.text.strip.empty?
model.content << PureTextElement.of_xml(n)
end
end
def content_to_xml(model, parent, doc)
model.content.each do |e|
e.add_to_xml parent, doc
end
end
end
end
end
end
module Relaton
module Model
class Sup < Shale::Mapper
include PureTextElement::Mapper
@xml_mapping.instance_eval do
root "sup"
end
end
end
end
Is it possible to implement this functionality within Shale? Also in some cases we need to add elements to a bundle:
<define name="strike">
<element name="strike">
<zeroOrMore>
<choice>
<ref name="PureTextElement"/>
<ref name="index"/>
<ref name="index-xref"/>
</choice>
</zeroOrMore>
</element>
</define>
To implement this I use the code:
module Relaton
module Model
class Strike < Shale::Mapper
class Content
def initialize(elements = [])
@elements = elements
end
def self.cast(value)
value
end
def self.of_xml(node)
elms = node.children.map do |n|
case n.name
when "index" then Index.of_xml n
when "index-xref" then IndexXref.of_xml n
else PureTextElement.of_xml n
end
end
new elms
end
def add_to_xml(parent, doc)
@elements.each { |e| e.add_to_xml parent, doc }
end
end
attribute :content, Content, collection: true
xml do
root "strike"
map_content to: :content, using: { from: :content_from_xml, to: :content_to_xml }
end
def content_from_xml(model, node)
model.content << Content.of_xml(node.instance_variable_get(:@node) || node)
end
def content_to_xml(model, parent, doc)
model.content.each { |e| e.add_to_xml parent, doc }
end
end
end
end
@andrew2net I've been looking into the code and @ronaldtse Suggestion in this ticket -> https://github.com/metanorma/sts-ruby/issues/12.
Currently, I'm working on adding map_all_content
and map_content_element
methods to xml so the classes will look something like below.
class TextWithTags < Shale::Mapper
attribute :content, Shale::Type::String
attribute :strong, Shale::Type::String, collection: true
attribute :em, Shale::Type::String, collection: true
xml do
root "text-with-tags"
map_all_content to: :content
map_content_element 'strong', to: :strong
map_content_element 'em', to: :em
end
end
@HassanAkbar in the example the content
, strong
, and em
elements are in predefined order. This issue is about having any number of the elements in any order. Will it be possible to achieve it with the update?
In grammar, which describes the Relaton data model, we have groups of elements included in other elements. For example:
With the ShaleI want to be able to use a pattern like:
We need to have any number of bundled elements (PureTextElements in the example) as children in any sequence. We need Shale to parse that sequence and render it in the same order.