PageRenderTime 57ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/source/src/Edit/Modify/edit_dynamic.cpp

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