Remove XML nodes with XQuery in a shell
Asked Answered
F

3

0

Sample XML:

<outerTag>
  <innerElement>
    <Id>1234</Id>
    <fName>Kim</fName>
    <lName>Scott</lName>
<customData1>Value1</customData1>
<customData2>Value2</customData2>
    <position>North<position>
    <title/>
  </innerElement>
  <innerElement>
    <Id>5678</Id>
    <fName>Brian</fName>
    <lName>Davis</lName>
<customData3>value3</customData3>
<customData4>value4</customData4>
<customData5>value5</customData5>
    <position>South<position>
    <title/>
  </innerElement>
</outerTag>

Expected output:

<outerTag>
  <innerElement>
    <Id>1234</Id>
    <fName>Kim</fName>
    <lName>Scott</lName>
    <customData1>Value1</customData1>
    <customData2>Value2</customData2>
    <position>North<position><title/></position>
  </position>
  </innerElement>
</outerTag>

I know I can just use the good select Xpath to do the trick, but I try to figure how to properly delete using XQuery and .

I tried from this:

xidel --xml -e '
    let $element := //innerElement[not(Id["1234"])]
    declare function local:process-xml ($element) {
        $element update delete node
    }
' file.xml

I get:

An unhandled exception occurred at $00000000006174CA:
ERangeError: Range check error
  $00000000006174CA
  $000000000061782C
  $0000000000629536
  $000000000062E638
  $000000000055C12B

Or

$ xidel --xquery '
    let $element := //innerElement[./not(Id["1234"])]
    declare function local:process-xml ($element) {
        $element update delete node                                                  
    }
' file.xml

I get the same error.

Or with pure XQuery:

$ xidel --xml --xquery '
    let $input := /outerTag
    modify delete node $input/innerElement[Id ne "1234"]
    return $input
' file.xml

I get error:

Error:
err:XPST0003: Missing whitespace or symbol
in line 2 column 5
    modify delete node $input/innerElement[Id ne "1234"]
    ^^^^  error occurs around here

Frustrating xidel can't handle this pure XQuery solution.

Florenceflorencia answered 23/4, 2023 at 17:31 Comment(0)
C
1

Here is a pure XQuery solution, tested in BaseX 10.5

XQuery

declare context item := document {
<outerTag>
    <innerElement>
        <Id>1234</Id>
        <fName>Kim</fName>
        <lName>Scott</lName>
        <customData1>Value1</customData1>
        <customData2>Value2</customData2>
        <position>North</position>
        <title/>
    </innerElement>
    <innerElement>
        <Id>5678</Id>
        <fName>Brian</fName>
        <lName>Davis</lName>
        <customData3>value3</customData3>
        <customData4>value4</customData4>
        <customData5>value5</customData5>
        <position>South</position>
        <title/>
    </innerElement>
</outerTag>
};

copy $input := .
modify delete node $input/outerTag/innerElement[Id ne "1234"]
return $input

Output

<outerTag>
  <innerElement>
    <Id>1234</Id>
    <fName>Kim</fName>
    <lName>Scott</lName>
    <customData1>Value1</customData1>
    <customData2>Value2</customData2>
    <position>North</position>
    <title/>
  </innerElement>
</outerTag>

XQuery #2

With file name as a parameter

Windows Version:

declare base-uri 'e:\Temp\';
declare option db:stripws "true";

copy $input := doc("input.xml")
modify delete node $input/outerTag/innerElement[Id ne "1234"]
return $input

Linux Version:

copy $input := doc("/tmp/file.xml")
modify delete node $input/outerTag/innerElement[Id ne "1234"]
return $input
Czech answered 23/4, 2023 at 19:20 Comment(0)
P
3

Xidel does not support "XQuery Update". See mailinglist discussion.
And as a side note: Although it's possible to edit XML with Xidel, it doesn't aim to be a fully fledged editor. Benito created Xidel to be an extractor in the first place.

You could use transform(), but the wiki hasn't been updated in ages and the function is deprecated at the moment:

$ xidel -s file.xml -e '
  transform(
    /,
    function($x){
      if (name($x)="innerElement" and $x/Id="5678")
      then ()
      else $x
    }
  )
' --output-node-format=xml --output-node-indent
pxp:transform(..) is deprecated, use x:transform-nodes(..)
<outerTag>
  <innerElement>
    <Id>1234</Id>
    <fName>Kim</fName>
    <lName>Scott</lName>
    <customData1>Value1</customData1>
    <customData2>Value2</customData2>
    <position>North</position>
    <title/>
  </innerElement>
</outerTag>

Instead I'd recommend x:replace-nodes(), which is much easier to use:

$ xidel -s file.xml -e 'x:replace-nodes(//innerElement[Id="5678"],())' \
  --output-node-format=xml --output-node-indent
Perutz answered 23/4, 2023 at 20:40 Comment(2)
@Gilles Quénot Relevant mailinglist discussion with potential wiki-material on x:replace-nodes(). I promised Benito I would update the wiki some day, but... that will take some time.Perutz
I edited the github's wiki pageKhartoum
C
1

Here is a pure XQuery solution, tested in BaseX 10.5

XQuery

declare context item := document {
<outerTag>
    <innerElement>
        <Id>1234</Id>
        <fName>Kim</fName>
        <lName>Scott</lName>
        <customData1>Value1</customData1>
        <customData2>Value2</customData2>
        <position>North</position>
        <title/>
    </innerElement>
    <innerElement>
        <Id>5678</Id>
        <fName>Brian</fName>
        <lName>Davis</lName>
        <customData3>value3</customData3>
        <customData4>value4</customData4>
        <customData5>value5</customData5>
        <position>South</position>
        <title/>
    </innerElement>
</outerTag>
};

copy $input := .
modify delete node $input/outerTag/innerElement[Id ne "1234"]
return $input

Output

<outerTag>
  <innerElement>
    <Id>1234</Id>
    <fName>Kim</fName>
    <lName>Scott</lName>
    <customData1>Value1</customData1>
    <customData2>Value2</customData2>
    <position>North</position>
    <title/>
  </innerElement>
</outerTag>

XQuery #2

With file name as a parameter

Windows Version:

declare base-uri 'e:\Temp\';
declare option db:stripws "true";

copy $input := doc("input.xml")
modify delete node $input/outerTag/innerElement[Id ne "1234"]
return $input

Linux Version:

copy $input := doc("/tmp/file.xml")
modify delete node $input/outerTag/innerElement[Id ne "1234"]
return $input
Czech answered 23/4, 2023 at 19:20 Comment(0)
F
0

Tried example from xidel doc:

$ xidel --xml -e '
    let $delete := //innerElement[not(Id="1234")]
    return transform(/, function($e){ $e[not($delete[$e is .])]})
'  file.xml

Looks a bit complex.

Edit: this syntax is now deprecated.

Will accept another answer if this could be simplified or using plain pure XQuery as this answer.

Florenceflorencia answered 23/4, 2023 at 18:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.