;; SaveData.lsp
;; Save *any* variable with the DWG
;;    Written  Nov 30, 1997.
;;    Updated for r15 and xdict-* functions added Dec 7, 1999.
;;
;;*************************************************************
;;   Copyright Vladimir Nesterovsky 1997, All Rights Reserved
;;
;; You may use, copy and distribute this program *unmodified*
;; for any non-commercial, non-profit purpose and without
;; charging any fee, if you keep this notice in its entirety
;;       in all copies and in all your derived works.
;;
;; This program is provided "AS IS" and has absolutely
;;  no warranty of any kind. Use it at your own risk.
;;
;; You're welcome to contact me about the possibility to use
;; this program commercially, and also with any comments and
;;        requests at vnestr@netvision.net.il,
;;              https://vnestr.tripod.com/
;;*************************************************************
;;
;; This file contains functions that will let you save any
;; list of data without dotted pairs inside dictionary with
;;      (dict-put "mydict" "mykey" "myval")
;; and retrieve it later with
;;      (dict-get "mydict" "mykey")
;; OR store it in xdictionary under some entity with
;;      (xdict-put ename "mykey" "myval")
;; and retrieve it similarly with
;;      (xdict-get ename "mykey")
;; xdictionary only allows for one key to hold the data,
;; regular dictionaries don't have this restriction, so you
;; may use any number of keys inside your dictionary
;;*************************************************************
;;
;; BEWARE that these functions use recursion in encoding / decoding
;; so for very long and complex lists they can use up all available
;; AutoLISP stack space and cause "AutoLISP stack overflow" error.
;;
;; look into https://vnestr.tripod.com/SaveData.txt !


;;;;;;;~~~~~~~~~~~~~~~~~~~~~~~~~~~~;;;;;;;
;;;;;;; The two working functions: ;;;;;;;
;;;;;;;____________________________;;;;;;;
;;
;;Sample use:
;; (dict-put "mydict" "mykey" '(1 (2 ("3" (44.4) (5.5 6.6 7.7) 8)) 999999))
;; (dict-get "mydict" "mykey")
;;POSSIBLE IMPROVEMENTS:
;; implement dict-append ( APPEND PREPEND )


;; Get A [Key] Value From Dictionary [Name]
(cond
 ((member (substr (getvar "acadver") 1 2)  '( "13" "14"))
  (defun dict-get ( name key ) ;; name and key As strings
    (x-relist                  ;; decode the data!
      (cdr (member '(100 . "AcDbXrecord")
        (dict-getrawdata name key))))))
 ((= "15" (substr (getvar "acadver") 1 2))
  (defun dict-get ( name key ) ;; name and key As strings
    (x-relist                  ;; decode the data!
      (cddr (member '(100 . "AcDbXrecord") ;; new (280) code group in r15
        (dict-getrawdata name key)))))))

;; Put A [Val]ue Into [Name] Dictionary Under [Key]
;; Dictionary is created automatically if was not existing
;; three arguments As strings
;; NAME may also be an entity name of some existing dictionary
(defun dict-put ( name key val )
  (dict-clear name key) ;;  clear the old value under this Key
  (dictadd
    (setq name (cond ((dict-name name)) ((dict-new name)) ))
    key
    (entmakex
      (cons '(0 . "XRECORD")
        (cons '(100 . "AcDbXrecord")
          (x-enlist val)))) ) ;; encode the data!
  name )  ;; return the dictionary altered


;;;;;;;~~~~~~~~~~~~~~~~~~~~~~~~~~~~;;;;;;;
;;        XDICTIONARY SUPPORT
;;  two more similar functions to use:
;;;;;;;~~~~~~~~~~~~~~~~~~~~~~~~~~~~;;;;;;;

(defun xdict-put ( ename key val )
 (addxdict ename (dict-put nil key val)))

(defun xdict-get ( ename key )
 (dict-get (cdr (assoc 360 (entget ename))) key))


;;;;;;; utility functions in use ;;;;;;;

;; a general utility -- pop first value off the list
(defun pop (a / b)
  (setq b (eval a))
  (set a (cdr b))
  (car b))

;; general functions re/en-list, just to try things out
;; re-list can be also used in decoding xdata
(defun re-list ( lst / tok )
 (re-list-aux))

(defun re-list-aux()
 (cond
  ((atom lst) lst)
  ((/= "}" (setq tok (pop 'lst)))
   (cons (if (= "{" tok) (re-list-aux) tok)
         (re-list-aux)))))

(defun en-list ( lst )
  (cond
   ((null lst) lst)
   ((atom lst) (list lst))
   ((cons "{"    ;; add the markers around the list!
      (reverse
        (cons "}"
          (reverse
            (apply 'append  ;; open the lists
              (mapcar 'en-list lst) ;; recursion!
      ))))))))

;; a special encoding function for Xrecords to be used in dictionaries
(defun x-enlist ( lst )  ;; encode!
  (cond
   ((null lst) lst)
   ((atom lst)           ;; automatic code groups
     (cond
      ((= 'REAL (type lst))
        (list (cons 40 lst)))
      ((= 'INT (type lst))
       (if               ;; special handling of long integers
         (< -32768 lst 32767)
          (list (cons 70 lst))
          (list (cons 41 (float lst)))))
      ((= 'STR (type lst))
        (list (cons  1 lst)))
      (T nil)))
   ((and (cdr lst) (atom (cdr lst)))
      (list lst))      ;; pass dotted pair AS IS -- must be valid!!
   ((and (= (length lst) 3)
         (apply 'and (mapcar 'numberp lst)))
     (list (cons 10 lst)))
   ((cons '(2 . "{")
     (reverse
      (cons '(2 . "}")
       (reverse
        (apply 'append
         (mapcar 'x-enlist lst) ))))))))


(defun x-relist ( lst / tok )  ;; decode it!
 (car (x-relist-aux)))

(defun x-relist-aux()
 (cond
  ((null lst) nil)
  ((not (equal '(2 . "}") (setq tok (pop 'lst))))
   (cons (if (equal '(2 . "{") tok)
           (x-relist-aux)
           (cond      ;; special processing of TOK to recover
             ((= 41 (car tok)) ;; long integers back
              (fix (cdr tok)))
             (T
              (cdr tok)))
           )
         (x-relist-aux)))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; dictionary functions for R14
;; POSSIBLE IMPROVEMENTS: support trees of dictionaries
;;      now: all custom dictionaries are under the Root.
;;
;; Get Dictionary Object Name From Root Dictionary
(defun dict-name ( name ) ;; name As string or ename of exist dict
 (cond
   ( (= 'STR (type name))
     (cdr (assoc -1 (dictsearch (namedobjdict) name))))
   ( (and (= 'ENAME (type name))
          (= "AcDbDictionary" (cdr (assoc 100 (entget name)))) )
     name )))

;; Remove Dictionary [Name] From Root Dictionary
(defun dict-remove ( name / d0 ) ;; name As string
  (if (dictsearch (Setq d0 (namedobjdict)) name)
    (dictremove d0 name))
  )

;; Make A New Empty Dictionary and put into into Root; Reset Existing.
(defun dict-new ( name / d0 ) ;; name As string
  (cond
    ( (/= 'STR (type name))
      (dict-new-orphan) )
    ('t
      (if (dictsearch (Setq d0 (namedobjdict)) name)
        (dictremove d0 name))
      (dictadd d0 name
        (dict-new-orphan) ))
    ))

;; (dict-new-ex parent name) parent := ROOT | ename (dict or reg for xdict)

(defun dict-new-orphan ()
  (entmakex '((0 . "DICTIONARY")(100 . "AcDbDictionary"))))

;; List A Dictionary As Pairs {Name . Object Name}
(defun dict-list ( dname / d nl ) ;; dname As object name or string else ROOT
  (cond
    ((and (Setq d (cond
                    ((= 'ENAME (type dname))
                      dname)
                    ((= 'STR (type dname))
                      (dict-name dname))
                    (T (namedobjdict))))
          (Setq d (entget d)))
      (while (setq d (member (assoc 3 d) d))
        (if (or (= 350 (caadr d)) ;; regular dict
                (= 360 (caadr d)) ;; extension dict
                )
          (setq nl (cons (cons (cdar d) (cdadr d)) nl)))
        (setq d (cdr d)))
      (reverse nl))))

;; Get Raw [Name] Dictionary Data for [Key]
(defun dict-getrawdata ( name key / d ) ;; name and key As strings
  (cond
    ((='STR (type name))
      (if (setq d (dict-name name))
        (dictsearch d key)))
    ((='ENAME (type name))
      (dictsearch name key) )
    ( (dictsearch (namedobjdict) key) )))

;; Clear The [Key] From Dictionary [Name]
(defun dict-clear ( name key / d) ;; name and key As strings
  (if (setq d (dict-name name))
    (dictremove d key)))

;; add xdictionary DK into the carrier entity data ECD
;; you can use it like (addxdict _entname_ (dict-put nil _key_ _val_))
;; eg. (addxdict (tblobjname"layer""0") (dict-put nil "MYDATA" 1.23))
;; to associate some data with layer "0".
;; you'll retrieve this data later with
;;   (setq d (cdr(assoc 360 (entget (tblobjname "layer""0")))))
;; and
;;   (dict-get d _key_)
(defun addxdict (ECD DK / TT)
 (cond
   ( (= 'ENAME (type ecd))
     (addxdict (entget ecd) dk) )
   ( (or (atom ecd) (atom (car ecd)) (not (atom (cdar ecd))))
     nil )
   ( (= 'STR (type dk))
     (addxdict ecd (dictsearch (namedobjdict) dk)) )
   ( (/= 'ENAME (type dk))
     nil )
   ( (/= "AcDbDictionary" (cdr (assoc 100 (entget dk))))
     nil )
   ( (assoc 102 ecd)
   ;; there already is an extension dictionary attached to this entity
   ;; can I prepend another dictionary into it?? No! :-(
     (while (/= (caar ecd) 102)
       (setq tt  (cons (car ecd) tt)
             ecd (cdr  ecd)))
     (setq tt  (cons (car ecd) tt) ;; {
           ecd (cdr ecd))
     (while (/= 102 (caar ecd))    ;; }
       (setq ecd (cdr ecd)))

     (setq ecd (cons (cons 360 dk) ecd) )
     (while tt
       (setq ecd (cons (car tt) ecd)
             tt  (cdr tt)))
     (entmod ecd))
   ( (assoc 100 ecd)
     (while (/= (caar ecd) 100)
       (setq tt (cons (car ecd) tt)
            ecd (cdr  ecd)))
     (setq ecd
       (cons (quote (102 . "{ACAD_XDICTIONARY"))
         (cons (cons 360 dk)
           (cons (quote (102 . "}")) ecd))))
     (while tt
       (setq ecd (cons (car tt) ecd)
             tt  (cdr tt)))
     (entmod ecd))
 ))

;; EOF SaveData.lsp