Skip to content
This repository has been archived by the owner on Jun 13, 2018. It is now read-only.

One XML node references to another #65

Open
canercandan opened this issue Dec 13, 2014 · 0 comments
Open

One XML node references to another #65

canercandan opened this issue Dec 13, 2014 · 0 comments

Comments

@canercandan
Copy link

Another feature that I would like to see as a builtin feature in GSL is XML node referencing. I have started to create much more complex model structures and one should avoid redundancy in MOP methods though, there are still situations when it occurs. Let's provide an example to illustrate the basic idea:

<project script="script.gsl">
  <component name="componentA">
    <element name="AAA"/>
    <element name="BBB"/>
    <element name="CCC"/>
  </component>

  <component name="componentB">
    <element name="AAA"/>
    <element name="BBB"/>
    <element name="CCC"/>
  </component>
</project>

In such case, a naive optimization one can approach to avoid the redundant component node is to identify the node component once, let's say using an id attribute and each time we create a component node with the same structure just references to the identified one thanks to a ref attribute. Let's provide an example of the meaning:

<project script="script.gsl">
  <component id="mycomponent">
    <element name="AAA"/>
    <element name="BBB"/>
    <element name="CCC"/>
  </component>

  <component ref="mycomponent" name="componentA"/>
  <component ref="mycomponent" name="componentB"/>
</project>

In this example, I removed the duplicated component node as well as the name attribute and created two other nodes with the respective names and referencing both to the component identified as mycomponent. The attribute name of the new nodes are only defined at their own level.

More interestingly, I'd like to reference to single nodes that were created in another level or context in my XML model as illustrated in the following example:

<project script="script.gsl">
  <component id="common">
    <component id="mycomponent">
      <element name="AAA"/>
      <element name="BBB"/>
      <element name="CCC"/>
    </component>
    …
  </component>

  <component id="module1">
    <component ref="common.mycomponent" name="componentA"/>
    <component ref="common.mycomponent" name="componentB"/>
  </component>
</project>

In the previous example, I illustrated how to refine the granularity of component nodes using several encapsulated component nodes or just organize them in a way if there are many nodes, they can be identified easily. To solve such a problem and as you may notice, the ref attributes can handle paths in almost a similar way than XPath mechanism's doing. In the example, I am using common.mycomponent to reference to the component node located in the component with the common id and component with the id mycomponent.

One can argue that GSL scripting can fix such a purpose but that's not the point, the point is that I dont want to resort to GSL each time I want to avoid redundancy issue within my XML models. That's why I introduced a few new functions:

  • get_node( PATH, NODENAME, INIT )
  • get_node_attempt( PATH, NODENAME, CONTEXT )
  • get_node_name( NODE, NODENAME )
  • get_node_id( NODE, DELIM, NODENAME )
  • get_node_attribute( NODE, ATTRIBUTE, DELIM, NODENAME )
  • get_node_length( NODE, NODENAME )
  • print_node( NODE, NODENAME )
  • override_attribute( N1, NN, ATTRIBUTE )
  • append_attribute( N1, NN, ATTRIBUTE )
  • override_reference( N1, ORIGIN, NODENAME, CONTEXT )
  • replace_by_reference( N1, N2, NODE_SRC, NODE_DST, CONTEXT )
  • process_references( NODE_SRC [, NODE_DST [, CONTEXT] ] )

But the most important one is:

process_references( NODE_SRC [, NODE_DST [, CONTEXT] ] )

process_references will recursively walk across every nodes that matches to a reference (attribute ref) and a specified node name.

Using the previous XML example, here is a script that actually processes every component that references to other component nodes:

.template 0
process_references('component')
.endtemplate

That was an easy model but what about handling this one:

<project script="script.gsl">
  <component id="common">
    <component id="mycomponent">
      <element name="AAA"/>
      <element name="BBB"/>
      <element name="CCC"/>
    </component>
    …
  </component>

  <component id="module1">
    <division ref="common.mycomponent" name="componentA"/>
    <division ref="common.mycomponent" name="componentB"/>
  </component>
</project>

In such case, I am still referencing to the same component mycomponent but this time it's done from a division node. To accomplish that, here is the script code:

.template 0
process_references('component', 'division')
.endtemplate

Caution: you cannot reference to a node that was not previously defined yet, this could be an enhancement to do that way one doesn't need to focus on the node placement anymore.

The ref attribute can handle a relative path or an absolute path. The both previous examples illustrated these kind of path. The function will first try to find a relative path id and if it doesn't it looks for an absolute path.

In the relative case, one can use : in order to go up to the parent node, for instance:

    …
    <division ref=":common.mycomponent" name="componentA"/>
    …

Nota: It is also possible to use name attribute instead of id to reference to a node.

And here is the effective source code:

.template 0

function global.get_node(path, nodename, init)
  my.init_path = my.path
  my.init ?= project
  my.xml = my.init

  while my.xml <> project & string.substr(my.path, 0, 0) = ":"
    my.xml = my.xml.parent()
    my.path = string.substr(my.path, 1)
  endwhile

  while string.cntch(my.path, '.')
    my.prefix = string.prefix(my.path, '.')
    my.xml = my.xml->$(my.nodename:)((defined(id) & id = my.prefix) | (defined(name) & name = my.prefix))?
    my.path = string.substr(my.path, string.length(my.prefix)+1)
  endwhile

  my.xml = my.xml->$(my.nodename:)((defined(id) & id = my.path) | (defined(name) & name = my.path))?

  return my.xml
endfunction

function global.get_node_attempt(path, nodename, context)
  my.res = get_node(my.path, my.nodename, my.context)?

  if !defined(my.res)
    my.res = get_node(my.path, my.nodename)?
  endif

  if !defined(my.res)
    error('$(my.nodename:) "$(my.path:)" not found')
  endif

  return my.res?
endfunction

function global.get_node_name(node, nodename)
  my.nodename ?= name(my.node)

  my.s = "$(rename(my.node.name))"
  my.node = my.node.parent()
  while defined(my.node) & my.node.name() = my.nodename
    my.s = "$(rename(my.node.name)).$(my.s:)"
    my.node = my.node.parent()
  endwhile

  return my.s
endfunction

function global.get_node_id(node, delim, nodename)
  my.nodename ?= name(my.node)
  my.delim ?= '.'

  my.s = "$(my.node.id:)"
  my.node = my.node.parent()
  while defined(my.node) & my.node.name() = my.nodename
    my.s = "$(my.node.id:)$(my.delim:)$(my.s:)"
    my.node = my.node.parent()
  endwhile

  return my.s
endfunction

function global.get_node_attribute(node, attribute, delim, nodename)
  my.nodename ?= name(my.node)
  my.delim ?= '.'
  my.attribute ?= 'id'

  my.s = "$(my.node.$(my.attribute:):)"
  my.node = my.node.parent()
  while defined(my.node) & my.node.name() = my.nodename
    my.s = "$(my.node.$(my.attribute:):)$(my.delim:)$(my.s:)"
    my.node = my.node.parent()
  endwhile

  return my.s
endfunction

function global.get_node_length(node, nodename)
  my.nodename ?= name(my.node)

  my.count = 0
  my.node = my.node.parent()
  while defined(my.node) & my.node.name() = my.nodename
    my.count += 1
    my.node = my.node.parent()
  endwhile

  return my.count
endfunction

function global.print_node(node, nodename)
  my.nodename ?= name(my.node)
  my.n = get_node(my.node, my.nodename)?

  if !defined(my.n)
    error('$(my.nodename:) "$(my.node:)" not found')
  endif

  my.n = get_node_name(my.n, my.nodename)

  return my.n
endfunction

function global.override_attribute(n1, nn, attribute)
  if defined(my.n1.$(my.attribute))
    my.nn.$(my.attribute) = my.n1.$(my.attribute)
  endif
endfunction

function global.append_attribute(n1, nn, attribute)
  my.nn.$(my.attribute) ?= ""
  if defined(my.n1.$(my.attribute))
    my.nn.$(my.attribute) += " " + my.n1.$(my.attribute)
  endif
endfunction

function global.override_reference(n1, origin, nodename, context)
  my.nn = my.context->$(my.nodename)?(!defined(ref) & defined(name) & name = my.n1.ref)?

  if !defined(my.nn)
    return
  endif

  override_attribute(my.n1, my.nn, 'id')
  override_attribute(my.n1, my.nn, 'name')
  append_attribute(my.n1, my.nn, 'description')
  define my.nn.origin = my.origin
endfunction

function global.replace_by_reference(n1, n2, node_src, node_dst, context)
  my.nodename = "$(my.node_dst:)_$(item():)"
  copy my.n2 to my.context before my.n1 as $(my.nodename:)
  my.origin = ""
  if defined(my.n2.id)
    my.origin = get_node_attribute(my.n2, 'id')
  else
    my.origin = get_node_attribute(my.n2, 'name')
  endif
  override_reference(my.n1, my.origin, my.nodename, my.context)
  delete my.n1
  move my.context->$(my.nodename) to my.context as $(my.node_dst:)
endfunction

function global.process_references(node_src, node_dst, context)
  my.node_dst ?= my.node_src
  my.context ?= project

  for my.context. as ii where ii.name()?"" <> ""
    process_references(my.node_src, my.node_dst, ii)
  endfor

  for my.context.$(my.node_dst:) as n where defined(n.ref)
    my.nn = get_node_attempt(n.ref, my.node_src, my.context)?
    if !defined(my.nn)
      next
    endif
    if defined(my.nn) & my.nn <> n
      replace_by_reference(n, my.nn, my.node_src, my.node_dst, my.context)
    endif
  endfor
endfunction

.endtemplate
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant