This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/main by this push:
     new a0f4a1e882b CAUSEWAY-3915: [Birdseye] Jsoup API Extensions (Commons)
a0f4a1e882b is described below

commit a0f4a1e882b0797770fa86a097083908cefdbeff
Author: a.huber <[email protected]>
AuthorDate: Sun Oct 26 09:52:45 2025 +0100

    CAUSEWAY-3915: [Birdseye] Jsoup API Extensions (Commons)
    
    initial commit
---
 .../causeway/commons/internal/html/_JsoupExt.java  | 286 +++++++++++++++++++++
 1 file changed, 286 insertions(+)

diff --git 
a/commons/src/main/java/org/apache/causeway/commons/internal/html/_JsoupExt.java
 
b/commons/src/main/java/org/apache/causeway/commons/internal/html/_JsoupExt.java
new file mode 100644
index 00000000000..228696c56d6
--- /dev/null
+++ 
b/commons/src/main/java/org/apache/causeway/commons/internal/html/_JsoupExt.java
@@ -0,0 +1,286 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.commons.internal.html;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import org.jsoup.nodes.Element;
+import org.jsoup.select.CombiningEvaluator;
+import org.jsoup.select.Evaluator;
+
+import org.springframework.util.StringUtils;
+
+import org.apache.causeway.commons.internal.base._Strings;
+
+import lombok.experimental.UtilityClass;
+
+/**
+ * Jsoup API extensions.
+ *
+ * <h1>- internal use only -</h1>
+ * <p><b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this 
package! <br/>
+ * These may be changed or removed without notice!
+ *
+ * @apiNote use via <pre>@ExtensionMethod({_JsoupExt.class})</pre>
+ * @since 4.0
+ */
+@UtilityClass
+public class _JsoupExt {
+
+    @FunctionalInterface
+    public interface HtmlAppender<T> {
+        Element appendTo(Element container, T model);
+    }
+    @FunctionalInterface
+    public interface HtmlAppender2<T1, T2> {
+        Element appendTo(Element container, T1 model1, T2 model2);
+    }
+    @FunctionalInterface
+    public interface HtmlAppender3<T1, T2, T3> {
+        Element appendTo(Element container, T1 model1, T2 model2, T3 model3);
+    }
+
+    // -- EVALUATOR LOGIC
+
+    public Evaluator not(final Evaluator evaluator) {
+        return new Evaluator() {
+            @Override
+            public boolean matches(final Element root, final Element element) {
+                return !evaluator.matches(root, element);
+            }
+        };
+    }
+
+    public Evaluator and(final Evaluator a, final Evaluator b) {
+        return new CombiningEvaluator.And(List.of(a, b));
+    }
+
+    // -- ATTRIB
+
+    public Element alt(final Element container, final String alt) {
+        return container.attr("alt", alt);
+    }
+
+    /**
+     * Directly replaces the current "class" attribute of the container.
+     */
+    public Element classes(final Element container, final String cssClasses) {
+        return container.attr("class", cssClasses);
+    }
+
+    public Element addClasses(final Element container, final String cssClass) {
+        if(StringUtils.hasText(cssClass)) {
+            Stream.of(StringUtils.delimitedListToStringArray(cssClass, " "))
+                .filter(s->StringUtils.hasLength(s))
+                .forEach(container::addClass);
+        }
+        return container;
+    }
+
+    public Element addClassNormalized(final Element container, final String 
cssClass) {
+        return container.addClass(cssNormalize(cssClass));
+    }
+
+    public Element href(final Element container, final String href) {
+        return container.attr("href", href);
+    }
+
+    public Element onClick(final Element container, final String onClick) {
+        return container.attr("onClick", onClick);
+    }
+
+    public Element role(final Element container, final String role) {
+        return container.attr("role", role);
+    }
+
+    public Element src(final Element container, final String src) {
+        return container.attr("src", src);
+    }
+
+    public Element tabindex(final Element container, final String tabindex) {
+        return container.attr("tabindex", tabindex);
+    }
+
+    public Element type(final Element container, final String type) {
+        return container.attr("type", type);
+    }
+
+    // -- APPENDERS
+
+    public Element appendDiv(final Element container) {
+        return container.appendElement("div");
+    }
+    public Element appendDiv(final Element container, final String cssClass) {
+        return container.appendElement("div")
+            .attr("class", cssClass);
+    }
+
+    public Element appendOl(final Element container) {
+        return container.appendElement("ol");
+    }
+    public Element appendOl(final Element container, final String cssClass) {
+        return container.appendElement("ol")
+            .attr("class", cssClass);
+    }
+
+    public Element appendUl(final Element container) {
+        return container.appendElement("ul");
+    }
+    public Element appendUl(final Element container, final String cssClass) {
+        return container.appendElement("ul")
+            .attr("class", cssClass);
+    }
+
+    public Element appendLi(final Element container) {
+        return container.appendElement("li");
+    }
+    public Element appendLi(final Element container, final String cssClass) {
+        return container.appendElement("li")
+            .attr("class", cssClass);
+    }
+
+    public Element appendSpan(final Element container) {
+        return container.appendElement("span");
+    }
+    public Element appendSpan(final Element container, final String cssClass) {
+        return container.appendElement("span")
+            .attr("class", cssClass);
+    }
+
+    // -- UTIL
+
+    //TODO must be disable for the coll endpoint, otherwise invalid ajax 
payload
+    public Element comment(final Element container, final String comment) {
+        //return container.append("<!-- " + comment + " -->");
+        return container;
+    }
+
+    /**
+     * Alias for {@link Element#appendText(String)}
+     */
+    public Element text(final Element container, final String text) {
+        return container.appendText(text);
+    }
+
+    public Element italic(final Element container, final String text) {
+        container.appendElement("i").appendText(text);
+        return container;
+    }
+
+    public Element bold(final Element container, final String text) {
+        container.appendElement("b").appendText(text);
+        return container;
+    }
+
+    public <T> Element model(final Element container, final HtmlAppender<T> 
appender, final T model) {
+        comment(container, appender.getClass().getName());
+        appender.appendTo(container, model);
+        return container;
+    }
+
+    public <T1, T2> Element model(final Element container, final 
HtmlAppender2<T1, T2> appender,
+            final T1 model1, final T2 model2) {
+        comment(container, appender.getClass().getName());
+        appender.appendTo(container, model1, model2);
+        return container;
+    }
+
+    public <T1, T2, T3> Element model(final Element container, final 
HtmlAppender3<T1, T2, T3> appender,
+        final T1 model1, final T2 model2, final T3 model3) {
+        comment(container, appender.getClass().getName());
+        appender.appendTo(container, model1, model2, model3);
+        return container;
+}
+
+    /**
+     * Unconditional branching.
+     */
+    public Element branch(final Element container, final Consumer<Element> 
branchAppender) {
+        branchAppender.accept(container);
+        return container;
+    }
+
+    /**
+     * Conditional branching, based on condition.
+     */
+    public Element branch(final Element container, final boolean condition,
+        final Consumer<Element> branchAppender) {
+        if(condition) branchAppender.accept(container);
+        return container;
+    }
+    /**
+     * Conditional branching with either/or semantics, based on condition.
+     */
+    public Element branch(final Element container, final boolean condition,
+        final Consumer<Element> branchAppender, final Consumer<Element> 
orElseAppender) {
+        if(condition) {
+            branchAppender.accept(container);
+        } else {
+            orElseAppender.accept(container);
+        }
+        return container;
+    }
+
+    /**
+     * Conditional branching, based on presence of given modelOpt.
+     */
+    public <T> Element branch(final Element container, final Optional<T> 
modelOpt,
+        final BiConsumer<Element, T> branchAppender) {
+        modelOpt.ifPresent(t->branchAppender.accept(container, t));
+        return container;
+    }
+
+    /**
+     * Conditional branching, based on presence of given modelOpt.
+     */
+    public <T> Element branch(final Element container, final Optional<T> 
modelOpt,
+        final BiConsumer<Element, T> branchAppender, final Consumer<Element> 
fallbackAppender) {
+        modelOpt
+            .ifPresentOrElse(
+                t->branchAppender.accept(container, t),
+                ()->fallbackAppender.accept(container));
+        return container;
+    }
+
+    /**
+     * Conditional mapping, based on presence of given modelOpt.
+     */
+    public <T, R> R map(final Element element, final Optional<T> modelOpt,
+        final BiFunction<Element, T, R> mapper, final Function<Element, R> 
fallbackMapper) {
+        return modelOpt
+            .map(t->mapper.apply(element, t))
+            .orElseGet(()->fallbackMapper.apply(element));
+    }
+
+    // -- HELPER
+
+    private String cssNormalize(final String cssClass) {
+        var trimmed = _Strings.blankToNullOrTrim(cssClass);
+        return _Strings.isNullOrEmpty(trimmed)
+                ? null
+                : cssClass.replaceAll("\\.", "-").replaceAll("[^A-Za-z0-9- ]", 
"").replaceAll("\\s+", "-");
+    }
+
+}

Reply via email to