InfiniteLoop
InfiniteLoop

Reputation: 431

Parsing properties from certain nodes in XML using Javascript when the name and the value are in separate XML nodes?

I want to be able to parse some values in browser-side (no Node etc.) Javascript from an XML of the following format:

<list>
<dict>
    <key>BuildMachineOSBuild</key>
    <string>16B2555</string>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>CFBundleDisplayName</key>
    <string>App AutoUpdate</string>
    <key>CFBundleExecutable</key>
    <string>App AutoUpdate</string>
    <key>CFBundleIconFile</key>
    <string>AppIcon</string>
    <key>CFBundleIdentifier</key>
    <string>com.app.autoupdate2</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>App AutoUpdate</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>3.8.1</string>
    <key>CFBundleSignature</key>
    <string>MSau</string>
    <key>CFBundleSupportedPlatforms</key>
    <array>
        <string>MacOSX</string>
    </array>
    <key>CFBundleVersion</key>
    <string>3.8.16112200</string>
    <key>DTCompiler</key>
    <string>com.apple.compilers.llvm.clang.1_0</string>
    <key>DTPlatformBuild</key>
    <string>8B62</string>
    <key>DTPlatformVersion</key>
    <string>GM</string>
    <key>DTSDKBuild</key>
    <string>16B2649</string>
    <key>DTSDKName</key>
    <string>macosx10.12</string>
    <key>DTXcode</key>
    <string>0810</string>
    <key>DTXcodeBuild</key>
    <string>8B62</string>
    <key>LSHasLocalizedDisplayName</key>
    <true/>
    <key>LSMinimumSystemVersion</key>
    <string>10.10</string>
    <key>LSRequiresNativeExecution</key>
    <true/>
    <key>MbuLoggingFullLegacyUpload</key>
    <true/>
    <key>AppBuildNumber</key>
    <string>161122</string>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>
    <key>NSMainNibFile</key>
    <string>SAUMainWindow</string>
    <key>NSPrincipalClass</key>
    <string>SAUApplication</string>
</dict>
</list>

Specifically, I want to be able to parse the values of CFBundleIdentifier and CFBundleShortVersionString. Suppose, I want to store them in an object metadata = {id, version}.

I learned from Parsing properties from certain nodes in XML using Javascript? how to parse data from XML nodes. The problem now is that unlike the abovementioned question, this format of XML doesn't store the values between the tags like so <CFBundleIdentifier>com.app.autoupdate2</CFBundleIdentifier>, but, instead, the name of the property I want to parse is between <key> tags and the property value follows after that--between the <string> tags.

If it can be done using XPATH, how would I form the query in this situation?

Upvotes: 1

Views: 69

Answers (2)

Martin Honnen
Martin Honnen

Reputation: 167571

Using JavaScript and the browser's XPath 1.0 evaluate method:

const xml = `<list>
<dict>
    <key>BuildMachineOSBuild</key>
    <string>16B2555</string>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>CFBundleDisplayName</key>
    <string>App AutoUpdate</string>
    <key>CFBundleExecutable</key>
    <string>App AutoUpdate</string>
    <key>CFBundleIconFile</key>
    <string>AppIcon</string>
    <key>CFBundleIdentifier</key>
    <string>com.app.autoupdate2</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>App AutoUpdate</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>3.8.1</string>
    <key>CFBundleSignature</key>
    <string>MSau</string>
    <key>CFBundleSupportedPlatforms</key>
    <array>
        <string>MacOSX</string>
    </array>
    <key>CFBundleVersion</key>
    <string>3.8.16112200</string>
    <key>DTCompiler</key>
    <string>com.apple.compilers.llvm.clang.1_0</string>
    <key>DTPlatformBuild</key>
    <string>8B62</string>
    <key>DTPlatformVersion</key>
    <string>GM</string>
    <key>DTSDKBuild</key>
    <string>16B2649</string>
    <key>DTSDKName</key>
    <string>macosx10.12</string>
    <key>DTXcode</key>
    <string>0810</string>
    <key>DTXcodeBuild</key>
    <string>8B62</string>
    <key>LSHasLocalizedDisplayName</key>
    <true/>
    <key>LSMinimumSystemVersion</key>
    <string>10.10</string>
    <key>LSRequiresNativeExecution</key>
    <true/>
    <key>MbuLoggingFullLegacyUpload</key>
    <true/>
    <key>AppBuildNumber</key>
    <string>161122</string>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>
    <key>NSMainNibFile</key>
    <string>SAUMainWindow</string>
    <key>NSPrincipalClass</key>
    <string>SAUApplication</string>
</dict>
</list>`;

const xmlDoc = new DOMParser().parseFromString(xml, 'application/xml');

console.log(["CFBundleIdentifier","CFBundleShortVersionString"].map(key => { return { [key] : xmlDoc.evaluate(`string(//key[. = '${key}']/following-sibling::*[1])`, xmlDoc, null, XPathResult.STRING_TYPE, null).stringValue };}));

Using JavaScript and Saxon-JS 2 to exploit XPath 3.1:

const xml = `<list>
<dict>
    <key>BuildMachineOSBuild</key>
    <string>16B2555</string>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>CFBundleDisplayName</key>
    <string>App AutoUpdate</string>
    <key>CFBundleExecutable</key>
    <string>App AutoUpdate</string>
    <key>CFBundleIconFile</key>
    <string>AppIcon</string>
    <key>CFBundleIdentifier</key>
    <string>com.app.autoupdate2</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>App AutoUpdate</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>3.8.1</string>
    <key>CFBundleSignature</key>
    <string>MSau</string>
    <key>CFBundleSupportedPlatforms</key>
    <array>
        <string>MacOSX</string>
    </array>
    <key>CFBundleVersion</key>
    <string>3.8.16112200</string>
    <key>DTCompiler</key>
    <string>com.apple.compilers.llvm.clang.1_0</string>
    <key>DTPlatformBuild</key>
    <string>8B62</string>
    <key>DTPlatformVersion</key>
    <string>GM</string>
    <key>DTSDKBuild</key>
    <string>16B2649</string>
    <key>DTSDKName</key>
    <string>macosx10.12</string>
    <key>DTXcode</key>
    <string>0810</string>
    <key>DTXcodeBuild</key>
    <string>8B62</string>
    <key>LSHasLocalizedDisplayName</key>
    <true/>
    <key>LSMinimumSystemVersion</key>
    <string>10.10</string>
    <key>LSRequiresNativeExecution</key>
    <true/>
    <key>MbuLoggingFullLegacyUpload</key>
    <true/>
    <key>AppBuildNumber</key>
    <string>161122</string>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>
    <key>NSMainNibFile</key>
    <string>SAUMainWindow</string>
    <key>NSPrincipalClass</key>
    <string>SAUApplication</string>
</dict>
</list>`;


console.log(["CFBundleIdentifier","CFBundleShortVersionString"].map(key => SaxonJS.XPath.evaluate(`parse-xml($xml)//key[. = $key] ! map { $key : following-sibling::*[1]/string() }`, [], { params : { xml : xml, key : key } })));
<script src="https://www.saxonica.com/saxon-js/documentation/SaxonJS/SaxonJS2.rt.js"></script>

Upvotes: 2

Jack Fleeting
Jack Fleeting

Reputation: 24930

Try something along these lines:

#this assumes your xml string is assigned to xmldoc

let domElement = new DOMParser().parseFromString(xmldoc,'text/xml');
identifiers = ["CFBundleIdentifier","CFBundleShortVersionString"]
for (let id of identifiers){
  exp = `//key[text()="${id}"]/following-sibling::string[1]/text()`;
  let target = domElement.evaluate(exp, domElement, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  console.log(target.snapshotItem(0).nodeValue);    
}

Output:

"com.app.autoupdate2"
"3.8.1"

You can then assign these to whatever variables you want.

Upvotes: 2

Related Questions