cli: Add --xml xpath option for virt-install and virt-xml
The --xml option allows users to request raw XML edits to virt-install or virt-xml generated XML. This gives users a bit of a workaround incase we don't have proper support for some XML property. The --xml option can gain more features in the future if it makes sense, like setting XML namespaces for example. Basic usage is like: virt-install --xml ./@foo=bar ... Which will change the generated <domain> XML to have <domain foo='bar' ... virt-xml works similarly. It can only be combined with --edit currently. This only works with xpaths rooted against the entire document. Signed-off-by: Cole Robinson <crobinso@redhat.com>
This commit is contained in:
parent
aa8572048b
commit
8560138cf2
|
@ -170,6 +170,48 @@ Use --sysinfo=? to see a list of all available sub options.
|
||||||
Complete details at L<https://libvirt.org/formatdomain.html#elementsSysinfo>
|
Complete details at L<https://libvirt.org/formatdomain.html#elementsSysinfo>
|
||||||
and L<https://libvirt.org/formatdomain.html#elementsOSBIOS> for B<smbios> XML element.
|
and L<https://libvirt.org/formatdomain.html#elementsOSBIOS> for B<smbios> XML element.
|
||||||
|
|
||||||
|
=item B<--xml> ARGS
|
||||||
|
|
||||||
|
Make direct edits to the generated XML using XPath syntax. Take an example like
|
||||||
|
|
||||||
|
virt-install --xml ./@foo=bar --xml ./newelement/subelement=1
|
||||||
|
|
||||||
|
This will alter the generated XML to contain:
|
||||||
|
|
||||||
|
<domain foo='bar' ...>
|
||||||
|
...
|
||||||
|
<newelement>
|
||||||
|
<subelement>1</subelement>
|
||||||
|
</newelement>
|
||||||
|
</domain>
|
||||||
|
|
||||||
|
The --xml option has 4 sub options:
|
||||||
|
|
||||||
|
=over 2
|
||||||
|
|
||||||
|
=item --xml xpath.set=XPATH[=VALUE]
|
||||||
|
|
||||||
|
The default behavior if no explicit suboption is set. Takes the form XPATH=VALUE
|
||||||
|
unless paired with B<xpath.value>. See below for how value is interpreted.
|
||||||
|
|
||||||
|
=item --xml xpath.value=VALUE
|
||||||
|
|
||||||
|
B<xpath.set> will be interpreted only as the XPath string, and B<xpath.value> will
|
||||||
|
be used as the value to set. May help sidestep problems if the string you need to
|
||||||
|
set contains a '=' equals sign.
|
||||||
|
|
||||||
|
If value is empty, it's treated as unsetting that particular node.
|
||||||
|
|
||||||
|
=item --xml xpath.create=XPATH
|
||||||
|
|
||||||
|
Create the node as an empty element. Needed for boolean elements like <readonly/>
|
||||||
|
|
||||||
|
=item --xml xpath.delete=XPATH
|
||||||
|
|
||||||
|
Delete the entire node specified by the xpath, and all its children
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
=item B<--qemu-commandline> ARGS
|
=item B<--qemu-commandline> ARGS
|
||||||
|
|
||||||
Pass options directly to the qemu emulator. Only works for the libvirt qemu driver. The option can take a string of arguments, for example:
|
Pass options directly to the qemu emulator. Only works for the libvirt qemu driver. The option can take a string of arguments, for example:
|
||||||
|
|
|
@ -240,6 +240,8 @@ variants.
|
||||||
|
|
||||||
=item B<--sysinfo>
|
=item B<--sysinfo>
|
||||||
|
|
||||||
|
=item B<--xml>
|
||||||
|
|
||||||
=item B<--qemu-commandline>
|
=item B<--qemu-commandline>
|
||||||
|
|
||||||
=item B<--launchSecurity>
|
=item B<--launchSecurity>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<domain type="kvm">
|
<domain type="kvm" foo="bar">
|
||||||
<name>fedora</name>
|
<name>fedora</name>
|
||||||
<uuid>00000000-1111-2222-3333-444444444444</uuid>
|
<uuid>00000000-1111-2222-3333-444444444444</uuid>
|
||||||
<metadata>
|
<metadata>
|
||||||
|
@ -419,8 +419,10 @@
|
||||||
<tpm model="tpm-crb">
|
<tpm model="tpm-crb">
|
||||||
<backend type="emulator" version="2.0"/>
|
<backend type="emulator" version="2.0"/>
|
||||||
</tpm>
|
</tpm>
|
||||||
<graphics type="sdl" display=":3.4" xauth="/tmp/.Xauthority"/>
|
<graphics type="sdl" display=":3.4" xauth="/tmp/.Xauthority">
|
||||||
<graphics type="spice" port="-1" tlsPort="-1" autoport="yes">
|
<ab>cd</ab>
|
||||||
|
</graphics>
|
||||||
|
<graphics type="spice" port="-1" tlsPort="-1" autoport="yes" ef="hg">
|
||||||
<image compression="off"/>
|
<image compression="off"/>
|
||||||
</graphics>
|
</graphics>
|
||||||
<graphics type="vnc" port="5950" keymap="ja" listen="1.2.3.4" passwd="foo"/>
|
<graphics type="vnc" port="5950" keymap="ja" listen="1.2.3.4" passwd="foo"/>
|
||||||
|
@ -609,4 +611,10 @@
|
||||||
<qemu:arg value="bar"/>
|
<qemu:arg value="bar"/>
|
||||||
<qemu:env name="DISPLAY" value=":0.1"/>
|
<qemu:env name="DISPLAY" value=":0.1"/>
|
||||||
</qemu:commandline>
|
</qemu:commandline>
|
||||||
|
<baz>wib</baz>
|
||||||
|
<deleteme/>
|
||||||
|
<t1>
|
||||||
|
<t2 foo="123"/>
|
||||||
|
</t1>
|
||||||
|
<barenode/>
|
||||||
</domain>
|
</domain>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
-<domain type="test">
|
||||||
|
+<domain type="test" foo="bar">
|
||||||
|
<name>test-for-virtxml</name>
|
||||||
|
<uuid>12345678-12f4-1234-1234-123456789012</uuid>
|
||||||
|
<description>Test VM for virtxml cli tests
|
||||||
|
@@
|
||||||
|
</libosinfo:libosinfo>
|
||||||
|
</metadata>
|
||||||
|
<memory unit="KiB">4194304</memory>
|
||||||
|
- <currentMemory unit="KiB">4194304</currentMemory>
|
||||||
|
<blkiotune>
|
||||||
|
<weight>100</weight>
|
||||||
|
<device>
|
||||||
|
@@
|
||||||
|
<dhCert>AQAAAAAOAAAAQAAAAAOAAAAQAAAAAOAAAAQAAAAAOAAAAQAAAAAOAAA</dhCert>
|
||||||
|
<session>IHAVENOIDEABUTJUSTPROVIDINGASTRING</session>
|
||||||
|
</launchSecurity>
|
||||||
|
+ <new>
|
||||||
|
+ <element>
|
||||||
|
+ <test>1</test>
|
||||||
|
+ </element>
|
||||||
|
+ </new>
|
||||||
|
</domain>
|
||||||
|
|
||||||
|
Domain 'test-for-virtxml' defined successfully.
|
||||||
|
Changes will take effect after the domain is fully powered off.
|
|
@ -722,6 +722,15 @@ source.reservations.managed=no,source.reservations.source.type=unix,source.reser
|
||||||
--qemu-commandline="-device vfio-pci,addr=05.0,sysfsdev=/sys/class/mdev_bus/0000:00:02.0/f321853c-c584-4a6b-b99a-3eee22a3919c"
|
--qemu-commandline="-device vfio-pci,addr=05.0,sysfsdev=/sys/class/mdev_bus/0000:00:02.0/f321853c-c584-4a6b-b99a-3eee22a3919c"
|
||||||
--qemu-commandline="-set device.video0.driver=virtio-vga"
|
--qemu-commandline="-set device.video0.driver=virtio-vga"
|
||||||
--qemu-commandline args="-foo bar"
|
--qemu-commandline args="-foo bar"
|
||||||
|
|
||||||
|
--xml /domain/@foo=bar
|
||||||
|
--xml xpath.set=./baz,xpath.value=wib
|
||||||
|
--xml ./deleteme/deleteme2/deleteme3=foo
|
||||||
|
--xml ./t1/t2/@foo=123
|
||||||
|
--xml ./devices/graphics[1]/ab=cd
|
||||||
|
--xml ./devices/graphics[2]/@ef=hg
|
||||||
|
--xml xpath.create=./barenode
|
||||||
|
--xml xpath.delete=./deleteme/deleteme2
|
||||||
""", "many-devices", predefine_check="5.3.0")
|
""", "many-devices", predefine_check="5.3.0")
|
||||||
|
|
||||||
|
|
||||||
|
@ -831,6 +840,8 @@ c.add_invalid("--boot uefi") # URI doesn't support UEFI bits
|
||||||
c.add_invalid("--connect %(URI-KVM)s --boot uefi,arch=ppc64") # unsupported arch for UEFI
|
c.add_invalid("--connect %(URI-KVM)s --boot uefi,arch=ppc64") # unsupported arch for UEFI
|
||||||
c.add_invalid("--features smm=on --machine pc") # smm=on doesn't work for machine=pc
|
c.add_invalid("--features smm=on --machine pc") # smm=on doesn't work for machine=pc
|
||||||
c.add_invalid("--graphics type=vnc,keymap", grep="Option 'keymap' had no value set.")
|
c.add_invalid("--graphics type=vnc,keymap", grep="Option 'keymap' had no value set.")
|
||||||
|
c.add_invalid("--xml FOOXPATH", grep="form of XPATH=VALUE") # failure parsing xpath value
|
||||||
|
c.add_invalid("--xml /@foo=bar", grep="/@foo xmlXPathEval") # failure processing xpath
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1186,6 +1197,7 @@ c.add_invalid("test-for-virtxml --edit --graphics password=foo,keymap= --update
|
||||||
c.add_invalid("--build-xml --memory 10,maxmemory=20") # building XML for option that doesn't support it
|
c.add_invalid("--build-xml --memory 10,maxmemory=20") # building XML for option that doesn't support it
|
||||||
c.add_invalid("test-state-shutoff --edit sparse=no --disk path=blah", grep="Don't know how to match device type 'disk' property 'sparse'")
|
c.add_invalid("test-state-shutoff --edit sparse=no --disk path=blah", grep="Don't know how to match device type 'disk' property 'sparse'")
|
||||||
c.add_invalid("test --edit --boot network,cdrom --define --no-define")
|
c.add_invalid("test --edit --boot network,cdrom --define --no-define")
|
||||||
|
c.add_invalid("test --add-device --xml ./@foo=bar", grep="--xml can only be used with --edit")
|
||||||
c.add_compare("test --print-xml --edit --vcpus 7", "print-xml") # test --print-xml
|
c.add_compare("test --print-xml --edit --vcpus 7", "print-xml") # test --print-xml
|
||||||
c.add_compare("--edit --cpu host-passthrough", "stdin-edit", input_file=(_VIRTXMLDIR + "virtxml-stdin-edit.xml")) # stdin test
|
c.add_compare("--edit --cpu host-passthrough", "stdin-edit", input_file=(_VIRTXMLDIR + "virtxml-stdin-edit.xml")) # stdin test
|
||||||
c.add_compare("--build-xml --cpu pentium3,+x2apic", "build-cpu")
|
c.add_compare("--build-xml --cpu pentium3,+x2apic", "build-cpu")
|
||||||
|
@ -1203,6 +1215,7 @@ c.add_compare("--connect %(URI-KVM)s test-many-devices --edit --cpu host-copy",
|
||||||
|
|
||||||
|
|
||||||
c = vixml.add_category("simple edit diff", "test-for-virtxml --edit --print-diff --define")
|
c = vixml.add_category("simple edit diff", "test-for-virtxml --edit --print-diff --define")
|
||||||
|
c.add_compare("""--xml ./@foo=bar --xml xpath.delete=./currentMemory --xml ./new/element/test=1""", "edit-xpaths")
|
||||||
c.add_compare("""--metadata name=foo-my-new-name,os_name=fedora13,uuid=12345678-12F4-1234-1234-123456789AFA,description="hey this is my
|
c.add_compare("""--metadata name=foo-my-new-name,os_name=fedora13,uuid=12345678-12F4-1234-1234-123456789AFA,description="hey this is my
|
||||||
new
|
new
|
||||||
very,very=new desc\\\'",title="This is my,funky=new title" """, "edit-simple-metadata")
|
very,very=new desc\\\'",title="This is my,funky=new title" """, "edit-simple-metadata")
|
||||||
|
|
|
@ -480,7 +480,7 @@ def get_domain_and_guest(conn, domstr):
|
||||||
|
|
||||||
def _get_completer_parsers():
|
def _get_completer_parsers():
|
||||||
return VIRT_PARSERS + [ParserCheck, ParserLocation,
|
return VIRT_PARSERS + [ParserCheck, ParserLocation,
|
||||||
ParserUnattended, ParserInstall, ParserCloudInit]
|
ParserUnattended, ParserInstall, ParserCloudInit, ParserXML]
|
||||||
|
|
||||||
|
|
||||||
def _virtparser_completer(prefix, **kwargs):
|
def _virtparser_completer(prefix, **kwargs):
|
||||||
|
@ -917,6 +917,14 @@ def add_os_variant_option(parser, virtinstall):
|
||||||
return osg
|
return osg
|
||||||
|
|
||||||
|
|
||||||
|
def add_xml_option(grp):
|
||||||
|
grp.add_argument("--xml", action="append", default=[],
|
||||||
|
help=_("Perform raw XML XPath options on the final XML. Example:\n"
|
||||||
|
"--xml ./cpu/@mode=host-passthrough\n"
|
||||||
|
"--xml ./devices/disk[2]/serial=new-serial\n"
|
||||||
|
"--xml xpath.delete=./clock"))
|
||||||
|
|
||||||
|
|
||||||
#############################################
|
#############################################
|
||||||
# CLI complex parsing helpers #
|
# CLI complex parsing helpers #
|
||||||
# (for options like --disk, --network, etc. #
|
# (for options like --disk, --network, etc. #
|
||||||
|
@ -1535,6 +1543,73 @@ class VirtCLIParser(metaclass=_InitClass):
|
||||||
"""Do nothing callback"""
|
"""Do nothing callback"""
|
||||||
|
|
||||||
|
|
||||||
|
#################
|
||||||
|
# --xml parsing #
|
||||||
|
#################
|
||||||
|
|
||||||
|
class _XMLCLIInstance:
|
||||||
|
"""
|
||||||
|
Helper class to parse --xml content into.
|
||||||
|
Generates XMLManualAction which actually performs the work
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.xpath_delete = None
|
||||||
|
self.xpath_set = None
|
||||||
|
self.xpath_create = None
|
||||||
|
self.xpath_value = None
|
||||||
|
|
||||||
|
def build_action(self):
|
||||||
|
from .xmlbuilder import XMLManualAction
|
||||||
|
if self.xpath_delete:
|
||||||
|
return XMLManualAction(self.xpath_delete,
|
||||||
|
action=XMLManualAction.ACTION_DELETE)
|
||||||
|
if self.xpath_create:
|
||||||
|
return XMLManualAction(self.xpath_create,
|
||||||
|
action=XMLManualAction.ACTION_CREATE)
|
||||||
|
|
||||||
|
xpath = self.xpath_set
|
||||||
|
if self.xpath_value:
|
||||||
|
val = self.xpath_value
|
||||||
|
else:
|
||||||
|
if "=" not in str(xpath):
|
||||||
|
fail("%s: Setting xpath must be in the form of XPATH=VALUE" %
|
||||||
|
xpath)
|
||||||
|
xpath, val = xpath.rsplit("=", 1)
|
||||||
|
return XMLManualAction(xpath, val or None)
|
||||||
|
|
||||||
|
|
||||||
|
class ParserXML(VirtCLIParser):
|
||||||
|
cli_arg_name = "xml"
|
||||||
|
supports_clearxml = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _init_class(cls, **kwargs):
|
||||||
|
VirtCLIParser._init_class(**kwargs)
|
||||||
|
cls.add_arg("xpath.delete", "xpath_delete", can_comma=True)
|
||||||
|
cls.add_arg("xpath.set", "xpath_set", can_comma=True)
|
||||||
|
cls.add_arg("xpath.create", "xpath_create", can_comma=True)
|
||||||
|
cls.add_arg("xpath.value", "xpath_value", can_comma=True)
|
||||||
|
|
||||||
|
def _parse(self, inst):
|
||||||
|
if not self.optstr.startswith("xpath."):
|
||||||
|
self.optdict.clear()
|
||||||
|
self.optdict["xpath.set"] = self.optstr
|
||||||
|
|
||||||
|
super()._parse(inst)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_xmlcli(guest, options):
|
||||||
|
"""
|
||||||
|
Parse --xml option strings and add the resulting XMLManualActions
|
||||||
|
to the Guest instance
|
||||||
|
"""
|
||||||
|
for optstr in options.xml:
|
||||||
|
inst = _XMLCLIInstance()
|
||||||
|
ParserXML(optstr).parse(inst)
|
||||||
|
manualaction = inst.build_action()
|
||||||
|
guest.add_xml_manual_action(manualaction)
|
||||||
|
|
||||||
|
|
||||||
########################
|
########################
|
||||||
# --unattended parsing #
|
# --unattended parsing #
|
||||||
########################
|
########################
|
||||||
|
|
|
@ -553,12 +553,14 @@ def _build_options_guest(conn, options):
|
||||||
# Fill in guest from the command line content
|
# Fill in guest from the command line content
|
||||||
set_explicit_guest_options(options, guest)
|
set_explicit_guest_options(options, guest)
|
||||||
cli.parse_option_strings(options, guest, None)
|
cli.parse_option_strings(options, guest, None)
|
||||||
|
cli.parse_xmlcli(guest, options)
|
||||||
|
|
||||||
# Call set_capabilities_defaults explicitly here rather than depend
|
# Call set_capabilities_defaults explicitly here rather than depend
|
||||||
# on set_defaults calling it. Installer setup needs filled in values.
|
# on set_defaults calling it. Installer setup needs filled in values.
|
||||||
# However we want to do it after parse_option_strings to ensure
|
# However we want to do it after parse_option_strings to ensure
|
||||||
# we are operating on any arch/os/type values passed in with --boot
|
# we are operating on any arch/os/type values passed in with --boot
|
||||||
guest.set_capabilities_defaults()
|
guest.set_capabilities_defaults()
|
||||||
|
|
||||||
return guest
|
return guest
|
||||||
|
|
||||||
|
|
||||||
|
@ -946,6 +948,7 @@ def parse_args():
|
||||||
cli.add_memory_option(geng, backcompat=True)
|
cli.add_memory_option(geng, backcompat=True)
|
||||||
cli.vcpu_cli_options(geng)
|
cli.vcpu_cli_options(geng)
|
||||||
cli.add_metadata_option(geng)
|
cli.add_metadata_option(geng)
|
||||||
|
cli.add_xml_option(geng)
|
||||||
geng.add_argument("-u", "--uuid", help=argparse.SUPPRESS)
|
geng.add_argument("-u", "--uuid", help=argparse.SUPPRESS)
|
||||||
geng.add_argument("--description", help=argparse.SUPPRESS)
|
geng.add_argument("--description", help=argparse.SUPPRESS)
|
||||||
|
|
||||||
|
|
|
@ -127,7 +127,7 @@ def check_action_collision(options):
|
||||||
|
|
||||||
def check_xmlopt_collision(options):
|
def check_xmlopt_collision(options):
|
||||||
collisions = []
|
collisions = []
|
||||||
for parserclass in cli.VIRT_PARSERS:
|
for parserclass in cli.VIRT_PARSERS + [cli.ParserXML]:
|
||||||
if getattr(options, parserclass.cli_arg_name):
|
if getattr(options, parserclass.cli_arg_name):
|
||||||
collisions.append(parserclass)
|
collisions.append(parserclass)
|
||||||
|
|
||||||
|
@ -297,9 +297,18 @@ def update_changes(domain, devs, action, confirm):
|
||||||
|
|
||||||
def prepare_changes(xmlobj, options, parserclass):
|
def prepare_changes(xmlobj, options, parserclass):
|
||||||
origxml = xmlobj.get_xml()
|
origxml = xmlobj.get_xml()
|
||||||
|
has_edit = options.edit != -1
|
||||||
|
is_xmlcli = parserclass is cli.ParserXML
|
||||||
|
|
||||||
if options.edit != -1:
|
if is_xmlcli and not has_edit:
|
||||||
devs = action_edit(xmlobj, options, parserclass)
|
fail(_("--xml can only be used with --edit"))
|
||||||
|
|
||||||
|
if has_edit:
|
||||||
|
if is_xmlcli:
|
||||||
|
devs = []
|
||||||
|
cli.parse_xmlcli(xmlobj, options)
|
||||||
|
else:
|
||||||
|
devs = action_edit(xmlobj, options, parserclass)
|
||||||
action = "update"
|
action = "update"
|
||||||
|
|
||||||
elif options.add_device:
|
elif options.add_device:
|
||||||
|
@ -391,6 +400,7 @@ def parse_args():
|
||||||
cli.add_metadata_option(g)
|
cli.add_metadata_option(g)
|
||||||
cli.add_memory_option(g)
|
cli.add_memory_option(g)
|
||||||
cli.vcpu_cli_options(g, editexample=True)
|
cli.vcpu_cli_options(g, editexample=True)
|
||||||
|
cli.add_xml_option(g)
|
||||||
cli.add_guest_xml_options(g)
|
cli.add_guest_xml_options(g)
|
||||||
cli.add_boot_options(g)
|
cli.add_boot_options(g)
|
||||||
cli.add_device_options(g)
|
cli.add_device_options(g)
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import libxml2
|
import libxml2
|
||||||
|
|
||||||
from . import xmlutil
|
from . import xmlutil
|
||||||
|
from .logger import log
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
|
|
||||||
|
@ -313,7 +314,12 @@ class _Libxml2API(_XMLBase):
|
||||||
|
|
||||||
def _find(self, fullxpath):
|
def _find(self, fullxpath):
|
||||||
xpath = _XPath(fullxpath).xpath
|
xpath = _XPath(fullxpath).xpath
|
||||||
node = self._ctx.xpathEval(xpath)
|
try:
|
||||||
|
node = self._ctx.xpathEval(xpath)
|
||||||
|
except Exception as e:
|
||||||
|
log.debug("fullxpath=%s xpath=%s eval failed",
|
||||||
|
fullxpath, xpath, exc_info=True)
|
||||||
|
raise RuntimeError("%s %s" % (fullxpath, str(e))) from None
|
||||||
return (node and node[0] or None)
|
return (node and node[0] or None)
|
||||||
|
|
||||||
def count(self, xpath):
|
def count(self, xpath):
|
||||||
|
|
|
@ -26,6 +26,35 @@ _allprops = []
|
||||||
_seenprops = []
|
_seenprops = []
|
||||||
|
|
||||||
|
|
||||||
|
class XMLManualAction(object):
|
||||||
|
"""
|
||||||
|
Helper class for tracking and performing the user requested manual
|
||||||
|
XML action
|
||||||
|
"""
|
||||||
|
ACTION_CREATE = 1
|
||||||
|
ACTION_DELETE = 2
|
||||||
|
ACTION_SET = 3
|
||||||
|
def __init__(self, xpath, value=None, action=-1):
|
||||||
|
self.xpath = xpath
|
||||||
|
self._value = value
|
||||||
|
|
||||||
|
self._action = self.ACTION_SET
|
||||||
|
if action != -1:
|
||||||
|
self._action = action
|
||||||
|
|
||||||
|
def perform(self, xmlstate):
|
||||||
|
xpath = self.xpath
|
||||||
|
if xpath.startswith("."):
|
||||||
|
xpath = xmlstate.make_abs_xpath(self.xpath)
|
||||||
|
if self._action == self.ACTION_DELETE:
|
||||||
|
setval = False
|
||||||
|
elif self._action == self.ACTION_CREATE:
|
||||||
|
setval = True
|
||||||
|
else:
|
||||||
|
setval = self._value
|
||||||
|
xmlstate.xmlapi.set_xpath_content(xpath, setval)
|
||||||
|
|
||||||
|
|
||||||
class _XMLPropertyCache(object):
|
class _XMLPropertyCache(object):
|
||||||
"""
|
"""
|
||||||
Cache lookup tables mapping classes to their associated
|
Cache lookup tables mapping classes to their associated
|
||||||
|
@ -489,6 +518,7 @@ class XMLBuilder(object):
|
||||||
|
|
||||||
self._validate_xmlbuilder()
|
self._validate_xmlbuilder()
|
||||||
self._initial_child_parse()
|
self._initial_child_parse()
|
||||||
|
self._manual_actions = []
|
||||||
|
|
||||||
def _validate_xmlbuilder(self):
|
def _validate_xmlbuilder(self):
|
||||||
# This is one time validation we run once per XMLBuilder class
|
# This is one time validation we run once per XMLBuilder class
|
||||||
|
@ -615,6 +645,13 @@ class XMLBuilder(object):
|
||||||
return 0
|
return 0
|
||||||
return int(xpath.rsplit("[", 1)[1].strip("]")) - 1
|
return int(xpath.rsplit("[", 1)[1].strip("]")) - 1
|
||||||
|
|
||||||
|
def add_xml_manual_action(self, manualaction):
|
||||||
|
"""
|
||||||
|
Register a manual XML action to perform at the end of the
|
||||||
|
XML building step. Triggered via --xml on the command line
|
||||||
|
"""
|
||||||
|
self._manual_actions.append(manualaction)
|
||||||
|
|
||||||
|
|
||||||
################
|
################
|
||||||
# Internal API #
|
# Internal API #
|
||||||
|
@ -796,3 +833,6 @@ class XMLBuilder(object):
|
||||||
elif key in childprops:
|
elif key in childprops:
|
||||||
for obj in xmlutil.listify(getattr(self, key)):
|
for obj in xmlutil.listify(getattr(self, key)):
|
||||||
obj._add_parse_bits(self._xmlstate.xmlapi)
|
obj._add_parse_bits(self._xmlstate.xmlapi)
|
||||||
|
|
||||||
|
for manualaction in self._manual_actions:
|
||||||
|
manualaction.perform(self._xmlstate)
|
||||||
|
|
Loading…
Reference in New Issue