diff --git a/NAMESPACE b/NAMESPACE
index 1904302c..db6f5ae0 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -107,6 +107,7 @@ export(xml_add_child)
export(xml_add_sibling)
export(xml_attr)
export(xml_attrs)
+export(xml_child)
export(xml_children)
export(xml_contents)
export(xml_find_all)
diff --git a/NEWS.md b/NEWS.md
index 7617f650..6f15f89e 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -3,6 +3,9 @@
* `read_xml()` and `read_html()` methods added for `httr::response()` objects.
(@jimhester, #63, #93)
+* `xml_child()` function to make selecting children a little easier
+ (@jimhester, #23, #94)
+
* `xml_find_one()` has been deprecated in favor of `xml_find_first()`
(@jimhester, #58, #92)
diff --git a/R/xml_children.R b/R/xml_children.R
index 8132062c..fbf7a403 100644
--- a/R/xml_children.R
+++ b/R/xml_children.R
@@ -4,11 +4,15 @@
#' all nodes. \code{xml_length} returns the number of children.
#' \code{xml_parent} returns the parent node, \code{xml_parents}
#' returns all parents up to the root. \code{xml_siblings} returns all nodes
-#' at the same level.
+#' at the same level. \code{xml_child} makes it easy to specify a specific
+#' child to return.
#'
#' @inheritParams xml_name
#' @param only_elements For \code{xml_length}, should it count all children,
#' or just children that are elements (the default)?
+#' @param search For \code{xml_child}, either the child number to return (by
+#' position), or the name of the child node to return. If there are multiple
+#' child nodes with the same name, the first will be returned
#' @return A node or nodeset (possibly empty). Results are always de-duplicated.
#' @export
#' @examples
@@ -28,10 +32,31 @@
#'
#' xml_length(x)
#' xml_length(x, only_elements = FALSE)
+#'
+#' # xml_child makes it easier to select specific children
+#' xml_child(x)
+#' xml_child(x, 2)
+#' xml_child(x, "baz")
xml_children <- function(x) {
nodeset_apply(x, node_children)
}
+#' @export
+#' @rdname xml_children
+xml_child <- function(x, search = 1, ns = xml_ns(x)) {
+ if (length(search) != 1) {
+ stop("`search` must be of length 1", call. = FALSE)
+ }
+
+ if (is.numeric(search)) {
+ xml_children(x)[[search]]
+ } else if (is.character(search)) {
+ xml_find_first(x, xpath = paste0("./", search), ns = ns)
+ } else {
+ stop("`search` must be `numeric` or `character`", call. = FALSE)
+ }
+}
+
#' @export
#' @rdname xml_children
xml_contents <- function(x) {
diff --git a/man/xml_children.Rd b/man/xml_children.Rd
index 17237671..a7442d06 100644
--- a/man/xml_children.Rd
+++ b/man/xml_children.Rd
@@ -1,6 +1,7 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/xml_children.R
\name{xml_children}
+\alias{xml_child}
\alias{xml_children}
\alias{xml_contents}
\alias{xml_length}
@@ -12,6 +13,8 @@
\usage{
xml_children(x)
+xml_child(x, search = 1, ns = xml_ns(x))
+
xml_contents(x)
xml_parents(x)
@@ -27,6 +30,17 @@ xml_root(x)
\arguments{
\item{x}{A document, node, or node set.}
+\item{search}{For \code{xml_child}, either the child number to return (by
+position), or the name of the child node to return. If there are multiple
+child nodes with the same name, the first will be returned}
+
+\item{ns}{Optionally, a named vector giving prefix-url pairs, as produced
+by \code{\link{xml_ns}}. If provided, all names will be explicitly
+qualified with the ns prefix, i.e. if the element \code{bar} is defined
+in namespace \code{foo}, it will be called \code{foo:bar}. (And
+similarly for atttributes). Default namespaces must be given an explicit
+name.}
+
\item{only_elements}{For \code{xml_length}, should it count all children,
or just children that are elements (the default)?}
}
@@ -38,7 +52,8 @@ A node or nodeset (possibly empty). Results are always de-duplicated.
all nodes. \code{xml_length} returns the number of children.
\code{xml_parent} returns the parent node, \code{xml_parents}
returns all parents up to the root. \code{xml_siblings} returns all nodes
-at the same level.
+at the same level. \code{xml_child} makes it easy to specify a specific
+child to return.
}
\examples{
x <- read_xml(" ")
@@ -57,5 +72,10 @@ xml_contents(x)
xml_length(x)
xml_length(x, only_elements = FALSE)
+
+# xml_child makes it easier to select specific children
+xml_child(x)
+xml_child(x, 2)
+xml_child(x, "baz")
}
diff --git a/tests/testthat/test-xml_children.R b/tests/testthat/test-xml_children.R
new file mode 100644
index 00000000..21257571
--- /dev/null
+++ b/tests/testthat/test-xml_children.R
@@ -0,0 +1,24 @@
+context("xml_children")
+
+x <- read_xml(" ")
+
+test_that("xml_child() returns the proper child", {
+ expect_equal(xml_child(x), xml_children(x)[[1L]])
+
+ expect_equal(xml_child(x, 2), xml_children(x)[[2L]])
+})
+
+test_that("xml_child() returns child by name", {
+ expect_equal(xml_child(x, "baz"), xml_find_first(x, "./baz"))
+})
+
+test_that("xml_child() errors if more than one search is given", {
+ expect_error(xml_child(x, 1:2), "`search` must be of length 1")
+})
+
+test_that("xml_child() errors if search is not numeric or character", {
+ expect_error(xml_child(x, TRUE), "`search` must be `numeric` or `character`")
+ expect_error(xml_child(x, as.factor("test")), "`search` must be `numeric` or `character`")
+ expect_error(xml_child(x, raw(1)), "`search` must be `numeric` or `character`")
+ expect_error(xml_child(x, list(1)), "`search` must be `numeric` or `character`")
+})