Question
· Oct 18, 2020

XML string query

Hi,

I am stuck with simple logic. I have a below string 

set a="<Name>ABC</Name><RollNo>45</RollNo><Name>XYZ</Name><RollNo>66</RollNo><Name>xyz</Name><RollNo>89</RollNo>"

I need logic to replace the values of roll no with "***". Is there any single command which can help in this scanerio.

I need to store this value in database after replace.

Thanks

Discussion (6)1
Log in or sign up to continue

this would be a typical exercise to use XSLT support

but for this simple case using $FIND Function in a loop may do it as well.

set a="<Name>ABC</Name><RollNo>45</RollNo><Name>XYZ</Name><RollNo>66</RollNo><Name>xyz</Name>RollNo>89</RollNo>"
set p=1
for  {
      set f=$find(a,"<RollNo>",p) quit:'f
      set t=$find(a,"<",f-1) quit:'t  set p=t-2
      set $e(a,f,p)="**"
     }

Hi - note that in order for this WHILE loop to iterate over the tXMLList (which was your XML string converted to a $LIST string) you need to also initialise the variable ptr = 0 outside the loop.

Also note this code only iterates over the source string and retrieves each value. - if you want to to replace your 'a' string as per your original post, use Robert's solution, or build this out in a new variable.   like this:

Set tXMLList = $LISTFROMSTRING(a, "</RollNo>")
Set ptr=0,newA=""
while $LISTNEXT(tXMLList,ptr,value) {
    Set $P(value,">",*) = "***"
    Set newA=newA_value_"</RollNo>"
}
Set a=newA

A warning on both solutions offered so far is that XML elements are not case-sensitive, so beware when the element "RollNo" is not provided to you in exactly that mix of lower/uppercase.

As Robert said, XSLT... but as he indicated too, in this case XSLT would be an overkill.

But there is also a "forgotten" function, $locate() too for such cases! This function is almost never used in DC examples. Two or three lines of code, and you have the perfect solution... but for "simple" cases only. Beware of nested tags!

SetFixedValue(str,tag,value) Public
{
   set i=0, t="(?i)<"_tag_">[^<]+", s=$l(tag)+2
   while $locate(str,t,i,j,v) { set $e(str,j+s-$l(v),j-1)=value, i=j+s-$l(v)+$l(value) }
   quit str
}

It works like a charm...

set a="<Name>..."
write $$SetFixedValue^test(a,"rollno","***")  -->  <Name>ABC</Name><RollNo>***</RollNo><Name>XYZ</Name><RollNo>***</RollNo><Name>xyz</Name><RollNo>***</RollNo>

You can use ReplaceAll method if we have RegEx.

 set a="<Name>ABC</Name><RollNo>45</RollNo><Name>XYZ</Name><RollNo>66</RollNo><Name>xyz</Name><RollNo>89</RollNo>"
 set matcher = ##class(%Regex.Matcher).%New("(?i)(<rollno>)([^<]+)",a)
 write matcher.ReplaceAll("$1***")
<Name>ABC</Name><RollNo>***</RollNo><Name>XYZ</Name><RollNo>***</RollNo><Name>xyz</Name><RollNo>***</RollNo>

As it looks like a security issue I would recommend a slightly different approach.

1. If you do not have the class for your XML, create it from XSD or manually.

2. Convert your XML string into an object of class (1).

3A. If you  want just to skip some properties (like RollNo) set availability of these projected properties to IN - this way property is used by import but is ignored on export. Alternatively disable projection of this property altogether.

3B. If you really want to return *** from your value (what's the use case?) add a new datatype test.PrivateString and use it to store RollNo value - during OBJ->XML projection it would be exported as ***.

Class test.PrivateString Extends %String
{

/// Declares the XSD type used when projecting XML Schemas.
Parameter XSDTYPE = "string";

/// Return "***"
ClassMethod LogicalToXSD(%val As %TimeStamp) As %String [ CodeMode = generator, ServerOnly = 1 ]
{
    If ($$$getClassType(%class)=$$$cCLASSCLASSTYPEDATATYPE) || $$$comMemberKeyGet(%class,$$$cCLASSparameter,"XMLENABLED",$$$cPARAMdefault) {
        Set %codemode=$$$cMETHCODEMODEEXPRESSION
        Set %code="""***"""
    } Else {
        Set %code=0
    }
    Quit $$$OK
}

}