From 85d75b7af4e9b87e5fc5eab66ba5217d5b2c17d6 Mon Sep 17 00:00:00 2001 From: Jim Hester Date: Fri, 27 May 2016 15:37:14 -0400 Subject: [PATCH] xml_child() function to make selection easier Fixes #23 --- NAMESPACE | 1 + NEWS.md | 3 +++ R/xml_children.R | 27 ++++++++++++++++++++++++++- man/xml_children.Rd | 22 +++++++++++++++++++++- tests/testthat/test-xml_children.R | 24 ++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 tests/testthat/test-xml_children.R 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`") +})