etree icon indicating copy to clipboard operation
etree copied to clipboard

Avoid panics when element doesn't exist

Open jerome-laforge opened this issue 1 year ago • 3 comments

On xml document, I want to do:

doc.SelectElement("//path1").SetText("reset")
doc.SelectElement("//path2").SetText("reset")
doc.SelectElement("//path3").SetText("reset")

But it is possible that some xpath doesn't exist. in this case, I just ignore it without panicking. Can we add something add something that return default value if no found. for example:

doc.SelectElement("//path1").NotNil().SetText("reset")
doc.SelectElement("//path2").NotNil().SetText("reset")
doc.SelectElement("//path3").NotNil().SetText("reset")

PS: I know that I can check if it is nil or not but it is for avoiding boilerplate.

jerome-laforge avatar Jan 12 '24 08:01 jerome-laforge

In order to do this properly, the API would need to stop returning a pointer to an Element. That would be a breaking change, so I cannot really do this.

You could achieve a similar effect by adding your own helper function:

func NotNil(e *etree.Element) *etree.Element {
    if e == nil {
        return etree.NewElement("");
    }
    return e
}

Then you could do this:

NotNil(doc.SelectElement("tag")).SetText("reset");

Alternatively, you could wrap the SelectElement function like this:

func SelectElementOrDefault(e *etree.Element, tag string) *etree.Element {
    e = e.SelectElement(tag)
    if e == nil {
        return etree.NewElement("");
    }
    return e
}

Then you could do this:

SelectElementOrDefault(doc, "tag").SetText("reset");

These proposals may not have the same fluent style your proposal has, but it would achieve the same result without requiring an API-breaking change to etree.

(Also, I think you may have meant FindElement instead of SelectElement, although the issues are the same.)

beevik avatar Jan 16 '24 00:01 beevik

Thank for your proposed code (this is what I already did).

From your point of view, this code is considered as a breaking change? (as this function goes on to return a pointer)

func (e *etree.Element) NotNil() *etree.Element {
    if e == nil {
        return etree.NewElement("");
    }
    return e
}

If you think so, you can close this issue. And I use my boilerplate.

But overall, I want to thank you for this great library.

jerome-laforge avatar Jan 17 '24 07:01 jerome-laforge

Huh. Thanks to you, today I learned that go functions can process a nil pointer receiver without a panic. I was under the impression that this was not possible.

Let me think about this further. I might be open to an API that allows the conversion of a nil element to an element with a caller-defined fallback tag. Maybe something like this:

func (e *Element) Fallback(tag string) *Element {
    if (e == nil) {
        return NewElement(tag)
    }
    return e
}

beevik avatar Jan 17 '24 20:01 beevik

I finally got back to this. Sorry for the delay.

After thinking more about this, I realized your original proposal made more sense than my alternative proposal. It's actually pretty useless being able to specify a fallback or default tag for the dummy element.

I've implemented NotNil in the latest commit.

Thanks for the suggestion!

beevik avatar Apr 28 '24 13:04 beevik