1
$\begingroup$

I have a file that, when imported as an "XMLObject", returns an XML object that possesses this general structure: (I've stripped the inconsequential junk wrapping the meat of the file)

xml = XMLElement["x", {"y" -> "1", "z" -> "2"},
       {
          XMLElement["noThanks", {"junk"}, {"garbage"}],

          "noisyString",

          XMLElement["yes1", {"info1a"}, {"info1b"}],
          "noisyString",
          XMLElement["yes2", {"info2a"}, {"info2b"}],

          "noisyString",

          XMLElement["yes1", {"info1c"}, {"info1d"}],
          "noisyString",
          XMLElement["yes2", {"info2c"}, {"info2d"}],

          "noisyString",

          XMLElement["noThanks", {"junk"}, {"garbage"}]
       }
      ];

I am trying to extract blocks of "adjacent" XMLElements according to the pattern

XMLElement["yes1", ___], _, XMLElement["yes2", ___]

But I am not sure how to do it.

One of the first things that occurred to me was to try PatternSequence:

Cases[
    xml,
    PatternSequence[XMLElement["yes1", foo___], _, XMLElement["yes2", bar___]] :> {foo, bar},
    Infinity
 ]
(* {} *)

As a second attempt, I tried to expand the pattern to include the entire list acting as third argument to XMLElement["noThanks", ...], which partially worked.

Cases[
    xml,
    {___, PatternSequence[XMLElement["yes1", foo___], _, XMLElement["yes2", bar___]], ___} :> {foo, bar},
    Infinity
 ] /. {s_String} :> s
(* {{"info1a", "info1b", "info2a", "info2b"}} *)

(whereas I'm expecting to grab all info-Strings)

Edit: One thing I neglected to mention is that I would also like to have the info-Strings from the same "blocks" to be grouped, so that the output gathers infoXa and infoXb, i.e.

(* {{"info1a", "info1b", "info2a", "info2b"}, {"info1c", "info1d", "info2c", "info2d"}} *)

If what I am asking is clear enough, is it doable with a pattern similar to what I am using? Or is my head in the wrong place?

P.S. Sincere apologies if a duplicate question exists.

$\endgroup$
  • $\begingroup$ How about SequenceCases[ Last@xml, {XMLElement["yes1", foo___], _, XMLElement["yes2", bar___]} :> {foo, bar}]? $\endgroup$ – Sjoerd Smit Feb 13 '18 at 11:14
3
$\begingroup$
xml = XMLElement[
   "x", {"y" -> "1", 
    "z" -> "2"}, {XMLElement["noThanks", {"junk"}, {"garbage"}], 
    "noisyString", XMLElement["yes1", {"info1a"}, {"info1b"}], 
    "noisyString", XMLElement["yes2", {"info2a"}, {"info2b"}], 
    "noisyString", XMLElement["yes1", {"info1c"}, {"info1d"}], 
    "noisyString", XMLElement["yes2", {"info2c"}, {"info2d"}], 
    "noisyString", XMLElement["noThanks", {"junk"}, {"garbage"}]}];

Extract the bits you want:

Cases[xml, XMLElement["yes1" | "yes2", __], \[Infinity]]

(* 
{XMLElement["yes1", {"info1a"}, {"info1b"}], 
 XMLElement["yes2", {"info2a"}, {"info2b"}], 
 XMLElement["yes1", {"info1c"}, {"info1d"}], 
 XMLElement["yes2", {"info2c"}, {"info2d"}]} *)

Before moving on to the next step I'm thinking that if you have this example:

xml2 = XMLElement[
   "x", {"y" -> "1", 
    "z" -> "2"}, {XMLElement["noThanks", {"junk"}, {"garbage"}], 
    "noisyString", XMLElement["yes1", {"info1a"}, {"info1b"}], 
    "noisyString", XMLElement["yes2", {"info2a"}, {"info2b"}], 
    "noisyString",
    XMLElement["yes1", {"info1x"}, {"info1x"}], "noisyString",
    XMLElement["yes1", {"info1c"}, {"info1d"}], "noisyString", 
    XMLElement["yes2", {"info2c"}, {"info2d"}], "noisyString", 
    XMLElement["noThanks", {"junk"}, {"garbage"}]}];

then you wouldn't want the info1x returned in your answer?? So the first intermediate step is

tmp = Cases[xml2, XMLElement["yes1" | "yes2", __], \[Infinity]]
(*

{XMLElement["yes1", {"info1a"}, {"info1b"}], 
 XMLElement["yes2", {"info2a"}, {"info2b"}], 
 XMLElement["yes1", {"info1x"}, {"info1x"}], 
 XMLElement["yes1", {"info1c"}, {"info1d"}], 
 XMLElement["yes2", {"info2c"}, {"info2d"}]} *)

and from here use ReplaceList

ReplaceList[tmp, {___, 
   PatternSequence[XMLElement["yes1", x__], 
    XMLElement["yes2", y__]], ___} :> Sequence[Flatten[{x, y}]]]

(*

{{"info1a", "info1b", "info2a", "info2b"}, {"info1c", "info1d", "info2c", "info2d"}}

*)
$\endgroup$
  • $\begingroup$ I've updated my xml example to be more usable - please let me know if something more concrete would be preferred. I noticed that the noise between desirable XMLElements were simply strings. Perhaps that makes things easier with respect to extracting XMLElements with tag yesX? $\endgroup$ – user170231 Feb 13 '18 at 3:01
  • $\begingroup$ @user170231 please see revised answer $\endgroup$ – Mike Honeychurch Feb 13 '18 at 3:23
  • $\begingroup$ "... then you wouldn't want the info1x returned in your answer ..." - this is exactly right! I only want those info-Strings that are part of those consecutive/adjacent XMLElements containing the tags yes1 and yes2. $\endgroup$ – user170231 Feb 13 '18 at 16:50
  • $\begingroup$ This is perfect for what I'm trying to do. ReplaceList is new territory for me - thanks a lot for demonstrating its usage! $\endgroup$ – user170231 Feb 13 '18 at 16:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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