Wednesday, August 20, 2008

Creating a RELAX NG schema from classes using XAML rules with IronRuby

In this post I'm going to show a little IronRuby program that generates a RELAX NG Schema Definition for XAML documents using some of the rules to map XML to classes.

This program is was created by modifiying the code of the
previous post to also generate RELAX NG Compact Syntax.

My goal with this post is to use the generated schema with a tool that supports RELAX NG to assist the creation of XAML document.

As with the previous post mentions, not all XAML rules can be expressed in XSD or in this case in RELAX NG. However I hope that the generated schema at least provides some help for editing XAML files.

Changes to RootClassNode and ClassNode classes

The RootClassNode and ClassNode contains information for classes that need an schema definition.

Here's the method to create the definition for one element:


def write_schema_definition_rlx(file)
ctype_name = @the_type.Name.to_s+"Atts"
file.print("#{ctype_name} = ")

write_properties_definition_rlx(file)

file.puts("")

if (!is_abstract)
file.print("#{@the_type.Name} = element #{@the_type.Name} { #{ctype_name} ,")
write_inner_elements_definition_rlx(file) unless is_abstract
file.puts("}")
end


@children.values.each {|c| c.write_schema_definition_rlx(file)}
end


What this code dos is to create a grammar definition for the attributes of the current class. This definition will be used in the definition of the current element (if not abstract) and in the definition of the descendants of the current element.

The write_properties_definition_rlx method writes the definition of the attributes using the defined properties.


def write_properties_definition_rlx(file)
atts = get_type_properties

file.print "("
file.print atts.map {|p| "attribute #{p.Name} { text } ?"}.join(" , ")
file.print ")*"
end


The write_inner_elements_definition_rlx method adds the definition of the content property if it exists. The add_group_base_type creates a group with all the descendants of the of the type of the content property.


def write_inner_elements_definition_rlx(file)
write_element_properties_definition_rlx(file)
if is_container
file.puts(",")
property_type = get_content_property_type
element_type = get_collection_element_type

if (element_type != nil)

group = @registry.add_group_base_type(element_type.FullName)
file.puts "(#{group} *)"
elsif (@registry.descends_from_base_type(property_type))

group = @registry.add_group_base_type(property_type.FullName)
file.puts(group)
else

file.puts(" text ")
end

end
end


The write_schema_definition_rlx creates the definition of a ClassNode which represents a class that inherits from another class.


def write_schema_definition_rlx(file)
if @the_type.contains_generic_parameters
ctype_name = @the_type.Name.to_s.gsub(/`/,'')+"Atts"
else
ctype_name = @the_type.Name.to_s + "Atts"
end

file.print("#{ctype_name} = #{@the_type.BaseType.Name}Atts")
if get_type_properties.length > 0
file.print(" , ")
write_properties_definition_rlx(file)
end

file.puts("")

if (!is_abstract)
file.puts(" #{@the_type.Name} = element #{@the_type.Name} {")
file.print("#{ctype_name},")
write_inner_elements_definition_rlx(file)
file.puts("}")

end

@children.values.each {|c| c.write_schema_definition_rlx(file)}
end


The generated schema

An example of the generated schema is the following:


TextBlockAtts = FrameworkElementAtts | (attribute FontSize { text } ? , attribute FontFamily { text } ? , attribute FontWeight { text } ? , attribute FontStyle { text } ? , attribute TextAlignment { text } ? , attribute Text { text } ?
... )*
TextBlock = element TextBlock {
TextBlockAtts,(element TextBlock.FontSize {AnyGenElement} | element TextBlock.FontFamily {AnyGenElement} | element TextBlock.FontWeight {AnyGenElement} | element TextBlock.FontStyle {AnyGenElement} | element TextBlock.TextAlignment {AnyGenElement} | element TextBlock.Text {AnyGenElement}
...)*,
(InlineElementGroup *)
}
...
InlineElementGroup = Run | LineBreak


Code for this post and the generated schema can be found here.

No comments: