# HG changeset patch
# User Markus Mützel <markus.muetzel@gmx.de>
# Date 1601843724 -7200
#      Sun Oct 04 22:35:24 2020 +0200
# Node ID b83830744de654def0d2e935fdd3f6cbc77ed024
# Parent  25b7ffb514f338a4caaf654c8167a91b31a6aa29
Add "xmlread" and "xmlwrite" functions (bug #59245).

* xmlread.m, xmlwrite.m: New functions.
* dom_node.m, dom_document.m: New classdef that mimick the XML DOM node and
  document used in Java.
* scripts/io/module.mk: Add new files to list.
* NEWS: Announce new functions.
* __unimplemented__.m: Remove files from list.
* libinterp/corefcn/xmlwrite.cc: Interface to RapidXML.
* libinterp/corefcn/module.mk: Add new file to list.
* configure.ac: Add check for RapidXML (header-only library).
* doc/interpreter/io.txi: Add docstrings of new functions to manual.

diff -r 25b7ffb514f3 -r b83830744de6 NEWS
--- a/NEWS	Fri Oct 09 18:25:25 2020 +0200
+++ b/NEWS	Sun Oct 04 22:35:24 2020 +0200
@@ -167,6 +167,8 @@
 * `rng`
 * `startsWith`
 * `streamribbon`
+* `xmlread`
+* `xmlwrite`
 
 ### Deprecated functions and properties
 
diff -r 25b7ffb514f3 -r b83830744de6 configure.ac
--- a/configure.ac	Fri Oct 09 18:25:25 2020 +0200
+++ b/configure.ac	Sun Oct 04 22:35:24 2020 +0200
@@ -1371,6 +1371,17 @@
 fi
 AC_LANG_POP([C++])
 
+### Check for RapidXML header only library.
+
+AC_LANG_PUSH(C++)
+AC_CHECK_HEADERS([rapidxml/rapidxml.hpp rapidxml/rapidxml_print.hpp],
+                 [have_rapidxml=yes], [have_rapidxml=no])
+
+if test $have_rapidxml = yes; then
+  AC_DEFINE(HAVE_RAPIDXML, 1, [Define to 1 if RapidXML is available.])
+fi
+AC_LANG_POP(C++)
+
 ### Check for readline library.
 
 OCTAVE_ENABLE_READLINE
diff -r 25b7ffb514f3 -r b83830744de6 doc/interpreter/io.txi
--- a/doc/interpreter/io.txi	Fri Oct 09 18:25:25 2020 +0200
+++ b/doc/interpreter/io.txi	Sun Oct 04 22:35:24 2020 +0200
@@ -222,12 +222,25 @@
 
 @DOCSTRING(csvread)
 
-Formatted data from can be read from, or written to, text files as well.
+Formatted data can be read from, or written to, text files as well.
 
 @DOCSTRING(textread)
 
 @DOCSTRING(textscan)
 
+XML data can be read, the DOM node tree can be traversed and changed, and
+the resulting tree can be exported.
+
+@DOCSTRING(xmlread)
+
+@DOCSTRING(xmlwrite)
+
+@c FIXME: How to insert documentation of classdef methods?
+@DOCSTRING(dom_document)
+
+@c FIXME: How to insert documentation of classdef methods?
+@DOCSTRING(dom_node)
+
 The @code{importdata} function has the ability to work with a wide
 variety of data.
 
diff -r 25b7ffb514f3 -r b83830744de6 libinterp/corefcn/module.mk
--- a/libinterp/corefcn/module.mk	Fri Oct 09 18:25:25 2020 +0200
+++ b/libinterp/corefcn/module.mk	Sun Oct 04 22:35:24 2020 +0200
@@ -270,6 +270,7 @@
   %reldir%/utils.cc \
   %reldir%/variables.cc \
   %reldir%/xdiv.cc \
+  %reldir%/xmlwrite.cc \
   %reldir%/xnorm.cc \
   %reldir%/xpow.cc \
   %reldir%/zfstream.cc \
diff -r 25b7ffb514f3 -r b83830744de6 libinterp/corefcn/xmlwrite.cc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libinterp/corefcn/xmlwrite.cc	Sun Oct 04 22:35:24 2020 +0200
@@ -0,0 +1,1221 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2020 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// This file is part of Octave.
+//
+// Octave is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Octave is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Octave; see the file COPYING.  If not, see
+// <https://www.gnu.org/licenses/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+
+//! @file ov-xml.cc
+//!
+//! Provides Octave's XML interface using RapidXML.
+
+#if defined (HAVE_CONFIG_H)
+#  include "config.h"
+#endif
+
+#include <iterator>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+#include <iostream>
+
+#include "defun.h"
+#include "error.h"
+#include "errwarn.h"
+#include "lo-sysdep.h"
+
+#if defined (HAVE_RAPIDXML_RAPIDXML_HPP)
+#  include <rapidxml/rapidxml.hpp>
+#endif
+
+#if defined (HAVE_RAPIDXML_RAPIDXML_PRINT_HPP)
+#  include <rapidxml/rapidxml_print.hpp>
+#endif
+
+#if defined (HAVE_RAPIDXML)
+
+namespace octave
+{
+  class type_info;
+}
+
+class OCTINTERP_API octave_xml_node : public octave_base_value
+{
+public:
+
+  octave_xml_node (void);
+
+  octave_xml_node (const octave_xml_node& xml_obj)
+    : octave_base_value (xml_obj)
+  { }
+
+  octave_xml_node (rapidxml::xml_node<char>* node)
+    : m_xml_node (node)
+  { }
+
+  ~octave_xml_node (void) {  }
+
+  octave_base_value * clone (void) const { return new octave_xml_node (*this); }
+  octave_base_value * empty_clone (void) const { return new octave_xml_node (); }
+
+  bool is_defined (void) const { return true; }
+
+  bool is_constant (void) const { return true; }
+
+  bool isstruct (void) const { return false; }
+
+  bool is_xml_document (void) const { return m_is_document; }
+
+  dim_vector dims (void) const;
+
+  void print (std::ostream& os, bool pr_as_read_syntax = false);
+
+  void print_raw (std::ostream& os, bool pr_as_read_syntax = false) const;
+
+  bool save_ascii (std::ostream& os);
+
+  bool load_ascii (std::istream& is);
+
+  bool save_binary (std::ostream& os, bool save_as_floats);
+
+  bool load_binary (std::istream& is, bool swap,
+                    octave::mach_info::float_format fmt);
+
+  bool save_hdf5 (octave_hdf5_id loc_id, const char *name,
+                  bool save_as_floats);
+
+  bool load_hdf5 (octave_hdf5_id loc_id, const char *name);
+
+  octave_value getChildNodes (void) const;
+
+
+private:
+
+  std::string m_filename;
+  char *m_buffer;
+  rapidxml::xml_node<char> *m_xml_node;
+
+public:
+
+  bool m_is_document = false;
+
+  int type_id (void) const { return t_id; }
+  std::string type_name (void) const { return t_name; }
+  std::string class_name (void) const { return "XML_node"; }
+
+  rapidxml::xml_node<char> *node (void) const
+    { return m_xml_node; }
+
+  octave_value get_owner_document (void);
+
+  static int static_type_id (void) { return t_id; }
+  static std::string static_type_name (void) { return t_name; }
+  static std::string static_class_name (void) { return "<unknown>"; }
+  static void register_type (octave::type_info&);
+
+private:
+
+  static int t_id;
+  static const std::string t_name;
+};
+
+
+//! Constructor.
+
+octave_xml_node::octave_xml_node (void)
+  : octave_base_value ()
+{ }
+
+int octave_xml_node::t_id (-1);
+
+const std::string octave_xml_node::t_name ("octave_xml_node");
+
+void
+octave_xml_node::register_type (octave::type_info& ti)
+{
+  t_id = ti.register_type (octave_xml_node::t_name, "<unknown>",
+                           octave_value (new octave_xml_node ()));
+}
+
+dim_vector
+octave_xml_node::dims (void) const
+{
+  return dim_vector (1, 1);
+}
+
+void
+octave_xml_node::print (std::ostream& os, bool)
+{
+  print_raw (os);
+  newline (os);
+}
+
+void
+octave_xml_node::print_raw (std::ostream& os, bool) const
+{
+  os << "<XML_node: " << "FIXME: What here?" << '>';
+}
+
+// FIXME: Need routines to actually save/load xml objects through Serialize.
+
+bool
+octave_xml_node::save_ascii (std::ostream& /* os */)
+{
+  warning ("save: unable to save XML objects, skipping");
+
+  return true;
+}
+
+bool
+octave_xml_node::load_ascii (std::istream& /* is */)
+{
+  // Silently skip over XML object that was not saved
+  return true;
+}
+
+bool
+octave_xml_node::save_binary (std::ostream& /* os */, bool /* save_as_floats */)
+{
+  warning ("save: unable to save XML objects, skipping");
+
+  return true;
+}
+
+bool
+octave_xml_node::load_binary (std::istream& /* is */, bool /* swap*/,
+                          octave::mach_info::float_format /* fmt */)
+{
+  // Silently skip over XML object that was not saved
+  return true;
+}
+
+bool
+octave_xml_node::save_hdf5 (octave_hdf5_id /* loc_id */, const char * /* name */,
+                        bool /* save_as_floats */)
+{
+  warning ("save: unable to save XML objects, skipping");
+
+  return true;
+}
+
+bool
+octave_xml_node::load_hdf5 (octave_hdf5_id /* loc_id */, const char * /* name */)
+{
+  // Silently skip object that was not saved
+  return true;
+}
+
+
+static octave_xml_node *
+get_xml_node (const octave_value& ov)
+{
+  const octave_base_value& rep = ov.get_rep ();
+
+  octave_base_value *ncrep = const_cast<octave_base_value *> (&rep);
+
+  octave_xml_node *xml = dynamic_cast<octave_xml_node *> (ncrep);
+  if (! xml)
+    error ("get_xml_node: dynamic_cast to octave_xml_node failed");
+
+  return xml;
+}
+
+
+class OCTINTERP_API octave_xml_doc : public octave_xml_node
+{
+public:
+
+  octave_xml_doc (void);
+
+  octave_xml_doc (const std::string& xml_file);
+
+  octave_xml_doc (const octave_xml_doc& xml_obj)
+    : octave_xml_node (xml_obj)
+  {
+    m_is_document = true;
+  }
+
+  ~octave_xml_doc (void) { release (); }
+
+  octave_base_value * clone (void) const { return new octave_xml_doc (*this); }
+  octave_base_value * empty_clone (void) const { return new octave_xml_doc (); }
+
+  bool is_defined (void) const { return true; }
+
+  bool is_constant (void) const { return true; }
+
+  bool isstruct (void) const { return false; }
+
+private:
+
+  void read_xml (const std::string& xml_file);
+
+  void release (void);
+
+  std::string m_filename;
+  char *m_buffer = nullptr;
+  rapidxml::xml_document<char> m_xml_doc;
+
+public:
+
+  int type_id (void) const { return t_id; }
+  std::string type_name (void) const { return t_name; }
+  std::string class_name (void) const { return "XML_document"; }
+
+  const rapidxml::xml_document<char> * document (void) const
+    { return &m_xml_doc; }
+
+  static int static_type_id (void) { return t_id; }
+  static std::string static_type_name (void) { return t_name; }
+  static std::string static_class_name (void) { return "<unknown>"; }
+  static void register_type (octave::type_info&);
+
+private:
+
+  static int t_id;
+  static const std::string t_name;
+
+};
+
+
+int octave_xml_doc::t_id (-1);
+
+const std::string octave_xml_doc::t_name ("octave_xml_doc");
+
+void
+octave_xml_doc::register_type (octave::type_info& ti)
+{
+  t_id = ti.register_type (octave_xml_doc::t_name, "<unknown>",
+                           octave_value (new octave_xml_doc ()));
+}
+
+
+//! Constructor.
+
+octave_xml_doc::octave_xml_doc (void)
+  : octave_xml_node ()
+{
+  m_is_document = true;
+}
+
+octave_xml_doc::octave_xml_doc (const std::string& xml_file)
+{
+  read_xml (xml_file);
+}
+
+
+static octave_xml_doc *
+get_xml_doc (const octave_value& ov)
+{
+  const octave_base_value& rep = ov.get_rep ();
+
+  octave_base_value *ncrep = const_cast<octave_base_value *> (&rep);
+
+  octave_xml_doc *xml = dynamic_cast<octave_xml_doc *> (ncrep);
+  if (! xml)
+    error ("get_xml_doc: dynamic_cast to octave_xml_doc failed");
+
+  return xml;
+}
+
+
+//! Read and parse XML file.
+
+void
+octave_xml_doc::read_xml (const std::string& xml_filename)
+{
+  std::ifstream file
+    = octave::sys::ifstream (xml_filename.c_str (),
+                             std::ios::in | std::ios::binary);
+
+  if (! file || file.fail ())
+    error ("xmlread: failed to open XML file: %s", xml_filename.c_str ());
+
+  file.seekg (0, std::ios::end);
+  size_t size = file.tellg ();
+
+  m_buffer = reinterpret_cast<char *> (std::malloc (size + 1));
+  file.seekg (0);
+
+  file.read (m_buffer, size); 
+  m_buffer[size] = '\0';
+
+  //rapidxml::xml_document<char> xml_doc;
+  m_xml_doc.parse<0> (m_buffer);
+
+  m_is_document = true;
+}
+
+
+void
+octave_xml_doc::release (void)
+{
+  m_xml_doc.clear ();
+
+  // buffer used to hold XML file content
+  if (m_buffer)
+    std::free (m_buffer);
+  m_buffer = nullptr;
+}
+
+
+octave_value
+octave_xml_node::get_owner_document (void)
+{
+  if (is_xml_document ())
+    {
+      octave_xml_doc *oct_doc = dynamic_cast<octave_xml_doc *> (this);
+      rapidxml::xml_document<char> *doc = oct_doc->document ()->document ();
+      return octave_value (new octave_xml_node (doc));
+    }
+  else
+    {
+      if (! node ())
+        return octave_value (Matrix ());
+      rapidxml::xml_document<char> *doc = this->node ()->document ();
+      if (doc)
+        return octave_value (new octave_xml_node (doc));
+    }
+
+  return octave_value (Matrix ());
+}
+
+#endif
+
+
+// DEFUN blocks below must be outside of HAVE_RAPIDXML block so that
+// documentation strings are always available, even when functions are not.
+
+
+DEFUN (__xml_new_document__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {@var{doc} =} __xml_new_document__ ()
+Create a XML document node that can own a DOM node tree.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () != 0)
+    print_usage ();
+
+  return octave_value (new octave_xml_doc ());
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_new_node__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {@var{node} =} __xml_new_node__ (@var{doc}, @var{type}, @var{name})
+Create a new XML DOM @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () < 2 || args.length () > 3)
+    print_usage ();
+
+  octave_xml_doc *xml = get_xml_doc (args(0));
+  const rapidxml::xml_document<char> *doc = xml->document ();
+
+  std::string type = args(1).xstring_value ("TYPE must be a string");
+
+  rapidxml::xml_node<char> *node;
+  if (type.compare ("element") == 0)
+    {
+      if (args.length () < 3)
+        error ("createElement: NAME must be specified");
+
+      std::string name = args(2).xstring_value ("NAME must be a string");
+
+      char *node_name
+        = doc->document ()->allocate_string (name.c_str (), name.length ());
+      node = doc->document ()->allocate_node (rapidxml::node_element,
+                                              node_name, nullptr,
+                                              name.length (), 0);
+    }
+  else if (type.compare ("text") == 0)
+    {
+      if (args.length () < 3)
+        error ("createTextNode: VALUE must be specified");
+
+      std::string value = args(2).xstring_value ("NAME must be a string");
+
+      char *node_value
+        = doc->document ()->allocate_string (value.c_str (), value.length ());
+      node = doc->document ()->allocate_node (rapidxml::node_data,
+                                              nullptr, node_value,
+                                              0, value.length ());
+    }
+  else
+    error ("dom_document: Unknown TYPE '%s'", type.c_str ());
+
+  return octave_value (new octave_xml_node (node));
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_open__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {@var{node} =} __xml_open__ (@var{filename})
+Open XML document, parse content and return an XML DOM @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () != 1)
+    print_usage ();
+
+  std::string filename
+    = args(0).xstring_value ("xmlread: FILENAME must be a string");
+
+  return octave_value (new octave_xml_doc (filename));
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_print_str__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {@var{str} =} __xml_print_str__ (@var{node})
+Serialize XML DOM @var{node} to string @var{str}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () != 1)
+    print_usage ();
+
+  octave_xml_doc *xml = get_xml_doc (args(0));
+  const rapidxml::xml_document<char> *doc = xml->document ();
+
+  std::string str;
+  rapidxml::print (std::back_inserter (str), *doc, 1);
+
+  return octave_value (str);
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_write_file__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {} __xml_print_str__ (@var{filename}, @var{node})
+Serialize XML DOM @var{node} to string @var{str}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () != 2)
+    print_usage ();
+
+  std::string filename
+    = args(0).xstring_value ("xmlread: FILENAME must be a string");
+  octave_xml_doc *xml = get_xml_doc (args(1));
+
+  std::ofstream ofile
+    = octave::sys::ofstream (filename.c_str (),
+                             std::ios::out | std::ios::binary);
+
+  if (! ofile.is_open ())
+    error ("xmlread: unable to open file '%s'", filename.c_str ());
+
+  octave::unwind_action close_file
+   ([] (std::ofstream *ofile_ptr) { ofile_ptr->close (); }, &ofile);
+
+  const rapidxml::xml_document<char> *doc = xml->document ();
+
+  rapidxml::print (std::ostream_iterator<char> (ofile), *doc, 1);
+//  ofile << *doc;
+
+  return octave_value (Matrix ());
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN(__xml_get_name__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {@var{name} =} __xml_get_name__ (node)
+Open XML document, parse content and return an XML DOM @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () != 1)
+    print_usage ();
+
+  octave_xml_node *xml = get_xml_node (args(0));
+
+  std::string name;
+  if (xml->is_xml_document ())
+    name = "#document";
+  else
+    name = std::string (xml->node ()->name (), xml->node ()->name_size ());
+
+  return octave_value (name);
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_value__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn  {} {@var{value} =} __xml_value__ (node)
+@deftypefnx {} {} __xml_value__ (node, value)
+Get or set @var{value} of XML DOM @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () < 1 || args.length () > 2)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+
+  std::string value;
+
+  if (node->is_xml_document ())
+    return octave_value (Matrix ());
+
+  if (args.length () == 1)
+    {
+      value = std::string (node->node ()->value (),
+                           node->node ()->value_size ());
+
+      return octave_value (value);
+    }
+  else
+    {
+      value = args(1).string_value ();
+      rapidxml::xml_document<char> *doc = node->node ()->document ();
+
+      if (! doc)
+        error ("setValue: NODE must have a parent for setting value");
+
+      char *val = doc->allocate_string (value.c_str (), value.length ());
+
+      node->node ()->value (val, value.length ());
+
+      return octave_value ();
+   }
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_get_type__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {@var{type} =} __xml_get_type__ (node)
+Get node @var{type} of XML DOM @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () < 1)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+
+  rapidxml::node_type type;
+  if (node->is_xml_document ())
+    {
+      octave_xml_doc *doc = dynamic_cast<octave_xml_doc *> (node);
+      type = doc->document ()->type ();
+    }
+  else
+    type = node->node ()->type ();
+
+  return octave_value (type);
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_get_parent__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {@var{parent_node} =} __xml_get_parent__ (node)
+Get @var{parent_node} of XML DOM @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () < 1)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+
+  if (! node->is_xml_document () && node->node ())
+    {
+      rapidxml::xml_node<char> *parent_node
+        = node->node ()->parent ();
+      return octave_value (new octave_xml_node (parent_node));
+    }
+
+  return octave_value (Matrix ());
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_get_first_child__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn  {} {@var{child_node} =} __xml_get_first_child__ (@var{node})
+@deftypefnx {} {@var{child_node} =} __xml_get_first_child__ (@var{node}, @var{name})
+Get first @var{child_node} of XML @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () < 1 || args.length () > 2)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+
+  rapidxml::xml_node<char> *child_node;
+  if (node->is_xml_document ())
+    {
+      octave_xml_doc *doc = dynamic_cast<octave_xml_doc *> (node);
+      if (! doc->document ())
+        return octave_value (Matrix ());
+
+      if (args.length () == 1)
+        child_node = doc->document ()->first_node ();
+      else
+        {
+          std::string name = args(1).xstring_value ("NAME must be a string");
+          child_node = doc->document ()->first_node (name.c_str ());
+        }
+    }
+  else
+    {
+      if (! node->node ())
+        return octave_value (Matrix ());
+
+      if (args.length () == 1)
+        child_node = node->node ()->first_node ();
+      else
+        {
+          std::string name = args(1).xstring_value ("NAME must be a string");
+          child_node = node->node ()->first_node (name.c_str ());
+        }
+    }
+
+  if (child_node)
+    return octave_value (new octave_xml_node (child_node));
+  else
+    return octave_value (Matrix ());
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_get_last_child__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn  {} {@var{child_node} =} __xml_get_last_child__ (@var{node})
+@deftypefnx {} {@var{child_node} =} __xml_get_last_child__ (@var{node}, @var{name})
+Get last @var{child_node} of XML @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () < 1 || args.length () > 2)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+
+  rapidxml::xml_node<char> *child_node;
+  if (node->is_xml_document ())
+    {
+      octave_xml_doc *doc = dynamic_cast<octave_xml_doc *> (node);
+      if (! doc->document ())
+        return octave_value (Matrix ());
+
+      if (args.length () == 1)
+        child_node = doc->document ()->last_node ();
+      else
+        {
+          std::string name = args(1).xstring_value ("NAME must be a string");
+          child_node = doc->document ()->last_node (name.c_str ());
+        }
+
+    }
+  else
+    {
+      if (! node->node ())
+        return octave_value (Matrix ());
+
+      if (args.length () == 1)
+        child_node = node->node ()->last_node ();
+      else
+        {
+          std::string name = args(1).xstring_value ("NAME must be a string");
+          child_node = node->node ()->last_node (name.c_str ());
+        }
+    }
+
+  if (child_node)
+    return octave_value (new octave_xml_node (child_node));
+  else
+    return octave_value (Matrix ());
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_get_previous_sibling__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn  {} {@var{sibling_node} =} __xml_get_previous_sibling__ (@var{node})
+@deftypefnx {} {@var{sibling_node} =} __xml_get_previous_sibling__ (@var{node}, @var{name})
+Get previous @var{sibling_node} of XML @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () < 1 || args.length () > 2)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+
+  rapidxml::xml_node<char> *sibling_node = nullptr;
+  if (! node->is_xml_document () && node->node ())
+    {
+      if (args.length () == 1)
+        sibling_node = node->node ()->previous_sibling ();
+      else
+        {
+          std::string name = args(1).xstring_value ("NAME must be a string");
+          sibling_node = node->node ()->previous_sibling (name.c_str ());
+        }
+    }
+
+  if (sibling_node)
+    return octave_value (new octave_xml_node (sibling_node));
+  else
+    return octave_value (Matrix ());
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_get_next_sibling__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn  {} {@var{sibling_node} =} __xml_get_next_sibling__ (@var{node})
+@deftypefnx {} {@var{sibling_node} =} __xml_get_next_sibling__ (@var{node}, @var{name})
+Get next @var{sibling_node} of XML @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () < 1 || args.length () > 2)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+
+  rapidxml::xml_node<char> *sibling_node = nullptr;
+  if (! node->is_xml_document () && node->node ())
+    {
+      if (args.length () == 1)
+        sibling_node = node->node ()->next_sibling ();
+      else
+        {
+          std::string name = args(1).xstring_value ("NAME must be a string");
+          sibling_node = node->node ()->next_sibling (name.c_str ());
+        }
+    }
+
+  if (sibling_node)
+    return octave_value (new octave_xml_node (sibling_node));
+  else
+    return octave_value (Matrix ());
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_attribute__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn  {} {@var{val} =} __xml_attribute__ (@var{node}, @var{attr})
+@deftypefnx {} {} __xml_attribute__ (@var{node}, @var{attr}, @var{val})
+Get value @var{val} of attribute @var{attr} of XML @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () < 2 || args.length () > 3)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+  std::string attr = args(1).string_value ();
+
+  if (! node->is_xml_document () && node->node ())
+    {
+      rapidxml::xml_attribute<char> *attribute
+        = node->node ()->first_attribute (attr.c_str ());
+      if (args.length () == 2)
+        {
+          if (attribute)
+            {
+              std::string val (attribute->value (), attribute->value_size ());
+              return octave_value (val);
+            }
+        }
+      else
+        {
+          rapidxml::xml_document<char> *doc = node->node ()->document ();
+          if (! doc)
+            error ("setValue: NODE must have a parent for setting attribute");
+
+          std::string val = args(2).string_value ();
+
+          if (attribute)
+            {
+              char *attr_val
+                = doc->allocate_string (val.c_str (), val.length ());
+
+              attribute->value (attr_val, val.length ());
+            }
+          else
+            {
+              char *attr_name
+                = doc->allocate_string (attr.c_str (), attr.length ());
+              char *attr_val
+                = doc->allocate_string (val.c_str (), val.length ());
+
+              rapidxml::xml_attribute<char> *new_attr
+                = doc->allocate_attribute
+                    (attr_name, attr_val, attr.length (), val.length ());
+
+              node->node ()->append_attribute (new_attr);
+            }
+        }
+    }
+
+  return octave_value (Matrix ());
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_remove_attribute__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn  {} {} __xml_remove_attribute__ (@var{node}, @var{attr})
+Remove attribute @var{attr} of XML @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () < 2 || args.length () > 3)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+  std::string attr = args(1).string_value ();
+
+  if (! node->is_xml_document () && node->node ())
+    {
+      rapidxml::xml_attribute<char> *attribute
+        = node->node ()->first_attribute (attr.c_str ());
+      if (attribute)
+        node->node ()->remove_attribute (attribute);
+    }
+
+  return octave_value (Matrix ());
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_get_owner_document__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {@var{owner_doc} =} __xml_get_owner_document__ (node)
+Get document @var{owner_doc} of which XML @var{node} is a child.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () < 1)
+    print_usage ();
+
+  octave_xml_node *oct_node = get_xml_node (args(0));
+
+  return oct_node->get_owner_document ();
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_insert_before__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {} __xml_insert_before__ (node, new_child, ref_child)
+Insert @var{new_child} in XML @var{node} before @var{ref_child}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () != 3)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+  octave_xml_node *new_child = get_xml_node (args(1));
+  octave_xml_node *ref_child = get_xml_node (args(2));
+
+  if (node->is_xml_document ())
+    {
+      octave_xml_doc *doc = dynamic_cast<octave_xml_doc *> (node);
+      if (! doc->document ())
+        return octave_value (Matrix ());
+
+      doc->document ()->document ()->insert_node (ref_child->node (),
+                                                  new_child->node ());
+    }
+  else
+    node->node ()->insert_node (ref_child->node (), new_child->node ());
+
+  return octave_value ();
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_remove_child__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {} __xml_remove_child__ (node, old_child)
+Remove @var{old_child} from XML @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () != 2)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+  octave_xml_node *old_child = get_xml_node (args(1));
+
+  if (node->is_xml_document ())
+    {
+      octave_xml_doc *doc = dynamic_cast<octave_xml_doc *> (node);
+      if (! doc->document ())
+        return octave_value (Matrix ());
+
+      doc->document ()->document ()->remove_node (old_child->node ());
+    }
+  else
+    node->node ()->remove_node (old_child->node ());
+
+  return octave_value ();
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_append_child__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {} __xml_append_child__ (node, new_child)
+Append @var{new_child} to XML @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () != 2)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+  octave_xml_node *new_child = get_xml_node (args(1));
+
+  if (node->is_xml_document ())
+    {
+      octave_xml_doc *doc = dynamic_cast<octave_xml_doc *> (node);
+      if (! doc->document ())
+        return octave_value (Matrix ());
+
+      doc->document ()->document ()->append_node (new_child->node ());
+    }
+  else
+    node->node ()->append_node (new_child->node ());
+
+  return octave_value ();
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
+
+
+DEFUN (__xml_clone_node__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {@var{clone} = } __xml_clone_node__ (node)
+Return @var{clone} of XML @var{node}.
+
+@end deftypefn */)
+{
+#if defined (HAVE_RAPIDXML)
+
+  if (args.length () != 1)
+    print_usage ();
+
+  octave_xml_node *node = get_xml_node (args(0));
+
+  rapidxml::xml_node<char> *clone
+    = node->node ()->document ()->clone_node (node->node ());
+
+  return octave_value (new octave_xml_node (clone));
+
+#else
+
+  octave_unused_parameter (args);
+
+  err_disabled_feature ("xmlread", "RapidXML");
+
+#endif
+}
diff -r 25b7ffb514f3 -r b83830744de6 scripts/help/__unimplemented__.m
--- a/scripts/help/__unimplemented__.m	Fri Oct 09 18:25:25 2020 +0200
+++ b/scripts/help/__unimplemented__.m	Sun Oct 04 22:35:24 2020 +0200
@@ -1326,8 +1326,6 @@
   "writeKeyUnit",
   "writetable",
   "writeVideo",
-  "xmlread",
-  "xmlwrite",
   "xslt",
   "xtickangle",
   "xtickformat",
diff -r 25b7ffb514f3 -r b83830744de6 scripts/io/dom_document.m
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/io/dom_document.m	Sun Oct 04 22:35:24 2020 +0200
@@ -0,0 +1,75 @@
+########################################################################
+##
+## Copyright (C) 2020 The Octave Project Developers
+##
+## See the file COPYRIGHT.md in the top-level directory of this
+## distribution or <https://octave.org/copyright/>.
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+##
+########################################################################
+
+classdef dom_document < dom_node
+
+  ## -*- texinfo -*-
+  ## @deftypefn  {} {@var{doc} =} dom_document (@var{xml_doc})
+  ## Class for the objects returned by @code{xmlread}.
+  ##
+  ## The class closely mimicks the DOM document interface used by Java for
+  ## hosting the owner document of the XML tree.  See also:
+  ## @url{https://docs.oracle.com/javase/6/docs/api/org/w3c/dom/Document.html}
+  ##
+  ## @seealso{xmlread, xmlwrite, dom_node}
+  ## @end deftypefn
+
+
+  methods (Hidden)
+
+    function node = get_dom_node (this)
+      node = this.m_dom_node;
+    endfunction
+
+  endmethods
+
+
+  methods
+
+    function this = dom_document (doc)
+
+      if (nargin == 1)
+        this.m_dom_node = doc;
+      else
+        this.m_dom_node = __xml_new_document__ ();
+      end
+
+    end
+
+
+    function new_node = createElement (this, name)
+      new_node = dom_node (__xml_new_node__ (this.m_dom_node,
+                                             "element", name));
+    endfunction
+
+
+    function new_node = createTextNode (this, value)
+      new_node = dom_node (__xml_new_node__ (this.m_dom_node,
+                                             "element", value));
+    endfunction
+
+  endmethods
+
+endclassdef
\ No newline at end of file
diff -r 25b7ffb514f3 -r b83830744de6 scripts/io/dom_node.m
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/io/dom_node.m	Sun Oct 04 22:35:24 2020 +0200
@@ -0,0 +1,533 @@
+########################################################################
+##
+## Copyright (C) 2020 The Octave Project Developers
+##
+## See the file COPYRIGHT.md in the top-level directory of this
+## distribution or <https://octave.org/copyright/>.
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+##
+########################################################################
+
+classdef dom_node < handle
+
+  ## -*- texinfo -*-
+  ## @deftypefn  {} {@var{node} =} dom_node (@var{xml_node})
+  ## Class for the node objects in the XML DOM tree.
+  ##
+  ## The class closely mimicks the DOM node interface used by Java for
+  ## traversing XML trees.  See also:
+  ## @url{https://docs.oracle.com/javase/6/docs/api/org/w3c/dom/Node.html}
+  ##
+  ## @seealso{xmlread, xmlwrite, dom_document}
+  ## @end deftypefn
+
+  properties (GetAccess = public, SetAccess = private, Hidden)
+    ## FIXME: These should be enumeration values
+    ELEMENT_NODE = 1
+    ATTRIBUTE_NODE = 2
+    TEXT_NODE = 3
+    CDATA_SECTION_NODE = 4
+    ENTITY_REFERENCE_NODE = 5
+    ENTITY_NODE = 6
+    PROCESSING_INSTRUCTION_NODE = 7
+    COMMENT_NODE = 8
+    DOCUMENT_NODE = 9
+    DOCUMENT_TYPE_NODE = 10
+    DOCUMENT_FRAGMENT_NODE = 11
+    NOTATION_NODE = 12
+
+    DOCUMENT_POSITION_DISCONNECTED = 1
+    DOCUMENT_POSITION_PRECEDING = 2
+    DOCUMENT_POSITION_FOLLOWING = 4
+    DOCUMENT_POSITION_CONTAINS = 8
+    DOCUMENT_POSITION_CONTAINED_BY = 16
+    DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32
+  endproperties
+
+
+  ## FIXME: The `SetAccess` should be `immutable` when that is implemented.
+  properties (GetAccess = protected, SetAccess = protected)
+    m_dom_node
+  endproperties
+
+
+  methods (Hidden)
+
+    function node = get_dom_node (this)
+      node = this.m_dom_node;
+    endfunction
+
+  endmethods
+
+
+  methods
+
+    function this = dom_node (node)
+
+      if (nargin == 0)
+        ## Called as super-class constructor
+        return;
+      endif
+
+      if (nargin > 1)
+        print_usage ();
+      endif
+
+      if (! any (strcmp (class (node), {"XML_node", "XML_document"})))
+        error ("dom_node: NODE must be of class `XML_node` or `XML_document`");
+      endif
+
+      this.m_dom_node = node;
+    endfunction
+
+
+    function node_name = getNodeName (this)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{node_name} =} @var{node}.getNodeName ()
+      ## Get the name of @var{node}.
+      ## @end deftypefn
+
+      node_name = __xml_get_name__ (this.m_dom_node);
+
+    endfunction
+
+
+    function node_value = getNodeValue (this)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{node_value} =} @var{node}.getNodeValue ()
+      ## Get the value of @var{node}.
+      ## @end deftypefn
+
+      node_value = __xml_value__ (this.m_dom_node);
+
+    endfunction
+
+
+    function setNodeValue (this, node_value)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {} @var{node}.getNodeValue (@var{node_value})
+      ## Set the value of @var{node}.
+      ## @end deftypefn
+
+      if (nargin !=2)
+        print_usage ();
+      endif
+      if (! ischar (node_value))
+        error ("setNodeValue: NODE_VALUE must be a string");
+      endif
+      __xml_value__ (this.m_dom_node, node_value);
+
+    endfunction
+
+
+    function node_type = getNodeType (this)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{node_type} =} @var{node}.getNodeType ()
+      ## Get the type of @var{node}.
+      ## @end deftypefn
+
+      node_type = dom_node (__xml_get_type__ (this.m_dom_node));
+
+    endfunction
+
+
+    function parent_node = getParentNode (this)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{parent_node} =} @var{node}.getParentNode ()
+      ## Get the parent of @var{node}.
+      ## @end deftypefn
+
+      parent_node = __xml_get_parent__ (this.m_dom_node);
+      if (isempty (parent_node))
+        return;
+      endif
+      parent_node = dom_node (parent_node);
+
+    endfunction
+
+
+    function nodes = getChildNodes (this)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{nodes} =} @var{node}.getChildNodes ()
+      ## Get array of all children @var{nodes}.
+      ## @end deftypefn
+
+      ## FIXME: How to create an array of classdef objects?
+      nodes = dom_node.empty ();
+      node = __xml_get_first_child__ (this.m_dom_node);
+      while (! isempty (node))
+        nodes(end+1) = dom_node (node);
+        node = __xml_get_next_sibling__ (this.m_dom_node);
+      endwhile
+
+    endfunction
+
+
+    function node = getFirstChild (this, varargin)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{child_node} =} @var{node}.getFirstChild ()
+      ## @deftypefnx {} {@var{child_node} =} @var{node}.getFirstChild (@var{name})
+      ## Get the first child of @var{node}.
+      ##
+      ## Optionally, get first child of @var{node} with name @var{name}.
+      ##
+      ## @end deftypefn
+
+      if (numel (varargin) > 1)
+        print_usage ();
+      endif
+
+      node = __xml_get_first_child__ (this.m_dom_node, varargin{:});
+      if (isempty (node))
+        return;
+      endif
+      node = dom_node (node);
+
+    endfunction
+
+
+    function node = getLastChild (this, varargin)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{child_node} =} @var{node}.getLastChild ()
+      ## @deftypefnx {} {@var{child_node} =} @var{node}.getLastChild (@var{name})
+      ## Get the last child of @var{node}.
+      ##
+      ## Optionally, get last child of @var{node} with name @var{name}.
+      ##
+      ## @end deftypefn
+
+      if (numel (varargin) > 1)
+        print_usage ();
+      endif
+
+      node = __xml_get_last_child__ (this.m_dom_node, varargin{:});
+      if (isempty (node))
+        return;
+      endif
+      node = dom_node (node);
+
+    endfunction
+
+
+    function node = getPreviousSibling (this, varargin)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{sibling_node} =} @var{node}.getPreviousSibling ()
+      ## @deftypefnx {} {@var{sibling_node} =} @var{node}.getPreviousSibling (@var{name})
+      ## Get the previous sibling of @var{node}.
+      ##
+      ## Optionally, get previous sibling of @var{node} with name @var{name}.
+      ##
+      ## @end deftypefn
+
+      if (numel (varargin) > 1)
+        print_usage ();
+      endif
+
+      node = __xml_get_previous_sibling__ (this.m_dom_node, varargin{:});
+      if (isempty (node))
+        return;
+      endif
+      node = dom_node (node);
+
+    endfunction
+
+
+    function node = getNextSibling (this, varargin)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{sibling_node} =} @var{node}.getNextSibling ()
+      ## @deftypefnx {} {@var{sibling_node} =} @var{node}.getNextSibling (@var{name})
+      ## Get the next sibling of @var{node}.
+      ##
+      ## Optionally, get next sibling of @var{node} with name @var{name}.
+      ##
+      ## @end deftypefn
+
+      if (numel (varargin) > 1)
+        print_usage ();
+      endif
+
+      node = __xml_get_next_sibling__ (this.m_dom_node, varargin{:});
+      if (isempty (node))
+        return;
+      endif
+      node = dom_node (node);
+
+    endfunction
+
+
+    function val = getAttribute (this, attr)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{val} =} @var{node}.getAttribute (@var{attr})
+      ## Get attribute @var{attr} of @var{node}.
+      ## @end deftypefn
+
+      if (nargin != 2)
+        print_usage ();
+      endif
+
+      if (! ischar (attr))
+        error ("getAttribute: ATTR must be a string");
+      endif
+
+      val = __xml_attribute__  (this.m_dom_node, attr);
+
+    endfunction
+
+
+    function setAttribute (this, attr, val)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {} @var{node}.setAttribute (@var{attr}, @var{val})
+      ## Set attribute @var{attr} of @var{node} to @var{val}.
+      ## @end deftypefn
+
+      if (nargin != 3)
+        print_usage ();
+      endif
+
+      if (! ischar (attr))
+        error ("getAttribute: CHAR must be a string");
+      endif
+
+      if (! ischar (val))
+        error ("getAttribute: VAL must be a string");
+      endif
+
+      __xml_attribute__  (this.m_dom_node, attr, val);
+
+    endfunction
+
+
+    function removeAttribute (this, attr)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {} @var{node}.removeAttribute (@var{attr})
+      ## Remove attribute @var{attr} of @var{node}.
+      ## @end deftypefn
+
+      if (nargin != 2)
+        print_usage ();
+      endif
+
+      if (! ischar (attr))
+        error ("getAttribute: CHAR must be a string");
+      endif
+
+      __xml_remove_attribute__  (this.m_dom_node, attr);
+
+    endfunction
+
+
+    function attributes = getAttributes (this)
+      error ("dom_node: Not yet implemented.");
+    endfunction
+
+
+    function doc = getOwnerDocument (this)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{doc} = } @var{node}.getOwnerDocument ()
+      ## Get node of the document that own this @var{node}.
+      ## @end deftypefn
+
+      doc = __xml_get_owner_document__ (this.m_dom_node);
+      if (isempty (doc))
+        return;
+      endif
+
+      doc = dom_node (doc);
+
+    endfunction
+
+
+    function new_node = insertBefore (this, new_child, ref_child)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{new_node} = } @var{node}.insertBefore (@var{new_child}, @var{ref_child})
+      ## Insert node @var{new_child} as a child of @var{node} behind existing child @var{ref_child}.
+      ## @end deftypefn
+
+      __xml_insert_before__ (this.m_dom_node, new_child.m_dom_node,
+                             ref_child.m_dom_node);
+      new_node = new_child;
+
+    endfunction
+
+
+    function node = replaceChild (this, new_child, old_child)
+      error("replaceChild: Not yet implemented");
+    endfunction
+
+
+    function old_node = removeChild (this, old_child)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{old_node} = } @var{node}.removeChild (@var{old_child})
+      ## Remove node @var{old_child} from @var{node}.
+      ## @end deftypefn
+
+      __xml_remove_child__ (this.m_dom_node, old_child.m_dom_node);
+      old_node = old_child;
+
+    endfunction
+
+
+    function new_node = appendChild (this, new_child)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{new_node} = } @var{node}.appendChild (@var{new_child})
+      ## Append @var{new_child} at the end of children of @var{node}.
+      ## @end deftypefn
+
+      __xml_append_child__ (this.m_dom_node, new_child.m_dom_node);
+      new_node = new_child;
+
+    endfunction
+
+
+    function has_kids = hasChildNodes (this)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{has_kids} = } @var{node}.hasChildNodes ()
+      ## Return true if node @var{node} has children.
+      ## @end deftypefn
+
+      node = __xml_get_first_child__ (this.m_dom_node);
+      has_kids = ! isempty (node);
+
+    endfunction
+
+
+    function new_node = cloneNode (this, deep)
+
+      ## -*- texinfo -*-
+      ## @deftypefn  {} {@var{new_node} = } @var{node}.hasChildNodes ()
+      ## @deftypefnx {} {@var{new_node} = } @var{node}.hasChildNodes (@var{deep})
+      ## Clone node @var{node} including all its children.
+      ##
+      ## Shallow copies are not yet implemented.  (So @var{deep} must be
+      ## @qcode{true}.)
+      ## @end deftypefn
+
+      if (nargin < 2)
+        deep = true;
+      endif
+
+      if (! deep)
+        error ("cloneNode: Shallow copies aren't implemented yet");
+      endif
+
+      new_node = __xml_clone_node__ (this.m_dom_node);
+      if (isempty (new_node))
+        return;
+      endif
+      new_node = dom_node (new_node);
+
+    endfunction
+
+
+    function normalize (this)
+      error("normalize: Not yet implemented");
+    endfunction
+
+    function is_support = isSupported (this, feature, version)
+      error("isSupported: Not yet implemented");
+    endfunction
+
+    function uri = getNamespaceURI (this)
+      error("getNamespaceURI: Not yet implemented");
+    endfunction
+
+    function prefix = getPrefix (this)
+      error("getPrefix: Not yet implemented");
+    endfunction
+
+    function setPrefix (this, prefix)
+      error("setPrefix: Not yet implemented");
+    endfunction
+
+    function local_name = getLocalName (this)
+      error("getLocalName: Not yet implemented");
+    endfunction
+
+    function has_attributes = hasAttributes (this)
+      error("hasAttributes: Not yet implemented");
+    endfunction
+
+    function base_uir = getBaseURI (this)
+      error("getBaseURI: Not yet implemented");
+    endfunction
+
+    function comp_pos = compareDocumentPosition (this, other)
+      error("compareDocumentPosition: Not yet implemented");
+    endfunction
+
+    function text_content = getTextContent (this)
+      error("getTextContent: Not yet implemented");
+    endfunction
+
+    function setTextContent (this, text_content)
+      error("setTextContent: Not yet implemented");
+    endfunction
+
+    function is_same = isSameNode (this, other)
+      error("isSameNode: Not yet implemented");
+    endfunction
+
+    function prefix = loopupPrefix (this, namespace_uri)
+      error("loopupPrefix: Not yet implemented");
+    endfunction
+
+    function is_default = isDefaultNamespace (this, namespace_uri)
+      error("isDefaultNamespace: Not yet implemented");
+    endfunction
+
+    function namespace_uri = lookupNamespaceURI (this, prefix)
+      error("lookupNamespaceURI: Not yet implemented");
+    endfunction
+
+    function is_equal = isEqualNode (this, node)
+      error("isEqualNode: Not yet implemented");
+    endfunction
+
+    function obj = getFeature (this, feature, version)
+      error("getFeature: Not yet implemented");
+    endfunction
+
+    function obj = setUserData (this, key, data, handler)
+      error("setUserData: Not yet implemented");
+    endfunction
+
+    function obj = getUserData (this, key)
+      error("getUserData: Not yet implemented");
+    endfunction
+
+  endmethods
+
+endclassdef
\ No newline at end of file
diff -r 25b7ffb514f3 -r b83830744de6 scripts/io/module.mk
--- a/scripts/io/module.mk	Fri Oct 09 18:25:25 2020 +0200
+++ b/scripts/io/module.mk	Sun Oct 04 22:35:24 2020 +0200
@@ -5,9 +5,13 @@
   %reldir%/csvread.m \
   %reldir%/csvwrite.m \
   %reldir%/dlmwrite.m \
+  %reldir%/dom_document.m \
+  %reldir%/dom_node.m \
   %reldir%/fileread.m \
   %reldir%/importdata.m \
-  %reldir%/is_valid_file_id.m
+  %reldir%/is_valid_file_id.m \
+  %reldir%/xmlread.m \
+  %reldir%/xmlwrite.m
 
 %canon_reldir%dir = $(fcnfiledir)/io
 
diff -r 25b7ffb514f3 -r b83830744de6 scripts/io/xmlread.m
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/io/xmlread.m	Sun Oct 04 22:35:24 2020 +0200
@@ -0,0 +1,61 @@
+########################################################################
+##
+## Copyright (C) 2020 The Octave Project Developers
+##
+## See the file COPYRIGHT.md in the top-level directory of this
+## distribution or <https://octave.org/copyright/>.
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+##
+########################################################################
+
+## -*- texinfo -*-
+## @deftypefn  {} {@var{dom_doc} =} xmlread (@var{filename})
+## @deftypefnx {} {@var{dom_doc} =} xmlread (@var{filename}, @var{"AllowDoctype"}, @var{doc_type})
+## Read XML from file @var{filename} and return XML DOM document @var{dom_doc}.
+##
+## @var{doc_type} is not yet implemented.
+##
+## @seealso{xmlwrite, dom_document, csvread, dlmread, csvwrite, dlmwrite}
+## @end deftypefn
+
+function x = xmlread (filename, varargin)
+
+  x = dom_document (__xml_open__ (filename));
+
+endfunction
+
+
+%!demo
+%! ## Create an svg file, which is nothing but an xml tree
+%! tk = graphics_toolkit ();
+%! graphics_toolkit ("fltk");
+%! hf = figure ();
+%! sombrero ();
+%! fname = [tempname(), ".svg"];
+%! print (fname);
+%! close (hf)
+%! graphics_toolkit (tk);
+%! 
+%! ## Read the svg file and check the root node is named "svg"
+%! dom = xmlread (fname);
+%! if (dom.hasChildNodes ())
+%!   root_node = dom.getFirstChild ();
+%!   printf ("The name of the root node is \"%s\"\n", ...
+%!            char (root_node.getNodeName ()))
+%! endif
+
diff -r 25b7ffb514f3 -r b83830744de6 scripts/io/xmlwrite.m
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/io/xmlwrite.m	Sun Oct 04 22:35:24 2020 +0200
@@ -0,0 +1,79 @@
+########################################################################
+##
+## Copyright (C) 2020 The Octave Project Developers
+##
+## See the file COPYRIGHT.md in the top-level directory of this
+## distribution or <https://octave.org/copyright/>.
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+##
+########################################################################
+
+## -*- texinfo -*-
+## @deftypefn  {} {} xmlwrite (@var{filename}, @var{dom_node})
+## @deftypefnx {} {@var{str} =} xmlwrite (@var{dom_node})
+## Write XML DOM @var{dom_node} to file @var{filename} or return as string.
+##
+## @var{doc_type} is not yet implemented.
+##
+## @seealso{xmlwrite, dom_node, csvread, dlmread, csvwrite, dlmwrite}
+## @end deftypefn
+
+function str = xmlwrite (filename, dom_node)
+
+  if (! ((nargin == 1) || (nargin == 2 && nargout == 0)))
+    print_usage ();
+  endif
+
+  if (nargin == 1)
+    dom_node = filename;
+    str = __xml_print_str__ (dom_node.get_dom_node ());
+  else
+    if (! ischar (filename))
+      error ("xmlwrite: FILENAME must be a string");
+    endif
+    if (! any (strcmp (class (dom_node), {"dom_node", "dom_document"})))
+      error ("xmlwrite: DOM_NODE must be a XML DOM object");
+    endif
+    __xml_write_file__ (filename, dom_node.get_dom_node ());
+  endif
+
+endfunction
+
+
+%!demo
+%! doc = dom_document ();
+%! 
+%! ## Create a root node and add it to the document
+%! root = doc.createElement ("RootNode");
+%! doc.appendChild (root);
+%! root.setAttribute ("created", datestr (date ()));
+%! 
+%! ## Create a child node, append it text data and add it to the
+%! ## root children
+%! child = doc.createElement ("TextChild");
+%! root.appendChild (child);
+%! child.setAttribute ("verbose", "yes");
+%! child.setAttribute ("useful", "no");
+%! child.setNodeValue ("This is some text data");
+%! 
+%! ## Serialize DOM document to string
+%! str = xmlwrite (doc)
+
+
+## FIXME: Add BISTs
+%!assert (1)
