Find and Replace Text - Command Line

Find and Replace Text - Command Line

DGCSCAD
Collaborator Collaborator
979 Views
9 Replies
Message 1 of 10

Find and Replace Text - Command Line

DGCSCAD
Collaborator
Collaborator

An idea that has been mentioned in quite a few threads I've explored looking for a simple command line approach that loosely resembles AutoCads native Find and Replace. This started out as a simple AI test to see how close it could get, but I was surprised at how accurate it was so I started refining it. This is a conglomeration of about 50/50 AI and human coding. It has passed every test I've thrown at it so far, and I should mention that I have no need for Blocks, Attributes, or Hyperlinks, so those are not included. This is a very simple and succinct function and I hope others find it useful either as-is, or as a start to expand upon.

 

 

;Find and Replace Text
(defun c:FRT (/ find replace rCount cCount row col cellText)
(setq find (getstring "\nEnter the text to find: "))
(setq replace (getstring "\nEnter the replacement text: "))
	(if (ssget '((0 . "TEXT,MTEXT,DIMENSION,ACAD_TABLE,MULTILEADER")))
		(progn
			(vlax-for obj (vla-get-ActiveSelectionSet (vla-get-ActiveDocument (vlax-get-acad-object)))
			(cond
				((= (vla-get-ObjectName obj) "AcDbText")
					(while (vl-string-search find (vla-get-TextString obj))
						(vla-put-TextString obj (vl-string-subst replace find (vla-get-TextString obj)))
					)
				)
				((= (vla-get-ObjectName obj) "AcDbMText")
					(while (vl-string-search find (vla-get-TextString obj))
						(vla-put-TextString obj (vl-string-subst replace find (vla-get-TextString obj)))
					)
				)
				((= (vla-get-ObjectName obj) "AcDbMLeader")
					(while (vl-string-search find (vla-get-TextString obj))
						(vla-put-TextString obj (vl-string-subst replace find (vla-get-TextString obj)))
					)
				)
				((= (wcmatch (vla-get-ObjectName obj) "AcDb*Dimension") T)
						(while (vl-string-search find (vla-get-TextOverride obj))
						(vla-put-TextOverride obj (vl-string-subst replace find (vla-get-TextOverride obj)))
					)
				)
				((= (vla-get-ObjectName obj) "AcDbTable")
					(setq rCount (vla-get-rows obj))
					(setq cCount (vla-get-columns obj))
					(setq row 0)
					(while (< row rCount)
						(setq col 0)
						(while (< col cCount)
							(setq cellText (vla-gettext obj row col))
							(while (vl-string-search find cellText)
                   						(setq cellText (vl-string-subst replace find cellText))
                   						(vla-SetText obj row col cellText)
							)
							(setq col (1+ col))
						)
						(setq row (1+ row))
					)
				)
			);cond
			);vlax-for
		)
	)
(princ)
)

 

AutoCad 2018 (full)
Win 11 Pro
0 Likes
980 Views
9 Replies
Replies (9)
Message 2 of 10

ronjonp
Mentor
Mentor

@DGCSCAD Nice work! I made a couple of edits, just food for thought 🙂

 

;; Find and Replace Text
(defun c:frt (/ ccount celltext col find rcount replace row)
  (setq find (getstring "\nEnter the text to find: "))
  (setq replace (getstring "\nEnter the replacement text: "))
  ;; RJP » 2024-10-29 Added ":L" to only allow selecting items on unlocked layers
  (if (ssget ":L" '((0 . "TEXT,MTEXT,DIMENSION,ACAD_TABLE,MULTILEADER")))
    (progn
      (vlax-for	obj (vla-get-activeselectionset (vla-get-activedocument (vlax-get-acad-object)))
	(cond
	  ;; RJP » 2024-10-29 Wcmatch consolidates the 3 object types
	  ((wcmatch (vla-get-objectname obj) "AcDbText,AcDbMText,AcDbMLeader")
	   (while (vl-string-search find (vla-get-textstring obj))
	     (vla-put-textstring obj (vl-string-subst replace find (vla-get-textstring obj)))
	   )
	  )
	  ((= (wcmatch (vla-get-objectname obj) "AcDb*Dimension") t)
	   (while (vl-string-search find (vla-get-textoverride obj))
	     (vla-put-textoverride obj (vl-string-subst replace find (vla-get-textoverride obj)))
	   )
	  )
	  ((= (vla-get-objectname obj) "AcDbTable")
	   (setq rcount (vla-get-rows obj))
	   (setq ccount (vla-get-columns obj))
	   (setq row 0)
	   (while (< row rcount)
	     (setq col 0)
	     (while (< col ccount)
	       (setq celltext (vla-gettext obj row col))
	       (while (vl-string-search find celltext)
		 (setq celltext (vl-string-subst replace find celltext))
		 (vla-settext obj row col celltext)
	       )
	       (setq col (1+ col))
	     )
	     (setq row (1+ row))
	   )
	  )
	)				;cond
      )					;vlax-for
    )
  )
  (princ)
)
0 Likes
Message 3 of 10

vladimir_michl
Advisor
Advisor

Note that there is one - quite substantial - difference in the behavior of your FRT LISP routine compared to the FIND command. FIND replaces ALL occurrences of a string in each selected text, while (vl-string-subst) replaces only the FIRST occurrence.

 

PS: there are many Search/Replace LISP tools - see e.g. BFind, srxText

 

Vladimir Michl, www.arkance.world  -  www.cadforum.cz

 

 

0 Likes
Message 4 of 10

DGCSCAD
Collaborator
Collaborator

@ronjonp 

Thank you

I hadn't considered those changes. Any contributions into helping this thing along is very much appreciated.

 

@vladimir_michl 

The use of the while loop changes all occurrences of the target string within each ss entity.

 

acad_NlWIElzulV.gif

AutoCad 2018 (full)
Win 11 Pro
0 Likes
Message 5 of 10

vladimir_michl
Advisor
Advisor

Be careful with that. It is not the same. E.g. replacing "11" with "21" in "11111" gives "21211" in FIND but "22221" in FRT.

 

Vladimir Michl, www.arkance.world  -  www.cadforum.cz

 

0 Likes
Message 6 of 10

DGCSCAD
Collaborator
Collaborator

@vladimir_michl 

 

10-4

 

It is still a WIP.

AutoCad 2018 (full)
Win 11 Pro
0 Likes
Message 7 of 10

Kent1Cooper
Consultant
Consultant

Case-sensitivity?  Consider forcing both strings to all uppercase for the comparison:

 

(vl-string-search (strcase find) (strcase (vla-get-textstring obj)))

 

But then the substitution would be challenging if the original could be in any case combination.  It might require using the forced-upper-case version to identify where in the string the 'find' part is, and instead of substituting in a way that requires case matching, break the string into before-find, find, and after-find pieces.  Then put it back together with the 'replace' part in place of the 'find' part by position.

Kent Cooper, AIA
0 Likes
Message 8 of 10

DGCSCAD
Collaborator
Collaborator

@Kent1Cooper 

Thats the kind of refinement I was eluding to when mentioning that I hope others can expand on it. It's not something I currently have a need for, but I'm sure others would.

AutoCad 2018 (full)
Win 11 Pro
0 Likes
Message 9 of 10

Kent1Cooper
Consultant
Consultant

@vladimir_michl wrote:

.... replacing "11" with "21" in "11111" gives "21211" in FIND but "22221" in FRT.


That could be handled, I think, by the use of the start-position option in (vl-string-search).  Set a position marker at zero when the (while) loop starts looking at the content, and when a substitution is made, reset the position marker value accordingly, so it looks the next time at the content only after the latest substitution.  But as with the case-sensitivity question, it could require not just substituting in the entire string, but breaking it into pieces, and putting it back together after working on the pieces.  In that case, presumably the relationship of the pieces could mean that the start-position option isn't needed.

Kent Cooper, AIA
0 Likes
Message 10 of 10

Kent1Cooper
Consultant
Consultant

Here's a sub-routine you could incorporate, that seems to handle both case-sensitivity [as an option] and multiple occurrences of the what-to-replace content in the same string [lightly tested]:

 

(defun newstr (str find repl cs / pre pos)
  ;; ARGUMENTS:
  ;; 'str' = initial text string
  ;; 'find' = what to find in it
  ;; 'repl' = what to replace that with
  ;; 'cs' = case-sensitive about 'find' [T or nil]
  (setq
    flen (strlen find)
    rlen (strlen repl)
    pre "" ; initially empty
  ); setq
  (while (setq pos (vl-string-search (if cs find (strcase find)) (if cs str (strcase str))))
    ; [when not case-sensitive, force all to uppercase for finding]
    (setq
      pre (strcat pre (substr str 1 pos) repl)
      str (substr str (+ pos flen 1)); keep what's after end of 'find' part
    ); setq
  ); while
  (setq new (strcat pre str)); no more? add what's left [entire original if no 'find' in it]
); defun

 

Some examples wanting to find "and" [except the last one] and replace it with "plus":

Command: (newstr "One and two and three and four" "and" "plus" T)
"One plus two plus three plus four" [replaced all -- same case]

Command: (newstr "One and two AND three and four" "and" "plus" T)
"One plus two AND three plus four" [did not replace AND because of case difference]

Command: (newstr "One AND two and three And four" "and" "plus" nil)
"One plus two plus three plus four" [replaced all, even AND/And case differences]

Command: (newstr "One AND two and three And four" "minus" "plus" nil)
"One AND two and three And four" [left alone -- nothing found to replace

 

It reports the result, but also leaves it in the 'new' variable, which is not localized so you can use it after this sub-routine is finished.

Kent Cooper, AIA
0 Likes