Updated Spanish translation
[evolution.git] / e-util / e-html-editor-utils.c
blobbb2516887c83758c7fe2eb4388d80cf09153e000
1 /*
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/>
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
25 #include "e-html-editor-utils.h"
26 #include "e-misc-utils.h"
27 #include <string.h>
29 /**
30 * e_html_editor_dom_node_find_parent_element:
31 * @node: Start node
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.
41 WebKitDOMElement *
42 e_html_editor_dom_node_find_parent_element (WebKitDOMNode *node,
43 const gchar *tagname)
45 gint taglen = strlen (tagname);
47 while (node) {
49 if (WEBKIT_DOM_IS_ELEMENT (node)) {
50 gchar *node_tagname;
52 node_tagname = webkit_dom_element_get_tag_name (
53 WEBKIT_DOM_ELEMENT (node));
55 if (node_tagname &&
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));
68 return NULL;
71 /**
72 * e_html_editor_dom_node_find_child_element:
73 * @node: Start node
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.
83 WebKitDOMElement *
84 e_html_editor_dom_node_find_child_element (WebKitDOMNode *node,
85 const gchar *tagname)
87 WebKitDOMNode *start_node = node;
88 gint taglen = strlen (tagname);
90 do {
91 if (WEBKIT_DOM_IS_ELEMENT (node)) {
92 gchar *node_tagname;
94 node_tagname = webkit_dom_element_get_tag_name (
95 WEBKIT_DOM_ELEMENT (node));
97 if (node_tagname &&
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);
111 } else {
112 node = webkit_dom_node_get_parent_node (node);
114 } while (!webkit_dom_node_is_same_node (node, start_node));
116 return NULL;
119 gboolean
120 e_html_editor_node_is_selection_position_node (WebKitDOMNode *node)
122 WebKitDOMElement *element;
124 if (!node || !WEBKIT_DOM_IS_ELEMENT (node))
125 return FALSE;
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");
133 WebKitDOMNode *
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);
156 return parent;
159 gboolean
160 element_has_id (WebKitDOMElement *element,
161 const gchar* id)
163 gchar *element_id;
165 if (!element)
166 return FALSE;
168 if (!WEBKIT_DOM_IS_ELEMENT (element))
169 return FALSE;
171 element_id = webkit_dom_element_get_id (element);
173 if (g_ascii_strcasecmp (element_id, id) != 0) {
174 g_free (element_id);
175 return FALSE;
177 g_free (element_id);
179 return TRUE;
182 gboolean
183 element_has_tag (WebKitDOMElement *element,
184 const gchar* tag)
186 gchar *element_tag;
188 if (!WEBKIT_DOM_IS_ELEMENT (element))
189 return FALSE;
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);
195 return FALSE;
197 g_free (element_tag);
199 return TRUE;
202 gboolean
203 element_has_class (WebKitDOMElement *element,
204 const gchar* class)
206 gchar *element_class;
208 if (!element)
209 return FALSE;
211 if (!WEBKIT_DOM_IS_ELEMENT (element))
212 return FALSE;
214 element_class = webkit_dom_element_get_class_name (element);
216 if (g_strstr_len (element_class, -1, class)) {
217 g_free (element_class);
218 return TRUE;
220 g_free (element_class);
222 return FALSE;
225 void
226 element_add_class (WebKitDOMElement *element,
227 const gchar* class)
229 gchar *element_class;
230 gchar *new_class;
232 if (!WEBKIT_DOM_IS_ELEMENT (element))
233 return;
235 if (element_has_class (element, class))
236 return;
238 element_class = webkit_dom_element_get_class_name (element);
240 if (g_strcmp0 (element_class, "") == 0)
241 new_class = g_strdup (class);
242 else
243 new_class = g_strconcat (element_class, " ", class, NULL);
245 webkit_dom_element_set_class_name (element, new_class);
247 g_free (element_class);
248 g_free (new_class);
251 void
252 element_remove_class (WebKitDOMElement *element,
253 const gchar* class)
255 gchar *element_class;
256 GString *result;
258 if (!WEBKIT_DOM_IS_ELEMENT (element))
259 return;
261 if (!element_has_class (element, class))
262 return;
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);
269 return;
272 result = e_str_replace_string (element_class, class, "");
273 if (result) {
274 webkit_dom_element_set_class_name (element, result->str);
275 g_string_free (result, TRUE);
278 g_free (element_class);
281 void
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. */
289 if (parent)
290 webkit_dom_node_remove_child (parent, node, NULL);
291 else
292 g_object_unref (node);
295 void
296 remove_node_if_empty (WebKitDOMNode *node)
298 if (!WEBKIT_DOM_IS_NODE (node))
299 return;
301 if (!webkit_dom_node_get_first_child (node)) {
302 remove_node (node);
303 } else {
304 gchar *text_content;
306 text_content = webkit_dom_node_get_text_content (node);
307 if (!text_content)
308 remove_node (node);
310 if (text_content && !*text_content)
311 remove_node (node);
313 g_free (text_content);
317 WebKitDOMNode *
318 split_node_into_two (WebKitDOMNode *item,
319 gint level)
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);
329 tmp = item;
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);
339 if (first_child)
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)
364 break;
366 current_level++;
369 tmp = webkit_dom_node_insert_before (
370 parent,
371 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (fragment)),
372 webkit_dom_node_get_next_sibling (prev_parent),
373 NULL);
374 remove_node_if_empty (prev_parent);
376 return tmp;
379 WebKitDOMElement *
380 create_selection_marker (WebKitDOMDocument *document,
381 gboolean start)
383 WebKitDOMElement *element;
385 element = webkit_dom_document_create_element (
386 document, "SPAN", NULL);
387 webkit_dom_element_set_id (
388 element,
389 start ? "-x-evo-selection-start-marker" :
390 "-x-evo-selection-end-marker");
392 return element;
395 void
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");
402 if (marker)
403 remove_node (WEBKIT_DOM_NODE (marker));
404 marker = webkit_dom_document_get_element_by_id (
405 document, "-x-evo-selection-end-marker");
406 if (marker)
407 remove_node (WEBKIT_DOM_NODE (marker));
410 void
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)),
424 NULL);
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)),
433 NULL);
434 if (selection_start_marker)
435 *selection_start_marker = marker;
438 void
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;
460 gboolean
461 node_is_list (WebKitDOMNode *node)
463 return node && (
464 WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (node) ||
465 WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (node));
468 gboolean
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))
488 return -1;
490 if (WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (node))
491 return format;
493 if (WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (node)) {
494 gchar *type_value = webkit_dom_element_get_attribute (
495 WEBKIT_DOM_ELEMENT (node), "type");
497 if (!type_value)
498 return E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST;
500 if (!*type_value)
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;
506 g_free (type_value);
508 return format;
511 return -1;
514 void
515 merge_list_into_list (WebKitDOMNode *from,
516 WebKitDOMNode *to,
517 gboolean insert_before)
519 WebKitDOMNode *item, *insert_before_node;
521 if (!(to && from))
522 return;
524 insert_before_node = webkit_dom_node_get_first_child (to);
525 while ((item = webkit_dom_node_get_first_child (from)) != NULL) {
526 if (insert_before)
527 webkit_dom_node_insert_before (
528 to, item, insert_before_node, NULL);
529 else
530 webkit_dom_node_append_child (to, item, NULL);
533 if (!webkit_dom_node_get_first_child (from))
534 remove_node (from);
537 void
538 merge_lists_if_possible (WebKitDOMNode *list)
540 EHTMLEditorSelectionBlockFormat format, prev, next;
541 gint ii, length;
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++) {
562 WebKitDOMNode *node;
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);
571 WebKitDOMElement *
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);
579 while (parent &&
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));
592 return parent;