home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-06-13 | 79.9 KB | 2,050 lines |
- ;;; db-file-io.el --- part of EDB, the Emacs database
-
- ;; See database.el for copyright notice, distribution conditions, etc.
-
- ;; Author: Michael Ernst <mernst@theory.lcs.mit.edu>
- ;; Keywords: EDB
-
- ;;; Commentary:
-
- ;; Read and write database files.
-
- ;;; Code:
-
-
- ;;; Variables used dynamically; avoid compiler messages about free variables.
- (defvar database)
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; DB variables
- ;;;
-
- ;; This won't be such a concern once every field is explicitly specified
- ;; when the database is written out. But until then, should it be
- ;; buffer-local? (It's not in the data display buffer that I care so much
- ;; but in the read-database buffer.)
-
- (deflocalvar db-default-field-type 'string
- "The type to use for record fields whose type is not explicitly specified.")
-
- (defvar db-format-file-path nil
- "List of directories (strings) to search, in order, for format files not
- found in the directory with their associated databases.")
-
- (defvar db-aux-file-path nil
- "List of directories (strings) to search, in order, for auxiliary files not
- found in the directory with their associated databases.")
-
-
- ;; T if there has been an error while reading or writing the database, else nil.
- ;; This is set on file read, but the value is not currently used.
- (defvar db-io-error-p nil)
-
- (defvar db-before-read-hooks nil
- "Function or list of functions run immediately before a database is first read
- but after all local variables are set.
- The hooks are run in the data display buffer with variable database bound.
- Variable db-buffer is bound to a buffer containing the database file.
-
- This is a global variable. If you set it to be specific to a particular
- database \(for instance, in the format or auxiliary file), then consider
- having its last action be to reset the variable to nil.")
-
- (defvar db-after-read-hooks nil
- "Function or list of functions run after a database is completely read.
- The hooks are run in the data display buffer with variable database bound.
- For databases with nonregular formats, you might put a call to
- `database-stored->actual' here, for instance.
-
- This is a global variable. If you set it to be specific to a particular
- database \(for instance, in the format or auxiliary file), then consider
- having its last action be to reset the variable to nil.")
-
- (defvar db-format-file-suffixes '(".dbf" ".fmt" "f")
- "List of format file suffixes; the basename is that of the database file.
- The suffixes are tried in order; the default is \(\".dbf\" \".fmt\" \"f\").
- The . that may precede the extension must be specified explicitly.")
-
- (defvar db-aux-file-suffixes '(".dba" ".aux" "a")
- "List of auxiliary file suffixes; the basename is that of the database file.
- The suffixes are tried in order; the default is \(\".dba\" \".aux\" \"a\").
- The . that may precede the extension must be specified explicitly.")
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Read a database file
- ;;;
-
-
- ;; This assumes that the database doesn't exist.
- ;; If the database exists, call read-database-file-helper instead to
- ;; refresh it (which requires getting the database file into a buffer first).
- ;; "Return the database. The data display buffer will be the first element of
- ;; its data-display-buffers slot.
- ;;
- ;; Optional arg FORMAT-FILE, a filename, specifies the format file which
- ;; describes how database records are displayed. If it is nil, the default
- ;; \(in the default-format-file slot of the database\) will be used, or one
- ;; returned by `db-file->format-file' will be used, or the user will be
- ;; prompted for one.
- ;;
- ;; If optional prefix arg CONFIRM is non-nil, then no default will be used
- ;; without confirmation from the user."
- (defun read-database-file (db-file &optional format-file confirm)
- (interactive "fDatabase file: \nP")
- (setq db-io-error-p nil)
- (let ((db-buffer (generate-new-buffer "read-database-file"))
- data-display-buffer
- database)
-
- ;; (make-database :file db-file)
- ;; (hack-local-variables t)
-
- (set-buffer db-buffer)
- (if (file-exists-p db-file)
- (insert-file-contents db-file nil)
- (message "New database file."))
-
- (setq database (read-database-internal-file-layout-maybe))
- (database-set-file database db-file)
- (database-set-modifiable-p database (file-writable-p db-file))
-
- (db-debug-message "read-database-file: database = %s" database)
-
- ;; This is done in db-setup-data-display-buffer.
- ;; (read-db-aux-file database)
- ;; (db-set-fieldname-vars database)
-
- ;; when could format-file be t?
- ;; (if (eq format-file t)
- ;; (setq format-file nil))
-
- (if (or (not format-file)
- confirm)
- ;; We may be calling this because the argument to
- ;; read-database-file wasn't specified.
- (setq format-file (choose-format-file database format-file confirm)))
-
- (setq data-display-buffer
- (db-setup-data-display-buffer format-file database t))
- (database-set-data-display-buffers database
- (cons data-display-buffer (database-data-display-buffers database)))
-
- ;; (db-debug-message "rdf: recordfieldspecs = %s" (database-recordfieldspecs database))
-
- (in-buffer data-display-buffer
- (run-hooks 'db-before-read-hooks))
-
- ;; Local variables are now set from database, aux, and format files.
- (read-database-file-helper db-buffer database)
-
- (in-buffer data-display-buffer
- (run-hooks 'db-after-read-hooks))
-
- database))
-
-
- ;; "Return a database.
- ;; If the buffer contains a database in internal file layout, it is returned.
- ;; \(The records are not read from the buffer, only the database structure.\)
- ;; Otherwise, an empty database is returned."
- (defun read-database-internal-file-layout-maybe ()
- (if (skip-string-forward ";; Database file written by EDB")
- (let ((here (point)))
- (emacs-lisp-mode)
- ;; Update old formats in small increments.
- (if (skip-string-forward "; format 0.1")
- (progn
- (delete-backward-char 1)
- (insert "2")
- (forward-sexp)
- (backward-char 1)
- ;; add locals slot to database
- (insert " nil")
- (goto-char here)))
- (if (skip-string-forward "; format 0.2")
- (progn
- (delete-backward-char 1)
- (insert "3")
- (forward-sexp)
- (backward-char 1)
- (backward-sexp 1)
- (backward-char 1)
- ;; add modified-p slot to database
- (insert " nil")
- (goto-char here)))
- (if (skip-string-forward "; format 0.3")
- (progn
- (delete-backward-char 1)
- (insert "4")
- (down-list 1)
- (forward-sexp 16)
- ;; add internal-file-layout-p slot to database
- (insert " t")
- (forward-sexp 14)
- ;; add modifiable-p slot to database
- (insert " t")
- (goto-char here)))
- ;; Don't forget to change write-database file if the format
- ;; number is updated.
- (if (not (skip-string-forward "; format 0.4"))
- (db-message "I don't know if I can read the database, but I'll try."))
- (read (current-buffer)))
- (make-database)))
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Format file
- ;;;
-
- (defun db-file->format-file (db-file)
- "Return a format file or nil."
- (let* ((subdir (file-name-directory db-file))
- (default-directory (if subdir
- (expand-file-name subdir)
- default-directory))
- (default-file (locate-file-with-extensions-on-path
- db-file db-format-file-suffixes db-format-file-path)))
- (if default-file
- (expand-file-name default-file))))
-
- ;; "Return a full pathname for the format file named FILENAME, or err."
- (defun locate-format-file (filename)
- ;; The expand-file-name is needed only if the file is in the
- ;; current directory. Perhaps locate-file-on-path should always
- ;; return a full pathname for the file.
- (let ((checked-filename (locate-file-on-path filename db-format-file-path)))
- (if checked-filename
- (expand-file-name checked-filename)
- (error "I can't find a format file named %s." filename))))
-
- ;; "Return a format file according to DATABASE or FORMAT-FILE-DEFAULT.
- ;; Prompt if CONFIRM is set or if we can't get one from DATABASE alone."
- (defun choose-format-file (database format-file-default confirm)
- (let* ((db-file (database-file database))
- (default (or format-file-default
- (database-default-format-file database)
- (db-file->format-file db-file))))
- (db-debug-message "db-file = %s, sans extension = %s"
- db-file (filename-sans-extension db-file))
- (if (and default (not confirm))
- (let* ((dir (file-name-directory db-file))
- (default-directory (if dir
- (expand-file-name dir)
- default-directory)))
- (locate-format-file default))
- ;; Don't need locate-format-file because MUSTMATCH arg
- ;; to read-file-name is t.
- (expand-file-name
- (read-file-name
- (if default
- (format "Display format for %s: [default %s] "
- (file-name-nondirectory db-file)
- (file-name-nondirectory default))
- (format "Display format for %s: "
- (file-name-nondirectory db-file)))
- (file-name-directory db-file)
- (if default
- (expand-file-name default (file-name-directory db-file)))
- t)))))
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Read database file: helper functions
- ;;;
-
- ;; The format and the auxiliary file should already be loaded; ie, all
- ;; special variables should be set.
-
- ;; This is separate because we might want to simply refresh a database from
- ;; disk without going through the hoopla of setting variables, etc.
- ;; "DB-BUFFER is a buffer containing the database file; DATABASE is the
- ;; database to be read into.
- ;; The local variables section of DB-BUFFER is not executed, nor are the
- ;; database's auxiliary file read, nor are any database slots filled \(except
- ;; for first-link\)."
- (defun read-database-file-helper (db-buffer database)
-
- (db-message "Reading database...")
-
- (set-buffer db-buffer)
-
- (if (database-internal-file-layout-p database)
- (let ((record-list (read (current-buffer))))
- (db-message "Linking records...")
- (database-set-links-from-list
- database
- (mapcar (function make-link-from-record)
- record-list)))
- (progn
- (database-io-setup database t)
- (database-delete-pre-and-post database)
-
- (if (database-read-record-from-region database)
- (read-database-file-custom database)
- (read-database-file-delimited database))))
-
- (db-debug-message "read-database-file-helper: about to kill %s"
- (current-buffer))
- (kill-buffer (current-buffer))
- (setq db-databases
- (cons database db-databases))
- (db-message "Done reading database. %s records total."
- (database-no-of-records database))
- database)
-
-
- (defun database-set-links-from-list (database list-of-links)
-
- ;; Takes a list of links, sets database-first-link to the first one and
- ;; sets link-next and link-prev of each such that all the links are
- ;; arranged in a doubly-linked list in the order of the list.
-
- (database-set-no-of-records database (length list-of-links))
- (db-debug-message "database-set-links-from-list: length = %s"
- (length list-of-links))
- (let ((prev-link (car list-of-links))
- this-link)
- (database-set-first-link database prev-link)
- (setq list-of-links (cdr list-of-links))
- (while list-of-links
- (setq this-link (car list-of-links))
- (link-two prev-link this-link)
- (setq prev-link this-link)
- (setq list-of-links (cdr list-of-links)))
- (link-two prev-link (database-first-link database))))
-
-
- ;; Body should setq this-record. Body uses dynamic variable database.
- (defmacro read-database-file-noninternalformat-macro (condition &rest body)
-
- (` (let* (this-record
- this-link
- last-link
- first-link)
-
- (setq dbc-index 0)
- (while (, condition)
- (setq dbc-index (1+ dbc-index))
-
- ;; This is noisy, and it obscures the warning messages, but
- ;; reading is so slow that it's necessary.
- (if (and db-inform-interval
- (zerop (% dbc-index db-inform-interval)))
- (db-message "Reading database...%d" dbc-index))
-
- (,@ body)
-
- (setq this-link (make-link-from-record this-record))
- (if (not first-link)
- (setq first-link this-link
- last-link this-link))
- (link-two last-link this-link)
- (setq last-link this-link))
-
- (db-debug-message "read-database-file-noninternalformat-macro: condition satisfied")
-
- (if first-link
- (link-two last-link first-link))
- (database-set-first-link database first-link)
- (database-set-no-of-records database dbc-index))))
- (put 'read-database-file-noninternalformat-macro 'lisp-indent-hook 1)
- (put 'read-database-file-noninternalformat-macro 'edebug-form-spec '(&rest form))
-
- ;; Uses database-read-record-from-region and sepinfo-sepfunc from
- ;; database-record-sepinfo.
-
- ;; Maybe turn some of the support structure that's used both here and in
- ;; read-database-file-delimited-internal (eg linking stuff) into a
- ;; macro.
-
- (defun read-database-file-custom (database)
- (let ((region->record (database-read-record-from-region database))
- (record-sepinfo (database-record-sepinfo database)))
-
- ;; Check that the sepinfo has enough information.
- (if (sepinfo-sep-regexp record-sepinfo)
- (if (not (sepinfo-sep-regexp-submatch record-sepinfo))
- (error "Need a submatch to go with sep-regexp `%s' of record sepinfo."
- (sepinfo-sep-regexp record-sepinfo)))
- (if (not (sepinfo-sep-function record-sepinfo))
- (let ((sep-string (sepinfo-sep-string record-sepinfo)))
- (if (or (not sep-string)
- (equal "" sep-string))
- (error "You haven't specified a string, regexp, or function in record sepinfo."))
- (sepinfo-set-regexp-and-submatch-from-string
- sepinfo-set-sep-regexp sepinfo-set-sep-regexp-submatch
- record-sepinfo
- (sepinfo-sep-string record-sepinfo) database))))
-
- ;; Merge this into the above, maybe, so I don't have to check twice
- ;; whether we're using sep-function or regexp.
-
- (db-debug-message "read-database-file-custom: done checking sepinfo.")
-
- (if (sepinfo-sep-function record-sepinfo)
- ;; use sep-function to delimit the records
-
- ;; return a pair of (end-pos . start-pos)
- (let ((delimit-record (sepinfo-sep-function record-sepinfo))
- ;; Read-record-from-region might do insertions or deletions,
- ;; so just remembering the position isn't enough.
- ;; I don't know that this works, though, due to narrowing.
- next-start-marker
- (prev-end-pos 'nil)
- (end-start-pair '(nil)))
- (goto-char (point-min))
- (setq next-start-marker (point-marker))
-
- ;; This assumes that there is at least one record in the file.
- ;; This is a bug. (Fix it at the beginning of this
- ;; function or in the caller.)
- (read-database-file-noninternalformat-macro
- (marker-position next-start-marker)
-
- (setq end-start-pair (funcall delimit-record prev-end-pos))
- (db-debug-message "read-database-file-custom: end-start-pair = %s"
- end-start-pair)
- (narrow-to-region (marker-position next-start-marker)
- (car end-start-pair))
- ;; Writers of record->region often forget to do this.
- (goto-char (point-min))
- (db-debug-message "read-database-file-custom: region = %s %s, next-start = %s"
- (point-min) (point-max) (cdr end-start-pair))
- (setq this-record (funcall region->record))
- ;; not (car end-start-pair) in case buffer was modified
- (setq prev-end-pos (point-max))
- (widen)
- ;; In 18.55, set-marker can only make markers point to the
- ;; accessible portion of the buffer, so do this after widening.
- ;; (This may have changed by 18.59.)
- ;; Don't use just (cdr end-start-pair) because the buffer may
- ;; have been modified.
- (set-marker next-start-marker (and (cdr end-start-pair)
- (+ (- (cdr end-start-pair)
- (car end-start-pair))
- prev-end-pos)))
- (db-debug-message "read-database-file-custom: next-start-marker = %s"
- next-start-marker)
- )
-
- )
- ;; use regexp to delimit the records
- (let ((record-sep-regexp (sepinfo-sep-regexp record-sepinfo))
- (record-sep-regexp-submatch (sepinfo-sep-regexp-submatch
- record-sepinfo))
- (next-start-pos (point-min))
- ;; How many characters to skip before looking for the start
- ;; of the next record. This is because region->record may
- ;; insert or delete, so we can't use a position, but markers
- ;; get squeezed when narrow-to-region is done.
- sep-length)
- ;; The caller also called database-delete-pre-and-post.
- (goto-char (point-min))
-
- (read-database-file-noninternalformat-macro
- (< next-start-pos (point-max))
-
- (goto-char next-start-pos)
- ;; re-search-forward errs if it fails
- (re-search-forward record-sep-regexp)
- (setq sep-length (- (match-end record-sep-regexp-submatch)
- (match-beginning record-sep-regexp-submatch)))
- (narrow-to-region next-start-pos
- (match-beginning record-sep-regexp-submatch))
- (setq this-record (funcall region->record))
- (setq next-start-pos (+ (point-max) sep-length))
- (widen))))))
-
-
-
- ;; If there are any regexps set in the spinfos at this point, then we
- ;; assume that the programmer specified them explicitly; we assume that if
- ;; any substitutions are requested, then the programmer knows that they
- ;; won't cause any ambiguities.
- (defun read-database-file-delimited (database)
-
- (db-debug-message "read-database-file-delimited: converting complex->simple")
-
- (goto-char (point-min))
-
- ;; We have now cleared away the pre- and post- garbage.
- (if (or (sepinfo-sep-regexp (database-field-sepinfo database))
- (sepinfo-sep-regexp (database-record-sepinfo database)))
- ;; There are regexps involved, so do no substitution on the separators,
- ;; except that the user has explicitly requrested; then read the database.
- (progn
- (database-perform-substitutions database t)
- (read-database-file-delimited-regexp database))
-
- ;; There are no regexps involved.
- (let* ((fieldsep-string (database-full-fieldsep-string database))
- (recordsep-string (database-full-recordsep-string database))
- confirmation-list)
-
- ;; Convert the database buffer from "complex" to "simple" form.
- (let* ((field-sepinfo (database-field-sepinfo database))
- (pre-first-field-string (sepinfo-pre-first-string field-sepinfo))
- (pre-first-field-regexp (sepinfo-pre-first-regexp field-sepinfo))
- ;; (post-last-field-string (sepinfo-post-last-string field-sepinfo))
- ;; (post-last-field-regexp (sepinfo-post-last-regexp field-sepinfo))
- )
- ;; database-delete-pre-and-post only did the record separators;
- ;; here we do the field separators as well.
- (goto-char (point-min))
- (cond (pre-first-field-regexp
- (if (skip-regexp-forward pre-first-field-regexp)
- (delete-region (point-min) (point))
- (error "Didn't find match for regexp `%s' leading the first field."
- pre-first-field-regexp)))
- (pre-first-field-string
- (if (skip-string-forward pre-first-field-string)
- (delete-region (point-min) (point))
- (error "Didn't find string `%s' leading the first field."
- pre-first-field-string))))
-
- (goto-char (point-max))
- ;; Don't get rid of post-last-field-regexp or post-last-field-string
- ;; because we look for them at the end of every record.
-
- ;; Make sure that record-sepinfo-sep-string appears at the end.
- (if (sepinfo-sep-string (database-record-sepinfo database))
- (if (skip-string-backward
- (sepinfo-sep-string (database-record-sepinfo database)))
- (goto-char (point-max))
- (insert (sepinfo-sep-string (database-record-sepinfo database)))))
- (if pre-first-field-string
- (insert pre-first-field-string)))
-
- ;; We're still inside the big let.
-
- ;; This pre-read confirmation should be optional. And it should be
- ;; able to deal with regexps.
- ;; When would this test fail? Won't fieldsep-string and
- ;; recordsep-string always be non-nil when we get here?
- (if (and fieldsep-string recordsep-string)
- (progn
- (db-message "Confirming database format...")
- (setq confirmation-list (database-confirm-fieldsep-and-recordsep
- fieldsep-string recordsep-string
- (database-no-of-fields database) nil))
- ;; (db-debug-message "read-database-file-delimited: confirmation-list = %s"
- ;; confirmation-list)
- (if (or (conflist-fieldsep-bad-p confirmation-list)
- (conflist-recordsep-bad-p confirmation-list))
- (progn
- (db-warning "The database file is malformed!")
- (if (conflist-fieldsep-bad-p confirmation-list)
- (db-warning "Extra field separator `%s' found in data."
- fieldsep-string))
- (if (conflist-recordsep-bad-p confirmation-list)
- (db-warning "Extra record separator `%s' found in data."
- recordsep-string))
- ;; show the db warning buffer
- (if (yes-or-no-p "Database file is improperly formatted; try to read it anyway? ")
- (db-message "Damaged database being read; expecting approximately %s records."
- (conflist-no-of-records confirmation-list))
- (progn
- (kill-buffer (current-buffer))
- (error "Aborted attempt to read database."))))
- (db-message "Database looks OK from here; expecting %d records."
- (conflist-no-of-records confirmation-list)))))
-
- ;; This recomputes the full-fieldsep and full-recordsep; oh well.
- ;; It also sets the io-sep variables.
- (database-substitute-for-read database)
-
- (read-database-file-delimited-string database)
- )))
-
-
- ;; When we call this, the database is in the following form:
- ;; Point at start of first field of first record.
- ;; Each field, except last, is ended by actual-fieldsep.
- ;; Each record, including last, is ended by actual-recordsep.
- ;; End of last recordsep-string = eob.
- ;; Field-sep and record-sep must be strings. (Perhaps permit regexps later.)
-
- (defun read-database-file-delimited-string (database)
-
- (db-message "Reading database...")
-
- (goto-char (point-min))
- (let* ((field-sep (database-sub-fieldsep-string database))
- (record-sep (database-sub-recordsep-string database))
- (field-sep-length (length field-sep))
- (record-sep-length (length record-sep))
- (no-of-fields (database-no-of-fields database))
- (max-field-no (1- (database-no-of-fields database)))
- (here (point))
- field-no
- ;; (recordfieldspecs (database-recordfieldspecs database))
- end-of-record-sep
- end-of-record)
-
- (read-database-file-noninternalformat-macro
- (not (eobp))
- ;; This progn is not strictly necessary, but it serves to delimit the
- ;; extent of the body of read-database-file-noninternalformat-macro.
- (progn
- ;; (db-message "read-database-file-delimited-string: not eobp at %s/%s" (point) (point-max))
- (setq this-record (make-vector no-of-fields nil))
- (if (search-forward record-sep nil t)
- (setq end-of-record-sep (point)
- end-of-record (- (point) record-sep-length))
- (progn
- (db-warning "Didn't find `%s' at end of last field of record %d, and I put it there myself!"
- record-sep dbc-index)
- (setq end-of-record-sep (point-max)
- end-of-record (point-max))))
- (goto-char here)
-
- (setq field-no 0)
- (while (< field-no max-field-no)
-
- ;; I should trap errors here. Don't check for record-sep in the
- ;; field text, however, because that's too slow.
-
- (if (search-forward field-sep end-of-record t)
- (progn
- (record-set-field-from-index
- this-record field-no
- (buffer-substring here
- ;; more efficient than (match-beginning 0)
- (- (point) field-sep-length))
- nil)
- (setq here (point))
- (setq field-no (1+ field-no)))
- (progn
- (db-warning "Hit the end of the record after %d fields of record %d (didn't find field-sep `%s')"
- field-no dbc-index field-sep)
- (record-set-field-from-index
- this-record field-no
- (buffer-substring here end-of-record)
- nil)
- (setq here end-of-record)
- (setq field-no (1+ field-no))
- (while (<= field-no max-field-no)
- (record-set-field-from-index
- this-record field-no
- (recordfieldspec-default-value
- (database-recordfieldspec database field-no))
- nil)
- (setq field-no (1+ field-no))))))
- ;; If there weren't too few fields, set the last one (else it's already set).
- (if (= field-no max-field-no)
- (progn
- (if (search-forward field-sep end-of-record t)
- (progn
- (db-warning "Extra fields in record %d packed into the last field; beware when writing."
- dbc-index)
- (setq db-io-error-p t)))
- (record-set-field-from-index this-record max-field-no
- (buffer-substring here end-of-record)
- nil)))
- (goto-char end-of-record-sep)
- (setq here (point))))
-
-
- ;; The caller takes care of this.
- ;; (kill-buffer (current-buffer))
-
- ;; Convert from stored to actual format. It's possible that it will be
- ;; cheaper to do something above without even creating the
- ;; about-to-be-trashed string (ie, operate directly on the buffer).
- ;; But that sounds like too much work by far.
- ;; (db-debug-message "read-database-file-delimited-string: converting")
-
- (database-stored->actual database)
-
- ;; (db-debug-message "read-database-file-delimited-string: returning")
-
- ;; This function is called for side-effect, but return the database anyway.
- database))
-
-
- ;; I should clearly merge this with ...-delimited-string eventually.
-
- ;; For now I don't deal with pre- and post- on the field sepinfo. I will soon.
- ;; (Actually, I do deal with post-, but don't tell.)
- (defun read-database-file-delimited-regexp (database)
-
- (db-message "Reading database...")
-
- (goto-char (point-min))
-
- ;; I have a feeling of deja vu. Have I don't this elsewhere before?
- ;; Maybe in read-sepinfo-items.
- (let* ((field-sepinfo (database-field-sepinfo database))
- (record-sepinfo (database-record-sepinfo database))
- (field-regexp (or (sepinfo-sep-regexp field-sepinfo)
- (regexp-quote (sepinfo-sep-string
- (database-field-sepinfo database)))))
- (field-regexp-submatch (or (sepinfo-sep-regexp-submatch field-sepinfo)
- 0))
- (record-regexp (or (sepinfo-sep-regexp record-sepinfo)
- (regexp-quote (sepinfo-sep-string record-sepinfo))))
- (record-regexp-submatch (or (sepinfo-sep-regexp-submatch record-sepinfo)
- 0))
-
- ;; I can't fold these in to the record-regexp because they may have
- ;; submatches of their own.
- (pre-first-field-string (sepinfo-pre-first-string field-sepinfo))
- (pre-first-field-regexp (or (sepinfo-pre-first-regexp field-sepinfo)
- (and pre-first-field-string
- (regexp-quote pre-first-field-string))))
- (pre-first-field-regexp-submatch (or (sepinfo-pre-first-regexp-submatch
- field-sepinfo)
- 0))
- (post-last-field-string (sepinfo-post-last-string field-sepinfo))
- (post-last-field-regexp (or (sepinfo-post-last-regexp field-sepinfo)
- (and post-last-field-string
- (regexp-quote post-last-field-string))))
- (post-last-field-regexp-submatch (or (sepinfo-post-last-regexp-submatch
- field-sepinfo)
- 0))
-
- (no-of-fields (database-no-of-fields database))
- (max-field-no (1- (database-no-of-fields database)))
- (here (point))
- field-no
- ;; (recordfieldspecs (database-recordfieldspecs database))
- end-of-record-regexp
- end-of-record)
-
- (read-database-file-noninternalformat-macro
- (not (eobp))
- ;; This progn is not strictly necessary, but it serves to delimit the
- ;; extent of the body of read-database-file-noninternalformat-macro.
- (progn
- ;; (debug "in read-database-file-delimited-macro")
- ;; (db-message "read-database-file-delimited-regexp: not eobp at %s/%s" (point) (point-max))
- (setq this-record (make-vector no-of-fields nil))
-
- ;; Note that in this case I didn't want to get rid of the pre-first stuff.
- (if pre-first-field-regexp
- (if (skip-regexp-forward pre-first-field-regexp)
- (progn
- (setq here (match-end pre-first-field-regexp-submatch))
- (goto-char here))
- (error "Didn't find pre-first stuff I expected.")))
-
- (setq field-no 0)
- (while (< field-no max-field-no)
-
- ;; I should trap errors here. Don't check for record-regexp in the
- ;; field text, however, because that's too slow.
-
- (if (re-search-forward field-regexp nil t)
- (progn
- (record-set-field-from-index
- this-record field-no
- (buffer-substring here
- (match-beginning field-regexp-submatch))
- nil)
- (setq here (match-end field-regexp-submatch))
- (setq field-no (1+ field-no)))
- (progn
- (db-warning "Hit the end of the database after %d fields of record %d (didn't find field-regexp `%s')"
- field-no dbc-index field-regexp)
- (setq field-no max-field-no))))
- (if (re-search-forward record-regexp nil t)
- (setq end-of-record-regexp (match-end record-regexp-submatch)
- end-of-record (match-beginning record-regexp-submatch))
- (progn
- (db-warning "Didn't find `%s' at end of last field of record %d."
- record-regexp dbc-index)
- (setq end-of-record-regexp (point-max)
- end-of-record (point-max))))
- (goto-char here)
- (if (re-search-forward field-regexp end-of-record t)
- (progn
- (db-warning "Too many fields in record %d; packing them all into the last field; beware when writing."
- dbc-index)
- (setq db-io-error-p t)))
- (record-set-field-from-index this-record max-field-no
- (buffer-substring here
- end-of-record)
- nil)
- (goto-char end-of-record-regexp)
- (setq here (point))))
-
-
- (database-stored->actual database)
-
- database))
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Read database utilities
- ;;;
-
-
- ;; Each field of each database record has a string; convert some of these
- ;; into some other internal representation (or even into different
- ;; strings).
-
- ;; "Convert string slot values in a newly-read database to the actual format.
- ;; If no argument is specified, use the value of the dynamic variable database.
- ;; This makes it possible to be put directly in db-after-read-hook."
- (defun database-stored->actual (&optional db)
-
- (database-stored->actual-internal (or db database)))
-
-
- ;; The following function used to be part of the above function, but
- ;; edebug2.7 had trouble debugging it for some reason.
-
- ;; "Do the real work of `database-stored->actual'."
- (defun database-stored->actual-internal (db)
- (let (fno-s->a-alist
- s->a
- (field-no 0)
- field-val
- (no-of-fields (database-no-of-fields db)))
- (while (< field-no no-of-fields)
- (setq s->a (recordfieldspec-stored->actual
- (database-recordfieldspec db field-no)))
- (if s->a
- (setq fno-s->a-alist
- (cons (cons field-no s->a) fno-s->a-alist)))
- (setq field-no (1+ field-no)))
- (db-debug-message "database-stored->actual: fno-s->a-alist = %s" fno-s->a-alist)
- (if fno-s->a-alist
- (progn
- (db-message "Converting from stored record format...")
- (maprecords-macro
- (let ((s->a-pairs fno-s->a-alist))
- (while s->a-pairs
- (setq field-no (car (car s->a-pairs))
- field-val (aref maprecords-record field-no)
- s->a (cdr (car s->a-pairs))
- s->a-pairs (cdr s->a-pairs))
- (record-set-field-from-index
- maprecords-record field-no
- (if (stringp field-val)
- (funcall s->a field-val)
- field-val)
- nil)))
- db nil "Converting record format...%s")
- (db-message "Converting record format...done.")))))
-
- ;; Takes a database file as input, reads the auxiliary file if it can.
- ;; Note that the `database' variable is dynamically bound when the
- ;; auxiliary file is read.
- (defun read-db-aux-file (database)
- (let ((aux-file (or (database-aux-file database)
- (locate-file-with-extensions-on-path
- (database-file database)
- db-aux-file-suffixes db-aux-file-path))))
- (if aux-file
- (load-file aux-file))))
-
- ;; This only does the RECORD separators.
- ;; "Modify current buffer according to DATABASE.
- ;; Remove pre-first-record- and post-last-record strings or regexps, then
- ;; add a field separator to the end so that every record is terminated by one."
- (defun database-delete-pre-and-post (database)
-
- (let ((record-sepinfo (database-record-sepinfo database)))
-
- ;; Remove gubbish before first record.
- (goto-char (point-min))
- (let ((pre-first-record-string (sepinfo-pre-first-string
- record-sepinfo))
- (pre-first-record-regexp (sepinfo-pre-first-regexp
- record-sepinfo)))
- (cond (pre-first-record-regexp
- (if (skip-regexp-forward pre-first-record-regexp)
- (progn
- (goto-char (match-end (sepinfo-pre-first-regexp-submatch
- record-sepinfo)))
- (delete-region (point-min) (point)))
- (error "Didn't find match for regexp `%s' leading the data."
- pre-first-record-string)))
- (pre-first-record-string
- (if (skip-string-forward pre-first-record-string)
- (delete-region (point-min) (point))
- (error "Didn't find string `%s' leading the data."
- pre-first-record-string)))))
-
- ;; Remove gubbish after last record.
- (goto-char (point-max))
- (let ((post-last-record-string (sepinfo-post-last-string
- record-sepinfo))
- (post-last-record-regexp (sepinfo-post-last-regexp
- record-sepinfo)))
- (db-debug-message "database-delete-pre-and-post: post-string = `%s', post-regexp = `%s'" post-last-record-string post-last-record-regexp)
- (cond (post-last-record-regexp
- (if (re-search-backward post-last-record-regexp)
- (progn
- (db-debug-message "regexp-deleting %d to %d" (point) (point-max))
- (goto-char (match-beginning (sepinfo-post-last-regexp-submatch
- record-sepinfo)))
- (delete-region (point) (point-max)))
- (error "Didn't find match for post-last-record-regexp `%s'."
- post-last-record-string)))
- (post-last-record-string
- (if (search-backward post-last-record-string)
- (delete-region (point) (point-max))
- (error "Didn't find post-last-record-string `%s'."
- post-last-record-string)))))
-
- ;; (debug)
-
- ;; Problem: what if only regexp (not sep-string) is specified?
- ;; Then this crashes.
-
- ;; Maybe this should be happening in the caller, not here.
- ;; If there isn't one there already, add a final record separator so that
- ;; every record is terminated by one.
- (let ((sep-string (sepinfo-sep-string record-sepinfo)))
- (db-debug-message "sep-string = `%s'" sep-string)
- (if (not (skip-string-backward sep-string))
- (insert sep-string)))
- ))
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Write database file
- ;;;
-
-
- ;; This is called from the data display buffer, and we need the values of
- ;; variables local to that buffer. It used to do output to the end of that
- ;; buffer, call write-region, and finally delete the output, but now it
- ;; uses a different buffer.
-
- ;; I process-current-record before calling this from
- ;; db-write-database-file, and it isn't called from anywhere else.
-
- (defun write-database-file (database &optional db-file)
-
- (setq db-io-error-p nil)
- (if db-file
- (if (or (not (file-name-directory db-file))
- (file-exists-p (file-name-directory db-file)))
- (database-set-file database db-file)
- (error "Can't write %s: directory does not exist." db-file))
- (setq db-file (database-file database)))
-
- (in-buffer (generate-new-buffer "write-database-file")
- (buffer-flush-undo (current-buffer))
- (auto-save-mode 0)
- (if (database-internal-file-layout-p database)
- (let ((fl (database-first-link database))
- (ddbs (database-data-display-buffers database))
- (modified-p (database-modified-p database))
- (standard-output (current-buffer)))
- (insert ";; Database file written by EDB; format 0.4\n")
- (unwind-protect
- (progn
- (database-set-first-link database nil)
- (database-set-data-display-buffers database nil)
- (database-set-modified-p database nil)
- (print database))
- (database-set-first-link database fl)
- (database-set-data-display-buffers database ddbs)
- ;; in case we abort out of the write
- (database-set-modified-p database modified-p)
- )
- (insert "(\n")
- (maprecords (function print) database)
- (insert "\n)\n")
- ;; This is not catching the error raised by basic-save-buffer if
- ;; the destination is not writable.
- (condition-case error
- (let ((require-final-newline nil)
- ;; get around write-file
- (auto-save-default nil))
- (write-file db-file)
- (database-set-modified-p database nil)
- (kill-buffer (current-buffer))
- )
- ;; Used to be file-error, which didn't catch the error raised
- ;; by basic-save-buffer if the destination is not writable.
- (error
- (setq db-io-error-p t)
- ;; This must come before the buffer is killed.
- (db-warning "Error `%s' while writing buffer %s to file %s."
- error (buffer-name (current-buffer)) db-file)
- ;; This ignores db-disable-debugging-support-p. Oh, well.
- (if (not db-debug-p)
- (progn
- ;; avoid further questions about killing the buffer.
- (set-buffer-modified-p nil)
- (kill-buffer (current-buffer))))))
- )
- ;; Don't use internal representation.
- (progn
- ;; Fine example of use of undocumented functionality
- ;; (this-buffer is bound by in-buffer).
- (db-debug-message "Calling copy-buffer-local-variables.")
- (copy-buffer-local-variables this-buffer)
- ;; We don't want to be *exactly* like the data display buffer.
- (setq major-mode 'write-database-mode
- buffer-read-only nil)
- (database-io-setup database)
- (let ((first-record t)
- (record-sepinfo (database-record-sepinfo database)))
- (db-debug-message "About to insert-maybe pre-first-record-string `%s'."
- (sepinfo-pre-first-string record-sepinfo))
- (if (sepinfo-pre-first-string record-sepinfo)
- (insert (sepinfo-pre-first-string record-sepinfo)))
- (if (database-write-region-from-record database)
- ;; Don't do any separator checking at all in the case of
- ;; write-record-function, even for recordsep, since I have no
- ;; idea how clever read-record-function is going to be. Also
- ;; don't do substitution or quoting. Probably I shouldn't even
- ;; be inserting record-sep-string, but it will probably be the
- ;; empty string anyway. [Another possibility is that if
- ;; I don't want it in the speciialized reading/writing
- ;; functions, I could just set the substitutions list to nil.]
- (let ((record-sep-string (sepinfo-sep-string record-sepinfo))
- (write-region-fn (database-write-region-from-record
- database)))
- (db-message "Writing database...")
- (maprecords (function (lambda (record)
- (if first-record
- (setq first-record nil)
- (insert record-sep-string))
- (funcall write-region-fn record)))
- database nil "Writing database...%d")
- (db-message "Writing database to disk..."))
- (write-database-file-internal-delimited database))
- (db-debug-message "done with write-database-file-internal-delimited")
- (if (sepinfo-post-last-string record-sepinfo)
- (insert (sepinfo-post-last-string record-sepinfo)))
-
- ;; Add the local variables, if any, to the end.
- (if (database-file-local-variables database)
- (insert (database-file-local-variables database)))
-
- (condition-case error
- (let ((require-final-newline nil)
- ;; get around write-file
- (auto-save-default nil))
- (write-file db-file)
- (database-set-modified-p database nil)
- (kill-buffer (current-buffer))
- )
- ;; Used to be file-error, which didn't catch the error raised
- ;; by basic-save-buffer if the destination is not writable.
- (error
- (setq db-io-error-p t)
- ;; This must come before the buffer is killed.
- (db-warning "Error `%s' while writing buffer %s to file %s."
- error (buffer-name (current-buffer)) db-file)
- ;; Did write-file
- (auto-save-mode 0)
- ;; This ignores db-disable-debugging-support-p. Oh, well.
- (if (not db-debug-p)
- (progn
- ;; avoid further questions about killing the buffer.
- (set-buffer-modified-p nil)
- (kill-buffer (current-buffer))))))
- ))
- )
- )
- ;; In case we wrote the curently-visible database. This probably fails
- ;; if both the data display buffer and the summary buffer are visible.
- ;; I guess I should have a procedure that fixes the modified bit for the
- ;; mode lines of both buffers.
- (if (database-buffer-p)
- (progn
- (dbc-update-database-modified-p)
- (force-mode-line-update)))
- )
-
-
- ;; This inserts the records, fields separated by fieldsep and records
- ;; separated by recordsep, into the current buffer; it uses delimited
- ;; format. It is called by write-database-file, which arranges
- ;; unwind-protect boundaries, calls the -internal function that uses the
- ;; proper output file layout, etc.
- (defun write-database-file-internal-delimited (database)
-
- (let* ((previous-point (point))
- (fno 0)
- (first-record t)
- (no-of-fields (database-no-of-fields database))
- (write-record-function (database-write-region-from-record database))
- (fieldsep-string (database-full-fieldsep-string database))
- (recordsep-string (database-full-recordsep-string database))
- (sub-fieldsep (or (database-sub-fieldsep-string database) fieldsep-string))
- (sub-recordsep (or (database-sub-recordsep-string database) recordsep-string))
- confirmation-list)
-
- ;; Check the delimiters
- (if (not (database-acceptable-delimiter-p sub-fieldsep))
- (setq sub-fieldsep (database-generate-delimiter nil)))
- (if (not (database-acceptable-delimiter-p sub-recordsep))
- (setq sub-recordsep (database-generate-delimiter nil)))
-
- (db-message "Writing database...")
- (maprecords
- (function
- (lambda (record)
- ;; (db-debug-message "record %s about to be written." record)
-
- ;; This isn't abstracted out, partly because I don't want to pay the
- ;; overhead of a function call.
-
- (setq fno 0)
- (while (< fno no-of-fields)
- (if (> fno 0) (insert sub-fieldsep))
- ;; this is record-field-stored, inlined.
- (insert (funcall-maybe (recordfieldspec-actual->stored
- (database-recordfieldspec database fno))
- (aref record fno)))
- (setq fno (1+ fno)))
- (insert sub-recordsep)))
- database nil "Writing database...%d")
- ;; (db-debug-message "wrote all records.")
-
- (db-message "Writing database...confirming")
-
- (narrow-to-region previous-point (point-max))
- (setq confirmation-list (database-confirm-fieldsep-and-recordsep
- sub-fieldsep sub-recordsep
- (database-no-of-fields database)
- (database-no-of-records database)))
- (db-debug-message "write-database-file-internal-delimited:\n confirmation-list = %s" confirmation-list)
- (if (or (conflist-fieldsep-bad-p confirmation-list)
- (conflist-recordsep-bad-p confirmation-list))
- (progn
- (db-debug-message "confirmation failed")
- (if (conflist-fieldsep-bad-p confirmation-list)
- (progn
- (db-warning "Tripped over an unexpected field separator `%s' in data; trying again."
- sub-fieldsep)
- (database-set-sub-fieldsep-string database
- (database-generate-delimiter database t))))
- (if (conflist-recordsep-bad-p confirmation-list)
- (progn
- (db-warning "Tripped over an unexpected record separator `%s' in data; trying again."
- sub-recordsep)
- (database-set-sub-recordsep-string database
- (database-generate-delimiter database t))))
-
- ;; We've chosen new separators; erase the work so far.
- (delete-region previous-point (point))
-
- ;; Call this function recursively.
- (write-database-file-internal-delimited database)
- ;; I've called this recursively; don't do any substitution
- ;; or quoting.
- )
- ;; Confirmation was OK: correct number of field and record
- ;; separators found.
- (progn
- (db-debug-message "confirmation succeeded")
-
- ;; Put off adding the pre- and post- field strings until
- ;; after checking separators, as they may contain anomolous
- ;; field separators, for instance.
-
- ;; But do it before substitution so that all field pre- and
- ;; post- strings are treated identically.
-
- ;; The whole point of using io-separators is so they
- ;; appear exactly as the user specified, unaffected by
- ;; substitution.
-
-
- ;; But note that pre- and post- record strings will be added
- ;; later, after substitution.
-
- (database-substitute-for-write database
- fieldsep-string sub-fieldsep
- recordsep-string sub-recordsep)
- ;; Convert from "simple" to "complex" form.
- (goto-char (point-min))
- (let ((pre-first (sepinfo-pre-first-string (database-field-sepinfo database))))
- (if pre-first
- (insert pre-first)))
-
- (goto-char (point-max))
- (db-debug-message "at point-max")
- (if (and (skip-string-backward (or (sepinfo-pre-first-string
- (database-field-sepinfo database))
- ""))
- (skip-string-backward (or (sepinfo-sep-string
- (database-record-sepinfo database))
- "")))
- (delete-region (point) (point-max))
- (error "Didn't find expected trailing junk `%s' or `%s'."
- (sepinfo-pre-first-string (database-field-sepinfo database))
- (sepinfo-sep-string (database-record-sepinfo database))))
- (db-debug-message "about to widen")
-
-
- ))
- (widen)))
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; I/O utilities
- ;;;
-
- (defun database-io-setup (database &optional dont-set-regexps)
-
- ;; Perhaps these should be dealt with via with- forms as well.
- ;; Or have local variables that they set.
- ;; Or global ones that they set unconditionally.
-
- ;; Some of this information may already be correctly set (especially if
- ;; we're now writing), but just in case some of the database slots have
- ;; changed since reading (eg, quotation character is different now).
-
- (db-debug-message "database-io-setup called.")
- (database-check-all-sepinfos database dont-set-regexps)
- (db-debug-message "database-check-all-sepinfos returned in database-io-setup.")
- (db-set-field-quotation-vars database)
- (setq db-substitution-no-no-string
- (apply (function concat)
- (mapcar (function (lambda (string-cons)
- (concat (car string-cons)
- (cdr string-cons))))
- (database-substitutions database))))
- (db-debug-message "database-io-setup returning.")
- )
-
-
- ;;; The user should set the slots directly.
- ;; Doesn't set unless the variables are non-nil.
- ;; (defun set-database-slots-from-buffer-local-values-maybe (database)
- ;; (if db-record-fieldnames
- ;; (database-set-fieldnames database db-record-fieldnames))
- ;; (if db-substitutions
- ;; (database-set-substitutions database db-substitutions)))
-
- ;; This is the stuff that needs to happen before the format is parsed but after
- ;; the format's local variables have been hacked.
- ;; It has nothing to do with the format per se, only with database slots.
- ;; Keep it abstracted out!
- (defun db-set-field-variables (database)
- ;; This is now done by database-set-fieldnames-to-list.
- ;; (db-set-fieldname-vars database)
- (if (not (database-field-priorities database))
- (database-set-field-priorities database
- (let ((fno -1))
- (cons
- (mapcar (function (lambda (fieldname)
- (setq fno (1+ fno))
- (list fno)))
- (database-fieldname-alist database))
- nil)))))
-
-
- ;; When converting strings to regexps, must be careful to watch out for
- ;; substitution and quotation: don't get fooled.
- (defun database-check-all-sepinfos (database &optional dont-set-regexps)
-
- ;; When writing to the file, we take the previous version's local
- ;; variables section verbatim.
-
- ;; We're only setting the regexp variables, which the average user won't
- ;; touch.
-
- ;; When reading, we'll get the variables from the database, auxiliary,
- ;; and format files anew each time anyway.
-
- (database-check-sepinfo (database-record-sepinfo database) database
- "record" "\n" dont-set-regexps)
- (database-check-sepinfo (database-field-sepinfo database) database
- "field" "\t" dont-set-regexps)
- (database-check-sepinfo (database-alternative-sepinfo database) database
- "alternative" t dont-set-regexps)
- )
-
-
- ;; It's good that this setting of -regexp slots doesn't happen until the
- ;; last possible moment, when quotation-char and other variables that these
- ;; values depend on are already set to their final values.
-
- (defun database-check-sepinfo (sepinfo database sepinfo-name
- &optional sep-string-default dont-set-regexps)
-
- ;; If the sep-string slot of the sepinfo is nil, then:
- ;; * if SEP-STRING-DEFAULT is nil, signal an error.
- ;; * if SEP-STRING-DEFAULT is t, do nothing.
- ;; * otherwise set the slot to SEP-STRING-DEFAULT.
-
- ;; If the string is the empty string, then we must have just set it that
- ;; way, which means that we either also just set the regexp to nil,
- ;; or we set the regexp to something we care about. In either case don't
- ;; mess further with the regexp.
-
- ;; The submatches could default to 0 if they're nil; but I want to be
- ;; more paranoid than that.
-
- (if (and (sepinfo-pre-first-string sepinfo)
- (equal "" (sepinfo-pre-first-string sepinfo)))
- ;; pre-first-string = ""
- (sepinfo-set-pre-first-string sepinfo nil))
- (if (sepinfo-pre-first-regexp sepinfo)
- (if (not (sepinfo-pre-first-regexp-submatch sepinfo))
- (error "Need a submatch to go with pre-first-regexp `%s' of %s."
- (sepinfo-pre-first-regexp sepinfo) sepinfo-name))
- (if (and (sepinfo-pre-first-string sepinfo)
- (not dont-set-regexps))
- (sepinfo-set-regexp-and-submatch-from-string
- sepinfo-set-pre-first-regexp sepinfo-set-pre-first-regexp-submatch
- sepinfo
- (sepinfo-pre-first-string sepinfo) database)))
-
- ;; Don't test for sep-function because the sepinfo must be valid for
- ;; output as well as input.
- ;; On the other hand, we don't need sep-string to be set if a wrfr
- ;; function is in use, but this function doesn't do any such checks.
- (if (or (not (sepinfo-sep-string sepinfo))
- (and (equal "" (sepinfo-sep-string sepinfo))
- (not (sepinfo-sep-regexp sepinfo))))
- ;; sep-string isn't set appropriately; it contains no information
- (cond ((eq t sep-string-default)
- ;; do nothing
- )
- ((not sep-string-default)
- (error "Sep-string must be non-empty in %s." sepinfo-name))
- (t
- (sepinfo-set-sep-string sepinfo sep-string-default))))
- (if (sepinfo-sep-regexp sepinfo)
- (if (not (sepinfo-sep-regexp-submatch sepinfo))
- (error "Need a submatch to go with sep-regexp `%s' of %s."
- (sepinfo-sep-regexp sepinfo) sepinfo-name))
- (if (and (not (sepinfo-sep-function sepinfo))
- (not dont-set-regexps))
- (sepinfo-set-regexp-and-submatch-from-string
- sepinfo-set-sep-regexp sepinfo-set-sep-regexp-submatch sepinfo
- (sepinfo-sep-string sepinfo) database)))
-
- (if (and (sepinfo-post-last-string sepinfo)
- (equal "" (sepinfo-post-last-string sepinfo)))
- (sepinfo-set-post-last-string sepinfo nil))
- (if (sepinfo-post-last-regexp sepinfo)
- (if (not (sepinfo-post-last-regexp-submatch sepinfo))
- (error "Need a submatch to go with post-last-regexp `%s' of %s."
- (sepinfo-post-last-regexp sepinfo) sepinfo-name))
- (if (and (sepinfo-post-last-string sepinfo)
- (not dont-set-regexps))
- (sepinfo-set-regexp-and-submatch-from-string
- sepinfo-set-post-last-regexp sepinfo-set-post-last-regexp-submatch
- sepinfo
- (sepinfo-post-last-string sepinfo) database))))
-
-
- (defun when-not-preceded-by-quotation-char (string database)
- (concat
- "[^" (database-quotation-char database) "]"
- "\\("
- (regexp-quote (make-string 2 (database-quotation-char database)))
- "\\)*"
- "\\(" (regexp-quote string) "\\)"))
-
- ;; These are macros because we're doing a setq on the variable names.
-
- ;; Shouldn't be using this global variable "database", perhaps.
-
- ;; Note that each argument will be evaluated only once; no need to let
- ;; a gensym to (, string), though that might result in smaller code.
-
-
- ;; (defmacro setf-regexp-and-submatch-from-string
- ;; (regexp-name submatch-name string database)
- ;; (` (let ((str (, string))
- ;; (db (, database)))
- ;; (cond ((or (null str) (equal "" str))
- ;; ((!!) setf (, regexp-name) nil
- ;; (, submatch-name) nil))
- ;; ((database-quotation-char db)
- ;; ((!!) setf (, regexp-name) (when-not-preceded-by-quotation-char
- ;; str db)
- ;; (, submatch-name) 2))
- ;; (t
- ;; ((!!) setf (, regexp-name) (regexp-quote str)
- ;; (, submatch-name) 0))))))
-
-
- ;; REGEXP-NAME and SUBMATCH-NAME are symbols
- (defmacro sepinfo-set-regexp-and-submatch-from-string
- (regexp-setter submatch-setter sepinfo string database)
- (` (let ((str (, string))
- (db (, database))
- (si (, sepinfo)))
- (cond ((or (null str) (equal "" str))
- ((, regexp-setter) si nil)
- ((, submatch-setter) si nil))
- ((database-quotation-char db)
- ((, regexp-setter) si (when-not-preceded-by-quotation-char
- str db))
- ((, submatch-setter) si 2))
- (t
- ((, regexp-setter) si (regexp-quote str))
- ((, submatch-setter) si 0))))))
-
- ;; All this hinges on the database fieldnames, which has been set to a list
- ;; but will now be converted to a vector.
-
- ;; "Set variables and slots of DATABASE that can be set from optional argument
- ;; FIELDNAMES-LIST or, if it is nil, from the fieldnames slot of the database."
- (defun db-set-fieldname-vars (database &optional fieldnames-list)
- (if (not fieldnames-list)
- (setq fieldnames-list (database-fieldnames database)))
- (db-debug-message "db-set-fieldname-vars: %s" fieldnames-list)
-
- ;; the `type' variable can be done away with.
- (let* ((no-of-fields (length fieldnames-list))
- (fno -1)
- type
- (recordfieldspecs (make-vector no-of-fields nil))
- (fieldnames-vector (make-vector no-of-fields nil)))
- (database-set-fieldname-alist database
- (mapcar (function
- (lambda (fname)
- (setq fno (1+ fno)
- type (if (consp fname)
- (prog1 (cdr fname)
- (setq fname (car fname)))
- db-default-field-type))
- (db-debug-message "%s %s %s" fname fno type)
- (if (recordfieldtype-p type)
- (aset recordfieldspecs fno type)
- (error "db-set-fieldname-vars: bad type %s" type))
- (aset fieldnames-vector fno fname)
- (cons fname fno)))
- fieldnames-list))
- ;; (db-debug-message "set-recordfieldspec-vars: recordfieldspec-fieldnames = %s" fieldnames-list)
- ;; (db-debug-message "set-recordfieldspec-vars: recordfieldspec-fieldname-alist = %s" fieldnames-and-numbers)
- (database-set-no-of-fields database no-of-fields)
- (database-set-fieldnames database fieldnames-vector)
- (database-set-recordfieldspecs database recordfieldspecs)))
-
- ;; (defun set-db-vars (database)
- ;; (db-set-field-quotation-vars database))
-
- (defun recordfieldtype->recordfieldspec (recordfieldtype)
- "Return the recordfieldspec associated with symbol RECORDFIELDTYPE."
- (let ((result (cdr (assoc recordfieldtype db-recordfieldtypes))))
- (cond ((recordfieldspec-p result)
- result)
- ((symbolp result)
- ;; recursive call
- (recordfieldtype->recordfieldspec result)))))
- ;; Was inlined when there was no chance of a recursive call.
- ;; (proclaim-inline recordfieldtype->recordfieldspec)
- ;; Not really necessary even now.
- ;; (proclaim-notinline recordfieldtype->recordfieldspec)
-
- (defun recordfieldtype-p (recordfieldtype)
- (assoc recordfieldtype db-recordfieldtypes))
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Substitution
- ;;;
-
- ;; When substitution is called, the database is always in "simple" format:
- ;; fields are separated by sub-fieldsep, and records (including the last
- ;; one) are followed by sub-recordsep.
-
- ;; The "complicated" format includes pre-first-field at the beginning and
- ;; post-last-field (without record-sep-string or pre-first-field, which are
- ;; the other elements of sub-recordsep) at the end.
-
- ;; Simple format is nice because it requires no special-casing at the
- ;; beginning (because there's no gubbish there) or at the end (because
- ;; what's there is exactly what's between each pair of records).
-
- ;; The caller of database-substitute-for-* arranges to convert to/from
- ;; simple format before/after the call.
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- ;; This is called only by read-database-file-delimited.
-
- ;; Its purspose is to convert field/record separators (perhaps choosing new
- ;; ones), so they won't get damaged by the substitution, and then doing the
- ;; substitution. It also must set the sub-{field,record}sep slots, because
- ;; later on in read-database-file-delimited-internal, those fields are
- ;; slavishly followed.
-
- ;; No substitution should occur if either of the separators is a regexp, as
- ;; I can't parse them (or do substitution in them).
-
- ;; Perhaps it should trust the settings for sub-fieldsep et al in the
- ;; database, and not do anything if they're set?
-
- (defun database-substitute-for-read (database)
- (let* ((fieldsep-orig (database-full-fieldsep-string database))
- (recordsep-orig (database-full-recordsep-string database))
- (fieldsep-sub (or (database-sub-fieldsep-string database)
- fieldsep-orig))
- (recordsep-sub (or (database-sub-recordsep-string database)
- recordsep-orig)))
-
- ;; fieldsep-sub and recordsep-sub are never used after this!
- (db-debug-message "d-s-f-r `%s' `%s' `%s' `%s'"
- fieldsep-orig recordsep-orig fieldsep-sub recordsep-sub)
-
- (if (or (sepinfo-sep-regexp (database-record-sepinfo database))
- (sepinfo-sep-regexp (database-field-sepinfo database)))
- (progn
- (database-set-sub-recordsep-string database
- (or (sepinfo-sep-regexp (database-record-sepinfo database))
- (database-sub-recordsep-string database)
- recordsep-orig))
- (database-set-sub-fieldsep-string database
- (or (sepinfo-sep-regexp (database-field-sepinfo database))
- (database-sub-fieldsep-string database)
- fieldsep-orig)))
- ;; No regexps to be found.
- (progn
- ;; If the recordsep is a substring of fieldsep, this order is a
- ;; disaster (unless we do substitution in the fieldsep string as well
- ;; as in the buffer, which is an interesting idea); perhaps get some
- ;; info back in the confirmation list about that, too. Or just trust
- ;; that that won't happen; fieldsep being a substring of recordsep is
- ;; much more likely, after all.
-
- ;; Since I'll be checking against a database file on which
- ;; substitution has already been performed, will the order of
- ;; reverse substitution be important?
-
- (db-debug-message "Checking recordsep-orig `%s'." recordsep-orig)
- (if (not recordsep-orig)
- (error "No record separator specified."))
- (if (database-acceptable-delimiter-p recordsep-orig)
- (database-set-sub-recordsep-string database recordsep-orig)
- (progn
- (db-message "Substituting record delimiter for read...")
- (database-set-sub-recordsep-string database
- (database-generate-delimiter database))
- (db-debug-message "Substituting record delimiter for read... (`%s' for `%s')"
- (database-sub-recordsep-string database) recordsep-orig)
- (goto-char (point-min))
- (replace-string recordsep-orig (database-sub-recordsep-string database))
- (setq fieldsep-orig (string-substitute-substring-general-case
- (database-sub-recordsep-string database) recordsep-orig
- fieldsep-orig))
- (db-message "Substituting record delimiter for read...done")
- ))
-
- (db-debug-message "Checking fieldsep-orig `%s'." fieldsep-orig)
- (if (or (database-acceptable-delimiter-p fieldsep-orig)
- (database-read-record-from-region database))
- (progn
- (db-message "fieldsep-orig `%s' is acceptable." fieldsep-orig)
- (db-message "because (or %s %s)"
- (database-acceptable-delimiter-p fieldsep-orig)
- (database-read-record-from-region database))
- (database-set-sub-fieldsep-string database fieldsep-orig))
- (progn
- (if (not fieldsep-orig)
- (error "No field separator specified."))
- (db-message "Substituting field delimiter for read...")
- (database-set-sub-fieldsep-string database
- (database-generate-delimiter database))
- (db-message "Substituting field delimiter for read... (`%s' for `%s')"
- (database-sub-fieldsep-string database) fieldsep-orig)
- (goto-char (point-min))
- (replace-string fieldsep-orig (database-sub-fieldsep-string database))
- (db-message "Substituting field delimiter for read...done")
- ))
- (db-message "sub-fieldsep = %s" (database-sub-fieldsep-string database))
-
- (db-debug-message "database-substitute-for-read: substitutions = %s"
- (database-substitutions database))
-
- (database-perform-substitutions database t)
- ))))
-
-
- ;; Perhaps have an optional argument which causes this to ignore problems
- ;; with ambiguities.
- (defun database-perform-substitutions (database backward)
- (if (database-substitutions database)
- (progn
- (db-debug-message "Substituting %s" (database-substitutions database))
- (db-message "Substituting...")
- (let ((ambiguities (buffer-substitute
- (database-substitutions database) backward t)))
- (if ambiguities
- (error "Ambiguities: %s" ambiguities)))
- (db-message "Substituting...done"))))
-
-
- ;; Database-substitute-for-write does the substitution, then, if field
- ;; separators were changed in order to prevent them from getting damaged by
- ;; the substitution, converts them back to the user-specified strings,
- ;; which might contain substrings that would have been substituted for in
- ;; the previous operation, had we not been careful.
- (defun database-substitute-for-write (database new-fieldsep sub-fieldsep
- new-recordsep sub-recordsep)
- ;; Check that there are the correct number of fieldseps and recordseps
- ;; here; if wrong number, choose new fieldsep and/or recordsep and take
- ;; it from the top.
- ;; Or will that have been done beforehand?
-
- (db-debug-message "database-substitute-for-write: %s %s %s %s"
- (prin1-to-string new-fieldsep)
- (prin1-to-string sub-fieldsep)
- (prin1-to-string new-recordsep)
- (prin1-to-string sub-recordsep))
-
- (db-debug-message "database-substitute-for-write: substitutions = %s"
- (database-substitutions database))
- (db-debug-message "database-substitute-for-write: about to call database-perform-substitutions")
-
- (database-perform-substitutions database nil)
-
- (if (not (equal sub-fieldsep new-fieldsep))
- (progn
- (db-debug-message "replacing fieldsep")
- (goto-char (point-min))
- (replace-string sub-fieldsep new-fieldsep)))
- (if (not (equal sub-recordsep new-recordsep))
- (progn
- (db-debug-message "replacing recordsep")
- (goto-char (point-min))
- (replace-string sub-recordsep new-recordsep)))
-
- ;; Now the buffer is ready to have the preceding and trailing junk
- ;; added and to be written to disk.
- )
-
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Substitution Utilities
- ;;;
-
-
- ;; Remember that the separators used when reading worked, and if they still
- ;; pass the substitution test, use them again when writing.
- ;; [Do this via a database slot.]
-
-
- ;; If there are no conflicts, these can be set to their usual values and
- ;; everything is happy. Otherwise...
-
-
- ;; Maybe get rid of "database-" from the name of this function.
-
- ;; Is there a better way (ie, fewer false alarms) of checking whether a
- ;; separator is OK (wrt substitution) than checking each character
- ;; individually?
-
- ;; If the seps are strings, I can test whether an overlapping match is
- ;; possible by testing whether each prefix of the string is a suffix. (Do
- ;; I need to cross-test both of them?) If I could show that overlaps are
- ;; impossible, I could use how-many (or how-many-string, etc.) directly.
-
- ;;; This is wrong: I always get it right the first time around, because I
- ;;; check for substitution conflicts.
- ;; If I've been too generous about the recordsep (ie, I know the fieldsep
- ;; is not OK but it looks like the only problem is that there are too many
- ;; fieldseps), then I can always catch it the next time around...when
- ;; reading, I always get it on the first try.
-
- ;; Return a list of (fieldsep-bad-p recordsep-bad-p no-of-records), aka a
- ;; conflist. Useful both when reading and when writing. (When reading,
- ;; database-no-of-records should be nil.)
- (defmacro conflist-fieldsep-bad-p (conflist) (` (car (, conflist))))
- (defmacro conflist-recordsep-bad-p (conflist) (` (car (cdr (, conflist)))))
- (defmacro conflist-no-of-records (conflist) (` (car (cdr (cdr (, conflist))))))
-
- ;; This assumes that recordsep appears at the end of the database as well.
-
- (defun database-confirm-fieldsep-and-recordsep (fieldsep recordsep no-of-fields no-of-records)
- (let* ((fieldsep-matches (progn (goto-char (point-min))
- (how-many-string-overlapping fieldsep)))
- (recordsep-matches (progn (goto-char (point-min))
- (how-many-string-overlapping recordsep)))
- (goal-fieldsep-matches (and no-of-records
- (* no-of-records
- (1- no-of-fields))))
- (goal-recordsep-matches no-of-records))
- ;; Possibly even this can be merged into the main body.
- ;; But keeping it separate might save some computation and special-casing.
- (if (equal fieldsep recordsep)
- (progn
- (db-debug-message "database-confirm-fieldsep-and-recordsep: fieldsep = recordsep")
- (db-debug-message " fm = %s, rm = %s, gfm = %s, grm = %s, nof = %s, nor = %s"
- fieldsep-matches recordsep-matches
- goal-fieldsep-matches goal-recordsep-matches
- no-of-fields no-of-records)
-
- (if no-of-records
- (if (= recordsep-matches (+ goal-recordsep-matches
- goal-fieldsep-matches))
- (list nil nil no-of-records)
- (list t t nil))
- ;; Remember that fieldsep = recordsep when reading this code.
- (if (zerop (% recordsep-matches no-of-fields))
- (list nil nil (/ recordsep-matches no-of-fields))
- (if (zerop (% (1+ recordsep-matches) no-of-fields))
- ;; A field separator at the end of the data was misinterpreted
- ;; as a record separator, so no record separator was added.
- (progn (goto-char (point-max))
- (insert recordsep)
- (list nil nil (/ (1+ recordsep-matches) no-of-fields)))
- (list t t (/ recordsep-matches no-of-fields))))))
- ;; fieldsep and recordsep unequal; see if one is a substring of the other.
-
- ;; At least one of these must be zero.
- (let ((f-in-r (how-many-substring-overlapping fieldsep recordsep))
- (r-in-f (how-many-substring-overlapping recordsep fieldsep)))
- (db-debug
- (db-debug-message "database-confirm-fieldsep-and-recordsep:")
- (db-debug-message " fm = %s, rm = %s, gfm = %s, grm = %s, nof = %s, nor = %s, f-in-r = %s, r-in-f = %s"
- fieldsep-matches recordsep-matches
- goal-fieldsep-matches goal-recordsep-matches
- no-of-fields no-of-records
- f-in-r r-in-f)
- )
-
- (if no-of-records
- (progn
- ;; at most one of these is nonzero, so cond is OK.
- (cond ((> f-in-r 0)
- (setq goal-fieldsep-matches
- (+ goal-fieldsep-matches
- (* goal-recordsep-matches f-in-r))))
- ((> r-in-f 0)
- (setq goal-recordsep-matches
- (+ goal-recordsep-matches
- (* goal-fieldsep-matches r-in-f)))))
- (list (not (= fieldsep-matches goal-fieldsep-matches))
- (not (= recordsep-matches goal-recordsep-matches))
- no-of-records))
- (progn
- (setq no-of-fields (+ no-of-fields f-in-r))
- (let ((apparent-records (/ fieldsep-matches (1- no-of-fields))))
- (setq goal-recordsep-matches
- (if (zerop apparent-records)
- 1
- (* apparent-records
- (1+ (* r-in-f fieldsep-matches)))))
- (db-debug
- (db-debug-message "database-confirm-fieldsep-and-recordsep: ar = %d, grm = %d, nof = %d"
- apparent-records goal-recordsep-matches no-of-fields))
- (list (or
- ;; Wrong number of fields: some record has too many or too few.
- ;; This clause equivalent to:
- ;; (not (zerop (% fieldsep-matches (1- no-of-fields))))
- ;; which is more efficient?
- (not (= fieldsep-matches (* apparent-records
- (1- no-of-fields))))
- ;; too many fieldseps compared to recordseps
- (< recordsep-matches goal-recordsep-matches))
- ;; too many recordseps compared to fieldseps
- (< goal-recordsep-matches recordsep-matches)
- apparent-records))))))
- ))
-
-
-
- ;; Note that this is global; hope we only write/read one database at a
- ;; time. It should be nilled out before starting to read/write a database.
- ;; Maybe don't bother with this and go searching down the substitutions
- ;; list (which won't be very long, typically) as is. Maybe use a big
- ;; vector (one elt per character) to show which characters are OK. Maybe
- ;; I'm worrying too much about this little issue.
-
- (defvar db-substitution-no-no-string nil)
-
- ;; Calling this repeatedly could be slow because of repeated computation of
- ;; substitution-no-no-string; maybe compute it (or, better, nil it out; can
- ;; check and set if need be) at the beginning of reading/writing.
-
- ;; Get rid of database argument.
-
- ;; Return t iff no characters of delimiter appear in substitution-no-no-string.
- (defun database-acceptable-delimiter-p (delimiter)
- (if delimiter
- (let ((result t)
- (string-index 0)
- (delimiter-length (length delimiter)))
- (while (and result (< string-index delimiter-length))
- (if (find-char (elt delimiter string-index)
- db-substitution-no-no-string)
- (setq result nil)
- (setq string-index (1+ string-index))))
- result)))
-
- ;; call this in the write buffer.
- (defun database-generate-delimiter (database &optional check-buffer)
- (let ((string (make-string 1 0))
- (candidate-char 0)
- result)
- (while (and (< candidate-char 256) (not result))
- (aset string 0 candidate-char)
- (if (and (database-acceptable-delimiter-p string)
- (not (and check-buffer
- (progn
- (goto-char (point-min))
- (search-forward string nil t))))
- (not (find-char candidate-char
- (or (database-sub-fieldsep-string database)
- (database-full-fieldsep-string database))))
- (not (find-char candidate-char
- (or (database-sub-recordsep-string database)
- (database-full-recordsep-string database)))))
- (setq result string)
- (setq candidate-char (1+ candidate-char))))
- (or result
- (error "I can't find an acceptable delimiter!"))))
-
-
- (defun database-set-temp-delimiters (database db-buffer)
- ;; DB-BUFFER is only set if this is for reading.
- ;; (Well, for writing I'm only going to call this if there was some
- ;; sort of trouble and I need to recompute from the database buffer,
- ;; so I suspect that DB-BUFFER will always be set. Maybe just use
- ;; the current buffer.)
-
-
- ;; If reading, we have to trust that there are only the correct number
- ;; of field delimiters currently in the buffer.
-
-
-
-
- )
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Quotation
- ;;;
-
- (defun db-set-field-quotation-vars (database)
- (let ((quotation-char (database-quotation-char database)))
- (if (stringp quotation-char)
- (let ((quot-char-len (length quotation-char)))
- (cond ((= 0 quot-char-len)
- (setq quotation-char nil))
- ((= 0 quot-char-len)
- (setq quotation-char (elt quotation-char 1)))
- (t
- (error "Quotation character should be just that, not some rambling string like `%s'." quotation-char)))
- (database-set-quotation-char database quotation-char)))
- (if quotation-char
- (progn
- (database-set-quotation-char-regexp database
- (regexp-quote (char-to-string (database-quotation-char database))))
- (database-set-actual-quoted-regexp database
- (or (database-quoted-regexp database)
- (mapconcat (function regexp-quote)
- (sort (or (database-quoted-strings database)
- (quoted-strings-default database))
- (function string-longer-p-function))
- "\\|")))))))
-
- ;; There are so many things that this could be dependent on (including all
- ;; the strings in all the sepinfos) that I have a hidden variable here.
- ;; This ensures that all the proper changes get propagated to the proper
- ;; places. (Say that ten times fast.)
-
-
- ;; (set-field-quotation-vars '("foo" "bar" "f" "foobar" "[]"))
-
- ;; This could be inefficient: since it will be called repeatedly, it would
- ;; be nice not to have to check database-quotation-character and other
- ;; database slots all the time.
-
- ;; One possibility is to put it all in a buffer (say, different fields on
- ;; different lines) and do a replace-regexp, then cons it all up. But then
- ;; again, I expect there to be very little quoting in general, and that
- ;; sounds like a painful solution.
-
- (defun field-quote (fieldvalue database)
- (if (and (database-quotation-char database)
- (string-match (database-quoted-regexp database) fieldvalue))
- (concat (substring fieldvalue 0 (match-beginning 0))
- (database-quotation-char database)
- (substring fieldvalue (match-beginning 0) (match-end 0))
- (field-quote (substring fieldvalue (match-end 0)) database))
- fieldvalue))
-
- ;; Is this use of actual-quoted-regexp correct?
- (defun field-unquote (fieldvalue database)
- (if (database-quotation-char database)
- (let ((i (string-match (database-quotation-char-regexp database)
- fieldvalue)))
- (if i
- (if (= (1+ i) (string-match (database-actual-quoted-regexp database)
- fieldvalue (1+ i)))
- (concat (substring fieldvalue 0 i)
- (substring fieldvalue (1+ i) (match-end 0))
- (field-unquote (substring fieldvalue (match-end 0))
- database))
- (concat (substring fieldvalue 0 (1+ i))
- (field-unquote (substring fieldvalue (1+ i))
- database)))
- fieldvalue))
- fieldvalue))
-
- ;; Later on the result will probably get sorted by length, but I don't do it now.
- (defun quoted-strings-default (database)
- (if (database-quotation-char database)
- (delq nil
- (list
- (sepinfo-pre-first-string (database-record-sepinfo database))
- (sepinfo-sep-string (database-record-sepinfo database))
- (sepinfo-post-last-string (database-record-sepinfo database))
-
- (sepinfo-pre-first-string (database-field-sepinfo database))
- (sepinfo-sep-string (database-field-sepinfo database))
- (sepinfo-post-last-string (database-field-sepinfo database))
-
- (sepinfo-pre-first-string (database-alternative-sepinfo database))
- (sepinfo-sep-string (database-alternative-sepinfo database))
- (sepinfo-post-last-string (database-alternative-sepinfo database))
-
- (char-to-string (database-quotation-char database))))))
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Random useful functions
- ;;;
-
- ;; A sep-function for records with n lines.
- (defun record-sep-lines-function (n)
- (` (lambda (post-last-item-pos)
- (next-line (, n))
- (let ((here (point)))
- (cons (1- here)
- (if (< here post-last-item-pos)
- here))))))
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;; Stuff
- ;;;
-
-
- ;; I don't want to have to use how-many-*-overlapping because I ought to
- ;; have been smarter than that in choosing my field separators (such that
- ;; there's no overlap). But, on the other hand, it is possible that after
- ;; all is said and done and I've replaced the actual for the generated
- ;; separator strings, then ambiguities arise. Eg, if one field ends "ab"
- ;; and the actual separator string is "aba". This seems to imply that
- ;; quoting alone is insufficient (unless I decided to quote single
- ;; characters; yes, that's the right thing to do).
-
- ;; [As mentioned elsewhere, this is a danger if (?? and only if ??) some
- ;; prefix of the separator is a suffix of the separator. (?? Check only
- ;; within wone separator, or check both? Overlapping has nothing to do
- ;; with having characters in common with substitutions, however; it is
- ;; after substituting in that ambiguities may arise. The only solution for
- ;; them is quoting, or substitution out of any possible ambiguities (ie,
- ;; substitution for every character of, or at least first and last
- ;; characters of, separators); this is feasible if separators are small,
- ;; but totherwise? ??)
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- ;; Quoting and substitution take too long (too many function calls, and too
- ;; much work usually for naught, too much garbage creation) when called on
- ;; individual strings. Don't want to put quoting/substitution in the s->a and
- ;; a->s functions (and in the constraint functions) because of the burden on
- ;; the user, even though that would amortize the time for this operation
- ;; (check each time a field is changed) and speed quoting/substitution (call
- ;; it only on the fields that might need it); besides, if it was called on
- ;; many fields, using s->a and a->s still results in many function calls.
- ;;
- ;; So do quoting and substitution on the database when it's in a buffer.
- ;;
- ;; Don't do them by record or by field, even though that would guarantee that
- ;; we didn't munge the separators, because it would be too slow, even in the
- ;; database buffer.
- ;;
- ;; If q/s won't munge the separators (look at the substitutions about to be
- ;; done), then great. Otherwise use some characters that are guaranteed to be
- ;; ok as field and record separators. When reading, read from disk, replace
- ;; the actual separators by these characters, do quoting/substitution, and
- ;; then parse the database using the generated characters. When writing,
- ;; write to a buffer with the characters, do quoting/substitution, replace the
- ;; charcters with the real delimiters, and finally write to disk.
- ;;
- ;;
- ;;
- ;; Have variables that control whether the separator characters are allowed in
- ;; the database; if no, add that to the constraint function for every field.
- ;; (Of course, this works only if display and stored representations are the
- ;; same; if they're different, then I'd have to call actual->stored just to
- ;; check this.)
- ;;
- ;;
- ;; [Only worry is that then something
- ;; will come out of the last field badly; perhaps insert an extra field
- ;; delimiter right before the record delimiter. Yes, that would make things
- ;; easy for me. But that sort of goes against the grain of what I want to do.]
- ;;
- ;;
- ;; Now, the real whole point of this was to permit a wide variety of
- ;; formats. But that's only useful if the formats are easy to read. Is
- ;; quoting, which changes the field value, really going to make it easier to
- ;; read? One should be able to tell where fields start and end... (actually,
- ;; I think quoting *does* help with that).
- ;; Furthermore, my functions only work if the fields are regular (well,
- ;; describable by regexps for reading in and by strings for writing out).
- ;; More complicated formats will need to have a custom Lisp function for
- ;; reading/writing anyway, so don't sweat too much over them.
- ;; The real problem, then, is substituting for the field separator
- ;; character(s). If the field separator is multi-char, we only need to make
- ;; sure that one character in any match is substituted for.
- ;; Or I could deal with the field separator character specially and disallow
- ;; it in the general checking function.
- ;; To try to write my own replace-one-char-by-one-char that's more efficient
- ;; than replace-string is lunacy. [Or is it? Search-forward, delete char,
- ;; insert char ought to be quicker than than serach-forward, replace-match.]
- ;;
- ;; How to devise the characters (or even strings, if I so choose) of my
- ;; devising? If they're in the text, I lose. I guess I have to check for
- ;; them all the time; but they might be in the actual or display rep and not
- ;; in the stored rep, or vice-versa. So only check for string types, mayhap.
- ;; Have a variable: if set, then check for the char. Do this via
- ;; count-occurrences. [Always do this; it won't be so terribly expensive.]
- ;; There need only be one special char (ie, not one for field and another
- ;; for record) if my replace-one-char-by-one-char takes a repetition count and
- ;; I call it repeatedly; but I don't particularly want that many function
- ;; calls. So make it a defsubst or something.
- ;;
- ;; The plan for writing:
- ;; Have a variable which controls whether to use a generated character (and
- ;; if so, which one, unless it's t, in which case try to find one, by trying
- ;; something, and if it doesn't work looking for a character that doesn't
- ;; appear; probably try again in any case if failure). [Maybe figure this out
- ;; by looking at the substitutions; though if quoting is used instead (should
- ;; I still have quoting at all?), then the substitutions tell me nothing.] If
- ;; not using a generated character, then either use the field separator
- ;; throughout (can substitute in at record boundaries later on) or use both
- ;; the field and record separators. Choosing a generated character:
- ;; shoudln't be in either side of any substitution stuff. Shouldn't be
- ;; quotation character.
- ;; write fields one after the other using this character only to separate
- ;; them.
- ;; check whether there are too many of the character or just the right
- ;; number.
- ;; do substitution
- ;; substitute for the separator character.
- ;;
- ;; Do I want to permit regexps instead of strings as separators? Do I want to
- ;; permit multi-char strings as separators?
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- ;; It may be necessary as a last resort to call quoting/substitution on
- ;; fields individually if every character appears in the database file
- ;; representation. In this case substitution *will* lose information.
- ;; What a nightmare!
-
- ;; When doing substitution, warn if information is about to be lost. (ie,
- ;; if target appears in database).
-
- ;;; db-file-io.el ends here
-