AutoCAD... AutoLISP... VisualLISP...

  [47] Plik tekstowy - odczyt

index  

  Gdy czasem znadzie się coś w "starociach"... zaskakującym może być że, owe starocie jeszcze całkiem dobrze działają... ;). Poniższy tekst ukazał się w nieistniejącym już piśmie "Magazyn 3D" (5/1999) w roku 1999!:
  Możliwość dostępu do danych zawartych w plikach zewnętrznych z poziomu AutoLISP-u, ma wielkie znaczenie dla różnego typu programów i aplikacji AutoCAD-a. Trudno sobie wyobrazić jakiekolwiek oprogramowanie do parametryzacji obiektów, bez dostępu do danych zewnętrznych. Przesyłanie danych do edytora graficznego AutoCAD-a, z programów obliczeniowych, tworzących zestawienia itp. często odbywa się poprzez pliki zewnętrzne, z reguły tekstowe. Mimo pewnych ograniczeń (przede wszystkim długi czas dostępu i wielkość plików danych), stosowanie mechanizmów dostępu do plików tekstowych, przez AutoLISP, nierzadko jest jedyną metodą na tworzenie, nawet dużych a wydajnych i elastycznych programów. Za stosowaniem plików tekstowych do wymiany danych z AutoCAD-em przemawiają również takie cechy jak prostota składni, możliwość edycji bez używania specjalnych narzędzi, wygoda i szybkość wprowadzania zmian, możliwość koncentrowania danych w jednym miejscu.

»  Odczyt danych z plików tekstowych

Wykorzystywanie standardowej funkcji (READ-LINE) posiada następujace ograniczenia:
  • każde wywolanie funkcji zwraca jedną linię (aż do napotkania znaku nowej linii)
  • wynikiem wywołania funkcji jest łańcuch tekstowy
  • linie czytane są kolejno poprzez każdorazowe wywołanie funkcji (read-line) (podczas otwarcia pliku funkcją OPEN - aż do napotkania znaku końca pliku)
  • READ-LINE nie potrafi cofnąć się do poprzednich linii (wcześniej czytanych)
Biorąc pod uwagę te ograniczenia (oraz inne - najważniejsze - to czas dostępu) bardzo wielką rolę odgrywa tu odpowiednie formatowanie pliku tekstowego. Najbardziej rozpowszechnione typy plików do przechowywania danych to:
  • Format SDF (Space Delimited Format)
    Dane sformatowane są w kolejnych liniach a jako separatory uzywane są jedna lub więcej spacji. Rekordy tworzą kolumny (każdy z nich zaczyna i kończy się w tej samej kolumnie). Zawartość takiego pliku może wyglądać tak:
    100.00 120.50 185.00 20
    100.50 122.75 190.50 35

    itd. kolejne linie...
    W pewnych sytacjach formatowanie takie, może okazać się niewygodne (dla danych o zmiennych lub nieznanych długościach).
  • Format CDF (Comma Delimited File)
    Dane sformatowane są w kolejnych liniach, separatorem jest przecinek. Każdy rekord może mieć różną długość. Plik może wyglądać tak:
    10.20,12.67,18.98
    100.20,122.67,138.98

    itd. kolejne linie...
    W celu przyśpieszenia odczytywania danych można zastosować odpowiedni "klucz" po którym odpowiednia linia jest poszukiwana, lub po jego wystąpieniu czytana jest jedna lub więcej linii (wyglądają tak pliki AutoCAD-a typu *.LIN i *.PAT)
W obu przypadkach zadaniem programu lispowego analizującego plik tekstowy jest odczytanie określonej linii pliku i zwrócenie wyniku jako listy danych. Pierwszym etapem realizacji tego zadania jest odczytanie linii pliku. W tym celu zdefiniowałem funkcję o nazwie jk:SYS_READ-FILES. Jest ona tak skonstruowana aby odczytywać dane z pliku na dwa sposoby: - odczytanie całego pliku i zwrócenie listy łańcuchów tekstowych (linii pliku) - odczytanie jednej linii określonej przez jej pozycję
;; ----------------------------------------------------------------------------- ;
;; ODCZYT PLIKU TEKSTOWEGO                                                       ;
;; (1999) kojacek                                                                ;
;;                                                                               ;
;; Funkcja zwraca listę, łańcuch lub NIL                                         ;
;;                                                                               ;
;; Argumenty funkcji:                                                            ;
;; ===================                                                           ;
;;  CODE - gdy równe NIL funkcja zwraca listę łańcuchów tekstowych będących      ;
;;         liniami całego pliku. Gdy CODE różne od NIL (musi być typu INTEGER)   ;
;;         zwraca łańcuch tekstowy będący linią określoną przez ten argument.    ;
;;         (Uwaga: dla pierwszej linii CODE=0 dla drugiej 1 itd.)                ;
;;         Gdy INT jest większe niż ilość linii w pliku funkcja zwraca NIL       ;
;;  FILE - nazwa pliku - gdy bez scieżki dostępu plik poszukiwany jest na        ;
;;         standardowej ścieżce poszukiwań AutoCAD-a. Jeśli plik nie znajduje    ;
;;         się na tej ścieżce trzeba podać nazwę pliku wraz z pełną ścieżką      ;
;;         dostępu. W obu przypadkach gdy plik nie zostanie znaleziony,          ;
;;         wyświetlane jest okienko ostrzegawcze i funkcja zwraca NIL            ;
  (defun jk:SYS_Read-Files (CODE FILE / FLN FLD FDAT X)
    (setq FLN (findfile FILE))
    (if FLN
      (progn
        (if CODE
          (progn
            (setq FLD (open FLN "r"))
            (repeat CODE (read-line FLD))
            (setq FDAT (read-line FLD))
            (close FLD)
          )
          (progn
            (setq FLD (open FLN "r"))
            (while
              (setq X (read-line FLD))
              (setq FDAT
                (append FDAT
                (list X)
                )
              )
            )
            (close FLD)
          )
        )
      )
      (alert (strcat "Nie znaleziono pliku " FILE " "))
    )
    (if FDAT FDAT NIL)
  )
;; ----------------------------------------------------------------------------- ;
Można przetestować teraz funkcję w następujący sposób: Po załadowaniu tego pliku w linii poleceń wywołanie:
(jk:SYS_Read-Files NIL "DANE.DAT")
lub
(jk:SYS_Read-Files 3 "DANE.DAT")
wyświetli okno ostrzegawcze i zwróci NIL, bo nie ma pliku DANE.DAT
natomiast wywołanie:
(jk:SYS_Read-Files NIL "ACAD.LIN")
zwróci listę wszystkich linii w pliku acad.lin, a wywołanie:
(nth 12 (jk:SYS_Read-Files NIL "ACAD.LIN"))
prawdopodobnie zwróci:
"*CENTER,Center ____ _ ____ _ ____ _ ____ _ ____ _ ____"
Inny sposób wybrania (tylko) dwunastej linii z tego pliku wygląda tak:
(jk:SYS_Read-Files 12 "ACAD.LIN")
zwróci:
Z punktu widzenia przykładowego programujacego, ważne jest pobranie tutaj trzynastego wiersza pliku acad.lin, zatem:
(jk:SYS_Read-Files 13 "ACAD.LIN")
zwróci:
"A,1.25,-.25,.25,-.25"
Oczywiście zamiast pliku ACAD.LIN można wykorzystać każdy inny plik tekstowy.

Funkcja jk:SYS_Read-Files oczywiście spełnia swoje zadanie, kiedy należy odczytać cały plik, lub odczytać linię na znanej pozycji. W przypadku gdy nie wiemy w którym miejscu pliku znajdują się dane, a znamy "identyfikator" po którym linia jest rozpoznawana, wykorzystamy funkcję o nazwie jk:SYS_Read-Lines, której działanie sprowadza się do:
- odczytania jednej, następnej linii po podanym "kluczu", lub
- odczytania jednej (tej samej linii) co podany "klucz".
Funkcja wygląda tak:
;; ----------------------------------------------------------------------------- ;
;; ODCZYT LINII PLIKU TEKSTOWEGO                                                 ;
;; (1999) kojacek                                                                ;
;;                                                                               ;
;; Funkcja zwraca łańcuch lub NIL                                                ;
;;                                                                               ;
;; Argumenty funkcji:                                                            ;
;; ===================                                                           ;
;;  CODE - gdy równe NIL funkcja zwraca łańcuch tekstowy będący linią pliku i    ;
;;         zawierający podany "klucz" (argument KEYN), w przeciwnym wypadku      ;
;;         (gdy CODE różne od NIL) zwraca następną linię. W przypadku gdy        ;
;;         KEYN nie istnieje funkcja zwraca NIL.                                 ;
;;         Gdy INT jest większe niż ilość linii w pliku funkcja zwraca NIL       ;
;;  FILE - nazwa pliku - gdy bez scieżki dostępu plik poszukiwany jest na        ;
;;         standardowej ścieżce poszukiwań AutoCAD-a. Jeśli plik nie znajduje    ;
;;         się na tej ścieżce trzeba podać nazwę pliku wraz z pełną ścieżką      ;
;;         dostępu. W obu przypadkach gdy plik nie zostanie znaleziony,          ;
;;         wyświetlane jest okienko ostrzegawcze i funkcja zwraca NIL            ;
;;  KEYN - "klucz" poszukiwań. Gdy w pliku występuje więcej odwołań do "klucza"  ;
;;         zwracane jest zawsze ostatnie napotkane wystąpienie.                  ;
;;         Rozpoznawane są duże i małe litery.                                   ;
  (defun jk:SYS_Read-Lines (CODE FILE KEYN / FLN FLD FDAT LEN BUF X)
    (setq LEN (strlen KEYN)
          FLN (findfile FILE)
    )
    (if FLN
      (progn
        (if CODE
          (progn
            (setq FLD (open FLN "r"))
            (while
              (setq BUF (read-line FLD))
              (if
                (= (substr BUF 1 LEN) KEYN)
                (setq FDAT BUF)
              )
            )
            (close FLD)
          )
          (progn
            (setq FLD (open FLN "r"))
            (while
              (setq BUF (read-line FLD))
              (if
                (= (substr BUF 1 LEN) KEYN)
                (setq FDAT (read-line FLD))
              )
            )
            (close FLD)
          )
        )
      )
      (alert (strcat "Nie znaleziono pliku " FILE " "))
    )
    (if FDAT FDAT NIL)
  )
;; ----------------------------------------------------------------------------- ;
Do testowania można wykorzystać plik acad.lin Wywołanie:
(jk:SYS_Read-Lines T "acad.lin" "*DASHDOT,")
zwróci:
"*DASHDOT,Dash dot __ . __ . __ . __ . __ . __ . __ . __"
czyli linię w której wystąpił klucz, natomiast:
(jk:SYS_Read-Lines NIL "acad.lin" "*DASHDOT,")
zwraca:
"A,.5,-.25,0,-.25"
Warto zaznaczyć że funkcję tę można wykorzystać do czytania plików typu *.INI Na przykład wywołanie: (jk:SYS_Read-Lines T "mtextmap.ini" "isocp.shx=") zwraca: "isocp.shx=ISOCP,0,0,0,2" Ponieważ READ-LINE zawsze zwraca dane jako łańcuch tekstowy osobnym problemem jest teraz "obróbka" takiego łańcucha otrzymanego przez funkcję jk:SYS_Read-Lines lub jk:SYS_Read-Fileses. Do tego celu służy przedstawiona poniżej funkcja S_PARSE, której autorem jest Bill Kramer, została ona opublikowana w miesięczniku CADENCE 4/1997 Funkcję realizującą to samo zadanie (podział łańcucha tekstowego) można również znaleŸć w książce "AutoLISP Praktyczny kurs" wydanej przez HELION, której autorem jest Marek Dudek. Ponieważ S_PARSE jest krótsza i bardziej prosta (jest bliższa memu sercu bo sam kiedyś popełniłem bardzo podobną funkcję) przedstawiam ją poniżej:
;; ----------------------------------------------------------------------------- ;
;; PODZIAŁ ŁAŃCUCHA TEKSTOWEGO NA LISTĘ                                          ;
;;                                                                               ;
;; Funkcja rozbija łańcuch tekstowy na listę                                     ;
;; Autor: Bill Kramer - opublikowana w CADENCE 4/97                              ;
;;                                                                               ;
;; Argumenty funkcji:                                                            ;
;; ===================                                                           ;
;;  S - łańcuch tekstowy do podziału                                             ;
;;  D - lista separatorów (lista łańcuchów tekstowych)                           ;
;;                                                                               ;
;; Uwaga: Jeżeli w łańcuchu do rozbicia znajdują się liczby typu INT, funkcja    ;
;;        zwraca je jako REAL                                                    ;
  (defun S_PARSE (S D / BUF RES CNT CH DIGS ISNUM)
    (setq BUF ""
          CNT 1
          ISNUM 'T
          DIGS '("." "+" "-" "0" "1" "2" "3"
                 "4" "5" "6" "7" "8" "9"
                )
    )
    (repeat 
      (strlen S)
      (setq CH (substr S CNT 1)
            CNT (1+ CNT)
      )
      (cond
        ((and 
           (member CH D)
           (> (strlen BUF) 0)
         )
         (setq RES
         (cons
           (if ISNUM
             (atof BUF)
             BUF
           )
           RES
          )
             BUF ""
             ISNUM 'T
         )
        )
        ((not (member CH D))
          (setq BUF (strcat BUF CH))
          (if 
            (null (member CH DIGS))
            (setq ISNUM NIL)
         )
       )
     )
   )
   (if 
     (> (strlen BUF) 0)
     (setq RES
       (cons
         (if 
           ISNUM
           (atof BUF)
           BUF
         )
         RES
       )
     )
   )
   (reverse RES)
 )
;; ----------------------------------------------------------------------------- ;
Do testowania tej funkcji można w linii poleceń napisać:
(S_PARSE "10.0,.20,tekst,-10" (list ","))
funkcja zwróci:
(10.0 0.2 "tekst" -10.0)
czyli całkiem piękną listę. Dalej można testować (wykorzystując poprzedni przykład - czyli dane trzynastej linii pliku ACAD.LIN):
(S_PARSE (jk:SYS_Read-Files 13 "ACAD.LIN")(list ","))
wynikiem będzie lista:
("A" 1.25 -0.25 0.25 -0.25)
lub:
(S_PARSE (jk:SYS_Read-Lines NIL "acad.lin" "*DASHDOT,")(list ","))
zwraca:
("A" 0.5 -0.25 0.0 -0.25)
a więc to o co nam chodzi. Odczytanie danych z pliku MTEXTMAP.INI może wyglądać tak:
(S_PARSE (jk:SYS_Read-Lines T "mtextmap.ini" "isocp.shx=")(list "," "="))
gdzie wynikiem będzie:
("isocp.shx" "ISOCP" 0.0 0.0 0.0 2.0)


W książce "AutoLISP czyli programowanie AutoCAD-a" wydanej przez "Helion" której autorami są Joseph Smith i Rusty Gesner podany jest przykład odczytywania danych z pliku typu SDF. Ma on jednak swoje ograniczenia (dane tylko liczbowe i o określonej liczbie znaków). Funkcja S_PARSE nadaje się również do odczytywania plików SDF, bez względu na typ danych i ich długość, jest zatem bardziej uniwersalna. Pamiętać należy tylko, iż bez względu na ilość spacji użytych do rozdzielenia poszczególnych rekordów w pliku, wywołując S_PARSE trzeba jako listę separatorów podać jedną spację. Oto przykład:
(S_PARSE (jk:SYS_Read-Files 51 "SITENAME.TXT")(list " "))
zwraca:
(431900.0 -770800.0 "Alma-Ata" "Kazachstan")

Wykorzystując przedstawione narzędzia, można pisać bardzo efektywne programy lispowe, czytające dane z plików tekstowych. Oczywiście dla bardziej skompliko- wanych formatów plików tekstowych, narzędzia te mogą okazać się niewystarczające, lecz na ich podstawie można zbudować inne funkcje. Odczyt plików tekstowych, za pomocą AutoLISP-u, to tylko fragment zagadnienia jakim jest dostęp do plików - istnieje jeszcze tworzenie, zapis i aktualizacja plików tekstowych, ale to już osobna opowieść...
  Tekst: 07-1999. Aktualizacja: 14-11-2009