import lxml.html

from brownant.pipeline.base import PipelineProperty


class ElementTreeProperty(PipelineProperty):
    """The element tree built from a raw html property.

    :param text_response_attr: optional. default: `"text_response"`.
    """

    def prepare(self):
        self.attr_names.setdefault("text_response_attr", "text_response")

    def provide_value(self, obj):
        text_response = self.get_attr(obj, "text_response_attr")
        return lxml.html.fromstring(text_response)


class XPathTextProperty(PipelineProperty):
    """The text extracted from a element tree property by XPath.

    :param xpath: the xpath expression for extracting text.
    :param etree_attr: optional. default: `"etree"`.
    :param strip_spaces: optional. default: `False`. if it be `True`,
                         the spaces in the beginning and the end of texts will
                         be striped.
    :param pick_mode: optional. default: `"join"`, and could be "join" or
                      "first". while `"join"` be detected, the texts will be
                      joined to one. otherwise the `"first"` be detected, only
                      the first text would be picked.
    :param joiner: optional. default is a space string. it is no sense in
                   assigning this parameter while the `pick_mode` is not
                   `"join"`. otherwise, the texts will be joined by this
                   string.
    """

    required_attrs = {"xpath"}

    def prepare(self):
        self.attr_names.setdefault("etree_attr", "etree")
        self.options.setdefault("strip_spaces", False)
        self.options.setdefault("pick_mode", "join")
        self.options.setdefault("joiner", " ")

    def choice_pick_impl(self):
        pick_mode = self.options["pick_mode"]
        impl = {
            "join": self.pick_joining,
            "first": self.pick_first,
        }.get(pick_mode)

        if not impl:
            raise ValueError("%r is not valid pick mode" % pick_mode)
        return impl

    def pick_joining(self, value):
        joiner = self.options["joiner"]
        return joiner.join(value)

    def pick_first(self, value):
        return value[0] if value else ""

    def provide_value(self, obj):
        etree = self.get_attr(obj, "etree_attr")
        value = etree.xpath(self.xpath)
        pick_value = self.choice_pick_impl()

        if self.options["strip_spaces"]:
            value = [v.strip() for v in value if v.strip()]

        return pick_value(value)

@@ -151,26 +151,727 @@
 m a 
-raw html property.
+text response property. There is an usage%0A    example::%0A%0A        class MySite(Dinergate):%0A            @property%0A            def text_response(self):%0A                return %22%3Chtml%3E%3C/html%3E%22%0A%0A            @property%0A            def div_response(self):%0A                return %22%3Cdiv%3E%3C/div%3E%22%0A%0A            etree = ElementTreeProperty()%0A            div_etree = ElementTreeProperty(text_response_attr=%22div_response%22)%0A%0A        site = MySite(request)%0A        print(site.etree)  # output: %3CElement html at 0x1f59350%3E%0A        print(site.div_etree)  # output: %3CElement div at 0x1f594d0%3E%0A%0A        from lxml.html import HtmlElement%0A        assert isinstance(site.etree, HtmlElement)%0A        assert isinstance(site.div_etree, HtmlElement)
 %0A%0A  

add example for etree pipeline property usage.