Search code examples
node.jstypescriptsystemjs

Parsing XML in typescript


Using Typescript 2.0 (tsc version 2.0.3).

I have unsuccessfully attempted to use the xml2js node library inside an angular2/typescript application (see https://www.npmjs.com/package/xml2js). Seems clear I am not familiar enough with the way the SystemJS sets up the library for consumption.

To install the xml2js library I did the following:

npm install --save xml2js
npm install --save @types/xml2js

Here are the relevant files:

tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false,
    "outDir" : "build"
  }
}

systemjs.config.js

/**
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
  System.config({
    paths: {
      // paths serve as alias
      'npm:': 'node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: 'build',
      // angular bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
      '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
      // other libraries
      'rxjs':                      'npm:rxjs',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
      'lodash' : 'npm:lodash',
      'xml2js' : 'npm:xml2js'
    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './main.js',
        defaultExtension: 'js'
      },
      rxjs: {
        defaultExtension: 'js'
      },
      lodash: {
        main : 'index.js',
        defaultExtension: 'js'
      },
      xml2js: {
        defaultExtension: 'js'
      }
    }
  });
})(this);

The consuming file:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/Rx';
import * as _ from 'lodash';
import * as xml2js from 'xml2js';
@Injectable()
export class CatalogService {
  constructor (private http:Http) {
  }
  getCatalog() {
    //return Promise.resolve(['This', 'That']);
    var xml = "<root>Hello xml2js!</root>"
    xml2js.parseString(xml, function (err, result) {
      alert(result);
    });

    alert(_.indexOf([1, 2, 1, 2], 2));

  }
}

index.html file

<html>
  <head>
    <title>Angular QuickStart</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">
    <!-- 1. Load libraries -->
     <!-- Polyfill for older browsers -->
    <script src="node_modules/core-js/client/shim.min.js"></script>
    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <!-- 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>
  <!-- 3. Display the application -->
  <body>
    <dsa-app>Loading DSA App...</dsa-app>
  </body>
</html>

With that setup, I do get an error as follows:

zone.js:1382 GET http://localhost:3000/node_modules/xml2js/ 404 (Not Found)

The lodash library loads fine, so I am pretty sure the issue here has to do with the way the xml2js library is defined more than my setup.

Closest solution I have seen for this is a bit dated for Angular 2.0, but I put it here for reference: Use JS library xml2js with Angular 2

Although the question specifically addresses using that library, any other ideas on how to parse/convert xml into something manageable inside TypeScript would also come in handy.


Update:

As suggested I started adding "main" definitions to my systemjs configuration. However, I though part of this was that the dependencies would be loaded/figured out from the node content.

Thus modifying the system.config.js to have:

  xml2js: {
    main: 'lib/xml2js.js', 
    defaultExtension: 'js'
  },

Move the issue to where now I get 404 entries for sax and xmlbuilder.

Thus, I added the following too:

  sax: {
    main: 'lib/sax.js',
    defaultExtension: 'js'        
  },
  xmlbuilder: {
    main: 'lib/index.js', 
    defaultExtension: 'js'        
  }

Which address the sax and xmlbuilder, but then more errors popup.

I thought the whole idea with using the SystemJS and the tsconfig would be that these would be figured out from the node modules, adding all the tree dependencies on the packages seems to defeat the purpose.


Solution

  • I think the issue here is in your systemjs config - you haven't defined main for xml2js. Because of that systemjs doesn't know which file to load, so tries to load the NPM directory directly for some reason (loading /node_modules/xml2js as a file, which clearly isn't going to work). Note that your lodash config does specify 'index.js', presumably leading to /node_modules/lodash/index.js being loaded when you import lodash, which is why that works.

    If you specify a 'main' for xml2js in your systemjs config that pointing to lib/xml2js.js then systemjs should be able to find it correctly.