1 ;;; kimport.el --- Convert and insert other outline file formats into koutlines.
3 ;; Copyright (C) Free Software Foundation, Inc.
4 ;; Developed with support from Motorola Inc.
6 ;; Author: Bob Weiner, Brown U.
8 ;; Maintainer: Mats Lidell <matsl@contactor.se>
9 ;; Keywords: data, outlines, wp
11 ;; This file is part of GNU Hyperbole.
13 ;; GNU Hyperbole is free software; you can redistribute it and/or
14 ;; modify it under the terms of the GNU General Public License as
15 ;; published by the Free Software Foundation; either version 3, or (at
16 ;; your option) any later version.
18 ;; GNU Hyperbole is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 ;; General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs; see the file COPYING. If not, write to the
25 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
26 ;; Boston, MA 02110-1301, USA.
32 ;; kfile.el requires kotl-mode.el which requires kimport.el.
39 ;; kimport:mode-alist and kimport:suffix-alist are defined in
47 (defun kimport:file (import-from output-to &optional children-p)
48 "Import a buffer or file IMPORT-FROM into the koutline in buffer or file OUTPUT-TO.
50 Any suffix in IMPORT-FROM's buffer name is used to determine the type of
51 importation. All others are imported as text, one paragraph per cell.
53 See the documentation for the variable, `kimport:suffix-alist' for
54 information on specific importation formats."
55 (interactive "FImport from buffer/file: \nFInsert into koutline buffer/file: \nP")
56 (let ((import-buf-name
57 (cond ((or (bufferp import-from)
58 (get-buffer import-from))
59 (buffer-name (get-buffer import-from)))
60 ((get-file-buffer import-from)
61 (buffer-name (get-file-buffer import-from)))
62 ((stringp import-from)
63 (file-name-nondirectory import-from))
64 (t (error "(kimport:buffer): `%s' is an invalid `import-from' argument"))))
67 (set-buffer import-buf-name)
68 (if (setq function (cdr (assq major-mode kimport:mode-alist)))
70 (let ((import-suffix (if (string-match "\\..+\\'" import-buf-name)
71 (match-string 0 import-buf-name)))
72 (suffix-alist kimport:suffix-alist)
74 (while (and import-suffix suffix-alist)
75 (setq suffix-regexp (car (car suffix-alist))
76 function (cdr (car suffix-alist))
77 suffix-alist (cdr suffix-alist))
78 (if (string-match suffix-regexp import-suffix)
81 (if function nil (setq function (cdr (assq t kimport:mode-alist))))))
82 (funcall function import-from output-to children-p)))
84 ;;; Augment right-side numbered files, blank line between cells
88 (defun kimport:aug-post-outline (import-from output-to &optional children-p)
89 "Insert Augment outline statements from IMPORT-FROM into koutline OUTPUT-TO.
90 Displays and leaves point in OUTPUT-TO. See documentation for
91 `kimport:initialize' for valid values of IMPORT-FROM and OUTPUT-TO and for
92 an explanation of where imported cells are placed.
94 If OUTPUT-TO is a new koutline, the first statement inserted will be the
95 first cell. Otherwise, it will be the successor of the current cell.
97 Each statement to be imported is delimited by an Augment relative id at the
98 end of the statement. \"1\" = level 1, \"1a\" = level 2 in outline and so
100 (interactive "FImport from Augment post-numbered buffer/file: \nFBuffer/file to insert cells into: \nP")
101 (let ((output-level 1) (klabel "1")
102 initially-empty-output no-renumber orig-point count total)
103 ;; Don't change the order of import-from and output-to inits here.
104 (setq import-from (kimport:copy-and-set-buffer import-from)
105 output-to (kimport:initialize output-to)
107 initially-empty-output (zerop (- (point-max) (point-min)))
108 no-renumber (or initially-empty-output
111 (kcell-view:sibling-p)))))
113 (if (eq import-from output-to)
114 (error "(kimport:aug-post-outline): Import and output buffers may not be the same."))
116 (set-buffer import-from)
119 (goto-char (point-min))
120 ;; Total number of Augement statements.
121 (setq total (read (count-matches
122 " +\\([0-9][0-9a-z]*\\)\n\\(\n\\|\\'\\)")))
123 (if initially-empty-output
125 ;; Insert first cell as sibling of current cell.
126 (set-buffer output-to)
128 ;; Insert as children.
129 (progn (setq klabel (klabel:child (kcell-view:label))
130 output-level (klabel:level klabel))
131 ;; Move to end of this cell since cell insertion will
133 (goto-char (kcell-view:end)))
134 ;; Insert as successors.
135 (setq klabel (klabel:increment (kcell-view:label))
136 output-level (klabel:level klabel))
137 ;; Move to start of line of next tree since cell insertion will occur
139 (goto-char (kotl-mode:tree-end))))
140 (setq count (kimport:aug-post-statements
141 import-from output-to klabel output-level 1 0 total)))
142 (pop-to-buffer output-to)
143 (kfile:narrow-to-kcells)
144 (if no-renumber nil (klabel-type:update-labels klabel))
145 (goto-char orig-point)
146 (if (kotl-mode:buffer-empty-p)
148 (kotl-mode:to-valid-position))
149 (message "Imported %d of %d Augment statements." count total)))
152 ;;; Emacs outliner style files, leading '*' cell delimiters
156 (defun kimport:star-outline (import-from output-to &optional children-p)
157 "Insert star outline nodes from IMPORT-FROM into koutline OUTPUT-TO.
158 Displays and leaves point in OUTPUT-TO. See documentation for
159 `kimport:initialize' for valid values of IMPORT-FROM and OUTPUT-TO and for
160 an explanation of where imported cells are placed.
162 \"* \" = level 1, \"** \" = level 2 in outline and so on."
163 (interactive "FImport from star delimited cells buffer/file: \nFBuffer/file to insert cells into: \nP")
164 (let ((output-level 1) (klabel "1")
165 initially-empty-output no-renumber orig-point count total)
166 ;; Don't change the order of import-from and output-to inits here.
167 (setq import-from (kimport:copy-and-set-buffer import-from)
168 output-to (kimport:initialize output-to)
170 initially-empty-output (zerop (- (point-max) (point-min)))
171 no-renumber (or initially-empty-output
174 (kcell-view:sibling-p)))))
176 (if (eq import-from output-to)
177 (error "(kimport:star-outline): Import and output buffers may not be the same."))
179 (set-buffer import-from)
182 (goto-char (point-min))
183 ;; If initial text in buffer is not an star outline node, add a star to
184 ;; make it one, so it is not deleted from the import.
185 (if (not (looking-at "[ \t]*\\*"))
187 (goto-char (point-min))
188 ;; Total number of top-level cells.
189 (setq total (read (count-matches "^[ \t]*\\*[ \t\n]")))
190 (if initially-empty-output
192 ;; Insert first cell as sibling of current cell.
193 (set-buffer output-to)
195 ;; Insert as children.
196 (progn (setq klabel (klabel:child (kcell-view:label))
197 output-level (klabel:level klabel))
198 ;; Move to end of this cell since cell insertion will
200 (goto-char (kcell-view:end)))
201 ;; Insert as successors.
202 (setq klabel (klabel:increment (kcell-view:label))
203 output-level (klabel:level klabel))
204 ;; Move to start of line of next tree since cell insertion will occur
206 (goto-char (kotl-mode:tree-end))))
207 (setq count (kimport:star-entries
208 import-from output-to klabel output-level 1 0 total)))
209 (pop-to-buffer output-to)
210 (kfile:narrow-to-kcells)
211 (if no-renumber nil (klabel-type:update-labels klabel))
212 (goto-char orig-point)
213 (if (kotl-mode:buffer-empty-p)
215 (kotl-mode:to-valid-position))
216 (message "Imported %d of %d star outline trees." count total)))
219 ;;; Generic text file import or koutline insertion.
223 (defun kimport:text (import-from output-to &optional children-p)
224 "Insert text paragraphs from IMPORT-FROM into koutline OUTPUT-TO.
225 Displays and leaves point in OUTPUT-TO. See documentation for
226 `kimport:initialize' for valid values of IMPORT-FROM and OUTPUT-TO and for
227 an explanation of where imported cells are placed.
229 Text paragraphs are imported as a sequence of same level cells. Koutlines
230 are imported with their structure intact.
232 The variable, 'paragraph-start,' is used to determine paragraphs."
233 (interactive "FImport from text/koutline buffer/file: \nFInsert cells into koutline buffer/file: \nP")
234 (let ((klabel "1") (output-level 1) (count 0) initially-empty-output
235 no-renumber orig-point total)
236 ;; Don't change the order of import-from and output-to inits here.
237 (setq import-from (kimport:copy-and-set-buffer import-from)
238 output-to (kimport:initialize output-to)
240 initially-empty-output (zerop (- (point-max) (point-min)))
241 no-renumber (or initially-empty-output
244 (kcell-view:sibling-p)))))
246 (if (eq import-from output-to)
247 (error "(kimport:text): Import and output buffers may not be the same."))
249 (set-buffer import-from)
250 (let ((kotl-import (eq major-mode 'kotl-mode))
253 (if initially-empty-output
255 ;; Insert first cell as sibling of current cell.
256 (set-buffer output-to)
258 ;; Insert as children.
259 (progn (setq klabel (klabel:child (kcell-view:label))
260 output-level (klabel:level klabel))
261 ;; Move to end of this cell since cell insertion will
263 (goto-char (kcell-view:end)))
264 ;; Insert as successors.
265 (setq klabel (klabel:increment (kcell-view:label))
266 output-level (klabel:level klabel))
267 ;; Move to start of line of next tree since cell insertion will occur
269 (goto-char (kotl-mode:tree-end)))
270 (set-buffer import-from))
273 ;; Importing from a koutline, so handle specially.
274 (progn (kotl-mode:beginning-of-buffer)
275 ;; Total number of cells.
276 (setq total (read (count-matches "[\n\r][\n\r]"))
277 visible-cells (read (count-matches "\n\n"))
278 count (save-excursion
279 ;; Incredible non-local exit to ensure that
280 ;; recursion ends at the right time.
282 (kimport:kcells import-from output-to klabel
287 (goto-char (point-min))
288 ;; Total number of paragraphs.
289 (setq total (read (count-matches paragraph-start))
290 count (kimport:text-paragraphs import-from output-to klabel
291 output-level count total))))
292 (pop-to-buffer output-to)
293 (kfile:narrow-to-kcells)
294 (if no-renumber nil (klabel-type:update-labels klabel))
295 (goto-char orig-point)
296 (if (kotl-mode:buffer-empty-p)
298 (kotl-mode:to-valid-position))
300 (message "Imported %d of %d visible cells from a %d cell outline."
301 count visible-cells total)
302 (message "Imported %d of %d paragraphs." count total)))))
305 ;;; Private functions - Don't call these functions from outside of this
306 ;;; module or you may misuse them and cause data corruption.
309 (defun kimport:aug-label-lessp (label1 label2)
310 "Return non-nil iff Augment-style LABEL1 is less than LABEL2."
311 (let ((lev1 (klabel:level-alpha label1))
312 (lev2 (klabel:level-alpha label2)))
313 (cond ((< lev1 lev2))
314 ((= lev1 lev2) (string-lessp label1 label2))
317 (defun kimport:aug-post-statements (import-from output-to klabel output-level
318 import-level count total)
319 "Insert post-numbered Augment statements (contents only) from IMPORT-FROM into existing OUTPUT-TO.
321 KLABEL is the label to use for the first imported statement.
322 OUTPUT-LEVEL is the level at which to insert the first statement.
323 IMPORT-LEVEL is the depth of the current statement in the import file,
326 COUNT of inserted cells starts at 0. TOTAL is the total number of statements
327 in IMPORT-FROM, used to show a running tally of the imported statements."
328 (set-buffer import-from)
329 (let ((cell-end-regexp " +\\([0-9][0-9a-z]*\\)\n\\(\n+\\|\\'\\)")
330 contents start subtree-p end end-contents statement-level
332 ;; While find cells at import-level or deeper ...
333 (while (and (setq start (point))
334 (re-search-forward cell-end-regexp nil t)
336 (setq statement-level
339 (match-beginning 1) (match-end 1))))))
340 (setq end-contents (match-beginning 0)
343 (skip-chars-forward " ")
344 (setq contents (kimport:unindent-region (point) end-contents))
346 (setq subtree-p (save-excursion
347 (if (re-search-forward cell-end-regexp nil t)
351 (match-beginning 1) (match-end 1)))))))
353 (set-buffer output-to)
354 ;; Add the cell starting at point.
355 (kview:add-cell klabel output-level contents nil t)
356 (if subtree-p (setq child-label (klabel:child klabel)))
357 (message "%d of %d statements converted..."
358 (setq count (1+ count)) total)
359 (setq klabel (klabel:increment klabel)))
361 ;; Current buffer returns to `import-from' here.
362 ;; Handle each sub-level through recursion.
364 ;; Subtree exists so insert its cells.
366 (kimport:aug-post-statements
367 import-from output-to child-label (1+ output-level)
368 (1+ import-level) count total))))
372 (defun kimport:copy-and-set-buffer (source)
373 "Copy and untabify SOURCE, set copy buffer as current buffer for this command and return the copy buffer.
374 SOURCE may be a buffer name, a buffer or a file name.
375 If SOURCE buffer name begins with a space, it is not copied under the
376 assumption that it already has been. If SOURCE is a koutline, it is not
377 copied since there is no need to copy it to import it."
378 ;; This buffer name format is used so that we can easily
379 ;; extract any file name suffix from the buffer name.
380 (setq source (set-buffer (or (get-buffer source)
381 (find-file-noselect source))))
382 (let ((mode (or (if (boundp 'kotl-previous-mode) kotl-previous-mode)
385 (if (or (eq mode 'kotl-mode)
386 (= ?\ (aref (buffer-name source) 0)))
388 (setq copy (get-buffer-create
389 (concat " " (if (string-match ".+[|<]" (buffer-name))
390 (substring (buffer-name)
391 0 (1- (match-end 0)))
394 (setq buffer-read-only nil
397 (insert-buffer source)
398 (untabify (point-min) (point-max))
399 ;; Ensure buffer ends with a newline so that we don't miss the last
400 ;; element during the import.
401 (goto-char (point-max))
402 (if (/= (preceding-char) ?\n) (insert "\n"))
403 (set-buffer-modified-p nil)
406 (defun kimport:initialize (output-to)
407 "Setup to import elements into koutline OUTPUT-TO.
408 Return OUTPUT-TO buffer and set current buffer for the current command
411 OUTPUT-TO may be a buffer, buffer-name or file name. If OUTPUT-TO exists
412 already, it must be a koutline or an error will be signaled. For an existing
413 OUTPUT-TO, the text cells are inserted after the cell at point or after the
414 first cell for a newly loaded koutline. If OUTPUT-TO is nil, the current
417 If OUTPUT-TO is an existing koutline, the first cell imported will be added
418 as the successor of the current cell. If an existing file is read in as
419 OUTPUT-TO within this function, point is left at the end of this buffer so
420 that imported cells will be appended to the buffer. For a new file, this
421 means the first cell imported will become the first outline cell.
423 If a non-nil third argument, CHILDREN-P, is given to the caller of this
424 function and OUTPUT-TO contains at least one cell, then the imported cells
425 will be added as children of the cell where this function leaves point
426 \(either the current cell or for a newly read in outline, the last cell)."
427 (let* ((output-existing-buffer-p
429 (or (get-buffer output-to) (get-file-buffer output-to))))
432 (or output-existing-buffer-p (file-exists-p output-to))
433 ;; current buffer will be used for output and it exists.
435 (setq output-to (if output-to
436 (or (get-buffer output-to)
437 (find-file-noselect output-to))
439 (set-buffer output-to)
441 (if (eq major-mode 'kotl-mode)
442 (if (kotl-mode:buffer-empty-p)
444 ;; Make imported cells be appended if the output buffer was
446 (if output-existing-buffer-p nil (goto-char (point-max)))
447 (kotl-mode:to-valid-position))
449 "(kimport:initialize): Second arg, %s, must be a koutline file."
450 (buffer-name output-to)))
451 (if (eq major-mode 'kotl-mode)
455 (delete-region (point-min) (point-max))))
458 (defun kimport:kcells (import-from output-to klabel output-level
459 import-level count total)
460 "Insert visible koutline cells (contents and attributes) from IMPORT-FROM into existing OUTPUT-TO.
462 KLABEL is the label to use for the first imported cell.
463 OUTPUT-LEVEL is the level at which to insert the first cell.
464 IMPORT-LEVEL is the depth of the current cell in the import file,
467 COUNT of inserted cells starts at 0. TOTAL is the total number of cells
468 in IMPORT-FROM, used to show a running tally of the imported cells."
469 (set-buffer import-from)
470 (goto-char (kcell-view:start))
471 (let ((again t) contents subtree-p child-label)
472 ;; While find cells at import-level or deeper ...
473 (while (<= import-level (kcell-view:level))
474 (setq subtree-p (kcell-view:child-p nil t)
475 contents (kcell-view:contents))
476 (goto-char (kcell-view:end-contents))
478 (set-buffer output-to)
479 ;; Add the cell starting at point.
480 (kview:add-cell klabel output-level contents nil t)
481 (if subtree-p (setq child-label (klabel:child klabel)))
482 (message "%d of %d cells inserted..."
483 (setq count (1+ count)) total)
484 (setq klabel (klabel:increment klabel)))
486 ;; Current buffer returns to `import-from' here.
487 ;; Handle each sub-level through recursion.
488 (if (and (setq again (kcell-view:next t)) subtree-p)
489 ;; Subtree exists so insert its cells.
492 import-from output-to child-label (1+ output-level)
493 (1+ import-level) count total)))
494 (if again nil (throw 'end count))))
497 (defun kimport:star-entries (import-from output-to klabel output-level
498 import-level count total)
499 "Insert visible star outline entries from IMPORT-FROM into existing OUTPUT-TO.
501 KLABEL is the label to use for the first imported entry.
502 OUTPUT-LEVEL is the level at which to insert the first entry.
503 IMPORT-LEVEL is the depth of the current entry in the import file,
506 COUNT of inserted entries starts at 0. TOTAL is the total number of entries
507 in IMPORT-FROM, used to show a running tally of the imported entries."
508 (set-buffer import-from)
509 (let ((start (point))
510 (rolo-entry-regexp "^[ \t]*\\(\\*+\\)")
511 subtree-p end contents node-level child-label)
512 ;; While find cells at import-level or deeper ...
513 (while (and (re-search-forward rolo-entry-regexp nil t)
518 (match-beginning 1) (match-end 1))))))
519 (skip-chars-forward " \t")
521 end (rolo-to-entry-end)
522 subtree-p (if (looking-at rolo-entry-regexp)
524 (length (buffer-substring
525 (match-beginning 1) (match-end 1))))))
526 (skip-chars-backward "\n\r")
527 (setq contents (kimport:unindent-region start (point)))
529 (set-buffer output-to)
530 ;; Add the cell starting at point.
531 (kview:add-cell klabel output-level contents nil t)
532 (if subtree-p (setq child-label (klabel:child klabel)))
533 (message "%d of %d trees converted..."
534 (if (= node-level 1) (setq count (1+ count)) count)
536 (setq klabel (klabel:increment klabel)))
538 ;; Current buffer returns to `import-from' here.
541 ;; Handle each sub-level through recursion.
543 ;; Subtree exists so insert its cells.
545 (kimport:star-entries import-from output-to child-label
546 (1+ output-level) (1+ import-level)
551 (defun kimport:text-paragraphs (import-from output-to klabel
552 output-level count total)
553 "Insert text paragraphs from IMPORT-FROM into existing OUTPUT-TO.
554 First cell is inserted with KLABEL at OUTPUT-LEVEL, as the sibling of the
555 previous cell, with the COUNT of inserted paragraphs starting at 0. TOTAL is
556 the total number of paragraphs in IMPORT-FROM, used to show a running tally
557 of the imported paragraphs.
559 The variable, 'paragraph-start' is used to determine paragraphs."
560 (set-buffer import-from)
561 (let* ((count 0) start end contents)
562 ;; Next line is needed when importing into an existing kview.
563 (goto-char (point-min))
564 ;; Move past blank lines at point.
565 (skip-chars-forward " \t\n\r")
567 (while (and (setq start (point)
568 end (re-search-forward paragraph-start nil t))
570 (setq contents (kimport:unindent-region start end))
571 (set-buffer output-to)
572 ;; Add the cell starting at point.
573 (kview:add-cell klabel output-level contents nil t)
574 (setq count (1+ count))
575 (message "%d of %d paragraphs converted..."
577 (setq klabel (klabel:increment klabel))
578 (set-buffer import-from)
580 ;; Move past blank lines separating paragraphs.
581 (skip-chars-forward " \t\n\r")
583 (message "%d of %d paragraphs converted" count total)
586 (defun kimport:unindent-region (start end)
587 "Calculate indent based upon the second line within the region START to END.
588 Remove the indent and return the remaining region as a string."
592 ;; Remove leading indent from lines in paragraph. Base paragraph
593 ;; indent on the 2nd paragraph line since the first line might be
594 ;; further indented or outdented.
596 (if (re-search-forward "[\n\r][ \t]+" end t)
597 (concat "^" (make-string (current-column) ?\ ))))
599 (hypb:replace-match-string
600 indent-regexp (buffer-substring start end) "" t)
601 (buffer-substring start end)))))
606 ;;; kimport.el ends here