1
2
3
4
5
6
7
8
9
10
11
12 package sk.uniba.euromath.document;
13
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.Comparator;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Set;
21 import java.util.TreeSet;
22
23 import javax.xml.namespace.QName;
24
25 import org.apache.commons.lang.NullArgumentException;
26 import org.apache.commons.lang.StringUtils;
27 import org.eclipse.jface.dialogs.IInputValidator;
28 import org.eclipse.jface.dialogs.InputDialog;
29 import org.eclipse.jface.dialogs.MessageDialog;
30 import org.eclipse.jface.window.Window;
31 import org.eclipse.swt.widgets.Display;
32 import org.eclipse.swt.widgets.Shell;
33 import org.w3c.dom.Attr;
34 import org.w3c.dom.Comment;
35 import org.w3c.dom.DocumentFragment;
36 import org.w3c.dom.Element;
37 import org.w3c.dom.Entity;
38 import org.w3c.dom.EntityReference;
39 import org.w3c.dom.Node;
40 import org.w3c.dom.ProcessingInstruction;
41 import org.w3c.dom.traversal.NodeFilter;
42
43 import sk.baka.ikslibs.DOMUtils;
44 import sk.baka.ikslibs.interval.DOMInterval;
45 import sk.baka.ikslibs.modify.DOMMutils;
46 import sk.baka.ikslibs.ptr.DOMPoint;
47 import sk.baka.ikslibs.ptr.DomPointer;
48 import sk.baka.ikslibs.ptr.DomPointerFactory;
49 import sk.baka.xml.gene.ExportException;
50 import sk.baka.xml.schematic.rules.AttributeRule;
51 import sk.baka.xml.schematic.rules.ElementRule;
52 import sk.baka.xml.schematic.rules.INameList;
53 import sk.baka.xml.schematic.rules.IValueRule;
54 import sk.baka.xml.schematic.rules.InsertList;
55 import sk.baka.xml.schematic.rules.NewElementRule;
56 import sk.uniba.euromath.document.schema.SchematicUtils;
57 import sk.uniba.euromath.editor.dialogs.Dialogs;
58 import sk.uniba.euromath.editor.dialogs.ProcessingInstructionDialog;
59 import sk.uniba.euromath.editor.dialogs.WidgetWrapperDialog;
60 import sk.uniba.euromath.editor.lang.Messages;
61 import sk.uniba.euromath.editor.widgets.EntityList;
62 import sk.uniba.euromath.editor.widgets.namelist.DisplayableNameItemTypeEnum;
63 import sk.uniba.euromath.editor.widgets.namelist.DisplayableNameList;
64 import sk.uniba.euromath.editor.widgets.namelist.DisplayableNameListImpl;
65 import sk.uniba.euromath.editor.widgets.namelist.NameListItemChooser;
66
67 /***
68 * Provides basic editing features, document modification driven by schema.
69 *
70 * @author Martin Vysny
71 * @author Tomáš Studva
72 * @TODO MOTO> Move to .editor package.
73 */
74 public final class DocumentModifyHelper {
75 /***
76 * Constuctor.
77 *
78 * @param xmlAccess
79 * the document instance.
80 */
81 public DocumentModifyHelper(XMLAccess xmlAccess) {
82 super();
83 this.xmlAccess = xmlAccess;
84 }
85
86 /***
87 * The document instance.
88 */
89 private final XMLAccess xmlAccess;
90
91 /***
92 * Manages the process of inserting a new attribute into given element
93 * by wizard.
94 *
95 * @param parent
96 * parent window. Should not be <code>null</code>.
97 * @param e
98 * the element where to insert new attribute.
99 * @throws ExportException
100 * when document modification finalization fails.
101 */
102 public void insertNewAttribute(Shell parent, Element e)
103 throws ExportException {
104 INameList<AttributeRule> list = xmlAccess.getSchema()
105 .getElementRule(e).getInsertableAttributes();
106 if (list.getForeignNames().size() + list.getLocalNames().size() == 0) {
107 MessageDialog
108 .openInformation(
109 parent,
110 Messages
111 .getString("INFORMATION"), Messages.getString("NO_INSERTABLE_ATTRIBUTE"));
112 return;
113 }
114 WidgetWrapperDialog<NameListItemChooser<AttributeRule>> dlg = Dialogs
115 .createAttributeCreator(parent, list,
116 xmlAccess, xmlAccess
117 .getNsManager());
118 if (dlg.open() != Window.OK)
119 return;
120
121 xmlAccess.getUndoManager().mark();
122 xmlAccess.getModifier().startModify();
123 xmlAccess.getModifier().createAttribute(e,
124 dlg.getWidget().getSelectedQName(),
125 dlg.getWidget().getSelectedValue());
126 xmlAccess.getModifier().endModify();
127 }
128
129 /***
130 * Manages the process of modifying the attribute value by wizard.
131 *
132 * @param parent
133 * parent window. Should not be <code>null</code>.
134 * @param attr
135 * the attribute whose value has to be modified.
136 * @throws ExportException
137 * when document modification finalization fails.
138 */
139 public void modifyAttribute(Shell parent, Attr attr)
140 throws ExportException {
141 AttributeRule rule = xmlAccess.getSchema().getElementRule(
142 attr.getOwnerElement()).getAttributeRule(attr);
143 WidgetWrapperDialog<NameListItemChooser<AttributeRule>> dlg = Dialogs
144 .createAttributeEditor(parent, attr, rule,
145 xmlAccess, xmlAccess
146 .getNsManager());
147 if (dlg.open() != Window.OK)
148 return;
149
150 xmlAccess.getUndoManager().mark();
151 xmlAccess.getModifier().startModify();
152 xmlAccess.getModifier().setText(attr,
153 dlg.getWidget().getSelectedValue());
154 xmlAccess.getModifier().endModify();
155 }
156
157 /***
158 * Tests if given attribute can be deleted.
159 *
160 * @param tested
161 * attribute
162 * @return null if can be deleted, otherwise error message
163 */
164 public String canDeleteAttribute(Attr attr) {
165 String result = checkInEntity(attr);
166 if (result != null)
167 return result;
168
169 return xmlAccess.getSchema().getElementRule(
170 attr.getOwnerElement()).isDeletableAttribute(
171 attr);
172 }
173
174 /***
175 * Manages the process of deleting the attribute.
176 *
177 * @param parent
178 * parent window. Should not be <code>null</code>.
179 * @param attr
180 * the attribute which has to be deleted.
181 * @throws ExportException
182 * when document modification finalization fails.
183 */
184 public void deleteAttribute(Shell parent, Attr attr)
185 throws ExportException {
186 final String errorMsg = canDeleteAttribute(attr);
187 if (errorMsg != null)
188 MessageDialog
189 .openError(
190 parent,
191 Messages
192 .getString("ERROR"),
193 Messages
194 .getString(
195 "ATTRIBUTE_CANNOT_BE_REMOVED", DOMUtils
196 .printQName(attr))
197 + "\n" + errorMsg);
198
199 final boolean result = MessageDialog
200 .openQuestion(
201 parent,
202 Messages.getString("QUESTION"), Messages.getString("REMOVE_ATTRIBUTE_QUESTION", DOMUtils.printQName(attr)));
203
204 if (!result)
205 return;
206
207 xmlAccess.getUndoManager().mark();
208 xmlAccess.getModifier().startModify();
209 xmlAccess.getModifier().remove(attr);
210 xmlAccess.getModifier().endModify();
211 }
212
213 /***
214 * Tests if elements can bedelted
215 *
216 * @param elements
217 * to test
218 * @param parent -
219 * output argument, method computes common parent of
220 * elements and stores it in parent
221 * @return null if can be deleted, otherwise error message
222 */
223 public String canDeleteElements(Set<Element> elements, Node parent) {
224 if (elements.size() == 0)
225 throw new IllegalArgumentException(
226 "The set of elements must not be empty.");
227
228 for (Element e : elements) {
229 if (parent == null)
230 parent = e.getParentNode();
231 else {
232 if (!parent.isSameNode(e.getParentNode()))
233 throw new IllegalArgumentException(
234 "The elements have no common parent.");
235 }
236 }
237 if (parent.getNodeType() != Node.ELEMENT_NODE) {
238 return Messages.getString("CANNOT_DELETE_ROOT");
239 }
240 return null;
241 }
242
243 /***
244 * Manages the process of deleting the elements. All elements must have
245 * the same parent.
246 *
247 * @param shell
248 * parent window. Should not be <code>null</code>.
249 * @param elements
250 * set of <code>Element</code> s that must be deleted.
251 * @throws ExportException
252 * when document modification finalization fails.
253 */
254 public void deleteElements(Shell shell, Set<Element> elements)
255 throws ExportException {
256 Node parent = null;
257 final String errorMsg = canDeleteElements(elements, parent);
258 if (errorMsg != null)
259 MessageDialog.openError(shell, Messages
260 .getString("ERROR"),
261 errorMsg);
262
263 ElementRule rule = xmlAccess.getSchema().getElementRule(
264 (Element) parent);
265 DeleteElementsManager dem = new DeleteElementsManager();
266 if (!dem.execute(shell, elements, DOMUtils.printQName(parent),
267 rule))
268 return;
269
270 if (dem.result == null) {
271 Set<Element> delete = new HashSet<Element>();
272 delete.add((Element) parent);
273 deleteElements(shell, delete);
274 return;
275 }
276 if (dem.result.size() != 0) {
277 Set<Element> delete = new HashSet<Element>(elements);
278 delete.addAll(dem.result);
279 elements = delete;
280 }
281
282 xmlAccess.getUndoManager().mark();
283 xmlAccess.getModifier().startModify();
284
285 for (Element e : elements) {
286 xmlAccess.getModifier().remove(e);
287 xmlAccess.getIDManager().removeTree(e);
288 }
289 xmlAccess.getModifier().endModify();
290 }
291
292 /***
293 * Manages the process of element deletion.
294 *
295 * @author Martin Vysny
296 */
297 private static class DeleteElementsManager {
298 /***
299 * List of elements that must be deleted aswell. If the set is
300 * empty then no other elements must be deleted. If the set is
301 * <code>null</code> then the parent element must be deleted.
302 * Valid after the <code>execute()</code> function returns
303 * <code>true</code>.
304 */
305 List<? extends Element> result = null;
306
307 /***
308 * Drives the user through the process.
309 *
310 * @param parent
311 * parent window.
312 * @param elements
313 * the list of elements that have to be deleted.
314 * @param parentName
315 * the name of the parent element. Used only to
316 * display the name on the shell.
317 * @param parentRule
318 * rule for parent element.
319 * @return true if user permitted the deletion, false if the
320 * request was rejected or an error occured.
321 */
322 boolean execute(Shell parent, Set<Element> elements,
323 String parentName, ElementRule parentRule) {
324 result = parentRule.areElementsDeletable(elements);
325 boolean dlgResult;
326 if (result == null) {
327 dlgResult = MessageDialog
328 .openQuestion(
329 parent,
330 Messages
331 .getString("QUESTION"),
332 Messages
333 .getString(
334 "REQ_PARENT_ELEMENT_DEL", parentName));
335 } else if (result.size() == 0) {
336 dlgResult = MessageDialog
337 .openQuestion(
338 parent,
339 Messages
340 .getString("QUESTION"),
341 Messages
342 .getString(
343 "ELEMENT_DELETE_QUERY", printList(elements)));
344 } else {
345 dlgResult = MessageDialog
346 .openQuestion(
347 parent,
348 Messages
349 .getString("QUESTION"),
350 Messages
351 .getString(
352 "REQ_ELEMENTS_DEL", printList(result)));
353 }
354 return dlgResult;
355 }
356
357 /***
358 * Prints given elements as a comma-separated list.
359 *
360 * @param elements
361 * the elements, whose names to print.
362 * @return comma separated element names.
363 */
364 private static String printList(
365 Collection<? extends Element> elements) {
366 StringBuilder builder = new StringBuilder();
367 for (Element e : elements) {
368 if (builder.length() != 0)
369 builder.append(", ");
370 builder.append(DOMUtils.printQName(e));
371 }
372 return builder.toString();
373 }
374 }
375
376 /***
377 * Tests if nodes can be enclosed.
378 *
379 * @param start
380 * start of the interval. Must point before the
381 * <code>end</code> parameter.
382 * @param end
383 * end of the interval.
384 * @param outElements -
385 * output argument, method computes elements that are
386 * allowed by schema to enclose interval
387 * @return null if can be enclosed, otherwise error message
388 */
389 public String canEncloseNodes(DomPointer start, DomPointer end,
390 INameList<NewElementRule> outElements) {
391 if (!start.parent.isSameNode(end.parent))
392 throw new IllegalArgumentException(
393 "The pointers must point into one node.");
394 if (start.compareTo(end) >= 0)
395 throw new IllegalArgumentException(
396 "The end pointer must point after the start pointer.");
397 if (start.inEntity()) {
398
399 return Messages.getString("CANNOT_MODIFY_ENTITY");
400 }
401 final ElementRule rule = xmlAccess.getSchema().getElementRule(
402 (Element) start.parent);
403 outElements = rule.getEnclosingElements(start.ip, end.ip);
404
405 if (outElements == null) {
406
407 return Messages.getString("ENCLOSE_FORBIDDEN");
408 }
409 return null;
410 }
411
412 /***
413 * Manages the process of enclosing the nodes. All nodes must have the
414 * same parent. Both pointers may point inside a text.
415 *
416 * @param start
417 * start of the interval. Must point before the
418 * <code>end</code> parameter.
419 * @param end
420 * end of the interval.
421 * @param parent
422 * parent window. Should not be <code>null</code>.
423 * @throws ExportException
424 * when document modification finalization fails.
425 */
426 public void encloseNodes(Shell parent, DomPointer start, DomPointer end)
427 throws ExportException {
428 final INameList<NewElementRule> elements = null;
429 final String errorMsg = canEncloseNodes(start, end, elements);
430
431 if (errorMsg != null) {
432
433 MessageDialog.openInformation(parent, Messages
434 .getString("ERROR"),
435 errorMsg);
436 return;
437 }
438 final DisplayableNameList<NewElementRule> dnl = new DisplayableNameListImpl<NewElementRule>(
439 elements, xmlAccess, xmlAccess.getNsManager(),
440 true);
441 final WidgetWrapperDialog<NameListItemChooser<NewElementRule>> dlg = Dialogs
442 .createElementCreator(
443 parent,
444 xmlAccess,
445 Messages
446 .getString("ENCLOSE_NODES"), dnl);
447 if (dlg.open() != Window.OK)
448 return;
449
450 xmlAccess.getUndoManager().mark();
451 xmlAccess.getModifier().startModify();
452 final QName qname = dlg.getWidget().getSelectedQName();
453 Element e = xmlAccess.getDocument().createElementNS(
454 qname.getNamespaceURI(),
455 DOMUtils.printQName(qname));
456
457
458 Node last;
459
460 if (end.ip.pos != 0) {
461 last = DOMMutils.splitText(end);
462 } else
463 last = end.pointsTo;
464
465 DOMMutils.insertNode(e, start, false);
466 Node first = e.getNextSibling();
467
468
469 while (first != last) {
470 Node next = first.getNextSibling();
471 DOMMutils.remove(first);
472 DOMMutils.insertNode(first, DomPointerFactory.create(e,
473 (Node) null), false);
474 first = next;
475 }
476 xmlAccess.getModifier().endModify();
477 }
478
479 /***
480 * Test if element can be declosed.
481 *
482 * @param e
483 * element to declose
484 * @return null if can be declosed, otherwise error message
485 */
486 public String canDecloseNodes(Element e) {
487 if ((e.getParentNode() == null)
488 || (e.getParentNode().getNodeType() != Node.ELEMENT_NODE)) {
489
490 return "Cannot declose root node.";
491 }
492 final ElementRule rule = xmlAccess.getSchema().getElementRule(
493 (Element) e.getParentNode());
494 return rule.isDeclosable(e);
495 }
496
497 /***
498 * Manages the process of declosing the nodes - replaces given element
499 * with its contents.
500 *
501 * @param parent
502 * parent window. Should not be <code>null</code>.
503 * @param e
504 * the element, that shall be replaced by its children if
505 * the declosing is allowed.
506 * @throws ExportException
507 * when document modification finalization fails.
508 */
509 public void decloseNodes(Shell parent, Element e)
510 throws ExportException {
511 final String errorMsg = canDecloseNodes(e);
512 if (errorMsg != null)
513 MessageDialog
514 .openInformation(
515 parent,
516 Messages
517 .getString("INFORMATION"),
518 Messages
519 .getString("DECLOSE_FORBIDDEN") +
520 "\n"
521 + errorMsg);
522 final boolean result = MessageDialog
523 .openQuestion(parent,
524 "Question",
525 Messages
526 .getString(
527 "DECLOSE_QUERY", DOMUtils.printQName(e)));
528 if (!result)
529 return;
530
531 xmlAccess.getUndoManager().mark();
532 xmlAccess.getModifier().startModify();
533 Node node = e.getFirstChild();
534 Node insertAfter = e;
535 while (node != null) {
536 Node next = node.getNextSibling();
537 xmlAccess.getModifier().remove(node);
538 DomPointer ptr = DomPointerFactory.create(insertAfter)
539 .getNextSibling();
540
541
542
543 DOMMutils.insertNode(node, ptr, next == null);
544 insertAfter = node;
545 node = next;
546 }
547
548 xmlAccess.getModifier().remove(e);
549 xmlAccess.getModifier().endModify();
550 }
551
552 /***
553 * Tests if processing instruction can be inserted.
554 *
555 * @param pointer
556 * @return null if can be inserted, otherwise error message
557 */
558 public String canInsertProcessingInstruction(DomPointer pointer) {
559 return checkInEntity(pointer);
560 }
561
562 /***
563 * Inserts processing instruction at pointer.
564 *
565 * @param shell
566 * parent window. !!! Cannot be <code>null</code>.
567 * @param pointer
568 * dom pointer where to insert
569 * @throws ExportException
570 */
571 public void insertProcessingInstruction(Shell shell, DomPointer pointer)
572 throws ExportException {
573 final String errorMsg = canInsertProcessingInstruction(pointer);
574 if (errorMsg != null)
575 MessageDialog.openInformation(shell, Messages
576 .getString("ERROR"),
577 errorMsg);
578 ProcessingInstructionDialog dialog = new ProcessingInstructionDialog(
579 shell,
580 Messages
581 .getString("INSERT_PROCESSING_INSTRUCTION"),
582 null, null);
583
584 if (dialog.open() == Window.OK) {
585 String target = dialog.getTarget().trim();
586 String data = dialog.getData().trim();
587 this.xmlAccess.getModifier().startModify();
588 this.xmlAccess.getUndoManager().mark();
589 DOMMutils.insertNode(this.xmlAccess.getDocument()
590 .createProcessingInstruction(target,
591 data), pointer, false);
592 this.xmlAccess.getModifier().endModify();
593 }
594 }
595
596 /***
597 * Modifies processing instruction node by dialog.
598 *
599 * @param shell
600 * parent window. !!! Cannot be <code>null</code>.
601 * @param node
602 * processing instruction node to modify
603 * @throws ExportException
604 */
605 public void modifyProcessingInstruction(Shell shell,
606 ProcessingInstruction pi) throws ExportException {
607 ProcessingInstructionDialog dialog = new ProcessingInstructionDialog(
608 shell,
609 Messages
610 .getString("INSERT_PROCESSING_INSTRUCTION"),
611 pi.getTarget(), pi.getData());
612
613 if (dialog.open() == Window.OK) {
614 String target = dialog.getTarget().trim();
615 String data = dialog.getData().trim();
616 this.xmlAccess.getModifier().startModify();
617 this.xmlAccess.getUndoManager().mark();
618 DomPointer pointer = DomPointerFactory.create(pi);
619 DOMMutils.insertNode(this.xmlAccess.getDocument()
620 .createProcessingInstruction(target,
621 data), pointer, false);
622 this.xmlAccess.getModifier().remove(pi);
623 this.xmlAccess.getModifier().endModify();
624 }
625 }
626
627 /***
628 * Tests if comment can be inserted.
629 *
630 * @param pointer
631 * @return null if can be inserted, otherwise error message
632 */
633 public String canInsertComment(DomPointer pointer) {
634 return checkInEntity(pointer);
635 }
636
637 /***
638 * Inserts processing instruction at pointer.
639 *
640 * @param shell
641 * parent window. !!! Cannot be <code>null</code>.
642 * @param pointer
643 * dom pointer where to insert
644 * @throws ExportException
645 */
646 public void insertComment(Shell shell, DomPointer pointer)
647 throws ExportException {
648 final String errorMsg = canInsertComment(pointer);
649 if (errorMsg != null)
650 MessageDialog.openInformation(shell, Messages
651 .getString("ERROR"),
652 errorMsg);
653 InputDialog dialog = new InputDialog(
654 shell,
655 Messages.getString("INSERT_COMMENT"),
656 Messages.getString("INSERT_COMMENT_ENTER_TEXT"), "", null);
657
658 if (dialog.open() == Window.OK) {
659 String inputStr = dialog.getValue().trim();
660 insertComment(inputStr, shell, pointer);
661 }
662 }
663
664 /***
665 * Inserts comment with text text at pointer.
666 *
667 * @param text
668 * text of comment to insert
669 * @param shell
670 * parent window. !!! Cannot be <code>null</code>.
671 * @param pointer
672 * dom pointer where to insert
673 * @throws ExportException
674 */
675 public void insertComment(String text, Shell shell, DomPointer pointer)
676 throws ExportException {
677 assert (text != null);
678 this.xmlAccess.getModifier().startModify();
679 this.xmlAccess.getUndoManager().mark();
680 DOMMutils.insertNode(this.xmlAccess.getDocument()
681 .createComment(text), pointer, false);
682 this.xmlAccess.getModifier().endModify();
683 }
684
685 /***
686 * Modifies comment node by dialog.
687 *
688 * @param shell
689 * parent window. !!! Cannot be <code>null</code>.
690 * @param node
691 * comment node to modify
692 * @throws ExportException
693 */
694 public void modifyComment(Shell shell, Comment comment)
695 throws ExportException {
696 InputDialog dialog = new InputDialog(shell,
697 "Modify comment node", "Modify comment node",
698 comment.getNodeValue(), null);
699 if (dialog.open() == Window.OK) {
700 String inputStr = dialog.getValue().trim();
701 this.xmlAccess.getUndoManager().mark();
702 this.xmlAccess.getModifier().startModify();
703 DOMMutils.setText(comment, inputStr);
704 this.xmlAccess.getModifier().endModify();
705 }
706 }
707
708 /***
709 * Manages the process of inserting an entity into selected position by
710 * dialog.
711 *
712 * @param ptr
713 * the desired place.
714 * @param parent
715 * parent window. Should not be <code>null</code>.
716 * @throws ExportException
717 * when document modification finalization fails.
718 */
719 public void insertEntity(Shell parent, DomPointer ptr)
720 throws ExportException {
721
722 List<String> validEntities = getInsertableEntities(parent, ptr);
723 if (validEntities == null)
724 return;
725
726 WidgetWrapperDialog<EntityList> dlg = Dialogs
727 .createEntityLister(
728 parent,
729 xmlAccess,
730 Messages
731 .getString("CREATE_ENTITY"), validEntities);
732 if (dlg.open() != Window.OK)
733 return;
734 final String entityName = dlg.getWidget().getEntityName();
735
736 insertEntity(entityName, ptr);
737 }
738
739 /***
740 * Inserts entity to given position.
741 *
742 * @param entityName
743 * the entity name.
744 * @param ptr
745 * the insert point.
746 * @throws ExportException
747 * if document modification finalization fails.
748 */
749 public void insertEntity(String entityName, DomPointer ptr)
750 throws ExportException {
751 xmlAccess.getUndoManager().mark();
752 xmlAccess.getModifier().startModify();
753 EntityReference er = xmlAccess.getDocument()
754 .createEntityReference(entityName);
755 DOMMutils.insertNode(er, ptr, false);
756 xmlAccess.getModifier().endModify();
757 }
758
759 /***
760 * Computes insertable entities. Shows various warning dialogs if no
761 * entities can be found etc.
762 *
763 * @param ptr
764 * the desired place.
765 * @param parent
766 * parent window. Can be <code>null</code>.
767 * @return entity names that are insertable at given pointer. May return
768 * <code>null</code> if no entities are suitable.
769 */
770 public List<String> getInsertableEntities(final Shell parent,
771 final DomPointer ptr) {
772 if (ptr == null)
773 throw new NullArgumentException("ptr");
774 if (checkInEntity(parent, ptr))
775 return null;
776 final ElementRule rule = xmlAccess.getSchema().getElementRule(
777 ptr.parentElement);
778
779 Set<String> entities = xmlAccess.getEntityManager()
780 .getEntityNames();
781 if (entities.isEmpty()) {
782
783 if (parent != null)
784 MessageDialog
785 .openInformation(
786 parent,
787 Messages
788 .getString("INFORMATION"), Messages.getString("NO_KNOWN_ENTITIES"));
789 return null;
790 }
791
792 final List<String> validEntities = new ArrayList<String>(
793 entities.size());
794 for (final String entityName : entities) {
795 final Entity entity = xmlAccess.getEntityManager()
796 .getEntity(entityName);
797 if (rule.isInsertable(ptr.ip, entity) == null)
798 validEntities.add(entityName);
799 }
800 if (validEntities.isEmpty()) {
801
802 if (parent != null)
803 MessageDialog
804 .openInformation(
805 parent,
806 Messages
807 .getString("INFORMATION"), Messages.getString("NO_INSERTABLE_ENTITY"));
808 return null;
809 }
810 return validEntities;
811 }
812
813 /***
814 * Computes list of names of elements, insertable at given pointer.
815 *
816 * @param pointer
817 * @return ordered list of qnames.
818 */
819 public List<QName> getInsertableElementsQNames(final DomPointer pointer) {
820 if (pointer == null)
821 throw new IllegalArgumentException("pointer is null.");
822 if (pointer.parentElement == null)
823 throw new IllegalArgumentException(
824 "Cannot insert elements before/after root element.");
825 final List<InsertList> insertLists = xmlAccess.getSchema()
826 .getElementRule(pointer.parentElement)
827 .getInsertableElements(pointer.ip);
828 final String localNamespace = pointer.parentElement
829 .getNamespaceURI();
830
831 final TreeSet<QName> result = new TreeSet<QName>(
832 new Comparator<QName>() {
833 public int compare(QName o1, QName o2) {
834 if (DOMUtils
835 .equalsURI(
836 o1
837 .getNamespaceURI(),
838 localNamespace)) {
839 if (!DOMUtils
840 .equalsURI(
841 o2
842 .getNamespaceURI(),
843 localNamespace))
844 return -1;
845 return o1
846 .getLocalPart()
847 .compareTo(
848 o2
849 .getLocalPart());
850 }
851 if (DOMUtils
852 .equalsURI(
853 o2
854 .getNamespaceURI(),
855 localNamespace))
856 return 1;
857
858
859 final int result = StringUtils
860 .defaultString(
861 o1
862 .getPrefix())
863 .compareTo(
864 StringUtils
865 .defaultString(o2
866 .getPrefix()));
867 if (result != 0)
868 return result;
869 return o1
870 .getLocalPart()
871 .compareTo(
872 o2
873 .getLocalPart());
874 }
875 });
876 for (final InsertList insertList : insertLists) {
877 final INameList<NewElementRule> nl = insertList
878 .getFirstNamelistAt(pointer.ip);
879 final DisplayableNameList<NewElementRule> dnl = new DisplayableNameListImpl<NewElementRule>(
880 nl, xmlAccess,
881 xmlAccess.getNsManager(), true);
882 final List<QName> qnames = getDomQnames(dnl);
883 result.addAll(qnames);
884 }
885 return new ArrayList<QName>(result);
886 }
887
888 /***
889 * Computes QNames of elements from DisplayableNameList.
890 *
891 * @param dnl
892 * the list. If <code>null</code> then empty list is
893 * returned.
894 * @return list of qnames with
895 * {@link NamespaceManager#getBestPrefix(String) best prefixes}.
896 */
897 public List<QName> getDomQnames(DisplayableNameList dnl) {
898 List<QName> result = new ArrayList<QName>();
899 if (dnl != null) {
900 for (int i = 0; i < dnl.getLength(); i++) {
901
902 if (dnl.getType(i) == DisplayableNameItemTypeEnum.ITEM_NAMESPACE)
903 continue;
904 final String ns = dnl.getDomNamespaceUri(i);
905 final String prefix = xmlAccess.getNsManager()
906 .getBestPrefix(ns);
907 result.add(dnl.getDomQName(i, prefix));
908 }
909 }
910 return result;
911 }
912
913 /***
914 * Inserts text to given point. Displays a dialog allowing user to enter
915 * text being inserted. Only valid text is allowed.
916 *
917 * @param point
918 * insert the text here.
919 * @param shell
920 * parent window. !!! Cannot be <code>null</code>.
921 * @param nodeType
922 * the type of node to insert. May be
923 * {@link Node#CDATA_SECTION_NODE} or
924 * {@link Node#TEXT_NODE} only.
925 * @throws ExportException
926 */
927 public void insertTextNode(final Shell shell, final DomPointer point,
928 final short nodeType) throws ExportException {
929 if (!DOMUtils.isText(nodeType))
930 throw new IllegalArgumentException(
931 "nodeType is not text");
932 if (point.inEntity()) {
933 MessageDialog
934 .openInformation(
935 shell,
936 Messages
937 .getString("ERROR"),
938 Messages
939 .getString("CANNOT_MODIFY_ENTITY"));
940 return;
941 }
942
943 final IInputValidator validator = SchematicUtils
944 .createTextValidator(xmlAccess.getSchema(),
945 new DOMInterval(point, point));
946 final String text = (nodeType == Node.TEXT_NODE) ? Messages
947 .getString("INSERT_TEXT_NODE_CONTENTS")
948 : Messages
949 .getString("INSERT_CDATA_NODE_CONTENTS");
950 final InputDialog dialog = new InputDialog(shell, Messages
951 .getString("INSERT_TEXT_NODE"),
952 text, "", validator);
953 if (dialog.open() == Window.OK) {
954 final String inputStr = dialog.getValue().trim();
955 final DocumentModifier dm = xmlAccess.getModifier();
956 xmlAccess.getUndoManager().mark();
957 dm.startModify();
958 DOMMutils.insertText(point, inputStr, nodeType);
959 dm.endModify();
960 }
961 }
962
963 /***
964 * Modifies text of node by dialog.
965 *
966 * @param node
967 * must be text node
968 * @param shell
969 * parent window. !!!Cannot be <code>null</code>.
970 * @throws ExportException
971 * if document fails to transform.
972 */
973 public void modifyTextNode(Shell shell, Node node)
974 throws ExportException {
975 if (shell == null)
976 throw new IllegalArgumentException(
977 "Shell cannot be null.");
978 if (!DOMUtils.isText(node))
979 throw new IllegalArgumentException(
980 "The node must be a text/cdata node");
981
982 final DomPointer ptr = DomPointerFactory.create(node);
983 final IInputValidator validator = SchematicUtils
984 .createTextValidator(
985 xmlAccess.getSchema(),
986 new DOMInterval(
987 ptr,
988 ptr
989 .getNextSibling()));
990 final InputDialog dialog = new InputDialog(
991 Display.getCurrent().getActiveShell(),
992 Messages.getString("MODIFY_TEXT_NODE"), Messages.getString("MODIFY_TEXT"), node
993 .getNodeValue(), validator);
994 if (dialog.open() == Window.OK) {
995 final DocumentModifier dm = xmlAccess.getModifier();
996 dm.startModify();
997 xmlAccess.getUndoManager().mark();
998 dm.setText(node, dialog.getValue());
999 xmlAccess.getUndoManager().mark();
1000 dm.endModify();
1001 }
1002 }
1003
1004 /***
1005 * Checks if given node is in entity. If yes then error dialog is shown.
1006 *
1007 * @param shell
1008 * parent, should not be <code>null</code>.
1009 * @param node
1010 * node to check
1011 * @return <code>true</code> if node is in entity, <code>false</code>
1012 * otherwise.
1013 */
1014 public boolean checkInEntity(final Shell shell, final Node node) {
1015 if (DOMUtils.isInEntity(node)) {
1016 MessageDialog
1017 .openInformation(
1018 shell,
1019 Messages
1020 .getString("ERROR"),
1021 Messages
1022 .getString("CANNOT_MODIFY_ENTITY"));
1023 return true;
1024 }
1025 return false;
1026 }
1027
1028 /***
1029 * Checks if given pointer points into entity. If yes then error dialog
1030 * is shown.
1031 *
1032 * @param shell
1033 * parent, should not be <code>null</code>.
1034 * @param ptr
1035 * the pointer to check
1036 * @return <code>true</code> if the pointer points into an entity,
1037 * <code>false</code> otherwise.
1038 */
1039 public boolean checkInEntity(final Shell shell, final DomPointer ptr) {
1040 if (ptr.inEntity()) {
1041 MessageDialog
1042 .openInformation(
1043 shell,
1044 Messages
1045 .getString("ERROR"),
1046 Messages
1047 .getString("CANNOT_MODIFY_ENTITY"));
1048 return true;
1049 }
1050 return false;
1051 }
1052
1053 /***
1054 * Checks if given node is in entity. If yes then error message is
1055 * returned.
1056 *
1057 * @param node
1058 * node to check
1059 * @return <code>not null</code> if node is in entity,
1060 * <code>null</code> otherwise.
1061 */
1062 public String checkInEntity(final Node node) {
1063 if (DOMUtils.isInEntity(node)) {
1064 return Messages.getString("CANNOT_MODIFY_ENTITY");
1065 }
1066 return null;
1067 }
1068
1069 /***
1070 * Checks if given pointer points into entity. If yes then error message
1071 * is returned.
1072 *
1073 * @param ptr
1074 * the pointer to check
1075 * @return <code>not null</code> if node is in entity,
1076 * <code>null</code> otherwise.
1077 */
1078 public String checkInEntity(final DomPointer ptr) {
1079 if (ptr.inEntity()) {
1080 return Messages.getString("CANNOT_MODIFY_ENTITY");
1081 }
1082 return null;
1083 }
1084
1085 /***
1086 * Deletes given text node by wizard.
1087 *
1088 * @param shell
1089 * parent window, should not be <code>null</code>.
1090 * @param node
1091 * node to delete, must not be <code>null</code>. May
1092 * be text, cdata or pure-text entity only.
1093 * @throws ExportException
1094 * if shit happens.
1095 */
1096 public void deleteTextNode(final Shell shell, final Node node)
1097 throws ExportException {
1098 if (!DOMUtils.isTextualNode(node))
1099 throw new IllegalArgumentException("illegal node type");
1100 if (checkInEntity(shell, node)) {
1101 return;
1102 }
1103
1104
1105
1106
1107 final Element e = (Element) node.getParentNode();
1108 if (!DOMUtils.containsElements(e)) {
1109
1110
1111 final ElementRule rule = xmlAccess.getSchema()
1112 .getElementRule(e);
1113 final IValueRule valRule = rule.getValueRule();
1114 final String newValue = DOMUtils.getTextContents(e,
1115 new NodeFilter() {
1116
1117
1118
1119
1120
1121 public short acceptNode(Node n) {
1122 if (node.isSameNode(n))
1123 return NodeFilter.FILTER_REJECT;
1124 return NodeFilter.FILTER_ACCEPT;
1125 }
1126 });
1127 if (!valRule.acceptsValue(newValue)) {
1128
1129
1130 if (MessageDialog
1131 .openQuestion(
1132 shell,
1133 Messages
1134 .getString("QUESTION"),
1135 Messages
1136 .getString("CANT_DELETE_TEXT")))
1137 {
1138 deleteElements(shell, Collections
1139 .singleton(e));
1140 }
1141 return;
1142 }
1143 }
1144
1145 xmlAccess.getUndoManager().mark();
1146 final DocumentModifier dm = xmlAccess.getModifier();
1147 dm.startModify();
1148 xmlAccess.getUndoManager().mark();
1149 dm.remove(node);
1150 xmlAccess.getUndoManager().mark();
1151 dm.endModify();
1152 }
1153
1154 /***
1155 * Tries to delete a single node.
1156 *
1157 * @param shell
1158 * parent shell, should not be <code>null</code>.
1159 * @param node
1160 * node to delete.
1161 * @throws ExportException
1162 * if shit happens.
1163 */
1164 public void deleteNode(final Shell shell, final Node node)
1165 throws ExportException {
1166 if (checkInEntity(shell, node))
1167 return;
1168
1169 if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
1170 deleteAttribute(shell, (Attr) node);
1171 return;
1172 }
1173
1174 if (DOMUtils.isTextualEntity(node)) {
1175 deleteTextNode(shell, node);
1176 return;
1177 }
1178
1179 if (node.getNodeType() == Node.ELEMENT_NODE) {
1180 deleteElements(shell, Collections
1181 .singleton((Element) node));
1182 return;
1183 }
1184
1185 if (node.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
1186 final int index = DOMUtils.getNodeIndex(node);
1187 final Element parent = (Element) node.getParentNode();
1188 final ElementRule rule = xmlAccess.getSchema()
1189 .getElementRule(parent);
1190 final DOMPoint start = new DOMPoint(index, 0);
1191 final DOMPoint end = new DOMPoint(index + 1, 0);
1192 final String errorMsg = rule.isDeletable(start, end);
1193 if (errorMsg != null) {
1194 MessageDialog
1195 .openError(
1196 shell,
1197 Messages
1198 .getString("ERROR"),
1199 Messages
1200 .getString("CANT_DELETE_ENTITY") +
1201 "\n"
1202 + errorMsg);
1203 return;
1204 }
1205 }
1206
1207
1208
1209 xmlAccess.getUndoManager().mark();
1210 final DocumentModifier dm = xmlAccess.getModifier();
1211 dm.startModify();
1212 dm.remove(node);
1213 xmlAccess.getUndoManager().mark();
1214 dm.endModify();
1215 }
1216
1217 /***
1218 * Tests if fragment can be inserted.
1219 *
1220 * @param ptr
1221 * place of insertion
1222 * @param fragment
1223 * to insert
1224 * @return null if can be inserted, otherwise error message
1225 */
1226 public String canInsertFragment(final DomPointer ptr,
1227 final DocumentFragment fragment) {
1228 String result = checkInEntity(ptr);
1229 if (result != null)
1230 return result;
1231 final ElementRule rule = xmlAccess.getSchema().getElementRule(
1232 (Element) ptr.parent);
1233 return rule.isInsertable(ptr.ip, fragment);
1234 }
1235
1236 /***
1237 * Tries to insert given document fragment into given position.
1238 *
1239 * @param shell
1240 * parent shell, should not be <code>null</code>.
1241 * @param ptr
1242 * the pointer where the fragment will be inserted. Must
1243 * not be <code>null</code>.
1244 * @param fragment
1245 * the fragment to insert. A clone of this fragment is
1246 * inserted hence the fragment is not modified by this
1247 * method. Must not be <code>null</code>.
1248 * @throws ExportException
1249 */
1250 public void insertFragment(final Shell shell, final DomPointer ptr,
1251 final DocumentFragment fragment) throws ExportException {
1252 if (ptr == null)
1253 throw new NullArgumentException("ptr");
1254 if (fragment == null)
1255 throw new NullArgumentException("fragment");
1256 final String errorMsg = canInsertFragment(ptr, fragment);
1257 if (errorMsg != null) {
1258 if (!MessageDialog
1259 .openQuestion(
1260 shell,
1261 Messages
1262 .getString("ERROR"),
1263 Messages
1264 .getString("FRAGMENT_NOT_INSERTABLE") +
1265 "\n"
1266 + errorMsg))
1267 return;
1268 final String textContents = fragment.getTextContent();
1269 insertTextAt(shell, ptr, textContents, Node.TEXT_NODE);
1270 return;
1271 }
1272
1273 final DocumentFragment clonedFragment = (DocumentFragment) fragment
1274 .cloneNode(true);
1275 xmlAccess.getUndoManager().mark();
1276 xmlAccess.getModifier().startModify();
1277 DOMMutils.insertFragment(clonedFragment, ptr);
1278 xmlAccess.getUndoManager().mark();
1279 xmlAccess.getModifier().endModify();
1280 }
1281
1282 /***
1283 * Tests if text can be inserted.
1284 *
1285 * @param ptr
1286 * place of insertion
1287 * @param string
1288 * to insert
1289 * @return null if can be inserted, otherwise error message
1290 */
1291 public String canInsertTextAt(final DomPointer ptr, final String string) {
1292 final ElementRule rule = xmlAccess.getSchema().getElementRule(
1293 (Element) ptr.parent);
1294 final IValueRule validator = rule.newTextValidator(ptr.ip,
1295 ptr.ip);
1296 return validator.getErrorMessage(string);
1297 }
1298
1299 /***
1300 * Tries to insert text at specified location.
1301 *
1302 * @param shell
1303 * parent shell, should not be <code>null</code>.
1304 * @param ptr
1305 * the pointer where the fragment will be inserted. Must
1306 * not be <code>null</code>.
1307 * @param string
1308 * a string to insert.
1309 * @param type
1310 * the type of node, {@link Node#TEXT_NODE} or
1311 * {@link Node#CDATA_SECTION_NODE}.
1312 * @throws ExportException
1313 */
1314 public void insertTextAt(final Shell shell, final DomPointer ptr,
1315 final String string, final short type)
1316 throws ExportException {
1317 final String error = canInsertTextAt(ptr, string);
1318 if (error != null) {
1319 MessageDialog
1320 .openError(
1321 shell,
1322 Messages
1323 .getString("ERROR"),
1324 Messages
1325 .getString("TEXT_CANNOT_BE_INSERTED_HERE")
1326 + error);
1327 return;
1328 }
1329 xmlAccess.getModifier().startModify();
1330 DOMMutils.insertText(ptr, string, type);
1331 xmlAccess.getModifier().endModify();
1332 }
1333
1334 /***
1335 * Tries to delete text from specified location.
1336 *
1337 * @param shell
1338 * parent shell, should not be <code>null</code>.
1339 * @param cut
1340 * the interval denoting text being deleted or replaced.
1341 * Must not be <code>null</code>. Caller must ensure
1342 * that there are no elements covered by this interval.
1343 * between the two pointers.
1344 * @param replaceWith
1345 * a string that will replace given interval, may be
1346 * <code>null</code>.
1347 * @throws ExportException
1348 */
1349 public void deleteReplaceText(final Shell shell, final DOMInterval cut,
1350 final String replaceWith) throws ExportException {
1351 final IInputValidator validator = SchematicUtils
1352 .createTextValidator(xmlAccess.getSchema(), cut);
1353 final String error = validator.isValid(replaceWith);
1354 if (error != null) {
1355 final String errorMsg = StringUtils
1356 .isEmpty(replaceWith) ? sk.uniba.euromath.document.lang.Messages
1357 .getString("TEXT_CANNOT_BE_DELETED")
1358 : sk.uniba.euromath.document.lang.Messages
1359 .getString("TEXT_CANNOT_BE_REPLACED");
1360 MessageDialog.openError(shell, Messages
1361 .getString("ERROR"),
1362 errorMsg + "\n" + error);
1363 return;
1364 }
1365 xmlAccess.getModifier().startModify();
1366 DOMMutils.remove(cut);
1367 if (!StringUtils.isEmpty(replaceWith))
1368 DOMMutils.insertText(cut.from.ip,
1369 cut.from.parentElement, replaceWith,
1370 Node.TEXT_NODE);
1371 xmlAccess.getModifier().endModify();
1372 }
1373 }