I have an XML file like this:

xml = XMLObject["Document"][
   "whatever", {}, {
     {XMLElement["forename", {"type" -> "first"}, {"Adrian"}], 
      XMLElement["surname", {}, {"Adgar"}]
    {XMLElement["forename", {"type" -> "first"}, {"Babar"}], 
     XMLElement["forename", {"type" -> "middle"}, {"Barbados"}], 
     XMLElement["surname", {}, {"Bode"}]
    {XMLElement["forename", {"type" -> "first"}, {"Charly"}], 
     XMLElement["surname", {}, {"Chimp"}]

I'd like to extract the first names, middle names if any, and surnames.

Using repeated patterns, I would have expected this to work:

Cases[xml, {(XMLElement["forename", _, x_])..., 
      XMLElement["surname", {}, y_]} -> {x, y}, Infinity]
(* {{{"Adrian"}, {"Adgar"}}, {{"Charly"}, {"Chimp"}}} *)

however it misses the middle names. Instead, I would like an output like:

{{"Adrian", "Adgar"}, {"Babar", "Barbados", "Bode"}, {"Charly", "Chimp"}}

I am also wondering what is wrong in this syntax.


2 Answers 2

Cases[xml,XMLElement["forename" | "surname", _, x_] :> x, \[Infinity]]

{{"Adrian"}, {"Adgar"}, {"Babar"}, {"Barbados"}, {"Bode"}, \
{"Charly"}, {"Chimp"}}


Looks like we have to guess at what the desired output is so here is guess #2 with first names and surnames grouped. In the example provided first names always precede surnames in the grouping therefore the solution is based on that fact.

Cases[xml, x : {__XMLElement} :> Flatten[x[[All, -1]]], {3}]
{{"Adrian", "Adgar"}, {"Babar", "Barbados", "Bode"}, {"Charly", 

  • $\begingroup$ The problem with that is I don't know if "Adgar" is the middle name of Mr. Babar, or if it's Mr. Adgar. $\endgroup$
    – anderstood
    Feb 22, 2018 at 22:24
  • $\begingroup$ @anderstood you need to make it clear in the question what you are seeking. Why not give the output you are after rather than make people guess? $\endgroup$ Feb 22, 2018 at 22:36

A minimal example showing why the original Cases doesn't work,

data = {{f[1], g[2]}, {f[1], f[2], g[3]}, {f[1], f[1], g[3]}};

 {(f[x_] ..), g[y_]}
(* {{f[1], g[2]}, {f[1], f[1], g[3]}} *)

 {(f[_] ..), g[y_]}
(* {{f[1], g[2]}, {f[1], f[2], g[3]}, {f[1], f[1], g[3]}} *)

Using a named pattern alongside Repeated means that the elements that match that named pattern need to be the same. So {f[1], f[2], g[3]} matches {(f[_] ..), g[y_]} but not {(f[x_] ..), g[y_]}.

This loses some brevity, but when in doubt I like to spell my patterns out explicitly.

forenamePattern = XMLElement["forename", _, _];
surnamePattern = XMLElement["surname", _, _];
namePattern = {forenamePattern .., surnamePattern};
getNamesFromPattern = 
  ReplaceAll[x : (forenamePattern | surnamePattern) :> Last@x];
Cases[xml, name : namePattern :> getNamesFromPattern[name], Infinity]
(* {{{"Adrian"}, {"Adgar"}}, {{"Babar"}, {"Barbados"}, \
{"Bode"}}, {{"Charly"}, {"Chimp"}}} *)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.