View Javadoc

1   /*
2    * Created on May 18, 2005. Copyright 1999-2006 Faculty of Mathematics, Physics
3    * and Informatics, Comenius University, Bratislava. This file is protected by
4    * the Mozilla Public License version 1.1 (the "License"); you may not use this
5    * file except in compliance with the License. You may obtain a copy of the
6    * License at http://euromath2.sourceforge.net/license.html Unless required by
7    * applicable law or agreed to in writing, software distributed under the
8    * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
9    * OF ANY KIND, either express or implied. See the License for the specific
10   * language governing permissions and limitations under the License.
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.eclipse.jface.dialogs.InputDialog;
26  import org.eclipse.jface.dialogs.MessageDialog;
27  import org.eclipse.jface.window.Window;
28  import org.eclipse.swt.widgets.Display;
29  import org.eclipse.swt.widgets.Shell;
30  import org.w3c.dom.Attr;
31  import org.w3c.dom.Element;
32  import org.w3c.dom.EntityReference;
33  import org.w3c.dom.Node;
34  import org.w3c.dom.traversal.NodeFilter;
35  
36  import sk.baka.ikslibs.DOMUtils;
37  import sk.baka.ikslibs.ids.IDManager;
38  import sk.baka.ikslibs.levelmapper.NodeListID;
39  import sk.baka.ikslibs.modify.DOMMutils;
40  import sk.baka.ikslibs.ptr.DOMPoint;
41  import sk.baka.ikslibs.ptr.DomPointer;
42  import sk.baka.ikslibs.ptr.DomPointerFactory;
43  import sk.baka.xml.gene.ExportException;
44  import sk.uniba.euromath.document.schema.AttributeRule;
45  import sk.uniba.euromath.document.schema.ElementRule;
46  import sk.uniba.euromath.document.schema.InsertList;
47  import sk.uniba.euromath.document.schema.INameList;
48  import sk.uniba.euromath.document.schema.NewElementRule;
49  import sk.uniba.euromath.document.schema.plug.IValueRule;
50  import sk.uniba.euromath.editor.dialogs.Dialogs;
51  import sk.uniba.euromath.editor.dialogs.WidgetWrapperDialog;
52  import sk.uniba.euromath.editor.lang.Messages;
53  import sk.uniba.euromath.editor.widgets.EntityList;
54  import sk.uniba.euromath.editor.widgets.namelist.DisplayableNameItemTypeEnum;
55  import sk.uniba.euromath.editor.widgets.namelist.DisplayableNameList;
56  import sk.uniba.euromath.editor.widgets.namelist.DisplayableNameListImpl;
57  import sk.uniba.euromath.editor.widgets.namelist.NameListItemChooser;
58  import sk.uniba.euromath.tools.StringTools;
59  
60  /***
61   * Provides basic editing features, document modification driven by schema.
62   * 
63   * @author Martin Vysny
64   * @author Tomáš Studva
65   * @TODO MOTO> Move to .editor package.
66   */
67  public final class DocumentModifyHelper {
68      /***
69       * Constuctor.
70       * 
71       * @param xmlAccess
72       *            the document instance.
73       */
74      public DocumentModifyHelper(XMLAccess xmlAccess) {
75          super();
76          this.xmlAccess = xmlAccess;
77      }
78  
79      /***
80       * The document instance.
81       */
82      private final XMLAccess xmlAccess;
83  
84      /***
85       * Used to c
86       */
87      private DisplayableNameListImpl<NewElementRule> dnl = null;
88  
89      /***
90       * Manages the process of inserting a new attribute into given element.
91       * 
92       * @param parent
93       *            parent window. Should not be <code>null</code>.
94       * @param e
95       *            the element where to insert new attribute.
96       * @throws ExportException
97       *             when document modification finalization fails.
98       */
99      public void insertNewAttribute(Shell parent, Element e) throws ExportException
100              {
101         INameList<AttributeRule> list = xmlAccess.getSchema().getElementRule(e)
102                 .getInsertableAttributes();
103         if (list.getForeignNames().size() + list.getLocalNames().size() == 0) {
104             MessageDialog
105                     .openInformation(
106                             parent,
107                             Messages.getString("INFORMATION"), Messages.getString("NO_INSERTABLE_ATTRIBUTE")); //$NON-NLS-1$ //$NON-NLS-2$
108             return;
109         }
110         WidgetWrapperDialog<NameListItemChooser<AttributeRule>> dlg = Dialogs
111                 .createAttributeCreator(parent, list, xmlAccess, xmlAccess
112                         .getNsManager());
113         if (dlg.open() != Window.OK)
114             return;
115         // create an attribute
116         xmlAccess.getUndoManager().mark();
117         xmlAccess.getModifier().startModify();
118         xmlAccess.getModifier().createAttribute(e,
119                 dlg.getWidget().getSelectedQName(),
120                 dlg.getWidget().getSelectedValue());
121         xmlAccess.getModifier().endModify();
122     }
123 
124     /***
125      * Manages the process of modifying the attribute value.
126      * 
127      * @param parent
128      *            parent window. Should not be <code>null</code>.
129      * @param attr
130      *            the attribute whose value has to be modified.
131      * @throws ExportException
132      *             when document modification finalization fails.
133      */
134     public void modifyAttribute(Shell parent, Attr attr) throws ExportException {
135         AttributeRule rule = xmlAccess.getSchema().getElementRule(
136                 attr.getOwnerElement()).getAttributeRule(attr);
137         WidgetWrapperDialog<NameListItemChooser<AttributeRule>> dlg = Dialogs
138                 .createAttributeEditor(parent, attr, rule, xmlAccess, xmlAccess
139                         .getNsManager());
140         if (dlg.open() != Window.OK)
141             return;
142         // modify the attribute value
143         xmlAccess.getUndoManager().mark();
144         xmlAccess.getModifier().startModify();
145         xmlAccess.getModifier().setText(attr,
146                 dlg.getWidget().getSelectedValue());
147         xmlAccess.getModifier().endModify();
148     }
149 
150     /***
151      * Manages the process of deleting the attribute.
152      * 
153      * @param parent
154      *            parent window. Should not be <code>null</code>.
155      * @param attr
156      *            the attribute which has to be deleted.
157      * @throws ExportException
158      *             when document modification finalization fails.
159      */
160     public void deleteAttribute(Shell parent, Attr attr) throws ExportException {
161         if (checkInEntity(parent, attr))
162             return;
163         if (!xmlAccess.getSchema().getElementRule(attr.getOwnerElement())
164                 .isDeletableAttribute(attr)) {
165             // the attribute cannot be deleted.
166             MessageDialog
167                     .openError(parent,
168                             Messages.getString("ERROR"), //$NON-NLS-1$
169                             Messages
170                                     .getString(
171                                             "ATTRIBUTE_CANNOT_BE_REMOVED", DOMUtils.printQName(attr))); //$NON-NLS-1$
172             return;
173         }
174         // attribute is deletable
175         final boolean result = MessageDialog
176                 .openQuestion(
177                         parent,
178                         Messages.getString("QUESTION"), Messages.getString("REMOVE_ATTRIBUTE_QUESTION", DOMUtils.printQName(attr))); //$NON-NLS-1$ //$NON-NLS-2$
179         // if user does not want to remove the attribute, bail out
180         if (!result)
181             return;
182         // create an attribute
183         xmlAccess.getUndoManager().mark();
184         xmlAccess.getModifier().startModify();
185         xmlAccess.getModifier().remove(attr);
186         xmlAccess.getModifier().endModify();
187     }
188 
189     /***
190      * Manages the process of deleting the elements. All elements must have the
191      * same parent.
192      * 
193      * @param shell
194      *            parent window. Should not be <code>null</code>.
195      * @param elements
196      *            set of <code>Element</code> s that must be deleted.
197      * @throws ExportException
198      *             when document modification finalization fails.
199      */
200     public void deleteElements(Shell shell, Set<Element> elements)
201             throws ExportException {
202         if (elements.size() == 0)
203             throw new IllegalArgumentException(
204                     "The set of elements must not be empty."); //$NON-NLS-1$
205         // get the parent of all elements
206         Node parent = null;
207         for (Element e : elements) {
208             if (parent == null)
209                 parent = e.getParentNode();
210             else {
211                 if (!parent.isSameNode(e.getParentNode()))
212                     throw new IllegalArgumentException(
213                             "The elements have no common parent."); //$NON-NLS-1$
214             }
215         }
216         if (parent.getNodeType() != Node.ELEMENT_NODE) {
217             MessageDialog.openError(shell, Messages.getString("ERROR"), //$NON-NLS-1$
218                     Messages.getString("CANNOT_DELETE_ROOT")); //$NON-NLS-1$
219             return;
220         }
221         ElementRule rule = xmlAccess.getSchema().getElementRule(
222                 (Element) parent);
223         DeleteElementsManager dem = new DeleteElementsManager();
224         if (!dem.execute(shell, elements, DOMUtils.printQName(parent), rule))
225             return;
226         // delete the elements
227         if (dem.result == null) {
228             Set<Element> delete = new HashSet<Element>();
229             delete.add((Element) parent);
230             deleteElements(shell, delete);
231             return;
232         }
233         if (dem.result.size() != 0) {
234             Set<Element> delete = new HashSet<Element>(elements);
235             delete.addAll(dem.result);
236             elements = delete;
237         }
238         // announce the start of modification
239         xmlAccess.getUndoManager().mark();
240         xmlAccess.getModifier().startModify();
241         // delete the elements
242         for (Element e : elements) {
243             xmlAccess.getModifier().remove(e);
244             xmlAccess.getIdManager().removeTree(e);
245         }
246         xmlAccess.getModifier().endModify();
247     }
248 
249     /***
250      * Manages the process of element deletion.
251      * 
252      * @author Martin Vysny
253      */
254     private static class DeleteElementsManager {
255         /***
256          * List of elements that must be deleted aswell. If the set is empty
257          * then no other elements must be deleted. If the set is
258          * <code>null</code> then the parent element must be deleted. Valid
259          * after the <code>execute()</code> function returns <code>true</code>.
260          */
261         List<? extends Element> result = null;
262 
263         /***
264          * Drives the user through the process.
265          * 
266          * @param parent
267          *            parent window.
268          * @param elements
269          *            the list of elements that have to be deleted.
270          * @param parentName
271          *            the name of the parent element. Used only to display the
272          *            name on the shell.
273          * @param parentRule
274          *            rule for parent element.
275          * @return true if user permitted the deletion, false if the request was
276          *         rejected or an error occured.
277          */
278         boolean execute(Shell parent, Set<Element> elements, String parentName,
279                 ElementRule parentRule) {
280             result = parentRule.areElementsDeletable(elements);
281             boolean dlgResult;
282             if (result == null) {
283                 dlgResult = MessageDialog
284                         .openQuestion(parent, Messages.getString("QUESTION"), //$NON-NLS-1$
285                                 Messages.getString(
286                                         "REQ_PARENT_ELEMENT_DEL", parentName)); //$NON-NLS-1$
287             } else if (result.size() == 0) {
288                 dlgResult = MessageDialog.openQuestion(parent, Messages
289                         .getString("QUESTION"), //$NON-NLS-1$
290                         Messages.getString(
291                                 "ELEMENT_DELETE_QUERY", printList(elements))); //$NON-NLS-1$
292             } else {
293                 dlgResult = MessageDialog.openQuestion(parent, Messages
294                         .getString("QUESTION"), //$NON-NLS-1$
295                         Messages.getString(
296                                 "REQ_ELEMENTS_DEL", printList(result))); //$NON-NLS-1$
297             }
298             return dlgResult;
299         }
300 
301         /***
302          * Prints given elements as a comma-separated list.
303          * 
304          * @param elements
305          *            the elements, whose names to print.
306          * @return comma separated element names.
307          */
308         private static String printList(Collection<? extends Element> elements) {
309             StringBuilder builder = new StringBuilder();
310             for (Element e : elements) {
311                 if (builder.length() != 0)
312                     builder.append(", "); //$NON-NLS-1$
313                 builder.append(DOMUtils.printQName(e));
314             }
315             return builder.toString();
316         }
317     }
318 
319     /***
320      * Manages the process of enclosing the nodes. All nodes must have the same
321      * parent. Both pointers may point inside a text.
322      * 
323      * @param start
324      *            start of the interval. Must point before the <code>end</code>
325      *            parameter.
326      * @param end
327      *            end of the interval.
328      * @param parent
329      *            parent window. Should not be <code>null</code>.
330      * @throws ExportException
331      *             when document modification finalization fails.
332      */
333     public void encloseNodes(Shell parent, DomPointer start, DomPointer end)
334             throws ExportException {
335         if (!start.parent.isSameNode(end.parent))
336             throw new IllegalArgumentException(
337                     "The pointers must point into one node."); //$NON-NLS-1$
338         if (start.compareTo(end) >= 0)
339             throw new IllegalArgumentException(
340                     "The end pointer must point after the start pointer."); //$NON-NLS-1$
341         if (start.inEntity()) {
342             // cannot modify entity, quit
343             MessageDialog.openInformation(parent, Messages.getString("ERROR"), //$NON-NLS-1$
344                     Messages.getString("CANNOT_MODIFY_ENTITY")); //$NON-NLS-1$
345             return;
346         }
347         final ElementRule rule = xmlAccess.getSchema().getElementRule(
348                 (Element) start.parent);
349         final INameList<NewElementRule> elements = rule.getEnclosingElements(
350                 start.ip, end.ip);
351         if (elements == null) {
352             // cannot enclose, quit
353             MessageDialog.openInformation(parent, Messages.getString("ERROR"), //$NON-NLS-1$
354                     Messages.getString("ENCLOSE_FORBIDDEN")); //$NON-NLS-1$
355             return;
356         }
357         final DisplayableNameList<NewElementRule> dnl = new DisplayableNameListImpl<NewElementRule>(
358                 elements, xmlAccess, xmlAccess.getNsManager(), true);
359         final WidgetWrapperDialog<NameListItemChooser<NewElementRule>> dlg = Dialogs
360                 .createElementCreator(parent, xmlAccess, Messages
361                         .getString("ENCLOSE_NODES"), dnl); //$NON-NLS-1$
362         if (dlg.open() != Window.OK)
363             return;
364         // enclose the nodes with selected element.
365         xmlAccess.getUndoManager().mark();
366         xmlAccess.getModifier().startModify();
367         final QName qname = dlg.getWidget().getSelectedQName();
368         Element e = xmlAccess.getDocument().createElementNS(
369                 qname.getNamespaceURI(), DOMUtils.printQName(qname));
370         // this node is the last node that will be moved into enclosing element
371         Node last;
372         // split the nodes and update the pointers if necessary
373         if (end.ip.pos != 0) {
374             last = DOMMutils.splitText(end);
375         } else
376             last = end.pointsTo;
377         // this splits the node pointed to by 'start' if neccessary.
378         DOMMutils.insertNode(e, start, false);
379         Node first = e.getNextSibling();
380         // move all nodes, starting with first to e. 'last' must not be moved.
381         while (first != last) {
382             Node next = first.getNextSibling();
383             DOMMutils.remove(first);
384             DOMMutils.insertNode(first,
385                     DomPointerFactory.create(e, (Node) null), false);
386             first = next;
387         }
388         xmlAccess.getModifier().endModify();
389     }
390 
391     /***
392      * Manages the process of declosing the nodes - replaces given element with
393      * its contents.
394      * 
395      * @param parent
396      *            parent window. Should not be <code>null</code>.
397      * @param e
398      *            the element, that shall be replaced by its children if the
399      *            declosing is allowed.
400      * @throws ExportException
401      *             when document modification finalization fails.
402      */
403     public void decloseNodes(Shell parent, Element e) throws ExportException {
404         if ((e.getParentNode() == null)
405                 || (e.getParentNode().getNodeType() != Node.ELEMENT_NODE)) {
406             // cannot declose root node
407             MessageDialog.openInformation(parent, Messages
408                     .getString("INFORMATION"), //$NON-NLS-1$
409                     Messages.getString("DECLOSE_FORBIDDEN")); //$NON-NLS-1$
410             return;
411         }
412         ElementRule rule = xmlAccess.getSchema().getElementRule(
413                 (Element) e.getParentNode());
414         if (!rule.isDeclosable(e)) {
415             // cannot declose, quit
416             MessageDialog.openInformation(parent, Messages
417                     .getString("INFORMATION"), //$NON-NLS-1$
418                     Messages.getString("DECLOSE_FORBIDDEN")); //$NON-NLS-1$
419             return;
420         }
421         final boolean result = MessageDialog.openQuestion(parent, "Question", //$NON-NLS-1$
422                 Messages.getString("DECLOSE_QUERY", DOMUtils.printQName(e))); //$NON-NLS-1$
423         if (!result)
424             return;
425         // carry the declose operation
426         xmlAccess.getUndoManager().mark();
427         xmlAccess.getModifier().startModify();
428         Node node = e.getFirstChild();
429         Node insertAfter = e;
430         while (node != null) {
431             Node next = node.getNextSibling();
432             xmlAccess.getModifier().remove(node);
433             DomPointer ptr = DomPointerFactory.create(insertAfter).getNextSibling();
434             // if next node is null then merge current node - it may be a text
435             // node thus it may be mergeable with its new sibling.
436             DOMMutils.insertNode(node, ptr, next == null);
437             insertAfter = node;
438             node = next;
439         }
440         // this will automatically merge first node if needed.
441         xmlAccess.getModifier().remove(e);
442         xmlAccess.getModifier().endModify();
443     }
444 
445     /***
446      * Manages the process of inserting an entity into selected position by
447      * dialog.
448      * 
449      * @param ptr
450      *            the desired place.
451      * @param parent
452      *            parent window. Should not be <code>null</code>.
453      * @throws ExportException
454      *             when document modification finalization fails.
455      */
456     public void insertEntity(Shell parent, DomPointer ptr)
457             throws ExportException {
458         // get valid entities
459         List<String> validEntities = getInsertableEntities(parent, ptr);
460 
461         WidgetWrapperDialog<EntityList> dlg = Dialogs.createEntityLister(
462                 parent, xmlAccess,
463                 Messages.getString("CREATE_ENTITY"), validEntities); //$NON-NLS-1$
464         if (dlg.open() != Window.OK)
465             return;
466         final String entityName = dlg.getWidget().getEntityName();
467         // ok, insert the entity at specified place
468         insertEntity(entityName, ptr);
469     }
470 
471     /***
472      * Inserts entity to given position.
473      * 
474      * @param entityName
475      *            the entity name.
476      * @param ptr
477      *            the insert point.
478      * @throws ExportException
479      *             if document modification finalization fails.
480      */
481     public void insertEntity(String entityName, DomPointer ptr)
482             throws ExportException {
483         xmlAccess.getUndoManager().mark();
484         xmlAccess.getModifier().startModify();
485         EntityReference er = xmlAccess.getDocument().createEntityReference(
486                 entityName);
487         DOMMutils.insertNode(er, ptr, false);
488         xmlAccess.getModifier().endModify();
489     }
490 
491     /***
492      * Computes insertable entities. Shows various warning dialogs if no
493      * entities can be found etc.
494      * 
495      * @param ptr
496      *            the desired place.
497      * @param parent
498      *            parent window. Can be <code>null</code>.
499      * @return entity names that are insertable at given pointer. May return
500      *         <code>null</code> if no entities are suitable.
501      */
502     public List<String> getInsertableEntities(final Shell parent,
503             final DomPointer ptr) {
504         if (ptr == null)
505             throw new IllegalArgumentException("ptr is null."); //$NON-NLS-1$
506         if (ptr.inEntity()) {
507             // cannot modify entity, quit
508             if (parent != null)
509                 MessageDialog
510                         .openInformation(
511                                 parent,
512                                 Messages.getString("INFORMATION"), Messages.getString("CANNOT_MODIFY_ENTITY")); //$NON-NLS-1$ //$NON-NLS-2$
513             return null;
514         }
515         final ElementRule rule = xmlAccess.getSchema().getElementRule(
516                 ptr.parentElement);
517         // get all entity names
518         Set<String> entities = xmlAccess.getEntityManager().getEntityNames();
519         if (entities.isEmpty()) {
520             // no entities available, quit
521             if (parent != null)
522                 MessageDialog
523                         .openInformation(
524                                 parent,
525                                 Messages.getString("INFORMATION"), Messages.getString("NO_KNOWN_ENTITIES")); //$NON-NLS-1$ //$NON-NLS-2$
526             return null;
527         }
528         // compute list of insertable entities
529         final List<String> validEntities = new ArrayList<String>(entities
530                 .size());
531         for (final String entityName : entities) {
532             if (rule.isInsertable(ptr.ip, entityName))
533                 validEntities.add(entityName);
534         }
535         if (validEntities.isEmpty()) {
536             // no entities available, quit
537             if (parent != null)
538                 MessageDialog
539                         .openInformation(
540                                 parent,
541                                 Messages.getString("INFORMATION"), Messages.getString("NO_INSERTABLE_ENTITY")); //$NON-NLS-1$ //$NON-NLS-2$
542             return null;
543         }
544         return validEntities;
545     }
546 
547     /***
548      * Computes DisplayableNameList of insertable elements.
549      * 
550      * @param pointer
551      *            to place where elements should be able to insert.
552      * @deprecated use getInsertableElementsQNames(DomPointer) instead. To
553      *             display qname use {@link DomTools#printQName(QName)}.
554      */
555     @Deprecated
556     public DisplayableNameList getInsertableElements(final DomPointer pointer) {
557         if (pointer == null)
558             throw new IllegalArgumentException("pointer is null."); //$NON-NLS-1$
559         final List<InsertList> insertLists = xmlAccess.getSchema()
560                 .getElementRule(pointer.parentElement).getInsertableElements(
561                         pointer.ip);
562 
563         final InsertList insertList = insertLists.get(0);
564         for (final sk.uniba.euromath.document.schema.ElementLoc loc : insertList) {
565             dnl = new DisplayableNameListImpl<NewElementRule>(loc.nameList,
566                     xmlAccess, xmlAccess.getNsManager(), true);
567             return dnl;
568         }
569         return null;
570     }
571 
572     /***
573      * Computes list of names of elements, insertable at given pointer.
574      * 
575      * @param pointer
576      * @return ordered list of qnames.
577      */
578     public List<QName> getInsertableElementsQNames(final DomPointer pointer) {
579         if (pointer == null)
580             throw new IllegalArgumentException("pointer is null."); //$NON-NLS-1$
581         // FIXME MOTO osetrit ak chcem vkladat pre root element, vtedy
582         // pointer.parentElement == null, neviem ci to ma vratit prazdny zoznam
583         // alebo IllegalArgumentEx
584         final List<InsertList> insertLists = xmlAccess.getSchema()
585                 .getElementRule(pointer.parentElement).getInsertableElements(
586                         pointer.ip);
587         final String localNamespace = pointer.parentElement.getNamespaceURI();
588         // maintain an ordered set of qnames found so far.
589         final TreeSet<QName> result = new TreeSet<QName>(
590                 new Comparator<QName>() {
591                     public int compare(QName o1, QName o2) {
592                         if (DOMUtils.equalsURI(o1.getNamespaceURI(),
593                                 localNamespace)) {
594                             if (!DOMUtils.equalsURI(o2.getNamespaceURI(),
595                                     localNamespace))
596                                 return -1;
597                             return o1.getLocalPart().compareTo(
598                                     o2.getLocalPart());
599                         }
600                         if (DOMUtils.equalsURI(o2.getNamespaceURI(),
601                                 localNamespace))
602                             return 1;
603                         // ok, both o1 and o2 are non-local
604                         final int result = StringTools.nonNullStr(
605                                 o1.getPrefix()).compareTo(
606                                 StringTools.nonNullStr(o2.getPrefix()));
607                         if (result != 0)
608                             return result;
609                         return o1.getLocalPart().compareTo(o2.getLocalPart());
610                     }
611                 });
612         for (final InsertList insertList : insertLists) {
613             final INameList<NewElementRule> nl = insertList
614                     .getFirstNamelistAt(pointer.ip);
615             final DisplayableNameList<NewElementRule> dnl = new DisplayableNameListImpl<NewElementRule>(
616                     nl, xmlAccess, xmlAccess.getNsManager(), true);
617             final List<QName> qnames = getDomQnames(dnl);
618             result.addAll(qnames);
619         }
620         return new ArrayList<QName>(result);
621     }
622 
623     /***
624      * Computes QNames of elements from DisplayableNameList.
625      */
626     public List<QName> getDomQnames(DisplayableNameList dnl) {
627         List<QName> result = new ArrayList<QName>();
628         if (dnl != null) {
629             for (int i = 0; i < dnl.getLength(); i++) {
630                 // ak je to namespace
631                 if (dnl.getType(i) == DisplayableNameItemTypeEnum.ITEM_NAMESPACE)
632                     continue;
633                 final String ns = dnl.getDomNamespaceUri(i);
634                 final String prefix = xmlAccess.getNsManager()
635                         .getBestPrefix(ns);
636                 result.add(dnl.getDomQName(i, prefix));
637             }
638         }
639         return result;
640     }
641 
642     /***
643      * Returns DomPointer which points to place in node tree to textual node
644      * with id nodeID and after text prevText. If nodeID is null or is not valid
645      * id of textual node then null is returned.
646      * 
647      * @param nodeID
648      *            id of textual node in which returned pointer points
649      * @param prevText
650      *            text after which returned pointer points
651      * 
652      */
653     public DomPointer getPointer(String nodeID, String prevText) {
654         if (nodeID == null)
655             return null;
656         final NodeListID nodeList = xmlAccess.getIdManager().getNodeNull(nodeID);
657         if (nodeList == null)
658             return null;
659 
660         // if textual node
661         if (nodeList.isTextual()) {
662             int textValPos = nodeList.resolveIndex(prevText, prevText.length());
663             return nodeList.getPointer(textValPos, true, false);
664         }
665 
666         return null;
667     }
668 
669     /***
670      * Inserts text to end of element by dialog.
671      * 
672      * @param element
673      *            where to insert text node as child
674      * @param shell
675      *            parent window. !!! Cannot be <code>null</code>.
676      * @throws DocumentException
677      * @throws ExportException
678      */
679     public void insertTextNode(Shell shell, Element element)
680             throws DocumentException, ExportException {
681         if (checkInEntity(shell, element))
682             return;
683         InputDialog dialog = new InputDialog(shell, "Insert text node action",
684                 "Write text for text node:", "", null);
685 
686         if (dialog.open() == Window.OK) {
687             String inputStr = dialog.getValue().trim();
688             DocumentModifier dm = xmlAccess.getModifier();
689 
690             final DOMPoint ip = DomPointerFactory.create(element,
691                     (Node) null).ip;
692 
693             final String id = xmlAccess.getIdManager().getIDNull(element);
694             final Element contextElement=xmlAccess.getIdManager().getElement(id);
695             dm.startModify();
696             DOMMutils.insertText(ip, contextElement , inputStr, Node.TEXT_NODE);
697             dm.endModify();
698         }
699     }
700 
701     /***
702      * Modifies text of node by dialog.
703      * 
704      * @param node
705      *            must be text node
706      * @param shell
707      *            parent window. !!!Cannot be <code>null</code>.
708      * @throws ExportException
709      *             if document fails to transform.
710      */
711     public void modifyTextNode(Shell shell, Node node) throws ExportException {
712         if (shell == null)
713             throw new IllegalArgumentException("Shell cannot be null."); //$NON-NLS-1$
714         final InputDialog dialog = new InputDialog(Display.getCurrent()
715                 .getActiveShell(), "Modify text of node", "Modify text", node
716                 .getNodeValue(), null);
717         if (dialog.open() == Window.OK) {
718             xmlAccess.getUndoManager().mark();
719             final DocumentModifier dm = xmlAccess.getModifier();
720             dm.startModify();
721             dm.setText(node, dialog.getValue().trim());
722             dm.endModify();
723         }
724     }
725 
726     /***
727      * Checks if given node is in entity. If yes then error dialog is shown.
728      * 
729      * @param shell
730      *            parent, should not be <code>null</code>.
731      * @param node
732      *            node to check
733      * @return <code>true</code> if node is in entity, <code>false</code>
734      *         otherwise.
735      */
736     public boolean checkInEntity(final Shell shell, final Node node) {
737         if (DOMUtils.isInEntity(node)) {
738             MessageDialog.openInformation(shell, Messages.getString("ERROR"), //$NON-NLS-1$
739                     Messages.getString("CANNOT_MODIFY_ENTITY")); //$NON-NLS-1$
740             return true;
741         }
742         return false;
743     }
744 
745     /***
746      * Deletes given text node.
747      * 
748      * @param shell
749      *            parent window, should not be <code>null</code>.
750      * @param node
751      *            node to delete, must not be <code>null</code>. May be text,
752      *            cdata or pure-text entity only.
753      * @throws ExportException
754      *             if shit happens.
755      */
756     public void deleteTextNode(final Shell shell, final Node node)
757             throws ExportException {
758         if (!DOMUtils.isTextualNode(node))
759             throw new IllegalArgumentException("illegal node type");//$NON-NLS-1$
760         if (checkInEntity(shell, node)) {
761             return;
762         }
763         // verify if there are some neighbour elements. If yes, then the rule
764         // for this text node may be AnyString only hence it can be deleted.
765         final Element e = (Element) node.getParentNode();
766         if (!DOMUtils.containsElements(e)) {
767             // check if element's contents stays valid when we remove the node.
768             final ElementRule rule = xmlAccess.getSchema().getElementRule(e);
769             final IValueRule valRule = rule.getValueRule();
770             final String newValue = DOMUtils.getTextContents(e,
771                     new NodeFilter() {
772                         /*
773                          * (non-Javadoc)
774                          * 
775                          * @see org.w3c.dom.traversal.NodeFilter#acceptNode(org.w3c.dom.Node)
776                          */
777                         public short acceptNode(Node n) {
778                             if (node.isSameNode(n))
779                                 return NodeFilter.FILTER_REJECT;
780                             return NodeFilter.FILTER_ACCEPT;
781                         }
782                     });
783             if (!valRule.acceptsValue(newValue)) {
784                 // nope, new value not accepted. try to delete parent element.
785                 if (MessageDialog.openQuestion(shell, Messages
786                         .getString("QUESTION"), //$NON-NLS-1$
787                         Messages.getString("CANT_DELETE_TEXT"))) //$NON-NLS-1$
788                 {
789                     deleteElements(shell, Collections.singleton(e));
790                 }
791                 return;
792             }
793         }
794         // OK we may delete the text node.
795         xmlAccess.getUndoManager().mark();
796         final DocumentModifier dm = xmlAccess.getModifier();
797         dm.startModify();
798         dm.remove(node);
799         dm.endModify();
800     }
801 
802     /***
803      * Tries to delete a single node.
804      * 
805      * @param shell
806      *            parent shell, should not be <code>null</code>.
807      * @param node
808      *            node to delete.
809      * @throws ExportException
810      *             if shit happens.
811      */
812     public void deleteNode(final Shell shell, final Node node)
813             throws ExportException {
814         if (checkInEntity(shell, node))
815             return;
816         // attribute?
817         if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
818             deleteAttribute(shell, (Attr) node);
819             return;
820         }
821         // textual?
822         if (DOMUtils.isTextualEntity(node)) {
823             deleteTextNode(shell, node);
824             return;
825         }
826         // element?
827         if (node.getNodeType() == Node.ELEMENT_NODE) {
828             deleteElements(shell, Collections.singleton((Element) node));
829             return;
830         }
831         // entity ref?
832         if (node.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
833             final int index = DOMUtils.getNodeIndex(node);
834             final Element parent = (Element) node.getParentNode();
835             final ElementRule rule = xmlAccess.getSchema().getElementRule(
836                     parent);
837             final DOMPoint start = new DOMPoint(index, 0);
838             final DOMPoint end = new DOMPoint(index + 1, 0);
839             if (!rule.isDeletable(start, end)) {
840                 MessageDialog.openError(shell, Messages.getString("ERROR"), //$NON-NLS-1$
841                         Messages.getString("CANT_DELETE_ENTITY")); //$NON-NLS-1$
842                 return;
843             }
844         }
845         // if the node is not entityref, textual nor element then it may be
846         // removed right away.
847         xmlAccess.getUndoManager().mark();
848         final DocumentModifier dm = xmlAccess.getModifier();
849         dm.startModify();
850         dm.remove(node);
851         dm.endModify();
852     }
853 }