Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xacro:loop macro #291

Draft
wants to merge 3 commits into
base: noetic-devel
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions examples/loop.xacro
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<macro_library xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:macro name="loop" params="items **body:=^">
<xacro:if value="${items}">
<!-- pop first item from list -->
<xacro:property name="item" value="${items[0]}"/>
<xacro:insert_block name="body" />
<!-- recursively call myself -->
<xacro:loop items="${items[1:]}"/>
</xacro:if>
</xacro:macro>

<xacro:property name="items" value="${python.range(5)}" />

<xacro:loop items="${items}">
<body>
<item>item: ${item}</item>
</body>
</xacro:loop>
</macro_library>
61 changes: 34 additions & 27 deletions src/xacro/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,8 @@ def parse_macro_arg(s):
param, forward, default, rest = m.groups()
if not default:
default = None
elif param[0] == '*':
raise XacroException("Invalid default '{}' for block argument '{}'".format(default, param), macro=m)
return param, (param if forward else None, default), rest
else:
# there is no default specified at all
Expand Down Expand Up @@ -658,8 +660,8 @@ def grab_property(elt, table):
return

if value is None:
name = '**' + name
value = elt # debug
name = '*' + name
value = first_child_element(elt)

replace_node(elt, by=None)

Expand Down Expand Up @@ -799,34 +801,33 @@ def handle_macro_call(node, macros, symbols):
scoped_symbols._setitem(name, eval_text(value, symbols), unevaluated=False)
node.setAttribute(name, "") # suppress second evaluation in eval_all()

# Evaluate block parameters in node
eval_all(node, macros, symbols)

# Fetch block parameters, in order
# Fetch block parameters (in order) or/and defaults for remaining arguments
block = first_child_element(node)
for param in params[:]:
if param[0] == '*':
if not block:
# get potential default
name, default = m.defaultmap.get(param, (None, None))
have_default = name is not None or default is not None

if param[0] == '*': # block argument expected
if block:
params.remove(param)
scoped_symbols[param] = block
block = next_sibling_element(block)
elif have_default:
default = eval_default_arg(name, default, symbols, m)
assert(isinstance(default, xml.dom.minidom.Element))
scoped_symbols._setitem(param, default, unevaluated=False)
params.remove(param)
else:
raise XacroException("Not enough blocks", macro=m)
elif have_default: # a default was specified
default = eval_default_arg(name, default, symbols, m)
scoped_symbols._setitem(param, default, unevaluated=False)
params.remove(param)
scoped_symbols[param] = block
block = next_sibling_element(block)

if block is not None:
raise XacroException("Unused block \"%s\"" % block.tagName, macro=m)

# Try to load defaults for any remaining non-block parameters
for param in params[:]:
# block parameters are not supported for defaults
if param[0] == '*':
continue

# get default
name, default = m.defaultmap.get(param, (None, None))
if name is not None or default is not None:
scoped_symbols._setitem(param, eval_default_arg(name, default, symbols, m), unevaluated=False)
params.remove(param)

if params:
raise XacroException("Undefined parameters [%s]" % ",".join(params), macro=m)

Expand Down Expand Up @@ -894,6 +895,14 @@ def remove_previous_comments(node):
return


def wrap_node(node):
impl = xml.dom.minidom.getDOMImplementation()
doc = impl.createDocument(None, "wrapper", None)
root = doc.documentElement
root.appendChild(node.cloneNode(deep=True))
return root


def eval_all(node, macros, symbols):
"""Recursively evaluate node, expanding macros, replacing properties, and evaluating expressions"""
# evaluate the attributes
Expand Down Expand Up @@ -928,11 +937,9 @@ def eval_all(node, macros, symbols):
else:
raise XacroException("Undefined block \"%s\"" % name)

# cloning block allows to insert the same block multiple times
block = block.cloneNode(deep=True)
# recursively evaluate block
eval_all(block, macros, symbols)
replace_node(node, by=block, content_only=content_only)
wrapper = wrap_node(block) # wrap block into a dummy node
eval_all(wrapper, macros, symbols) # to allow evaluation of the block itself
replace_node(node, by=first_child_element(wrapper), content_only=content_only)

elif node.tagName == 'xacro:include':
process_include(node, macros, symbols, eval_all)
Expand Down