T-SQL Replace XML node - sql-server

I would like to replace an XML node with a new node. I am trying to make this dynamic so the replacement node name is a variable
DECLARE #xmlSource AS XML = '<Root><Transactions><ReplaceMe>This information should be gone</ReplaceMe></Transactions></Root>'
DECLARE #xmlInsert AS XML = '<NewNode>New Information</NewNode>'
DECLARE #NodeName NVARCHAR(500) = 'ReplaceMe'
The resulting XML should look like:
<Root><Transactions><NewNode>New Information</NewNode></Transactions></Root>

There is no direct approach to replace a complete node with another one.
But you can delete it and insert the new one:
DECLARE #xmlSource AS XML = '<Root><Transactions><ReplaceMe>This information should be gone</ReplaceMe></Transactions></Root>'
DECLARE #xmlInsert AS XML = '<NewNode>New Information</NewNode>'
DECLARE #NodeName NVARCHAR(500) = 'ReplaceMe'
SET #xmlSource.modify('delete /Root/Transactions/*[local-name(.) eq sql:variable("#NodeName")]');
SELECT #xmlSource; --ReplaceMe is gone...
SET #xmlSource.modify('insert sql:variable("#xmlInsert") into (/Root/Transactions)[1]');
SELECT #xmlSource;
The result
<Root>
<Transactions>
<NewNode>New Information</NewNode>
</Transactions>
</Root>
UPDATE a generic solution
From your comments I understand, that you have no idea about the XML, just the need to replace one node where you know the name with another node...
This solution is string based (which is super ugly anyway) and has some flaws:
If there are several nodes with this name, only the first one will be taken
If the node exists multiple times with the same content, it would be replaced in both places
If your xml contains CDATA-sections they will be transfered into properly escaped normal XML implicitly. No semantic loss, but this could break structural validations...
This should work even with special characters, as all conversions are from XML to NVARCHAR and back. Escaped characters should stay the same on both sides.
Otherwise one had to use a recursive approach to get the full path to the node and build my first statement dynamically. This was cleaner but more heavy...
DECLARE #xmlSource AS XML = '<Root><Transactions><ReplaceMe>This information should be gone</ReplaceMe></Transactions></Root>'
DECLARE #xmlInsert AS XML = '<NewNode>New Information</NewNode>'
DECLARE #NodeName NVARCHAR(500) = 'ReplaceMe'
SELECT
CAST(
REPLACE(CAST(#xmlSource AS NVARCHAR(MAX))
,CAST(#xmlSource.query('//*[local-name(.) eq sql:variable("#NodeName")][1]') AS NVARCHAR(MAX))
,CAST(#xmlInsert AS NVARCHAR(MAX))
)
AS XML)

To replace in place with XML we'll need to insert our new nodes immediately before (or after) the nodes we want to replace.
Start off by getting the number of nodes we want to replace:
DECLARE #numToReplace int = #xmlSource.value('count(//*[local-name(.) eq sql:variable("#NodeName")])', 'int')
Then iterate through each node and flag the node to be deleted (this lets us replace the nodes with a node of the same name).
DECLARE #iterator int = #numToReplace
WHILE #iterator > 0
BEGIN
SET #xmlSource.modify('insert attribute ToDelete {"delete"} into ((//*[local-name(.) eq sql:variable("#NodeName")])[sql:variable("#iterator")])[1]');
SET #iterator = #iterator - 1
END
n.b. you need to nest the target query ((*query*)[sql:variable("#numToReplace")])[1], it doesn't like variables in the last node indexer
Then insert the new node before each old node
SET #iterator = #numToReplace
WHILE #iterator > 0
BEGIN
SET #xmlSource.modify('insert sql:variable("#xmlInsert") before ((//*[local-name(.) eq sql:variable("#NodeName")][#ToDelete="true"])[sql:variable("#iterator")])[1]')
SET #iterator = #iterator - 1;
END
Then you can just remove all the old nodes
SET #xmlSource.modify('delete (//*[local-name(.) eq sql:variable("#NodeName")][#ToDelete="true"])')

Related

Function Efficiency

I am a neophyte to creating stored procedures and functions and I just can't figure out why one of these versions runs so much faster than the other. This is a function that just returns a string with a description when called. The original function relies on supplying about 10 variables (Version running in about 4 seconds). I wanted to cut that down to a single variable (version running long).
The code below the declaration of the variables is identical, the only difference is that I'm attempting to pull the variables from the appropriate within the function itself rather than having to supply them on the query side.
i.e. dbo.cf_NoRateReason(V1) as ReasonCode
rather than
dbo.cf_NoRateReason(V1,V2,V3,V4,V5,V6,V7,V8,V9,V10,V11,V12)
I apologize up front if I am not supplying enough information, as I said, new to functions/stored procedures.
This version runs in about 2.5 minutes to run
declare #Agencyid int
declare #ServiceCode varchar(10)
declare #Mod1 varchar(2)=null
declare #Mod2 varchar(2)=null
declare #Mod3 varchar(2)=null
declare #Mod4 varchar(2)=null
declare #POS int
declare #ServiceDate datetime
declare #ProvType varchar(1)
declare #PayerID int
declare #BirthDate datetime
declare #RenderingStaffID int
declare #SupervisingStaffID int
Select #Agencyid=s.agencyid, #ServiceCode = ServiceCode,
#Mod1 = ModifierCodeId, #Mod2 = ModifierCodeId2,
#Mod3 = ModifierCodeId3, #Mod4 = ModifierCodeId4,
#POS=PlaceOfServiceId, #ServiceDate = ServiceDate,
#RenderingStaffId=isnull(dbo.GetProviderStaffId('S',s.ServiceTransactionId,'82'),0),
#SupervisingStaffId=isnull(dbo.GetProviderStaffId('C',ClaimId,'DQ'),0),
#ProvType=s.servicetype, #Payerid=pmt.payerid,
#BirthDate=i.birthdate
From ServiceTransaction s
join individual i on s.servicetransactionid = i.individualid
join pmtadjdetail pmt on s.servicetransactionid = pmt.servicetransactionid
declare #Result Varchar(100) = ''
declare #Age int = dbo.getageatservicedate(#birthdate, #ServiceDate)
declare #ModString varchar(8) = dbo.sortmodifiers(#Mod1, #Mod2, #Mod3, #Mod4)
declare #DirectSupervision int = (iif(#Mod1 in ('U1','U6','U7','U9','UA')
or #Mod2 in ('U1','U6','U7','U9','UA')
or #Mod3 in ('U1','U6','U7','U9','UA')
or #Mod4 in ('U1','U6','U7','U9','UA'),1,0))
'************************************************************************************'
'This version takes about 4 seconds to run'
'************************************************************************************'
begin
declare #Result Varchar(100) = ''
declare #Age int = dbo.getageatservicedate(#birthdate, #ServiceDate)
declare #RenderingStaffID int = dbo.getstaffid(#STID,'DQ')
declare #SupervisingStaffID int = dbo.getstaffid(#STID,'82')
declare #ModString varchar(8) = dbo.sortmodifiers(#Mod1, #Mod2, #Mod3, #Mod4)
declare #DirectSupervision int = (iif(#Mod1 in ('U1','U6','U7','U9','UA')
or #Mod2 in ('U1','U6','U7','U9','UA')
or #Mod3 in ('U1','U6','U7','U9','UA')
or #Mod4 in ('U1','U6','U7','U9','UA'),1,0))
This kind of falls under "typo" or simple oversight, but....
When you see that big of a performance difference, for no discernible reason (those functions were used in the original version as well), that is usually when you need to start look for these kinds of mistakes: typos, missing conditions, incorrect conditions from leaning too hard on intellisense/code-completion, etc...
When replacing multiple parameters with one that can used to retrieve the others automatically, always make sure to actually use that parameter.
The version you listed first has no filter (no WHERE clause) on the SELECT it uses to get the "parameter" values it is normally passed. You're effectively getting the entire join resultset, with the cost of the function calls for every result row, and only taking last result's values.
You are correct - the only difference is using the function. Please see similar questions where this has been addressed.
In short, functions are going to be performed on a row-by-row basis whereas code on the query side is going to have other options with no overhead calls to the function.
You may be able to use a scalar function with schema binding and nulls return nulls for better performance.
Additional consideration for the schema plan would be valuable. There are also joins and other embedded logics here that aren't clear without sample data.

SQL replace value of with always random xml code

I have the following SQL XML in several rows of a table (table is tbldatafeed column in configuration_xml). All of the UserName="" and Password="" is different each time for each row and does not repeat so I can not find/replace off of that. I am trying to write a query that finds all of those and replaces them with Username/Passwords I choose.
<DataFeed xmlns="http://www.tech.com/datafeed/dfx/2010/04" xmlns:plugin="pluginExtensions" Type="TODO" Guid="TODO" UserAccount="DF_LEAN_PopulateCommentsSubForm" Locale="en-US" DateFormat="" ThousandSeparator="" NegativeSymbol="" DecimalSymbol="" SendingNotifications="false" SendJobStatusNotifications="false" RecipientUserIds="" RecipientGroupIds="" RecipientEmailAddresses="" Name="CI_C11.01_Lean-Lean_Reject Comments_A2A" >
<Transporter>
<transporters:ArcherWebServiceTransportActivity xmlns:transporters="clr-namespace:ArcherTech.DataFeed.Activities.Transporters;assembly=ArcherTech.DataFeed" xmlns:out="clr-namespace:ArcherTech.DataFeed;assembly=ArcherTech.DataFeed" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:compModel="clr-namespace:ArcherTech.DataFeed.ComponentModel;assembly=ArcherTech.DataFeed" xmlns:channel="clr-namespace:ArcherTech.DataFeed.Engine.Channel;assembly=ArcherTech.DataFeed" xmlns:engine="clr-namespace:ArcherTech.DataFeed.Engine;assembly=ArcherTech.DataFeed" xmlns:kernel="clr-namespace:ArcherTech.Kernel.Channel;assembly=ArcherTech.Kernel" xmlns="clr-namespace:ArcherTech.DataFeed;assembly=ArcherTech.DataFeed" xmlns:schema="clr-namespace:System.Xml.Schema;assembly=System.Xml" xmlns:xmlLinq="clr-namespace:System.Xml.Linq;assembly=System.Xml" xmlns:domain="clr-namespace:ArcherTech.Common.Domain;assembly=ArcherTech.Common" xmlns:s="clr-namespace:System;assembly=mscorlib" x:Key="transportActivity" SearchType="ReportId" Uri="https://arcs-d" RecordsPerFile="100" ReportID="EC514865-88D5-49CE-A200-7769EC1C2A88" UseWindowsAuth="false" IsWindowsAuthSpecific="false" WindowsAuthUserName="i9XzCczAQ7J2rHwkg6wG9QF8+O9NCYJZP6y5Kzw4be0+cdvUaGu/9+rHuLstU736pnQrRcwmnSIhd6oPKIvnLA==" WindowsAuthPassword="+y0tCAKysxEMSGv1unpHxfg6WjH5XWylgP45P5MLRdQ6+zAdOLSVy7s3KJa3+9j2i83qn8I8K7+1+QBlCJT1E7sLQHWRFOCEdJgXaIr1gWfUEO+7kjuJnZcIEKZJa2wHyqc2Z08J2SKfdCLh7HoLtg==" WindowsAuthDomain="" ProxyName="" ProxyPort="8080" ProxyUsername="" ProxyPassword="" ProxyDomain="" IsProxyActive="False" ProxyOption="None" InstanceName="ARCS-D" TempFileOnSuccessAction="DoNothing" TempFileOnSuccessRenameString="" TempFileOnErrorAction="DoNothing" TempFileOnErrorRenameString="" Transform="{engine:DataFeedBinding Path=Transform}" SessionContext="{engine:DataFeedBinding Path=Session}">
<transporters:ArcherWebServiceTransportActivity.Credentials>
<NetworkCredentialWrapper UserName="TeSZmI1SqO0eJ0G2nDVU+glFg/9eZfeMppYQnPfbeg8=" Password="Slt4VHqjkYscWyCwZK40QJ7KOQroG9OTKr+RGt9bQjE=" />
</transporters:ArcherWebServiceTransportActivity.Credentials>
</transporters:ArcherWebServiceTransportActivity>
</Transporter>
</DataFeed>
I need to be able to set a value and replace it with a query
I have written the following
select #config_xml=configuration_xml from bldatafeed where datafeed_name = 'REMOVED'
update tbldatafeed set configuration_xml.modify(//*:NetworkCredentialWrapper/#UserName)[1] with "abc" ')
where datafeed_name = 'REMOVED'
This does the trick but it only works if I set the "abc" password each time in each area and in some cases I am running this against 50+ rows.
I also tried:
Declare #server nvarchar(max) = 'abc'
Declare #config_xml xml
select #config_xml=configuration_xml from bldatafeed where datafeed_name = 'REMOVED'
update tbldatafeed set configuration_xml.modify(//*:NetworkCredentialWrapper/#UserName)[1] with #server ')
where datafeed_name = 'REMOVED'
The error from this is that: XQuery [tbldatafeed.configuration_xml.modify()]: Top-level attribute nodes are not supported
What I would like to be able to do is set my variable and utilize that as I will be setting this up for multiple rows and unfortunately this error is making this a very difficult problem to solve.
Thanks for any help, this has kept me confused for a bit.
Use the function sql:variable() to use a variable in the XQuery expression.
declare #T table(X xml);
insert into #T values('<X UserName=""/>');
declare #UserName nvarchar(max) = 'abc'
update #T set
X.modify('replace value of (/X/#UserName)[1]
with sql:variable("#UserName")');

How to access a Row Type within an Array Type in DB2 SQL PL

I have a java front end that has a table of data. We need to save this data to the Database via stored procedure. If the passed parameter is a just an array, I am able to access the contents easily. But if the contents of the array is a also a row type, thats the part im having trouble with.
I dont know how to access the contents of the array.
Im using DB2 10.1
CREATE TABLE "TEST"."CHART_ACCT" (
"ACCT_NO" VARCHAR(10) NOT NULL,
"ACCT_DESC" VARCHAR(40) NOT NULL
)
ORGANIZE BY ROW
DATA CAPTURE NONE
IN "USERSPACE1"
COMPRESS YES ADAPTIVE
VALUE COMPRESSION#
CREATE OR REPLACE TYPE TEST.ACCT AS ROW ANCHOR ROW OF TEST.CHART_ACCT#
CREATE OR REPLACE TYPE TEST.ACCT_ARR AS TEST.ACCT ARRAY[]#
CREATE OR REPLACE PROCEDURE TEST.TEST_ARRAY (IN P_ACCT_ARR TEST.ACCT_ARR)
P1: BEGIN
-- #######################################################################
-- #
-- #######################################################################
DECLARE i INTEGER;
SET i = 1;
WHILE i < 10 DO
CALL DBMS_OUTPUT.PUT_LINE(P_GLACCT_ARR[i]);
set i = i + 1;
END WHILE;
END P1#
You need to declare a temporary variable of the row type and assign array elements to it in a loop:
CREATE OR REPLACE PROCEDURE TEST_ARRAY (IN P_ACCT_ARR ACCT_ARR)
P1: BEGIN
DECLARE i INTEGER;
DECLARE v_acct acct;
SET i = 1;
WHILE i < CARDINALITY(p_acct_arr) DO
SET v_acct = p_acct_arr[i];
CALL DBMS_OUTPUT.PUT_LINE('Account number = ' || v_acct.acct_no);
set i = i + 1;
END WHILE;
END P1#
However, a more concise way to do that is to use the collection-derived table reference:
CREATE OR REPLACE PROCEDURE TEST_ARRAY (IN P_ACCT_ARR ACCT_ARR)
P1: BEGIN
FOR r AS (SELECT * FROM UNNEST(p_acct_arr) t ) DO
CALL DBMS_OUTPUT.PUT_LINE('Account number = ' || r.acct_no);
END FOR;
END P1#

Cannot insert duplicate key in object (GetReparentedValue / hierarchyid)

Using examples I found on the web I have created a function which reparents children using the GetReparentedValue.
However when I have ran the code I get the following error: Cannot insert duplicate key in object.
I understand why (because I am trying to reparent the children and the new parent already has children so I need to know the MAX path (hierarchyid) of the child within the new parent structure, but I don't understand how I'm actually going to do that.
path 0x58
oldPath 0x
new path 0x68
SqlCommand command = new SqlCommand("UPDATE Structure SET " +
"Path = " + path + ".GetReparentedValue" +
"(" +
oldPath + ", " + newPath +
")" +
"ParentID = #id " +
"WHERE Path = " + path, _connection);
I have to do this when adding a child so I thought it would need to add this somewhere to the query above but I dont know where path + ".GetDescendant(" + lastChildPath + ", NULL)
Database Table
StructureID int Unchecked
Path hierarchyid Unchecked
PathLevel ([Path].[GetLevel]()) Checked
Description nvarchar(50) Checked
ParentID int Checked
ParentPath ([Path].[GetAncestor]((1))) Checked
Anyone have any suggestion?
Thanks in advance for any help :-)
Clare
There are a couple of changes you can make to get this to work. First, you don't need the oldPath that represents the parent of the node that you want to move. In the .GetReparentedValue function, you put the hierarchyid of the node that is moving, which is the value in path.
The second change is to add another SELECT statement to apply your GetDescendant function. Here's a sample script that you can try in SQL Server Management Studio (SSMS), or alter to incorporate into your SQLCommand calls. The first few lines (variable declarations are assignments) are only for running in SSMS. You would transfer the last SELECT and the UPDATE statements to the calling code.
DECLARE #Path hierarchyid
DECLARE #oldPath hierarchyid
DECLARE #newPath hierarchyid
SELECT #Path=0x58, #oldPath=0x, #newPath=0x68
SELECT #newPath = #newPath.GetDescendant(MAX(Path), NULL)
FROM Structure
WHERE path.GetAncestor(1)=#newPath;
UPDATE Structure
SET Path = Path.GetReparentedValue(#Path, #newPath)
WHERE Path = #Path;
Your UPDATE statement and this revision will only re-parent a single node. It will not automatically move the children of the moving node. Children of the moving-node will be orphaned.
If you need to move the selected node and all descendants of the node, you can use the following variation of the previous statements.
DECLARE #Path hierarchyid
DECLARE #oldPath hierarchyid
DECLARE #newPath hierarchyid
SELECT #Path=0x58, #oldPath=0x, #newPath=0x68
SELECT #newPath = #newPath.GetDescendant(MAX(Path), NULL)
FROM Structure
WHERE Path.GetAncestor(1) = #newPath ;
UPDATE Structure
SET Path = Path.GetReparentedValue(#Path, #newPath)
WHERE Path.IsDescendantOf(#Path) = 1;
Actually, the only change from the first script to this script is in the very last line. The Path.IsDescendantOf(#Path) = 1 test is true for all descendants of #Path, including #Path. The hierarchical relationships will be maintained after the update.
This is another example of moving a subtree and all of it's children. It is essentially the same as the accepted answer. This is taken from the Docs:
CREATE PROCEDURE MoveOrg(#oldMgr nvarchar(256), #newMgr nvarchar(256) )
AS
BEGIN
DECLARE #nold hierarchyid, #nnew hierarchyid
SELECT #nold = OrgNode FROM HumanResources.EmployeeDemo WHERE LoginID = #oldMgr ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT #nnew = OrgNode FROM HumanResources.EmployeeDemo WHERE LoginID = #newMgr ;
SELECT #nnew = #nnew.GetDescendant(max(OrgNode), NULL)
FROM HumanResources.EmployeeDemo WHERE OrgNode.GetAncestor(1)=#nnew ;
UPDATE HumanResources.EmployeeDemo
SET OrgNode = OrgNode.GetReparentedValue(#nold, #nnew)
WHERE OrgNode.IsDescendantOf(#nold) = 1 ;
COMMIT TRANSACTION
END ;
GO

How to do hit-highlighting of results from a SQL Server full-text query

We have a web application that uses SQL Server 2008 as the database. Our users are able to do full-text searches on particular columns in the database. SQL Server's full-text functionality does not seem to provide support for hit highlighting. Do we need to build this ourselves or is there perhaps some library or knowledge around on how to do this?
BTW the application is written in C# so a .Net solution would be ideal but not necessary as we could translate.
Expanding on Ishmael's idea, it's not the final solution, but I think it's a good way to start.
Firstly we need to get the list of words that have been retrieved with the full-text engine:
declare #SearchPattern nvarchar(1000) = 'FORMSOF (INFLECTIONAL, " ' + #SearchString + ' ")'
declare #SearchWords table (Word varchar(100), Expansion_type int)
insert into #SearchWords
select distinct display_term, expansion_type
from sys.dm_fts_parser(#SearchPattern, 1033, 0, 0)
where special_term = 'Exact Match'
There is already quite a lot one can expand on, for example the search pattern is quite basic; also there are probably better ways to filter out the words you don't need, but it least it gives you a list of stem words etc. that would be matched by full-text search.
After you get the results you need, you can use RegEx to parse through the result set (or preferably only a subset to speed it up, although I haven't yet figured out a good way to do so). For this I simply use two while loops and a bunch of temporary table and variables:
declare #FinalResults table
while (select COUNT(*) from #PrelimResults) > 0
begin
select top 1 #CurrID = [UID], #Text = Text from #PrelimResults
declare #TextLength int = LEN(#Text )
declare #IndexOfDot int = CHARINDEX('.', REVERSE(#Text ), #TextLength - dbo.RegExIndexOf(#Text, '\b' + #FirstSearchWord + '\b') + 1)
set #Text = SUBSTRING(#Text, case #IndexOfDot when 0 then 0 else #TextLength - #IndexOfDot + 3 end, 300)
while (select COUNT(*) from #TempSearchWords) > 0
begin
select top 1 #CurrWord = Word from #TempSearchWords
set #Text = dbo.RegExReplace(#Text, '\b' + #CurrWord + '\b', '<b>' + SUBSTRING(#Text, dbo.RegExIndexOf(#Text, '\b' + #CurrWord + '\b'), LEN(#CurrWord) + 1) + '</b>')
delete from #TempSearchWords where Word = #CurrWord
end
insert into #FinalResults
select * from #PrelimResults where [UID] = #CurrID
delete from #PrelimResults where [UID] = #CurrID
end
Several notes:
1. Nested while loops probably aren't the most efficient way of doing it, however nothing else comes to mind. If I were to use cursors, it would essentially be the same thing?
2. #FirstSearchWord here to refers to the first instance in the text of one of the original search words, so essentially the text you are replacing is only going to be in the summary. Again, it's quite a basic method, some sort of text cluster finding algorithm would probably be handy.
3. To get RegEx in the first place, you need CLR user-defined functions.
It looks like you could parse the output of the new SQL Server 2008 stored procedure sys.dm_fts_parser and use regex, but I haven't looked at it too closely.
You might be missing the point of the database in this instance. Its job is to return the data to you that satisfies the conditions you gave it. I think you will want to implement the highlighting probably using regex in your web control.
Here is something a quick search would reveal.
http://www.dotnetjunkies.com/PrintContent.aspx?type=article&id=195E323C-78F3-4884-A5AA-3A1081AC3B35
Some details:
search_kiemeles=replace(lcase(search),"""","")
do while not rs.eof 'The search result loop
hirdetes=rs("hirdetes")
data=RegExpValueA("([A-Za-zöüóőúéáűíÖÜÓŐÚÉÁŰÍ0-9]+)",search_kiemeles) 'Give back all the search words in an array, I need non-english characters also
For i=0 to Ubound(data,1)
hirdetes = RegExpReplace(hirdetes,"("&NoAccentRE(data(i))&")","<em>$1</em>")
Next
response.write hirdetes
rs.movenext
Loop
...
Functions
'All Match to Array
Function RegExpValueA(patrn, strng)
Dim regEx
Set regEx = New RegExp ' Create a regular expression.
regEx.IgnoreCase = True ' Set case insensitivity.
regEx.Global = True
Dim Match, Matches, RetStr
Dim data()
Dim count
count = 0
Redim data(-1) 'VBSCript Ubound array bug workaround
if isnull(strng) or strng="" then
RegExpValueA = data
exit function
end if
regEx.Pattern = patrn ' Set pattern.
Set Matches = regEx.Execute(strng) ' Execute search.
For Each Match in Matches ' Iterate Matches collection.
count = count + 1
Redim Preserve data(count-1)
data(count-1) = Match.Value
Next
set regEx = nothing
RegExpValueA = data
End Function
'Replace non-english chars
Function NoAccentRE(accent_string)
NoAccentRE=accent_string
NoAccentRE=Replace(NoAccentRE,"a","§")
NoAccentRE=Replace(NoAccentRE,"á","§")
NoAccentRE=Replace(NoAccentRE,"§","[aá]")
NoAccentRE=Replace(NoAccentRE,"e","§")
NoAccentRE=Replace(NoAccentRE,"é","§")
NoAccentRE=Replace(NoAccentRE,"§","[eé]")
NoAccentRE=Replace(NoAccentRE,"i","§")
NoAccentRE=Replace(NoAccentRE,"í","§")
NoAccentRE=Replace(NoAccentRE,"§","[ií]")
NoAccentRE=Replace(NoAccentRE,"o","§")
NoAccentRE=Replace(NoAccentRE,"ó","§")
NoAccentRE=Replace(NoAccentRE,"ö","§")
NoAccentRE=Replace(NoAccentRE,"ő","§")
NoAccentRE=Replace(NoAccentRE,"§","[oóöő]")
NoAccentRE=Replace(NoAccentRE,"u","§")
NoAccentRE=Replace(NoAccentRE,"ú","§")
NoAccentRE=Replace(NoAccentRE,"ü","§")
NoAccentRE=Replace(NoAccentRE,"ű","§")
NoAccentRE=Replace(NoAccentRE,"§","[uúüű]")
end function

Resources