1 
2 /******************************************************************************
3 * MODULE     : edit_dynamic.cpp
4 * DESCRIPTION: editing dynamic content
5 * COPYRIGHT  : (C) 1999  Joris van der Hoeven
6 *******************************************************************************
7 * This software falls under the GNU general public license version 3 or later.
8 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
9 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
10 ******************************************************************************/
11 
12 #include "edit_dynamic.hpp"
13 #include "tree_analyze.hpp"
14 
15 /******************************************************************************
16 * Constructors and destructors
17 ******************************************************************************/
18 
edit_dynamic_rep()19 edit_dynamic_rep::edit_dynamic_rep () {}
~edit_dynamic_rep()20 edit_dynamic_rep::~edit_dynamic_rep () {}
21 
22 /******************************************************************************
23 * Subroutines for inactive content
24 ******************************************************************************/
25 
26 static bool env_locked   = false;
27 static bool env_in_source= false;
28 
29 bool
in_source()30 edit_dynamic_rep::in_source () {
31   // FIXME: we use a very dirty trick to "lock the environment",
32   // so that no new look-ups are made during modifications of et or tp.
33   // In fact, we would need to retypeset the document in order to
34   // get a correct value.
35   if (!env_locked)
36     env_in_source= get_env_string (MODE) == "src";
37   return env_in_source;
38 }
39 
40 path
find_dynamic(path p)41 edit_dynamic_rep::find_dynamic (path p) {
42   path parent= path_up (p);
43   if (!(rp < parent)) return path ();
44   if (drd->is_dynamic (subtree (et, parent))) return p;
45   return find_dynamic (parent);
46 }
47 
48 /******************************************************************************
49 * Making general compound objects
50 ******************************************************************************/
51 
52 bool
is_multi_paragraph_macro(tree t)53 edit_dynamic_rep::is_multi_paragraph_macro (tree t) {
54   int n= arity (t);
55   if (is_document (t) || is_func (t, PARA) || is_func (t, SURROUND))
56     return true;
57   if (is_func (t, MACRO) || is_func (t, WITH) ||
58       is_func (t, LOCUS) ||
59       is_func (t, CANVAS) || is_func (t, ORNAMENT))
60     return is_multi_paragraph_macro (t [n-1]);
61   if (is_extension (t) && (!is_compound (t, "footnote"))) {
62     int i;
63     for (i=1; i<n; i++)
64       if (is_multi_paragraph_macro (t[i]))
65 	return true;
66     tree f= get_env_value (t[0]->label);
67     return is_multi_paragraph_macro (f);
68   }
69   return false;
70 }
71 
72 static bool
contains_table_format(tree t,tree var)73 contains_table_format (tree t, tree var) {
74   // FIXME: this should go into the DRD
75   if (is_atomic (t)) return false;
76   else {
77     int i, n= N(t);
78     for (i=0; i<n; i++)
79       if (contains_table_format (t[i], var))
80 	return true;
81     return is_func (t, TFORMAT) && (t[N(t)-1] == tree (ARG, var));
82   }
83 }
84 
85 void
make_compound(tree_label l,int n=-1)86 edit_dynamic_rep::make_compound (tree_label l, int n= -1) {
87   //cout << "Make compound " << as_string (l) << ", " << n << "\n";
88   eval ("(use-modules (generic generic-edit))");
89   if (n == -1) {
90     for (n=0; true; n++) {
91       if (drd->correct_arity (l, n) &&
92 	  ((n>0) || (drd->get_arity_mode (l) == ARITY_NORMAL))) break;
93       if (n == 100) return;
94     }
95   }
96 
97   tree t (l, n);
98   path p (0, 0);
99   int  acc=0;
100   for (; acc<n; acc++)
101     if (drd->is_accessible_child (t, acc))
102       break;
103   if (acc<n) p->item= acc;
104   if (n == 0) insert_tree (t, 1);
105   else if (is_with_like (t) && as_bool (call ("with-like-check-insert", t)));
106   else {
107     string s= as_string (l);
108     tree f= get_env_value (s);
109     bool block_macro= (N(f) == 2) && is_multi_paragraph_macro (f);
110     bool table_macro= (N(f) == 2) && contains_table_format (f[1], f[0]);
111     // FIXME: why do we take the precaution N(f) == 2 ?
112     if (s == "explain") block_macro= true;
113 
114     tree sel= "";
115     if (selection_active_small () ||
116 	(block_macro && selection_active_normal ()))
117       sel= selection_get_cut ();
118     if ((block_macro && (!table_macro)) ||
119 	(l == make_tree_label ("footnote")))
120       {
121 	t[0]= tree (DOCUMENT, "");
122 	p   = path (0, 0, 0);
123       }
124     if (!drd->all_accessible (l))
125       if (get_init_string (MODE) != "src" && !inside ("show-preamble")) {
126         t= tree (INACTIVE, t);
127         p= path (0, p);
128       }
129     insert_tree (t, p);
130     if (table_macro) make_table (1, 1);
131     if (sel != "") insert_tree (sel, end (sel));
132 
133     tree mess= concat ();
134     if (drd->get_arity_mode (l) != ARITY_NORMAL)
135       mess= concat (kbd ("A-right"), ": insert argument");
136     if (!drd->all_accessible (l)) {
137       if (mess != "") mess << ", ";
138       mess << kbd ("return") << ": activate";
139     }
140     if (mess == concat ()) mess= "Move to the right when finished";
141     set_message (mess, drd->get_name (l));
142   }
143 }
144 
145 void
activate()146 edit_dynamic_rep::activate () {
147   path p= search_upwards (INACTIVE);
148   if (is_nil (p)) return;
149   tree st= subtree (et, p * 0);
150 
151   if (is_func (st, COMPOUND) && is_atomic (st[0])) {
152     tree u (make_tree_label (st[0]->label));
153     u << A (st (1, N(st)));
154     assign (p, u);
155     call ("notify-activated", object (subtree (et, p)));
156     go_to (end (et, p));
157     correct (path_up (p));
158   }
159   else {
160     bool acc= (p < path_up (tp) && drd->is_accessible_child (st, tp[N(p)]));
161     remove_node (p * 0);
162     call ("notify-activated", object (subtree (et, p)));
163     if (!acc) go_to (end (et, p));
164     correct (path_up (p));
165   }
166 }
167 
168 /******************************************************************************
169 * Inserting and removing arguments
170 ******************************************************************************/
171 
172 void
go_to_argument(path p,bool start_flag)173 edit_dynamic_rep::go_to_argument (path p, bool start_flag) {
174   tree t= subtree (et, path_up (p));
175   bool inactive= is_func (subtree (et, path_up (p, 2)), INACTIVE);
176   int i= last_item (p), n= N(t);
177   if (i < 0) go_to_start (path_up (p, inactive? 2: 1));
178   else if (i >= n) go_to_end (path_up (p, inactive? 2: 1));
179   else {
180     if ((!drd->is_accessible_child (t, i)) &&
181 	(!inactive) && (!in_source ()))
182       {
183 	insert_node (path_up (p) * 0, INACTIVE);
184         call ("notify-disactivated", object (subtree (et, path_up (p) * 0)));
185 	p= path_up (p) * path (0, i);
186       }
187     if (start_flag) go_to_start (p);
188     else go_to_end (p);
189   }
190 }
191 
192 void
insert_argument(path p,bool forward)193 edit_dynamic_rep::insert_argument (path p, bool forward) {
194   tree t= subtree (et, path_up (p));
195   int i= last_item (p), n= N(t), d= 1;
196   if (is_func (t, WITH) ||
197       is_func (t, STYLE_WITH) ||
198       is_func (t, VAR_STYLE_WITH))
199     if (i == n-1) i--;
200   if ((!in_source ()) || drd->contains (as_string (L(t)))) {
201     if (forward) do i++; while ((i<=n) && (!drd->insert_point (L(t), i, n)));
202     else while ((i>=0) && (!drd->insert_point (L(t), i, n))) i--;
203     if ((i<0) || (i>n)) return;
204     while (!drd->correct_arity (L(t), n+d)) d++;
205   }
206   else if (forward) i++;
207   path q= path_up (p) * i;
208   tree ins (L(t), d);
209   insert (q, ins);
210   go_to_argument (q, forward);
211 }
212 
213 void
insert_argument(bool forward)214 edit_dynamic_rep::insert_argument (bool forward) {
215   path p= find_dynamic (tp);
216   if (is_nil (p)) return;
217   if (p == tp) p= find_dynamic (path_up (tp));
218   if (is_nil (p)) return;
219   insert_argument (p, forward);
220 }
221 
222 void
remove_empty_argument(path p,bool forward)223 edit_dynamic_rep::remove_empty_argument (path p, bool forward) {
224   tree t= subtree (et, path_up (p));
225   int i= last_item (p), j, d, n= N(t);
226   bool src_flag= in_source () && (!drd->contains (as_string (L(t))));
227 
228   for (d=1; d<=n-i; d++)
229     if ((src_flag && (d==1)) ||
230 	((!src_flag) &&
231 	 drd->correct_arity (L(t), n-d) &&
232 	 drd->insert_point (L(t), i, n-d)))
233       {
234 	bool flag= true;
235 	for (j=0; j<d; j++)
236 	  flag= flag && is_empty (t[i+j]);
237 	if (flag) {
238 	  bool old_locked= env_locked; env_locked= true;
239 	  remove (p, d);
240 	  if ((d == n) && is_mod_active_once (subtree (et, path_up (p, 2)))) {
241 	    remove_node (path_up (p, 2) * 0);
242 	    go_to_border (path_up (p, 2), forward);
243 	  }
244 	  else if (forward) go_to_argument (path_up (p) * i, true);
245 	  else go_to_argument (path_up (p) * (i-1), false);
246 	  env_locked= old_locked;
247 	  return;
248 	}
249 	else break;
250       }
251 
252   bool flag= true;
253   for (j=0; j<n; j++)
254     flag= flag && is_empty (t[j]);
255   if (flag) {
256     assign (path_up (p), "");
257     tree st= subtree (et, path_up (p, 2));
258     if ((is_mod_active_once (st) || is_compound (st, "doc-inactive")) &&
259 	(st[0] == "")) {
260       assign (path_up (p, 2), "");
261       correct (path_up (p, 3));
262     }
263     // FIXME: temporary hack for doc-data and doc-author-data
264     else if (is_compound (st, "doc-data")      ||
265 	     is_compound (st, "abstract-data") ||
266 	     is_compound (st, "doc-author")    ||
267              is_compound (st, "author-data")) {
268       if (N(st)==1) {
269 	assign (path_up (p, 2), "");
270 	correct (path_up (p, 3));
271       }
272       else {
273 	int i= last_item (path_up (p)) + (forward? 0: -1);
274 	remove (path_up (p), 1);
275 	if (i<0) go_to_start (path_up (p, 2));
276 	else go_to_border (path_up (p, 2) * i, forward);
277       }
278     }
279     else correct (path_up (p, 2));
280     return;
281   }
282 
283   if (forward) go_to_argument (path_up (p) * (i+1), true);
284   else go_to_argument (path_up (p) * (i-1), false);
285 }
286 
287 void
remove_argument(path p,bool forward)288 edit_dynamic_rep::remove_argument (path p, bool forward) {
289   tree t= subtree (et, path_up (p));
290   int i= last_item (p), n= N(t), d= 1;
291   if ((!in_source ()) || drd->contains (as_string (L(t)))) {
292     if (forward) do i++; while (i<=n && !drd->insert_point (L(t), i, n));
293     else while (i>=0 && !drd->insert_point (L(t), i, n)) i--;
294     if ((i<0) || (i>n)) return;
295     while (i>=d && !drd->correct_arity (L(t), n-d)) d++;
296     if (i<d || n<=d || !drd->insert_point (L(t), i-d, n-d)) return;
297   }
298   else {
299     if (forward) i++;
300     if (i<d || n<=d) return;
301   }
302   path q= path_up (p) * (i-d);
303   remove (q, d);
304   go_to_argument (q, forward);
305 }
306 
307 void
remove_argument(bool forward)308 edit_dynamic_rep::remove_argument (bool forward) {
309   path p= find_dynamic (tp);
310   if (is_nil (p)) return;
311   if (p == tp) p= find_dynamic (path_up (tp));
312   if (is_nil (p)) return;
313   remove_argument (p, forward);
314 }
315 
316 /******************************************************************************
317 * Backspace and delete
318 ******************************************************************************/
319 
320 void
back_monolithic(path p)321 edit_dynamic_rep::back_monolithic (path p) {
322   if (!is_concat (subtree (et, path_up (p)))) assign (p, "");
323   else remove (p, 1);
324   correct (path_up (p));
325 }
326 
327 void
back_general(path p,bool forward)328 edit_dynamic_rep::back_general (path p, bool forward) {
329   tree st= subtree (et, p);
330   int n= N(st);
331   if ((L(st) >= START_EXTENSIONS) && in_source () && (forward || (n == 0))) {
332     tree u (COMPOUND, copy (as_string (L(st))));
333     u << copy (A (st));
334     assign (p, u);
335     go_to_border (p * 0, forward);
336   }
337   else if (n==0) back_monolithic (p);
338   else if ((n==1) && is_func (st[0], DOCUMENT, 1) &&
339 	   (is_func (st[0][0], TFORMAT) || is_func (st[0][0], TABLE)))
340     back_table (p * path (0, 0), forward);
341   else if ((n==1) && (is_func (st[0], TFORMAT) || is_func (st[0], TABLE)))
342     back_table (p * 0, forward);
343   else go_to_argument (p * (forward? 0: n-1), forward);
344 }
345 
346 void
back_in_general(tree t,path p,bool forward)347 edit_dynamic_rep::back_in_general (tree t, path p, bool forward) {
348   if (is_func (subtree (et, path_up (p, 2)), INACTIVE) || in_source ())
349     if ((L(t) >= START_EXTENSIONS) && (last_item (p) == 0) && (!forward)) {
350       bool src_flag= in_source () && (!drd->contains (as_string (L(t))));
351       tree u (COMPOUND, copy (as_string (L(t))));
352       if (is_empty (t[0]) && src_flag) u << A (copy (t (1, N(t))));
353       else u << A (copy (t));
354       assign (path_up (p), u);
355       go_to_end (p);
356       return;
357     }
358   remove_empty_argument (p, forward);
359 }
360 
361 /******************************************************************************
362 * The WITH tag
363 ******************************************************************************/
364 
365 static tree
remove_changes_in(tree t,string var)366 remove_changes_in (tree t, string var) {
367   if (is_atomic (t)) return t;
368   else if (is_func (t, WITH)) {
369     int i, n=N(t), k=(n-1)>>1;
370     if (k==1) {
371       tree r= remove_changes_in (t[2], var);
372       if (t[0] != var) r= tree (WITH, t[0], t[1], r);
373       return simplify_correct (r);
374     }
375     tree r (WITH);
376     for (i=0; i<k; i++)
377       if (t[i<<1] != var) r << t[i<<1] << t[(i<<1)+1];
378     r << remove_changes_in (t[i<<1], var);
379     return simplify_correct (r);
380   }
381   else if (is_format (t) || is_func (t, SURROUND)) {
382     int i, n= N(t);
383     tree r (t, n);
384     for (i=0; i<n; i++)
385       r[i]= remove_changes_in (t[i], var);
386     return simplify_correct (r);
387   }
388   else return t;
389 }
390 
391 void
make_with(string var,string val)392 edit_dynamic_rep::make_with (string var, string val) {
393   if (selection_active_normal ()) {
394     tree t= remove_changes_in (selection_get (), var);
395     selection_cut ();
396     insert_tree (tree (WITH, var, val, t), path (2, end (t)));
397   }
398   else insert_tree (tree (WITH, var, val, ""), path (2, 0));
399 }
400 
401 void
insert_with(path p,string var,tree val)402 edit_dynamic_rep::insert_with (path p, string var, tree val) {
403   tree st= subtree (et, p);
404   if (is_func (st, WITH)) {
405     int i, n= N(st)-1;
406     for (i=0; i<n; i+=2)
407       if (st[i] == var) {
408 	assign (p * (i+1), copy (val));
409 	return;
410       }
411     insert (p * n, copy (tree (WITH, var, val)));
412   }
413   else if ((rp < p) && is_func (subtree (et, path_up (p)), WITH))
414     insert_with (path_up (p), var, val);
415   else insert_node (p * 2, copy (tree (WITH, var, val)));
416 }
417 
418 void
remove_with(path p,string var)419 edit_dynamic_rep::remove_with (path p, string var) {
420   tree st= subtree (et, p);
421   if (is_func (st, WITH)) {
422     int i, n= N(st)-1;
423     for (i=0; i<n; i+=2)
424       if (st[i] == var) {
425 	remove (p * i, 2);
426 	if (n == 2) remove_node (p * 0);
427 	return;
428       }
429   }
430   else if ((rp < p) && is_func (subtree (et, path_up (p)), WITH))
431     remove_with (path_up (p), var);
432 }
433 
434 void
back_in_with(tree t,path p,bool forward)435 edit_dynamic_rep::back_in_with (tree t, path p, bool forward) {
436   if (is_func (subtree (et, path_up (p, 2)), INACTIVE) ||
437       ((is_func (t, WITH) || is_func (t, LOCUS)) && in_source ()))
438     back_in_general (t, p, forward);
439   else if (t[N(t)-1] == "") {
440     assign (path_up (p), "");
441     correct (path_up (p, 2));
442   }
443   else go_to_border (path_up (p), !forward);
444 }
445 
446 /******************************************************************************
447 * Style file editing
448 ******************************************************************************/
449 
450 void
make_mod_active(tree_label l)451 edit_dynamic_rep::make_mod_active (tree_label l) {
452   if (selection_active_normal ()) {
453     tree t= selection_get ();
454     selection_cut ();
455     insert_tree (tree (l, t), path (0, end (t)));
456   }
457   else if ((l == VAR_STYLE_ONLY) || (l == VAR_ACTIVE) || (l == VAR_INACTIVE))
458     insert_tree (tree (l, ""), path (0, 0));
459   else {
460     path p= path_up (tp);
461     if (is_atomic (subtree (et, p))) p= path_up (p);
462     if (rp < p) insert_node (p * 0, l);
463   }
464 }
465 
466 void
insert_style_with(path p,string var,string val)467 edit_dynamic_rep::insert_style_with (path p, string var, string val) {
468   if (!(rp < p)) return;
469   tree st= subtree (et, path_up (p));
470   if (is_func (st, STYLE_WITH)) {
471     int i, n= N(st);
472     for (i=n-1; i>=0; i-=2)
473       if (st[i] == var) {
474 	assign (path_up (p) * (i+1), copy (val));
475 	return;
476       }
477     insert (path_up (p) * (n-1), tree (STYLE_WITH, copy (var), copy (val)));
478   }
479   else insert_node (p * 2, copy (tree (STYLE_WITH, var, val)));
480 }
481 
482 void
make_style_with(string var,string val)483 edit_dynamic_rep::make_style_with (string var, string val) {
484   if (selection_active_normal ()) {
485     tree t= selection_get ();
486     selection_cut ();
487     if (subtree (et, path_up (tp)) == "") {
488       insert_style_with (path_up (tp), var, val);
489       insert_tree (t);
490     }
491     else insert_tree (tree (STYLE_WITH, var, val, t), path (2, end (t)));
492   }
493   else insert_style_with (path_up (tp), var, val);
494 }
495 
496 /******************************************************************************
497 * The HYBRID and LATEX tags
498 ******************************************************************************/
499 
500 void
make_hybrid()501 edit_dynamic_rep::make_hybrid () {
502   tree t (HYBRID, "");
503   if (selection_active_small ())
504     t[0]= selection_get_cut ();
505   if (is_func (t, HYBRID, 1) && (t[0] != "") &&
506       (!(is_atomic (t[0]) && drd->contains (t[0]->label))))
507     t= tree (HYBRID, "", t[0]);
508   path p= end (t, path (0));
509   if (in_source ()) insert_tree (t, p);
510   else insert_tree (tree (INACTIVE, t), path (0, p));
511   set_message (concat (kbd ("return"), ": activate symbol or macro"),
512 	       "hybrid");
513 }
514 
515 bool
activate_latex()516 edit_dynamic_rep::activate_latex () {
517   path p= search_upwards (LATEX);
518   if (is_nil (p)) p= search_upwards (HYBRID);
519   if (is_nil (p)) return false;
520   tree st= subtree (et, p);
521   if (is_atomic (st[0])) {
522     if (is_func (subtree (et, path_up (p)), INACTIVE))
523       p= path_up (p);
524     string  s= st[0]->label, help;
525     command cmd;
526     if (kbd_get_command (s, help, cmd)) {
527       cut (p * 0, p * 1);
528       cmd ();
529       if (N(st) == 2) insert_tree (copy (st[1]));
530       return true;
531     }
532     set_message ("Error: not a command name",
533 		 "activate latex command");
534   }
535   return false;
536 }
537 
538 void
activate_hybrid(bool with_args_hint)539 edit_dynamic_rep::activate_hybrid (bool with_args_hint) {
540   // WARNING: update edit_interface_rep::set_hybrid_footer when updating this
541   if (activate_latex ()) return;
542   set_message ("", "");
543   path p= search_upwards (HYBRID);
544   if (is_nil (p)) return;
545   tree st= subtree (et, p);
546   if (is_compound (st[0])) return;
547   if (is_func (subtree (et, path_up (p)), INACTIVE))
548     p= path_up (p);
549 
550   // activate macro argument
551   string name= st[0]->label;
552   path mp= search_upwards (MACRO);
553   if (is_nil (mp)) mp= search_upwards ("edit-macro");
554   if (!is_nil (mp)) {
555     tree mt= subtree (et, mp);
556     int i, n= N(mt)-1;
557     for (i=0; i<n; i++)
558       if (mt[i] == name) {
559 	assign (p, tree (ARG, copy (name)));
560 	go_to (end (et, p));
561 	correct (path_up (p));
562 	return;
563       }
564   }
565 
566   // built-in primitives, macro applications and values
567   bool old_locked= env_locked; env_locked= true;
568   tree f= get_env_value (name);
569   if ((drd->contains (name) && (f == UNINIT)) ||
570       is_func (f, MACRO) || is_func (f, XMACRO)) {
571     assign (p, "");
572     correct (path_up (p));
573     make_compound (make_tree_label (name));
574     if (N(st) == 2) insert_tree (st[1]);
575   }
576   else if (f != UNINIT) {
577     assign (p, tree (VALUE, copy (name)));
578     go_to (end (et, p));
579     correct (path_up (p));
580   }
581   else if (in_source ()) {
582     assign (p, "");
583     correct (path_up (p));
584     make_compound (make_tree_label (name), with_args_hint? 1: 0);
585     if (N(st) == 2) insert_tree (st[1]);
586   }
587   else set_message ("Error: unknown command",
588 		    "activate hybrid command");
589   env_locked= old_locked;
590 }
591 
592 /******************************************************************************
593 * Other special tags (SYMBOL and COMPOUND)
594 ******************************************************************************/
595 
596 void
activate_symbol()597 edit_dynamic_rep::activate_symbol () {
598   path p= search_upwards (SYMBOL);
599   if (is_nil (p)) return;
600   tree st= subtree (et, p);
601   if (is_func (subtree (et, path_up (p)), INACTIVE))
602     p= path_up (p);
603   string s= st[0]->label;
604   if (is_int (s))
605     assign (p, string (((char) as_int (s))));
606   else {
607     int i, n= N(s);
608     for (i=0; i<n; i++)
609       if ((s[i]=='<') || (s[i]=='>'))
610 	{ s= ""; break; }
611     assign (p, "<" * s * ">");
612   }
613   go_to (end (et, p));
614   correct (path_up (p));
615 }
616 
617 /******************************************************************************
618 * Temporary fixes for block structures
619 ******************************************************************************/
620 
621 bool
make_return_before()622 edit_dynamic_rep::make_return_before () {
623   bool flag;
624   path q= tp;
625   while (!is_document (subtree (et, path_up (q)))) q= path_up (q);
626   flag= (N (subtree (et, path_up (q))) == (q->item+1)) || (tp != end (et, q));
627   if (flag) {
628     flag= insert_return ();
629     go_to (end (et, q));
630   }
631   return flag;
632 }
633 
634 bool
make_return_after()635 edit_dynamic_rep::make_return_after () {
636   path q= tp;
637   while (!is_document (subtree (et, path_up (q)))) {
638     q= path_up (q);
639     if (!(rp < q)) return false;
640   }
641   if (tp == start (et, q)) return false;
642   return insert_return ();
643 }
644 
645 void
temp_proof_fix()646 edit_dynamic_rep::temp_proof_fix () {
647   /* this routine should be removed as soon as possible */
648   path p = search_upwards ("proof");
649   if (is_nil (p) || (N(tp) < N(p)+2)) return;
650   path q = head (tp, N(p)+2);
651   tree st= subtree (et, path_up (q));
652   if ((!is_document (st)) || (last_item (q) != (N(st)-1))) return;
653   insert (path_inc (q), tree (DOCUMENT, ""));
654 }
655