Skip to content

Commit

Permalink
xml_child() function to make selection easier
Browse files Browse the repository at this point in the history
Fixes #23
  • Loading branch information
jimhester committed May 27, 2016
1 parent d7b883d commit 85d75b7
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 2 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
27 changes: 26 additions & 1 deletion R/xml_children.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down
22 changes: 21 additions & 1 deletion man/xml_children.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions tests/testthat/test-xml_children.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
context("xml_children")

x <- read_xml("<foo> <bar><boo /></bar> <baz/> </foo>")

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`")
})

0 comments on commit 85d75b7

Please sign in to comment.