diff --git a/html_text/README.rst b/html_text/README.rst
new file mode 100644
index 000000000..b70b6fbfd
--- /dev/null
+++ b/html_text/README.rst
@@ -0,0 +1,119 @@
+====================
+Text from HTML field
+====================
+
+.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github
+ :target: https://github.com/OCA/server-tools/tree/14.0/html_text
+ :alt: OCA/server-tools
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/server-tools-14-0/server-tools-14-0-html_text
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
+ :target: https://runbot.odoo-community.org/runbot/149/14.0
+ :alt: Try me on Runbot
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+This module provides some technical features that allow to extract text from
+any chunk of HTML, without HTML tags or attributes. You can chose either:
+
+* To truncate the result by amount of words or characters.
+* To append an ellipsis (or any character(s)) at the end of the result.
+
+It can be used to easily generate excerpts.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Usage
+=====
+
+This module just adds a technical utility, but nothing for the end user.
+
+If you are a developer and need this utility for your module, see these
+examples and read the docs inside the code.
+
+Python example::
+
+ def some_method(self):
+ # Get truncated text from an HTML field. It will 40 words and 100
+ # characters at most, and will have "..." appended at the end if it
+ # gets truncated.
+ truncated_text = self.env["ir.fields.converter"].text_from_html(
+ self.html_field, 40, 100, "...")
+
+QWeb example::
+
+
+
+.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
+ :alt: Try me on Runbot
+ :target: https://runbot.odoo-community.org/runbot/149/11.0
+
+Known issues / Roadmap
+======================
+
+* An option could be added to try to respect the basic HTML tags inside the
+ excerpt (````, ````, ``
``, etc.).
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+~~~~~~~
+
+* Grupo ESOC Ingeniería de Servicios
+* Tecnativa
+* Onestein
+
+Contributors
+~~~~~~~~~~~~
+
+* Dennis Sluijk
+* `Tecnativa `_:",
+* Helly kapatel
+* Mantas Šniukas
+
+ * Jairo Llopis
+ * Vicent Cubells
+ * Víctor Martínez
+
+Maintainers
+~~~~~~~~~~~
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+This module is part of the `OCA/server-tools `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/html_text/__init__.py b/html_text/__init__.py
new file mode 100644
index 000000000..31660d6a9
--- /dev/null
+++ b/html_text/__init__.py
@@ -0,0 +1,3 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from . import models
diff --git a/html_text/__manifest__.py b/html_text/__manifest__.py
new file mode 100644
index 000000000..746e99635
--- /dev/null
+++ b/html_text/__manifest__.py
@@ -0,0 +1,19 @@
+# Copyright 2016-2017 Jairo Llopis
+# Copyright 2016 Tecnativa - Vicent Cubells
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+{
+ "name": "Text from HTML field",
+ "summary": "Generate excerpts from any HTML field",
+ "version": "15.0.1.0.0",
+ "category": "Tools",
+ "website": "https://github.com/OCA/server-tools",
+ "author": "Grupo ESOC Ingeniería de Servicios, "
+ "Tecnativa, "
+ "Onestein, "
+ "Odoo Community Association (OCA)",
+ "license": "AGPL-3",
+ "application": False,
+ "installable": True,
+ "external_dependencies": {"python": ["lxml"]},
+ "depends": ["base"],
+}
diff --git a/html_text/i18n/ca.po b/html_text/i18n/ca.po
new file mode 100644
index 000000000..b82487c21
--- /dev/null
+++ b/html_text/i18n/ca.po
@@ -0,0 +1,45 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * html_text
+#
+# Translators:
+# Marc Tormo i Bochaca , 2017
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 9.0c\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-04-19 18:00+0000\n"
+"PO-Revision-Date: 2017-04-19 18:00+0000\n"
+"Last-Translator: Marc Tormo i Bochaca , 2017\n"
+"Language-Team: Catalan (https://www.transifex.com/oca/teams/23907/ca/)\n"
+"Language: ca\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model,name:html_text.model_ir_fields_converter
+#, fuzzy
+msgid "Fields Converter"
+msgstr "ir.fields.converter"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__id
+msgid "ID"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__smart_search
+msgid "Smart Search"
+msgstr ""
diff --git a/html_text/i18n/de.po b/html_text/i18n/de.po
new file mode 100644
index 000000000..e75d5aa47
--- /dev/null
+++ b/html_text/i18n/de.po
@@ -0,0 +1,45 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * html_text
+#
+# Translators:
+# Rudolf Schnapka , 2017
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 9.0c\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-04-19 18:00+0000\n"
+"PO-Revision-Date: 2017-04-19 18:00+0000\n"
+"Last-Translator: Rudolf Schnapka , 2017\n"
+"Language-Team: German (https://www.transifex.com/oca/teams/23907/de/)\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model,name:html_text.model_ir_fields_converter
+#, fuzzy
+msgid "Fields Converter"
+msgstr "ir.fields.converter"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__id
+msgid "ID"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__smart_search
+msgid "Smart Search"
+msgstr ""
diff --git a/html_text/i18n/es.po b/html_text/i18n/es.po
new file mode 100644
index 000000000..d32bd69b4
--- /dev/null
+++ b/html_text/i18n/es.po
@@ -0,0 +1,45 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * html_text
+#
+# Translators:
+# Pedro M. Baeza , 2016
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 9.0c\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-12-17 02:07+0000\n"
+"PO-Revision-Date: 2021-03-19 17:46+0000\n"
+"Last-Translator: Ana Suárez \n"
+"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.3.2\n"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model,name:html_text.model_ir_fields_converter
+msgid "Fields Converter"
+msgstr "Convertidor de Campos"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__id
+msgid "ID"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__smart_search
+msgid "Smart Search"
+msgstr ""
diff --git a/html_text/i18n/es_ES.po b/html_text/i18n/es_ES.po
new file mode 100644
index 000000000..b6e252571
--- /dev/null
+++ b/html_text/i18n/es_ES.po
@@ -0,0 +1,46 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * html_text
+#
+# Translators:
+# Fernando Lara , 2017
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 9.0c\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-02-16 10:39+0000\n"
+"PO-Revision-Date: 2017-02-16 10:39+0000\n"
+"Last-Translator: Fernando Lara , 2017\n"
+"Language-Team: Spanish (Spain) (https://www.transifex.com/oca/teams/23907/"
+"es_ES/)\n"
+"Language: es_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model,name:html_text.model_ir_fields_converter
+#, fuzzy
+msgid "Fields Converter"
+msgstr "ir.documentos.conversor"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__id
+msgid "ID"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__smart_search
+msgid "Smart Search"
+msgstr ""
diff --git a/html_text/i18n/hr.po b/html_text/i18n/hr.po
new file mode 100644
index 000000000..a0cc5ac2f
--- /dev/null
+++ b/html_text/i18n/hr.po
@@ -0,0 +1,43 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * html_text
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2019-11-13 17:34+0000\n"
+"Last-Translator: Bole \n"
+"Language-Team: none\n"
+"Language: hr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Generator: Weblate 3.8\n"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model,name:html_text.model_ir_fields_converter
+msgid "Fields Converter"
+msgstr "Pretvaranje polja"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__id
+msgid "ID"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__smart_search
+msgid "Smart Search"
+msgstr ""
diff --git a/html_text/i18n/html_text.pot b/html_text/i18n/html_text.pot
new file mode 100644
index 000000000..382577d81
--- /dev/null
+++ b/html_text/i18n/html_text.pot
@@ -0,0 +1,39 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * html_text
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 14.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model,name:html_text.model_ir_fields_converter
+msgid "Fields Converter"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__id
+msgid "ID"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__smart_search
+msgid "Smart Search"
+msgstr ""
diff --git a/html_text/i18n/it.po b/html_text/i18n/it.po
new file mode 100644
index 000000000..1c4d41231
--- /dev/null
+++ b/html_text/i18n/it.po
@@ -0,0 +1,45 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * html_text
+#
+# Translators:
+# Paolo Valier , 2018
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 10.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-01-06 02:25+0000\n"
+"PO-Revision-Date: 2018-01-06 02:25+0000\n"
+"Last-Translator: Paolo Valier , 2018\n"
+"Language-Team: Italian (https://www.transifex.com/oca/teams/23907/it/)\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model,name:html_text.model_ir_fields_converter
+#, fuzzy
+msgid "Fields Converter"
+msgstr "ir.fields.converter"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__id
+msgid "ID"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__smart_search
+msgid "Smart Search"
+msgstr ""
diff --git a/html_text/i18n/tr.po b/html_text/i18n/tr.po
new file mode 100644
index 000000000..c5d8d9aa8
--- /dev/null
+++ b/html_text/i18n/tr.po
@@ -0,0 +1,45 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * html_text
+#
+# Translators:
+# Ahmet Altinisik , 2016
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 9.0c\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-12-29 03:40+0000\n"
+"PO-Revision-Date: 2016-12-29 03:40+0000\n"
+"Last-Translator: Ahmet Altinisik , 2016\n"
+"Language-Team: Turkish (https://www.transifex.com/oca/teams/23907/tr/)\n"
+"Language: tr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model,name:html_text.model_ir_fields_converter
+#, fuzzy
+msgid "Fields Converter"
+msgstr "ir.fields.converter"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__id
+msgid "ID"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__smart_search
+msgid "Smart Search"
+msgstr ""
diff --git a/html_text/i18n/zh_CN.po b/html_text/i18n/zh_CN.po
new file mode 100644
index 000000000..7aea0bc70
--- /dev/null
+++ b/html_text/i18n/zh_CN.po
@@ -0,0 +1,42 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * html_text
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2019-08-31 06:18+0000\n"
+"Last-Translator: 黎伟杰 <674416404@qq.com>\n"
+"Language-Team: none\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 3.8\n"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model,name:html_text.model_ir_fields_converter
+msgid "Fields Converter"
+msgstr "字段转换器"
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__id
+msgid "ID"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: html_text
+#: model:ir.model.fields,field_description:html_text.field_ir_fields_converter__smart_search
+msgid "Smart Search"
+msgstr ""
diff --git a/html_text/models/__init__.py b/html_text/models/__init__.py
new file mode 100644
index 000000000..e21238ee9
--- /dev/null
+++ b/html_text/models/__init__.py
@@ -0,0 +1,3 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from . import ir_fields_converter
diff --git a/html_text/models/ir_fields_converter.py b/html_text/models/ir_fields_converter.py
new file mode 100644
index 000000000..7a07a359d
--- /dev/null
+++ b/html_text/models/ir_fields_converter.py
@@ -0,0 +1,74 @@
+# Copyright 2016-2017 Jairo Llopis
+# Copyright 2016 Tecnativa - Vicent Cubells
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+import logging
+
+from lxml import etree, html
+
+from odoo import api, models
+
+_logger = logging.getLogger(__name__)
+
+
+class IrFieldsConverter(models.AbstractModel):
+ _inherit = "ir.fields.converter"
+
+ @api.model
+ def text_from_html(
+ self, html_content, max_words=None, max_chars=None, ellipsis=u"…", fail=False
+ ):
+ """Extract text from an HTML field in a generator.
+
+ :param str html_content:
+ HTML contents from where to extract the text.
+
+ :param int max_words:
+ Maximum amount of words allowed in the resulting string.
+
+ :param int max_chars:
+ Maximum amount of characters allowed in the resulting string. If
+ you apply this limit, beware that the last word could get cut in an
+ unexpected place.
+
+ :param str ellipsis:
+ Character(s) to be appended to the end of the resulting string if
+ it gets truncated after applying limits set in :param:`max_words`
+ or :param:`max_chars`. If you want nothing applied, just set an
+ empty string.
+
+ :param bool fail:
+ If ``True``, exceptions will be raised. Otherwise, an empty string
+ will be returned on failure.
+ """
+ # Parse HTML
+ try:
+ doc = html.fromstring(html_content)
+ except (TypeError, etree.XMLSyntaxError, etree.ParserError):
+ if fail:
+ raise
+ else:
+ _logger.exception("Failure parsing this HTML:\n%s", html_content)
+ return ""
+
+ # Get words
+ words = u"".join(doc.xpath("//text()")).split()
+
+ # Truncate words
+ suffix = max_words and len(words) > max_words
+ if max_words:
+ words = words[:max_words]
+
+ # Get text
+ text = u" ".join(words)
+
+ # Truncate text
+ suffix = suffix or max_chars and len(text) > max_chars
+ if max_chars:
+ text = text[: max_chars - (len(ellipsis) if suffix else 0)].strip()
+
+ # Append ellipsis if needed
+ if suffix:
+ text += ellipsis
+
+ return text
diff --git a/html_text/readme/CONTRIBUTORS.rst b/html_text/readme/CONTRIBUTORS.rst
new file mode 100644
index 000000000..cb155b7d8
--- /dev/null
+++ b/html_text/readme/CONTRIBUTORS.rst
@@ -0,0 +1,8 @@
+* Dennis Sluijk
+* `Tecnativa `_:",
+* Helly kapatel
+* Mantas Šniukas
+
+ * Jairo Llopis
+ * Vicent Cubells
+ * Víctor Martínez
diff --git a/html_text/readme/DESCRIPTION.rst b/html_text/readme/DESCRIPTION.rst
new file mode 100644
index 000000000..579fd7667
--- /dev/null
+++ b/html_text/readme/DESCRIPTION.rst
@@ -0,0 +1,7 @@
+This module provides some technical features that allow to extract text from
+any chunk of HTML, without HTML tags or attributes. You can chose either:
+
+* To truncate the result by amount of words or characters.
+* To append an ellipsis (or any character(s)) at the end of the result.
+
+It can be used to easily generate excerpts.
diff --git a/html_text/readme/ROADMAP.rst b/html_text/readme/ROADMAP.rst
new file mode 100644
index 000000000..0a77fddef
--- /dev/null
+++ b/html_text/readme/ROADMAP.rst
@@ -0,0 +1,2 @@
+* An option could be added to try to respect the basic HTML tags inside the
+ excerpt (````, ````, ````, etc.).
diff --git a/html_text/readme/USAGE.rst b/html_text/readme/USAGE.rst
new file mode 100644
index 000000000..ae34a9b25
--- /dev/null
+++ b/html_text/readme/USAGE.rst
@@ -0,0 +1,21 @@
+This module just adds a technical utility, but nothing for the end user.
+
+If you are a developer and need this utility for your module, see these
+examples and read the docs inside the code.
+
+Python example::
+
+ def some_method(self):
+ # Get truncated text from an HTML field. It will 40 words and 100
+ # characters at most, and will have "..." appended at the end if it
+ # gets truncated.
+ truncated_text = self.env["ir.fields.converter"].text_from_html(
+ self.html_field, 40, 100, "...")
+
+QWeb example::
+
+
+
+.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
+ :alt: Try me on Runbot
+ :target: https://runbot.odoo-community.org/runbot/149/11.0
diff --git a/html_text/static/description/icon.png b/html_text/static/description/icon.png
new file mode 100644
index 000000000..3a0328b51
Binary files /dev/null and b/html_text/static/description/icon.png differ
diff --git a/html_text/static/description/index.html b/html_text/static/description/index.html
new file mode 100644
index 000000000..b626cc6ca
--- /dev/null
+++ b/html_text/static/description/index.html
@@ -0,0 +1,463 @@
+
+
+
+
+
+
+Text from HTML field
+
+
+
+
+
Text from HTML field
+
+
+

+
This module provides some technical features that allow to extract text from
+any chunk of HTML, without HTML tags or attributes. You can chose either:
+
+- To truncate the result by amount of words or characters.
+- To append an ellipsis (or any character(s)) at the end of the result.
+
+
It can be used to easily generate excerpts.
+
Table of contents
+
+
+
+
This module just adds a technical utility, but nothing for the end user.
+
If you are a developer and need this utility for your module, see these
+examples and read the docs inside the code.
+
Python example:
+
+def some_method(self):
+ # Get truncated text from an HTML field. It will 40 words and 100
+ # characters at most, and will have "..." appended at the end if it
+ # gets truncated.
+ truncated_text = self.env["ir.fields.converter"].text_from_html(
+ self.html_field, 40, 100, "...")
+
+
QWeb example:
+
+<t t-esc="env['ir.fields.converter'].text_from_html(doc.html_field)"/>
+
+

+
+
+
+
+- An option could be added to try to respect the basic HTML tags inside the
+excerpt (<b>, <i>, <p>, etc.).
+
+
+
+
+
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
+
+
+
+
+
+
+- Grupo ESOC Ingeniería de Servicios
+- Tecnativa
+- Onestein
+
+
+
+
+
+
This module is maintained by the OCA.
+

+
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
This module is part of the OCA/server-tools project on GitHub.
+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
+
+
+
+
+
diff --git a/html_text/tests/__init__.py b/html_text/tests/__init__.py
new file mode 100644
index 000000000..d9d2b331a
--- /dev/null
+++ b/html_text/tests/__init__.py
@@ -0,0 +1,3 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from . import test_extractor
diff --git a/html_text/tests/test_extractor.py b/html_text/tests/test_extractor.py
new file mode 100644
index 000000000..8036eace4
--- /dev/null
+++ b/html_text/tests/test_extractor.py
@@ -0,0 +1,56 @@
+# Copyright 2016-2017 Jairo Llopis
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from lxml import etree
+
+from odoo.tests.common import TransactionCase
+from odoo.tools import mute_logger
+
+
+class ExtractorCase(TransactionCase):
+ def setUp(self):
+ super().setUp()
+ # Shortcut
+ self.text_from_html = self.env["ir.fields.converter"].text_from_html
+
+ def test_excerpts(self):
+ """Text gets correctly extracted."""
+ html = u"""
+
+
+
+
I'm a title
+
I'm a paragraph
+
¡Pues yo soy español!
+
+
+
+ """
+ self.assertEqual(
+ self.text_from_html(html),
+ u"I'm a title I'm a paragraph ¡Pues yo soy español!",
+ )
+ self.assertEqual(
+ self.text_from_html(html, 8), u"I'm a title I'm a paragraph ¡Pues yo…"
+ )
+ self.assertEqual(
+ self.text_from_html(html, 8, 31), u"I'm a title I'm a paragraph ¡P…"
+ )
+ self.assertEqual(
+ self.text_from_html(html, 7, ellipsis=""),
+ u"I'm a title I'm a paragraph ¡Pues",
+ )
+
+ @mute_logger("odoo.addons.html_text.models.ir_fields_converter")
+ def test_empty_html(self):
+ """Empty HTML handled correctly."""
+ self.assertEqual(self.text_from_html(""), "")
+ with self.assertRaises(etree.ParserError):
+ self.text_from_html("", fail=True)
+
+ @mute_logger("odoo.addons.html_text.models.ir_fields_converter")
+ def test_false_html(self):
+ """``False`` HTML handled correctly."""
+ self.assertEqual(self.text_from_html(False), "")
+ with self.assertRaises(TypeError):
+ self.text_from_html(False, fail=True)
diff --git a/requirements.txt b/requirements.txt
index 7a9599183..21bd1f228 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,6 @@
# generated from manifests external_dependencies
dataclasses
+lxml
mako
odoorpc
openupgradelib
diff --git a/setup/html_text/odoo/addons/html_text b/setup/html_text/odoo/addons/html_text
new file mode 120000
index 000000000..a8751217b
--- /dev/null
+++ b/setup/html_text/odoo/addons/html_text
@@ -0,0 +1 @@
+../../../../html_text
\ No newline at end of file
diff --git a/setup/html_text/setup.py b/setup/html_text/setup.py
new file mode 100644
index 000000000..28c57bb64
--- /dev/null
+++ b/setup/html_text/setup.py
@@ -0,0 +1,6 @@
+import setuptools
+
+setuptools.setup(
+ setup_requires=['setuptools-odoo'],
+ odoo_addon=True,
+)