macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Using Perl to Manage Plist Files, Part 2

by James Reynolds
08/02/2005

This article is the second part of a two-part series showing how to manage Plist files with Perl using the PerlObjCBridge included with Mac OS X since version 10.3. The first article included a brief Cocoa introduction, an explanation about how the bridge works, showed how to load Plist files from disk, how to read Plist values, and ended with a shortcut subroutine that makes reading nested values a snap. This article builds on that information. If you haven't read the first tutorial, you should before proceeding here, especially if you are unfamiliar with Cocoa.

This piece goes into much more detail on managing Plist files. You'll change some values and save the altered Plist file back to disk.

Then you'll loop over entries in a Plist file, get a dump of the NetInfo database, and print all the users in the database using Perl foreach loops. To do that you'll convert the Cocoa dictionaries and arrays to Perl hashes and arrays.

Finally, you'll create a Plist file from scratch, build the sample Xgrid cal job listed in the Xgrid man page by creating the structure using Perl hashes and arrays, and then convert them to Cocoa equivalents.

Requirements for this Article

In the first article you set up a library file so you can store common subroutines in it, and use it in the scripts without having to duplicate the code. I showed you how to do this so that the examples don't duplicate code, namely the perlValue and getPlistObject subroutines. The examples in this article will continue to use these subroutines.

To sum up the process of using a library file, create a file at /Users/yourname/Desktop/perlplist.pl (always change "yourname" to your username) and include this text:


use Foundation;

sub perlValue {
  my ( $object ) = @_;
  return $object->description()->UTF8String();
}

sub getPlistObject {
  my ( $object, @keysIndexes ) = @_;
  if ( @keysIndexes ) {
    foreach my $keyIndex ( @keysIndexes ) {
      if ( $object and $$object ) {
        if ( $object->isKindOfClass_( NSArray->class ) ) {
          $object = $object->objectAtIndex_( $keyIndex );
        } elsif ( $object->isKindOfClass_( NSDictionary->class ) ) {
          $object = $object->objectForKey_( $keyIndex );
        } else {
          print STDERR "Unknown type (not an array or a dictionary):\n";
          return;
        }
      } else {
        print STDERR "Got nil or other error for $keyIndex.\n";
        return;
      }
    }
  }
  return $object;
}

# more subroutines go here...

1;

In the scripts that use the subroutines, include this text at the top of the file:


use lib "/Users/yourname/Desktop/";
require "perlplist.pl"; # for perlValue and getPlistObject

If you do not use a library file, you will need to include these subroutines in your scripts in order to use them.

If you ever get an error that looks like one of the following, it is because the path to your library is incorrect or because the subroutine is missing from the library.


Can't locate perlplist.pl in @INC 
(@INC contains: /Users/yourname/Desktop/ ...snip... .) at test line 5.

Undefined subroutine &main::getPlistObject called at test line 13.

Changing a Plist Value

Now let's jump right back into Plist files by changing a value and saving the Plist back to disk.

But first, a word of caution. Changing the Plist file of a running application is strongly discouraged since an application will not know to reread the file, and in fact the application could overwrite your changes. You could also accidentally clobber the file you are saving by accidentally overwriting something with the wrong data type or deleting nested containers. For example, it is possible to overwrite the root dictionary with an empty dictionary.

We will read the computer's network preferences.plist file, but we don't want to change it. Instead, we will save the changed version to the Desktop. If you want to make your changes take effect, you could make a backup of the original file and move the changed file into its place and reboot the computer, or find another way to force the system to reread the preferences.

Now, NSDictionary and NSArray can not have their values changed once they are set. These are optimized objects that you can use if you know you don't need to change them. On the other hand, if you know you need to change their values, then you use the mutable versions: NSMutableDictionary and NSMutableArray.

The mutable versions inherit the methods from the nonmutable versions. So you can call the same methods that are in the nonmutable versions, like dictionaryWithContentsOfFile_(), and writeToFile_atomically_().

In addition, these versions have extra methods that allow their values to be changed, like setObject_forKey_() for dictionaries and replaceObjectAtIndex_withObject_() for arrays. There are many more methods you can use to manipulate dictionaries and arrays. See Apple's NSMutableDictionary and NSMutableArray documentation to see the available methods.

Notice that setObject_forKey_() has two underscores, so it requires two parameters. The first parameter is the object, the second is the key. The method replaceObjectAtIndex_withObject_() also requires two parameters, the first is the index, and the second is the object.


$file = "/Library/Preferences/SystemConfiguration/preferences.plist";
$plist = NSMutableDictionary->dictionaryWithContentsOfFile_( $file );
$plist->setObject_forKey_("some object", "some key");
print perlValue( $plist ) . "\n";

When adding objects to an array, replaceObjectAtIndex_withObject_() and insertObject_atIndex() must have indexes that do not exceed the size of the array. The former method will replace the object at the given index, the later method will make room for the new object by moving the objects in its way. To insert an object to the end of the array, just use addObject_().

When adding objects to a dictionary with setObject_forKey_(), the key-object pair will be created if the key does not exist. In the example above, since "some key" did not exist, the key-object pair is added to the dictionary. If a key-object pair already exists in the Plist, then the old object in the Plist is replaced with the new object. In the following example, new objects are added for keys that already exist. This example essentially clobbers all the settings. The changes are not saved to disk, so it is safe.


#!/usr/bin/perl

use Foundation;
use lib "/Users/yourname/Desktop/";
require "perlplist.pl"; # for perlValue

$file = "/Library/Preferences/SystemConfiguration/preferences.plist";
$plist = NSMutableDictionary->dictionaryWithContentsOfFile_( $file );
if ( $plist and $$plist) {

  $plist->setObject_forKey_("clobber one", "CurrentSet");
  $plist->setObject_forKey_("clobber two", "NetworkServices");
  $plist->setObject_forKey_("clobber red", "Sets");
  $plist->setObject_forKey_("clobber blue", "System");

  print perlValue( $plist ) . "\n";

} else {
  die "Error loading file.\n";
}

Pages: 1, 2, 3, 4, 5, 6

Next Pagearrow