Search/Replace text only when the parastyle is set to "Chord" via Macro <Solved>

I have the following macro which searches and replaces ALL text, I only need to replace text that has a paragraph style of “Chord”. Failing that, possibly just a character style of Bold might work. How can I search on just that style?

Sub convert
	Dim I As Long
	Dim Doc As Object
	Dim Replace As Object
	Dim SourceWords(17) As String
	Dim DestWords(17) As String
    Dim Enum1 As Object
	Dim Enum2 As Object
	Dim TextElement As Object
	Dim TextPortion As Object
'On Local Error Goto Fin
	Doc = ThisComponent
	Enum1 = Doc.Text.createEnumeration
	 
	' loop over all paragraphs
	 While Enum1.hasMoreElements
	  TextElement = Enum1.nextElement
	
		  If TextElement.supportsService("com.sun.star.text.Paragraph") Then
		    Enum2 = TextElement.createEnumeration
		    ' loop over all paragraph portions
		
			    While Enum2.hasMoreElements
			      TextPortion = Enum2.nextElement
			
		
		    	SourceWords() = Array("Gb","G","G#","Ab", "A", "A#", "Bb", "B", "C", "C#", "Db", "D", "D#","Eb","E","F","F#")
			 
		    	DestWords() = Array("1b","1","1#","2b", "2", "2#", "3b", "3", "4", "4#", "5b", "5", "5#", "6b","6", "7", "7#")
			
				Replace = Doc.createReplaceDescriptor
			
				For I = 0 To 17
					   If TextPortion.ParaStyleName = "Chord"	 Then
						  Replace.SearchString = SourceWords(I)
						  Replace.ReplaceString = DestWords(I)
						  Doc.replaceAll(Replace)
					  	End If
					Next I
				Wend
	    	Endif
	    Wend	
Fin:
End Sub

just simply:

def remove_by_stylename(style_name="Chord"):
    doc = XSCRIPTCONTEXT.getDocument()
    for paragraf in doc.Text:
        if paragraf.ParaStyleName == style_name:
            paragraf.String =""

or better:

def remove_by_stylename(style_name="Chord"):
    doc = XSCRIPTCONTEXT.getDocument()
    for paragraf in doc.Text:
        if paragraf.ParaStyleName == style_name:
            paragraf.dispose()

I do NOT want to remove the style, I only want to REPLACE characters based ON THAT Style. ie. if there is a character “A” that has that style, I need to replace it with character “1” with THE SAME style. Of course if character “A” DOES NOT have that style, then it MUST remain as “A”.

With macro recording it is possible to select all Chord text.
A second recorded macro can replace within the selection.
A main routine may look like:

Sub Main
aStr = getTranspositions("C") 'get array of replacements for given key.
rec_Select_Chords 'recorded to select chords paragraphs
for i = 0 to uBound(aStr)
  strA = aStr(i)(0)
  strB = aStr(i)(1)
  rec_Replace_in_Selection(strA, strB)
next
End Sub

sub rec_Replace_in_Selection(strA$, strB$)
REM recorded macro with search string and replace string paramerized

End Sub

Some description for .uno:ExecuteSearch that is recorded by Macro Recorder for Find&Replace.

1 Like

It is not possible to use oDoc.replaceAll only for some part of text, it always replaces in all document. Searching the Styles is more complicated than searching only some easy prooperties.
In your replacements, you don’t need find Gb, G, G# and replace ones for 1b, 1, 1#, it is possible only to find G and replace it by 1, b or # will stay.
Macro also keeps formatting (based on: LibreOffice Writer Macro to run on Selected Text Only - #13 by KamilLanda).

Sub findReplaceInParagraph
	on local error goto bug
	dim oDoc as object, oEnum as object, oCur as object, oDesc as object, oFound as object, s$, i&, a&, undoMgr as object, iLen&, p()
	const cUndo="F&R in Paragraph Chord"

	p=array( array("G", "1"), array("A", "2"), array("B", "3"), array("C", "4"), array("D", "5"), array("E", "6"), array("F", "7")) 'array("Find", "Replace")'
	
	oDoc=ThisComponent
	oDoc.lockControllers 'turn off the screen rendering (it can be faster)'
	undoMgr=oDoc.UndoManager 'undo manager'
	undoMgr.enterUndoContext(cUndo) 'only one step in Undo'
	
	oDesc=oDoc.createSearchDescriptor() 'parametes for search'
	with oDesc
		.SearchRegularExpression=true
		.SearchString=joinArray(p) 'search the expression like: (G|A|B| ...)'
	end with

	oEnum=oDoc.Text.createEnumeration
	while oEnum.hasMoreElements
		oCur=oDoc.Text.createTextCursorByRange(oEnum.nextElement) 'current paragraph as cursor'
		if oCur.ParaStyleName="Chord" then 'name of Paragraph Style'
			oFound=oDoc.findNext(oCur.Start, oDesc) 'find 1st occurence in paragraph'
			do while not isNull(oFound)
				a=oDoc.Text.compareRegionEnds(oFound, oCur) 'compare the Ends'
				if a<>-1 then 'the occurence is in paragraph'
					for i=lbound(p) to ubound(p)
						if oFound.String=p(i)(0) then 'check what subexpression is found'
							iLen=Len(p(i)(0)) 'length of found string'
							with oFound
								.collapseToEnd 'cursor to the end'
								.String=p(i)(1) 'new string'
								.collapseToStart 'cursor back to start'
								.goLeft(iLen, true) 'cursor to found string'
								.String="" 'delete found string'
								.collapseToEnd 'cursor to start of new string'
								.goRight(Len(p(i)(1)), false) 'cursor to the end of new string'
							end with
							exit for
						end if
					next i
				else
					exit do
				end if
				oFound=oDoc.findNext(oFound.End, oDesc)
			loop
		end if
	wend
	
	undoMgr.leaveUndoContext(cUndo)
	goto konec
bug:
	bug(Erl, Err, Error, "findReplaceInParagraph")
konec:
	if oDoc.hasControllersLocked then oDoc.unlockControllers 'turn on the screen rendering'
End Sub

Function joinArray(p) as string 'create regular expression from 1st values in array'
	on local error goto bug
	dim p1(ubound(p)), i&
	for i=lbound(p) to ubound(p)
		p1(i)=p(i)(0) 'p1() has the 1st values from p()'
	next i
	joinArray="(" & join(p1, "|")  & ")" 'output string like: (aa|bb|c)'
	exit function
bug:
	bug(Erl, Err, Error, "joinArray")
End Function

Sub bug(sErl$, sErr$, sError$, sFce$) 'show the error message'
	msgbox(sErr & ": " & sError & chr(13) & "Line: " & sErl & chr(13), 16, sFce)
End Sub

Thank You that works beautifully. There is a reason to change G# and Gb because, they may not always change to to 1# and 1b, but I now get the idea.

Thanks for that elegant solution. I can now go on to make it even more useful to me.

Thank you for your patience This has grown into a nearly perfect solution for me. Is there any way to possibly exclude certain CHARACTER styles or search on JUST those Char styles i.e.

if parastyle(main) and charstyle(thisone) then code else other code endif

EDIT: I’m looking specifically at Superscript and Subscript formatting. I can find plenty of stuff on Bold and other formatting aids, but, precious little on Super and Subscript.

This is way how to detect the parts with different character properties in selection, probably you can remake it to Function and use oFound instead oSel. But the conditions if ParaName= AND Style<> AND Props= then will be more complicated.

Select some text that has the different formatted parts (bold, superscript, underline etc.) and run macro

Sub differentCharProperties 'show the parts that has different character properties in selection
	dim oDoc as object, oSel as object, oEnum as object, o as object, oEnum2 as object, o2 as object
	oDoc=ThisComponent
	oSel=oDoc.CurrentController.Selection.getByIndex(0)
	oEnum=oSel.createEnumeration
	do while oEnum.hasMoreElements
		o=oEnum.nextElement
		oEnum2=o.createEnumeration
		do while oEnum2.hasMoreElements
			o2=oEnum2.nextElement
			msgbox o2.string 'show part of string with different properties than other parts
			'xray o2
		loop
	loop
End Sub

There are the properties CharEscapement and CharEscapementHeight (or maybe you also use CharAutoEscapement) for Super/Sub-scriptCharEscapement has plus values for Superscript (for example 30), and minus values for Subscript (-30).

Be carefull when make the replace definitions, longer ones have to be before shorter. In your example SourceWords() = Array("Gb","G","G#", ...) → there will never be found G# because single expression G is before longer expression G# → so it will replace all G that means also all G from G#.

I’m aware of the order of precedence in search functions and have already taken care of that, i.e. altering “G#” and “Gb” before “G”.

CharEscapement does not seem to show any differences when inspecting the particular character in the watch list whilst running the code.

I have Yet to find a reference to superscript and subscript in any of the formatting code, so, even though your code shows text that changes format, it does not tell me what, exactly it is finding different. I Need to identify the difference between the number 4 and the superscripted (or subscripted) 4, or any other number in the range 1 - 7.

An overview of the project I am working on is displaying a chord chart for a song on screen and being able to change to Nashville Numbers and back, including transpositions. So far, changing from a Chord to NN works fine, it is changing from NN back into chords.

I appreciate your help, Everything is, so far, coming together beautifully.

do while oEnum2.hasMoreElements
			o2=oEnum2.nextElement
			msgbox o2.CharEscapement & chr(13) and o2.CharEscapementHeight
		loop

If it is not functional to see the changes in CharEscapement, please upload some (short) example.

Thank You for your time, I’ve had a change of thinking on my numbering so that rather than using numbers “1 - 9” if I Use roman numerals “I-IX” then the issue with superscript and subscript is passe.

I shall call this problem solved by a change of direction.
Thanks again for your help.
this is a test document showing what I hope to achieve
Untitled 1.odt (21.7 KB)

There have to be also the replacements for theoretical forms like Cb, E# etc. I added the simple condition to detect the Character Styles (Function detectCharStyle) → replacements are now only for No Character Styles.
lsemmens.odt (25.9 kB)

Roman numerals aren’t well arranged, the Widths are very different, it could be problem to write VI# or VII in monospace font.
For interest, you can look to my script for guitar (Spanish romance in ODT) Bug : Libre office WRITER looses text box color after saving - #5 by KamilLanda :slight_smile:

Fair point. Monospaced fonts will have some minor issues, far less than Proportional, it is definitely worth considering, however, I am going to persevere with this solution for now.

I shall spend some time looking at your linked thread when I have a few moments. (I’ve just won - at auction - a convertible laptop that I hope will replace an iPad for my chord charts) so, the next few days will be spent, setting that up and (hopefully) will utilise this script) If all works well, I shall, happily, share my solution with whosoever is interested.

Thank You for all your help, Just FYI here is a copy of the code I have created. It converts to any Key and even to NN. Converting from NN does work, but with the issue that chord modifiers are also Converted so any 2,4,5,6,7th chords become chords with the appropriate referred note. e.g. 4 7th converted to C would become F B. It’s a nuisance, but not a major problem.
TEST.odt (40.6 KB)

1 Like

Just an update of the code to fix an issue with one transposition and prompt to save the modified document
Happy Birthday (C).odt (17.4 KB)

@LSemmens hint Code from your makros… doesnt fly by magic into other computers meanwhile downloading your attachments :thinking:

I Don’t quite understand your question. Do you mean that it is not portable? Given that it is Libre Office Basic code, it should be portable to any system running LibreOffice, Of course the Folder/Directory conversions might need be addressed for Windows or Mac, I have not tested those.

What @karolus is saying is that your macros are not stored in the document(s) but on your computer. Thus we don’t see them.

1 Like

Thanks for that, it occurred to me after I replied that this might be the case. I’ve now fixed that.
Happy Birthday (C).odt (27.8 KB)try again! Sorry folks!