553b551586
Reflect in the HTML code of a message that there is no signature set, so we behave consistently when opening a message from drats or templates. Improve the code to create the DOM structure of the signature instead of constructing it from the string. Also save the signature ID to the element id attribute instead of the name attribute (as element's id was set to '1' but not never used).
610 lines
16 KiB
C
610 lines
16 KiB
C
/*
|
|
* e-html-editor-utils.c
|
|
*
|
|
* Copyright (C) 2012 Dan Vrátil <dvratil@redhat.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with the program; if not, see <http://www.gnu.org/licenses/>
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include "e-html-editor-utils.h"
|
|
#include "e-misc-utils.h"
|
|
#include <string.h>
|
|
|
|
/**
|
|
* e_html_editor_dom_node_find_parent_element:
|
|
* @node: Start node
|
|
* @tagname: Tag name of element to search
|
|
*
|
|
* Recursively searches for first occurance of element with given @tagname
|
|
* that is parent of given @node.
|
|
*
|
|
* Returns: A #WebKitDOMElement with @tagname representing parent of @node or
|
|
* @NULL when @node has no parent with given @tagname. When @node matches @tagname,
|
|
* then the @node is returned.
|
|
*/
|
|
WebKitDOMElement *
|
|
e_html_editor_dom_node_find_parent_element (WebKitDOMNode *node,
|
|
const gchar *tagname)
|
|
{
|
|
gint taglen = strlen (tagname);
|
|
|
|
while (node) {
|
|
|
|
if (WEBKIT_DOM_IS_ELEMENT (node)) {
|
|
gchar *node_tagname;
|
|
|
|
node_tagname = webkit_dom_element_get_tag_name (
|
|
WEBKIT_DOM_ELEMENT (node));
|
|
|
|
if (node_tagname &&
|
|
(strlen (node_tagname) == taglen) &&
|
|
(g_ascii_strncasecmp (node_tagname, tagname, taglen) == 0)) {
|
|
g_free (node_tagname);
|
|
return WEBKIT_DOM_ELEMENT (node);
|
|
}
|
|
|
|
g_free (node_tagname);
|
|
}
|
|
|
|
node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node));
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_dom_node_find_child_element:
|
|
* @node: Start node
|
|
* @tagname: Tag name of element to search.
|
|
*
|
|
* Recursively searches for first occurrence of element with given @tagname that
|
|
* is a child of @node.
|
|
*
|
|
* Returns: A #WebKitDOMElement with @tagname representing a child of @node or
|
|
* @NULL when @node has no child with given @tagname. When @node matches @tagname,
|
|
* then the @node is returned.
|
|
*/
|
|
WebKitDOMElement *
|
|
e_html_editor_dom_node_find_child_element (WebKitDOMNode *node,
|
|
const gchar *tagname)
|
|
{
|
|
WebKitDOMNode *start_node = node;
|
|
gint taglen = strlen (tagname);
|
|
|
|
do {
|
|
if (WEBKIT_DOM_IS_ELEMENT (node)) {
|
|
gchar *node_tagname;
|
|
|
|
node_tagname = webkit_dom_element_get_tag_name (
|
|
WEBKIT_DOM_ELEMENT (node));
|
|
|
|
if (node_tagname &&
|
|
(strlen (node_tagname) == taglen) &&
|
|
(g_ascii_strncasecmp (node_tagname, tagname, taglen) == 0)) {
|
|
g_free (node_tagname);
|
|
return WEBKIT_DOM_ELEMENT (node);
|
|
}
|
|
|
|
g_free (node_tagname);
|
|
}
|
|
|
|
if (webkit_dom_node_has_child_nodes (node)) {
|
|
node = webkit_dom_node_get_first_child (node);
|
|
} else if (webkit_dom_node_get_next_sibling (node)) {
|
|
node = webkit_dom_node_get_next_sibling (node);
|
|
} else {
|
|
node = webkit_dom_node_get_parent_node (node);
|
|
}
|
|
} while (!webkit_dom_node_is_same_node (node, start_node));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
e_html_editor_node_is_selection_position_node (WebKitDOMNode *node)
|
|
{
|
|
WebKitDOMElement *element;
|
|
|
|
if (!node || !WEBKIT_DOM_IS_ELEMENT (node))
|
|
return FALSE;
|
|
|
|
element = WEBKIT_DOM_ELEMENT (node);
|
|
|
|
return element_has_id (element, "-x-evo-selection-start-marker") ||
|
|
element_has_id (element, "-x-evo-selection-end-marker");
|
|
}
|
|
|
|
WebKitDOMNode *
|
|
e_html_editor_get_parent_block_node_from_child (WebKitDOMNode *node)
|
|
{
|
|
WebKitDOMNode *parent = node;
|
|
|
|
if (!WEBKIT_DOM_IS_ELEMENT (parent) ||
|
|
e_html_editor_node_is_selection_position_node (parent))
|
|
parent = webkit_dom_node_get_parent_node (parent);
|
|
|
|
if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-temp-text-wrapper") ||
|
|
element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quoted") ||
|
|
element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quote-character") ||
|
|
element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-signature") ||
|
|
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) ||
|
|
element_has_tag (WEBKIT_DOM_ELEMENT (parent), "b") ||
|
|
element_has_tag (WEBKIT_DOM_ELEMENT (parent), "i") ||
|
|
element_has_tag (WEBKIT_DOM_ELEMENT (parent), "u"))
|
|
parent = webkit_dom_node_get_parent_node (parent);
|
|
|
|
if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quoted") ||
|
|
element_has_class (WEBKIT_DOM_ELEMENT (parent), "Apple-tab-span"))
|
|
parent = webkit_dom_node_get_parent_node (parent);
|
|
|
|
return parent;
|
|
}
|
|
|
|
gboolean
|
|
element_has_id (WebKitDOMElement *element,
|
|
const gchar* id)
|
|
{
|
|
gchar *element_id;
|
|
|
|
if (!element)
|
|
return FALSE;
|
|
|
|
if (!WEBKIT_DOM_IS_ELEMENT (element))
|
|
return FALSE;
|
|
|
|
element_id = webkit_dom_element_get_id (element);
|
|
|
|
if (g_ascii_strcasecmp (element_id, id) != 0) {
|
|
g_free (element_id);
|
|
return FALSE;
|
|
}
|
|
g_free (element_id);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
element_has_tag (WebKitDOMElement *element,
|
|
const gchar* tag)
|
|
{
|
|
gchar *element_tag;
|
|
|
|
if (!WEBKIT_DOM_IS_ELEMENT (element))
|
|
return FALSE;
|
|
|
|
element_tag = webkit_dom_node_get_local_name (WEBKIT_DOM_NODE (element));
|
|
|
|
if (g_ascii_strcasecmp (element_tag, tag) != 0) {
|
|
g_free (element_tag);
|
|
return FALSE;
|
|
}
|
|
g_free (element_tag);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
element_has_class (WebKitDOMElement *element,
|
|
const gchar* class)
|
|
{
|
|
gchar *element_class;
|
|
|
|
if (!element)
|
|
return FALSE;
|
|
|
|
if (!WEBKIT_DOM_IS_ELEMENT (element))
|
|
return FALSE;
|
|
|
|
element_class = webkit_dom_element_get_class_name (element);
|
|
|
|
if (g_strstr_len (element_class, -1, class)) {
|
|
g_free (element_class);
|
|
return TRUE;
|
|
}
|
|
g_free (element_class);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
element_add_class (WebKitDOMElement *element,
|
|
const gchar* class)
|
|
{
|
|
gchar *element_class;
|
|
gchar *new_class;
|
|
|
|
if (!WEBKIT_DOM_IS_ELEMENT (element))
|
|
return;
|
|
|
|
if (element_has_class (element, class))
|
|
return;
|
|
|
|
element_class = webkit_dom_element_get_class_name (element);
|
|
|
|
if (g_strcmp0 (element_class, "") == 0)
|
|
new_class = g_strdup (class);
|
|
else
|
|
new_class = g_strconcat (element_class, " ", class, NULL);
|
|
|
|
webkit_dom_element_set_class_name (element, new_class);
|
|
|
|
g_free (element_class);
|
|
g_free (new_class);
|
|
}
|
|
|
|
void
|
|
element_remove_class (WebKitDOMElement *element,
|
|
const gchar* class)
|
|
{
|
|
gchar *element_class;
|
|
GString *result;
|
|
|
|
if (!WEBKIT_DOM_IS_ELEMENT (element))
|
|
return;
|
|
|
|
if (!element_has_class (element, class))
|
|
return;
|
|
|
|
element_class = webkit_dom_element_get_class_name (element);
|
|
|
|
if (g_strcmp0 (element_class, class) == 0) {
|
|
webkit_dom_element_remove_attribute (element, "class");
|
|
g_free (element_class);
|
|
return;
|
|
}
|
|
|
|
result = e_str_replace_string (element_class, class, "");
|
|
if (result) {
|
|
webkit_dom_element_set_class_name (element, result->str);
|
|
g_string_free (result, TRUE);
|
|
}
|
|
|
|
g_free (element_class);
|
|
}
|
|
|
|
void
|
|
remove_node (WebKitDOMNode *node)
|
|
{
|
|
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
|
|
|
|
/* Check if the parent exists, if so it means that the node is still
|
|
* in the DOM or at least the parent is. If it doesn't exists it is not
|
|
* in the DOM and we can free it. */
|
|
if (parent)
|
|
webkit_dom_node_remove_child (parent, node, NULL);
|
|
else
|
|
g_object_unref (node);
|
|
}
|
|
|
|
void
|
|
remove_node_if_empty (WebKitDOMNode *node)
|
|
{
|
|
if (!WEBKIT_DOM_IS_NODE (node))
|
|
return;
|
|
|
|
if (!webkit_dom_node_get_first_child (node)) {
|
|
remove_node (node);
|
|
} else {
|
|
gchar *text_content;
|
|
|
|
text_content = webkit_dom_node_get_text_content (node);
|
|
if (!text_content)
|
|
remove_node (node);
|
|
|
|
if (text_content && !*text_content)
|
|
remove_node (node);
|
|
|
|
g_free (text_content);
|
|
}
|
|
}
|
|
|
|
WebKitDOMNode *
|
|
split_node_into_two (WebKitDOMNode *item,
|
|
gint level)
|
|
{
|
|
gint current_level = 1;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMDocumentFragment *fragment;
|
|
WebKitDOMNode *parent, *prev_parent, *tmp;
|
|
|
|
document = webkit_dom_node_get_owner_document (item);
|
|
fragment = webkit_dom_document_create_document_fragment (document);
|
|
|
|
tmp = item;
|
|
parent = webkit_dom_node_get_parent_node (item);
|
|
while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
|
|
WebKitDOMNode *clone, *first_child, *insert_before = NULL, *sibling;
|
|
|
|
first_child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment));
|
|
clone = webkit_dom_node_clone_node (parent, FALSE);
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (fragment), clone, first_child, NULL);
|
|
|
|
if (first_child)
|
|
insert_before = webkit_dom_node_get_first_child (first_child);
|
|
|
|
while (first_child && (sibling = webkit_dom_node_get_next_sibling (first_child)))
|
|
webkit_dom_node_insert_before (first_child, sibling, insert_before, NULL);
|
|
|
|
while ((sibling = webkit_dom_node_get_next_sibling (tmp)))
|
|
webkit_dom_node_append_child (clone, sibling, NULL);
|
|
|
|
webkit_dom_node_insert_before (
|
|
clone, tmp, webkit_dom_node_get_first_child (clone), NULL);
|
|
|
|
prev_parent = parent;
|
|
tmp = webkit_dom_node_get_next_sibling (parent);
|
|
parent = webkit_dom_node_get_parent_node (parent);
|
|
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
|
|
first_child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment));
|
|
insert_before = webkit_dom_node_get_first_child (first_child);
|
|
while (first_child && (sibling = webkit_dom_node_get_next_sibling (first_child))) {
|
|
webkit_dom_node_insert_before (
|
|
first_child, sibling, insert_before, NULL);
|
|
}
|
|
}
|
|
|
|
if (current_level >= level && level >= 0)
|
|
break;
|
|
|
|
current_level++;
|
|
}
|
|
|
|
tmp = webkit_dom_node_insert_before (
|
|
parent,
|
|
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)),
|
|
webkit_dom_node_get_next_sibling (prev_parent),
|
|
NULL);
|
|
remove_node_if_empty (prev_parent);
|
|
|
|
return tmp;
|
|
}
|
|
|
|
WebKitDOMElement *
|
|
create_selection_marker (WebKitDOMDocument *document,
|
|
gboolean start)
|
|
{
|
|
WebKitDOMElement *element;
|
|
|
|
element = webkit_dom_document_create_element (
|
|
document, "SPAN", NULL);
|
|
webkit_dom_element_set_id (
|
|
element,
|
|
start ? "-x-evo-selection-start-marker" :
|
|
"-x-evo-selection-end-marker");
|
|
|
|
return element;
|
|
}
|
|
|
|
void
|
|
remove_selection_markers (WebKitDOMDocument *document)
|
|
{
|
|
WebKitDOMElement *marker;
|
|
|
|
marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-start-marker");
|
|
if (marker)
|
|
remove_node (WEBKIT_DOM_NODE (marker));
|
|
marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-end-marker");
|
|
if (marker)
|
|
remove_node (WEBKIT_DOM_NODE (marker));
|
|
}
|
|
|
|
void
|
|
add_selection_markers_into_element_start (WebKitDOMDocument *document,
|
|
WebKitDOMElement *element,
|
|
WebKitDOMElement **selection_start_marker,
|
|
WebKitDOMElement **selection_end_marker)
|
|
{
|
|
WebKitDOMElement *marker;
|
|
|
|
remove_selection_markers (document);
|
|
marker = create_selection_marker (document, FALSE);
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (element),
|
|
WEBKIT_DOM_NODE (marker),
|
|
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)),
|
|
NULL);
|
|
if (selection_end_marker)
|
|
*selection_end_marker = marker;
|
|
|
|
marker = create_selection_marker (document, TRUE);
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (element),
|
|
WEBKIT_DOM_NODE (marker),
|
|
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)),
|
|
NULL);
|
|
if (selection_start_marker)
|
|
*selection_start_marker = marker;
|
|
}
|
|
|
|
void
|
|
add_selection_markers_into_element_end (WebKitDOMDocument *document,
|
|
WebKitDOMElement *element,
|
|
WebKitDOMElement **selection_start_marker,
|
|
WebKitDOMElement **selection_end_marker)
|
|
{
|
|
WebKitDOMElement *marker;
|
|
|
|
remove_selection_markers (document);
|
|
marker = create_selection_marker (document, TRUE);
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (marker), NULL);
|
|
if (selection_start_marker)
|
|
*selection_start_marker = marker;
|
|
|
|
marker = create_selection_marker (document, FALSE);
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (marker), NULL);
|
|
if (selection_end_marker)
|
|
*selection_end_marker = marker;
|
|
}
|
|
|
|
gboolean
|
|
node_is_list (WebKitDOMNode *node)
|
|
{
|
|
return node && (
|
|
WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (node) ||
|
|
WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (node));
|
|
}
|
|
|
|
gboolean
|
|
node_is_list_or_item (WebKitDOMNode *node)
|
|
{
|
|
return node && (node_is_list (node) || WEBKIT_DOM_IS_HTMLLI_ELEMENT (node));
|
|
}
|
|
/**
|
|
* get_list_format_from_node:
|
|
* @node: an #WebKitDOMNode
|
|
*
|
|
* Returns block format of given list.
|
|
*
|
|
* Returns: #EHTMLEditorSelectionBlockFormat
|
|
*/
|
|
EHTMLEditorSelectionBlockFormat
|
|
get_list_format_from_node (WebKitDOMNode *node)
|
|
{
|
|
EHTMLEditorSelectionBlockFormat format =
|
|
E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST;
|
|
|
|
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node))
|
|
return -1;
|
|
|
|
if (WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (node))
|
|
return format;
|
|
|
|
if (WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (node)) {
|
|
gchar *type_value = webkit_dom_element_get_attribute (
|
|
WEBKIT_DOM_ELEMENT (node), "type");
|
|
|
|
if (!type_value)
|
|
return E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST;
|
|
|
|
if (!*type_value)
|
|
format = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST;
|
|
else if (g_ascii_strcasecmp (type_value, "A") == 0)
|
|
format = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA;
|
|
else if (g_ascii_strcasecmp (type_value, "I") == 0)
|
|
format = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN;
|
|
g_free (type_value);
|
|
|
|
return format;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
merge_list_into_list (WebKitDOMNode *from,
|
|
WebKitDOMNode *to,
|
|
gboolean insert_before)
|
|
{
|
|
WebKitDOMNode *item, *insert_before_node;
|
|
|
|
if (!(to && from))
|
|
return;
|
|
|
|
insert_before_node = webkit_dom_node_get_first_child (to);
|
|
while ((item = webkit_dom_node_get_first_child (from)) != NULL) {
|
|
if (insert_before)
|
|
webkit_dom_node_insert_before (
|
|
to, item, insert_before_node, NULL);
|
|
else
|
|
webkit_dom_node_append_child (to, item, NULL);
|
|
}
|
|
|
|
if (!webkit_dom_node_get_first_child (from))
|
|
remove_node (from);
|
|
}
|
|
|
|
void
|
|
merge_lists_if_possible (WebKitDOMNode *list)
|
|
{
|
|
EHTMLEditorSelectionBlockFormat format, prev, next;
|
|
gint ii, length;
|
|
WebKitDOMNode *prev_sibling, *next_sibling;
|
|
WebKitDOMNodeList *lists;
|
|
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (list));
|
|
next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (list));
|
|
|
|
format = get_list_format_from_node (list),
|
|
prev = get_list_format_from_node (prev_sibling);
|
|
next = get_list_format_from_node (next_sibling);
|
|
|
|
if (format == prev && format != -1 && prev != -1)
|
|
merge_list_into_list (prev_sibling, list, TRUE);
|
|
|
|
if (format == next && format != -1 && next != -1)
|
|
merge_list_into_list (next_sibling, list, FALSE);
|
|
|
|
lists = webkit_dom_element_query_selector_all (
|
|
WEBKIT_DOM_ELEMENT (list), "ol + ol, ul + ul", NULL);
|
|
length = webkit_dom_node_list_get_length (lists);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *node;
|
|
|
|
node = webkit_dom_node_list_item (lists, ii);
|
|
merge_lists_if_possible (node);
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (lists);
|
|
}
|
|
|
|
WebKitDOMElement *
|
|
get_parent_block_element (WebKitDOMNode *node)
|
|
{
|
|
WebKitDOMElement *parent = webkit_dom_node_get_parent_element (node);
|
|
|
|
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent))
|
|
return WEBKIT_DOM_ELEMENT (node);
|
|
|
|
while (parent &&
|
|
!WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) &&
|
|
!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) &&
|
|
!WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (parent) &&
|
|
!WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (parent) &&
|
|
!WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent) &&
|
|
!WEBKIT_DOM_IS_HTML_HEADING_ELEMENT (parent) &&
|
|
!WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (parent) &&
|
|
!element_has_tag (parent, "address")) {
|
|
parent = webkit_dom_node_get_parent_element (
|
|
WEBKIT_DOM_NODE (parent));
|
|
}
|
|
|
|
return parent;
|
|
}
|
|
|
|
void
|
|
dom_element_rename_attribute (WebKitDOMElement *element,
|
|
const gchar *from,
|
|
const gchar *to)
|
|
{
|
|
gchar *value;
|
|
|
|
if (!webkit_dom_element_has_attribute (element, from))
|
|
return;
|
|
|
|
value = webkit_dom_element_get_attribute (element, from);
|
|
webkit_dom_element_set_attribute (element, to, (value && *value) ? value : "", NULL);
|
|
webkit_dom_element_remove_attribute (element, from);
|
|
g_free (value);
|
|
}
|