2 * e-html-editor-utils.c
4 * Copyright (C) 2012 Dan Vrátil <dvratil@redhat.com>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) version 3.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with the program; if not, see <http://www.gnu.org/licenses/>
25 #include "e-html-editor-utils.h"
26 #include "e-misc-utils.h"
30 * e_html_editor_dom_node_find_parent_element:
32 * @tagname: Tag name of element to search
34 * Recursively searches for first occurance of element with given @tagname
35 * that is parent of given @node.
37 * Returns: A #WebKitDOMElement with @tagname representing parent of @node or
38 * @NULL when @node has no parent with given @tagname. When @node matches @tagname,
39 * then the @node is returned.
42 e_html_editor_dom_node_find_parent_element (WebKitDOMNode
*node
,
45 gint taglen
= strlen (tagname
);
49 if (WEBKIT_DOM_IS_ELEMENT (node
)) {
52 node_tagname
= webkit_dom_element_get_tag_name (
53 WEBKIT_DOM_ELEMENT (node
));
56 (strlen (node_tagname
) == taglen
) &&
57 (g_ascii_strncasecmp (node_tagname
, tagname
, taglen
) == 0)) {
58 g_free (node_tagname
);
59 return WEBKIT_DOM_ELEMENT (node
);
62 g_free (node_tagname
);
65 node
= WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node
));
72 * e_html_editor_dom_node_find_child_element:
74 * @tagname: Tag name of element to search.
76 * Recursively searches for first occurrence of element with given @tagname that
77 * is a child of @node.
79 * Returns: A #WebKitDOMElement with @tagname representing a child of @node or
80 * @NULL when @node has no child with given @tagname. When @node matches @tagname,
81 * then the @node is returned.
84 e_html_editor_dom_node_find_child_element (WebKitDOMNode
*node
,
87 WebKitDOMNode
*start_node
= node
;
88 gint taglen
= strlen (tagname
);
91 if (WEBKIT_DOM_IS_ELEMENT (node
)) {
94 node_tagname
= webkit_dom_element_get_tag_name (
95 WEBKIT_DOM_ELEMENT (node
));
98 (strlen (node_tagname
) == taglen
) &&
99 (g_ascii_strncasecmp (node_tagname
, tagname
, taglen
) == 0)) {
100 g_free (node_tagname
);
101 return WEBKIT_DOM_ELEMENT (node
);
104 g_free (node_tagname
);
107 if (webkit_dom_node_has_child_nodes (node
)) {
108 node
= webkit_dom_node_get_first_child (node
);
109 } else if (webkit_dom_node_get_next_sibling (node
)) {
110 node
= webkit_dom_node_get_next_sibling (node
);
112 node
= webkit_dom_node_get_parent_node (node
);
114 } while (!webkit_dom_node_is_same_node (node
, start_node
));
120 e_html_editor_node_is_selection_position_node (WebKitDOMNode
*node
)
122 WebKitDOMElement
*element
;
124 if (!node
|| !WEBKIT_DOM_IS_ELEMENT (node
))
127 element
= WEBKIT_DOM_ELEMENT (node
);
129 return element_has_id (element
, "-x-evo-selection-start-marker") ||
130 element_has_id (element
, "-x-evo-selection-end-marker");
134 e_html_editor_get_parent_block_node_from_child (WebKitDOMNode
*node
)
136 WebKitDOMNode
*parent
= node
;
138 if (!WEBKIT_DOM_IS_ELEMENT (parent
) ||
139 e_html_editor_node_is_selection_position_node (parent
))
140 parent
= webkit_dom_node_get_parent_node (parent
);
142 if (element_has_class (WEBKIT_DOM_ELEMENT (parent
), "-x-evo-temp-text-wrapper") ||
143 element_has_class (WEBKIT_DOM_ELEMENT (parent
), "-x-evo-quoted") ||
144 element_has_class (WEBKIT_DOM_ELEMENT (parent
), "-x-evo-quote-character") ||
145 element_has_class (WEBKIT_DOM_ELEMENT (parent
), "-x-evo-signature") ||
146 WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent
) ||
147 element_has_tag (WEBKIT_DOM_ELEMENT (parent
), "b") ||
148 element_has_tag (WEBKIT_DOM_ELEMENT (parent
), "i") ||
149 element_has_tag (WEBKIT_DOM_ELEMENT (parent
), "u"))
150 parent
= webkit_dom_node_get_parent_node (parent
);
152 if (element_has_class (WEBKIT_DOM_ELEMENT (parent
), "-x-evo-quoted") ||
153 element_has_class (WEBKIT_DOM_ELEMENT (parent
), "Apple-tab-span"))
154 parent
= webkit_dom_node_get_parent_node (parent
);
160 element_has_id (WebKitDOMElement
*element
,
168 if (!WEBKIT_DOM_IS_ELEMENT (element
))
171 element_id
= webkit_dom_element_get_id (element
);
173 if (g_ascii_strcasecmp (element_id
, id
) != 0) {
183 element_has_tag (WebKitDOMElement
*element
,
188 if (!WEBKIT_DOM_IS_ELEMENT (element
))
191 element_tag
= webkit_dom_node_get_local_name (WEBKIT_DOM_NODE (element
));
193 if (g_ascii_strcasecmp (element_tag
, tag
) != 0) {
194 g_free (element_tag
);
197 g_free (element_tag
);
203 element_has_class (WebKitDOMElement
*element
,
206 gchar
*element_class
;
211 if (!WEBKIT_DOM_IS_ELEMENT (element
))
214 element_class
= webkit_dom_element_get_class_name (element
);
216 if (g_strstr_len (element_class
, -1, class)) {
217 g_free (element_class
);
220 g_free (element_class
);
226 element_add_class (WebKitDOMElement
*element
,
229 gchar
*element_class
;
232 if (!WEBKIT_DOM_IS_ELEMENT (element
))
235 if (element_has_class (element
, class))
238 element_class
= webkit_dom_element_get_class_name (element
);
240 if (g_strcmp0 (element_class
, "") == 0)
241 new_class
= g_strdup (class);
243 new_class
= g_strconcat (element_class
, " ", class, NULL
);
245 webkit_dom_element_set_class_name (element
, new_class
);
247 g_free (element_class
);
252 element_remove_class (WebKitDOMElement
*element
,
255 gchar
*element_class
;
258 if (!WEBKIT_DOM_IS_ELEMENT (element
))
261 if (!element_has_class (element
, class))
264 element_class
= webkit_dom_element_get_class_name (element
);
266 if (g_strcmp0 (element_class
, class) == 0) {
267 webkit_dom_element_remove_attribute (element
, "class");
268 g_free (element_class
);
272 result
= e_str_replace_string (element_class
, class, "");
274 webkit_dom_element_set_class_name (element
, result
->str
);
275 g_string_free (result
, TRUE
);
278 g_free (element_class
);
282 remove_node (WebKitDOMNode
*node
)
284 WebKitDOMNode
*parent
= webkit_dom_node_get_parent_node (node
);
286 /* Check if the parent exists, if so it means that the node is still
287 * in the DOM or at least the parent is. If it doesn't exists it is not
288 * in the DOM and we can free it. */
290 webkit_dom_node_remove_child (parent
, node
, NULL
);
292 g_object_unref (node
);
296 remove_node_if_empty (WebKitDOMNode
*node
)
298 if (!WEBKIT_DOM_IS_NODE (node
))
301 if (!webkit_dom_node_get_first_child (node
)) {
306 text_content
= webkit_dom_node_get_text_content (node
);
310 if (text_content
&& !*text_content
)
313 g_free (text_content
);
318 split_node_into_two (WebKitDOMNode
*item
,
321 gint current_level
= 1;
322 WebKitDOMDocument
*document
;
323 WebKitDOMDocumentFragment
*fragment
;
324 WebKitDOMNode
*parent
, *prev_parent
, *tmp
;
326 document
= webkit_dom_node_get_owner_document (item
);
327 fragment
= webkit_dom_document_create_document_fragment (document
);
330 parent
= webkit_dom_node_get_parent_node (item
);
331 while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent
)) {
332 WebKitDOMNode
*clone
, *first_child
, *insert_before
= NULL
, *sibling
;
334 first_child
= webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment
));
335 clone
= webkit_dom_node_clone_node (parent
, FALSE
);
336 webkit_dom_node_insert_before (
337 WEBKIT_DOM_NODE (fragment
), clone
, first_child
, NULL
);
340 insert_before
= webkit_dom_node_get_first_child (first_child
);
342 while (first_child
&& (sibling
= webkit_dom_node_get_next_sibling (first_child
)))
343 webkit_dom_node_insert_before (first_child
, sibling
, insert_before
, NULL
);
345 while ((sibling
= webkit_dom_node_get_next_sibling (tmp
)))
346 webkit_dom_node_append_child (clone
, sibling
, NULL
);
348 webkit_dom_node_insert_before (
349 clone
, tmp
, webkit_dom_node_get_first_child (clone
), NULL
);
351 prev_parent
= parent
;
352 tmp
= webkit_dom_node_get_next_sibling (parent
);
353 parent
= webkit_dom_node_get_parent_node (parent
);
354 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent
)) {
355 first_child
= webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment
));
356 insert_before
= webkit_dom_node_get_first_child (first_child
);
357 while (first_child
&& (sibling
= webkit_dom_node_get_next_sibling (first_child
))) {
358 webkit_dom_node_insert_before (
359 first_child
, sibling
, insert_before
, NULL
);
363 if (current_level
>= level
&& level
>= 0)
369 tmp
= webkit_dom_node_insert_before (
371 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment
)),
372 webkit_dom_node_get_next_sibling (prev_parent
),
374 remove_node_if_empty (prev_parent
);
380 create_selection_marker (WebKitDOMDocument
*document
,
383 WebKitDOMElement
*element
;
385 element
= webkit_dom_document_create_element (
386 document
, "SPAN", NULL
);
387 webkit_dom_element_set_id (
389 start
? "-x-evo-selection-start-marker" :
390 "-x-evo-selection-end-marker");
396 remove_selection_markers (WebKitDOMDocument
*document
)
398 WebKitDOMElement
*marker
;
400 marker
= webkit_dom_document_get_element_by_id (
401 document
, "-x-evo-selection-start-marker");
403 remove_node (WEBKIT_DOM_NODE (marker
));
404 marker
= webkit_dom_document_get_element_by_id (
405 document
, "-x-evo-selection-end-marker");
407 remove_node (WEBKIT_DOM_NODE (marker
));
411 add_selection_markers_into_element_start (WebKitDOMDocument
*document
,
412 WebKitDOMElement
*element
,
413 WebKitDOMElement
**selection_start_marker
,
414 WebKitDOMElement
**selection_end_marker
)
416 WebKitDOMElement
*marker
;
418 remove_selection_markers (document
);
419 marker
= create_selection_marker (document
, FALSE
);
420 webkit_dom_node_insert_before (
421 WEBKIT_DOM_NODE (element
),
422 WEBKIT_DOM_NODE (marker
),
423 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element
)),
425 if (selection_end_marker
)
426 *selection_end_marker
= marker
;
428 marker
= create_selection_marker (document
, TRUE
);
429 webkit_dom_node_insert_before (
430 WEBKIT_DOM_NODE (element
),
431 WEBKIT_DOM_NODE (marker
),
432 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element
)),
434 if (selection_start_marker
)
435 *selection_start_marker
= marker
;
439 add_selection_markers_into_element_end (WebKitDOMDocument
*document
,
440 WebKitDOMElement
*element
,
441 WebKitDOMElement
**selection_start_marker
,
442 WebKitDOMElement
**selection_end_marker
)
444 WebKitDOMElement
*marker
;
446 remove_selection_markers (document
);
447 marker
= create_selection_marker (document
, TRUE
);
448 webkit_dom_node_append_child (
449 WEBKIT_DOM_NODE (element
), WEBKIT_DOM_NODE (marker
), NULL
);
450 if (selection_start_marker
)
451 *selection_start_marker
= marker
;
453 marker
= create_selection_marker (document
, FALSE
);
454 webkit_dom_node_append_child (
455 WEBKIT_DOM_NODE (element
), WEBKIT_DOM_NODE (marker
), NULL
);
456 if (selection_end_marker
)
457 *selection_end_marker
= marker
;
461 node_is_list (WebKitDOMNode
*node
)
464 WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (node
) ||
465 WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (node
));
469 node_is_list_or_item (WebKitDOMNode
*node
)
471 return node
&& (node_is_list (node
) || WEBKIT_DOM_IS_HTMLLI_ELEMENT (node
));
474 * get_list_format_from_node:
475 * @node: an #WebKitDOMNode
477 * Returns block format of given list.
479 * Returns: #EHTMLEditorSelectionBlockFormat
481 EHTMLEditorSelectionBlockFormat
482 get_list_format_from_node (WebKitDOMNode
*node
)
484 EHTMLEditorSelectionBlockFormat format
=
485 E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST
;
487 if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node
))
490 if (WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (node
))
493 if (WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (node
)) {
494 gchar
*type_value
= webkit_dom_element_get_attribute (
495 WEBKIT_DOM_ELEMENT (node
), "type");
498 return E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST
;
501 format
= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST
;
502 else if (g_ascii_strcasecmp (type_value
, "A") == 0)
503 format
= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA
;
504 else if (g_ascii_strcasecmp (type_value
, "I") == 0)
505 format
= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN
;
515 merge_list_into_list (WebKitDOMNode
*from
,
517 gboolean insert_before
)
519 WebKitDOMNode
*item
, *insert_before_node
;
524 insert_before_node
= webkit_dom_node_get_first_child (to
);
525 while ((item
= webkit_dom_node_get_first_child (from
)) != NULL
) {
527 webkit_dom_node_insert_before (
528 to
, item
, insert_before_node
, NULL
);
530 webkit_dom_node_append_child (to
, item
, NULL
);
533 if (!webkit_dom_node_get_first_child (from
))
538 merge_lists_if_possible (WebKitDOMNode
*list
)
540 EHTMLEditorSelectionBlockFormat format
, prev
, next
;
542 WebKitDOMNode
*prev_sibling
, *next_sibling
;
543 WebKitDOMNodeList
*lists
;
545 prev_sibling
= webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (list
));
546 next_sibling
= webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (list
));
548 format
= get_list_format_from_node (list
),
549 prev
= get_list_format_from_node (prev_sibling
);
550 next
= get_list_format_from_node (next_sibling
);
552 if (format
== prev
&& format
!= -1 && prev
!= -1)
553 merge_list_into_list (prev_sibling
, list
, TRUE
);
555 if (format
== next
&& format
!= -1 && next
!= -1)
556 merge_list_into_list (next_sibling
, list
, FALSE
);
558 lists
= webkit_dom_element_query_selector_all (
559 WEBKIT_DOM_ELEMENT (list
), "ol + ol, ul + ul", NULL
);
560 length
= webkit_dom_node_list_get_length (lists
);
561 for (ii
= 0; ii
< length
; ii
++) {
564 node
= webkit_dom_node_list_item (lists
, ii
);
565 merge_lists_if_possible (node
);
566 g_object_unref (node
);
568 g_object_unref (lists
);
572 get_parent_block_element (WebKitDOMNode
*node
)
574 WebKitDOMElement
*parent
= webkit_dom_node_get_parent_element (node
);
576 if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent
))
577 return WEBKIT_DOM_ELEMENT (node
);
580 !WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent
) &&
581 !WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent
) &&
582 !WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (parent
) &&
583 !WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (parent
) &&
584 !WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent
) &&
585 !WEBKIT_DOM_IS_HTML_HEADING_ELEMENT (parent
) &&
586 !WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (parent
) &&
587 !element_has_tag (parent
, "address")) {
588 parent
= webkit_dom_node_get_parent_element (
589 WEBKIT_DOM_NODE (parent
));