Search code examples
javascriptregexios-ui-automation

Most efficient way to grab XML tag from file with JavaScript and Regex


I'm doing some more advanced automation on iOS devices and simulators for an enterprise application. The automation is written in browserless Javascript. One of the methods works on the device but not on the simulator, so I need to code a workaround. For the curious, it's UIATarget.localTarget().frontMostApp().preferencesValueForKey(key).

What we need to do is read a path to a server (which varies) from a plist file on disk. As a workaround on the simulator, I've used the following lines to locate the plist file containing the preferences:

// Get the alias of the user who's logged in
var result = UIATarget.localTarget().host().performTaskWithPathArgumentsTimeout("/usr/bin/whoami", [], 5).stdout;

// Remove the extra newline at the end of the alias we got
result = result.replace('\n',"");

// Find the location of the plist containing the server info
result = UIATarget.localTarget().host().performTaskWithPathArgumentsTimeout("/usr/bin/find", ["/Users/"+result+"/Library/Application Support/iPhone Simulator", "-name", "redacted.plist"], 100);

// For some reason we need a delay here
UIATarget.localTarget().delay(.5);

// Results are returned in a single string separated by newline characters, so we can split it into an array
// This array contains all of the folders which have the plist file under the Simulator directory
var plistLocations = result.stdout.split("\n");

...

// For this example, let's just assume we want slot 0 here to save time
var plistBinaryLocation = plistLocations[0];
var plistXMLLocation =  plistLocations[i] + ".xml";
result = UIATarget.localTarget().host().performTaskWithPathArgumentsTimeout("/usr/bin/plutil", ["-convert","xml1", plistBinaryLocation,"-o", plistXMLLocation], 100);

From here, I think the best way to get the contents is to cat or grep the file, since we can't read the file directly from disk. However, I'm having trouble getting the syntax down. Here's an edited snippet of the plist file I'm reading:

<key>server_url</key>
<string>http://pathToServer</string>

There are a bunch of key/string pairs in the file, where the server_url key is unique. Ideally I'd do something like a lookback, but because JavaScript doesn't appear to support it, I figured I'd just get the pair from the file and whittle it down a bit later.

I can search for the key with this:

// This line works
var expression = new RegExp(escapeRegExp("<key>server_url</key>"));

if(result.stdout.match(expression))
{
    UIALogger.logMessage("FOUND IT!!!");
}
else
{
    UIALogger.logMessage("NOPE :(");
}

Where the escapeRegExp method looks like this:

function escapeRegExp(str) 
{
    var result =  str.replace(/([()[{*+.$^\\|?])/g, '\\$1');

    UIALogger.logMessage("NEW STRING: " + result);
    return result;
}

Also, this line returns a value (but gets the wrong line):

var expression = new RegExp(escapeRegExp("<string>(.*?)</string>"));

However, when you put the two together, it (the Regex syntax) works on the terminal but doesn't work in code:

var expression = new RegExp(escapeRegExp("<key>server_url</key>[\s]*<string>(.*?)</string>"));

What am I missing? I also tried grep and egrep without any luck.


Solution

  • There are two problems affecting you here getting the regex to work in your JavaScript code.

    • First, you are escaping the whole regex expression string, which means that your capturing (.*?) and your whitespace ignoring [\s]* will also be escaped and won't be evaluated the way you're expecting. You need to escape the XML parts and add in the regex parts without escaping them.
    • Second, the whitespace ignoring part, [\s]* is falling prey to JavaScript's normal string escaping rules. the "\s" is turning into "s" in the output. You need to escape that backslash with "\s" so that it stays as "\s" in the string that you pass to construct the regular expression.

    I've built a working script that I've verified in the UI Automation engine itself. It should extract and print out the expected URL:

    var testString = "" +
    "<plistExample>\n" +
    "   <key>dont-find-me</key>\n" +
    "   <string>bad value</string>\n" +
    "   <key>server_url</key>\n" +
    "   <string>http://server_url</string>\n" +
    "</plistExample>";
    
    function escapeRegExp(str) 
    {
        var result =  str.replace(/([()[{*+.$^\\|?])/g, '\\$1');
    
        UIALogger.logMessage("NEW STRING: " + result);
        return result;
    }
    
    var strExp = escapeRegExp("<key>server_url</key>") + "[\\s]*" + escapeRegExp("<string>") + "(.*)" + escapeRegExp("</string>");
    
    UIALogger.logMessage("Expression escaping only the xml parts:" + strExp);
    
    var exp = new RegExp(strExp);
    var match = testString.match(exp);
    
    UIALogger.logMessage("Match: " + match[1]);
    

    I should point out, though, that the only thing you need to escape in the regex is the forward slashes in the XML closing tags. That means that you don't need your escapeRegExp() function and can write the expression you want like this:

    var exp = new RegExp("<key>server_url<\/key>[\\s]*<string>(.*)<\/string>");