593 lines
16 KiB
C
593 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"))
|
|
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;
|
|
}
|