Parsing XML, CSV and TSV files with Ionic

December 18, 2016, 5:34 pm Categories:

Categories

It's all about the data

** UPDATED FOR IONIC 3 **

Content might very well be king but without a way to parse different data formats our mobile apps might find themselves struggling to access any content whatsoever.

Despite the popularity and widespread use of JSON for exchanging data there are still occasions where you might find yourself having to work with formats such as XML, CSV (Comma Separated Values) and, as incredible as it might sound, even TSV (Tabbed Separated Values).

From legacy systems that can't be upgraded without significant cost and disruption to the organisation concerned to low budget solutions that just get the job done there are many reasons why clients still rely on such data formats as these.

Over the course of this tutorial I'm going to show you how we can successfully import and parse XML, CSV and TSV files into JavaScript objects for use in our Ionic applications.

What we'll be building

For this tutorial we'll be creating a single page app with 3 basic lists displaying the titles of (somewhat) forgotten British comics from the 60's through to the 80's. Each list will use data imported and parsed from one of the 3 different formats we'll be covering over the course of the following sections.

Once the coding for our application has been completed, and subsequently launched in the desktop browser, we should see output akin to the following being rendered to the screen:

British comics from the 60's and 80's displayed in an Ionic list component

*NB - For any comics aficionados out there I'm well aware that 2000AD is still being published AND that the above lists are NOT exhaustive!

Okay, with that somewhat slight disclaimer out of the way, let's make a start with actually creating the basic skeleton for the app itself.

In your command line utility of choice (I.e. Terminal on Mac OS X or Command Prompt on Windows) type out the following Ionic CLI commands:

ionic start comic blank

There's not a great deal that we'll need to do configuration wise for our app, other than install one additional node package (which we'll cover in the next section), as most of our time will be spent implementing the coding logic for parsing the different data formats we'll be working with.

Now that the basic structure of our app is in place let's move on to importing and parsing the XML data that we'll be using.

Loading & parsing XML

Once seen as the defining standard for universal data exchange XML (short for eXtensible Markup Language) is a tag based language which, like HTML, is derived from SGML (Standard Generalized Markup Language).

Despite the widespread acceptance and support for the language there are some who point to issues with this data format including:

  • Difficulties in mapping XML to other data formats and database table structures
  • Not so easy to share data between applications
  • The relative complexity of the syntax (in comparison to other formats such as YAML or JSON)
  • The processing overhead with parsing XML nodes

Strangely, Angular (the underlying front-end development framework for Ionic) does NOT provide native support for parsing XML which means we have to rely on third-party packages to implement this functionality in our applications.

This is where the wonderful world of the Node ecosystem comes to the rescue.

For the purposes of this tutorial we're going to use, out of the different XML parsing options available, the node-xml2js package. This provides a very simple and easy to use API allowing us to quickly parse XML into JavaScript arrays.

Returning back to your command line software run the following instruction, while in the root of your comics directory:

npm install xml2js

If you should encounter any permission errors on unix based systems (I.e. Mac OS X, Linux etc) prefix the above command with the sudo utility. This provides temporary root-like privileges which helps overcome those frustrating permission denied errors.

With the software successfully installed we can now define the data structure for our XML and implement the actual data that we'll be using.

In your code editor of choice create the following XML content and save this as comics.xml to the comics/src/assets/data directory (you'll need to create the data sub-directory):

<?xml version="1.0"?>
<comics> 
   <publication>
      <id>1</id>
      <title>Valiant</title>
      <publisher>Fleetway</publisher>
      <genre>Action/Adventure</genre>
   </publication> 
   <publication>
      <id>2</id>
      <title>Victor</title>
      <publisher>IPC Magazines</publisher>
      <genre>Action/Adventure/War</genre>
   </publication>
   <publication>
      <id>3</id>
      <title>Lion</title>
      <publisher>IPC Magazines</publisher>
      <genre>Action/Adventure/War/Science Fiction/Information</genre>
   </publication>
   <publication>
      <id>4</id>
      <title>Battle</title>
      <publisher>IPC Magazines</publisher>
      <genre>Action/Adventure/War</genre>
   </publication>
   <publication>
      <id>5</id>
      <title>2000AD</title>
      <publisher>IPC Magazines</publisher>
      <genre>Thriller/Science Fiction/Myth/History</genre>
   </publication>
   <publication>
      <id>6</id>
      <title>Warlord</title>
      <publisher>IPC Magazines</publisher>
      <genre>War</genre>
   </publication>
   <publication>
      <id>7</id>
      <title>Eagle</title>
      <publisher>IPC Magazines</publisher>
      <genre>Science Fiction/Thriller/Adventure</genre>
   </publication>
   <publication>
      <id>8</id>
      <title>Transformers UK</title>
      <publisher>Marvel</publisher>
      <genre>Science Fiction/Action</genre>
   </publication>
</comics>

As you can see it's a relatively simple XML structure containing a list of publications with data about each publication such as the title, publisher and genre.

Nothing terribly complicated or challenging for the purposes of this tutorial!

Now let's create the logic for parsing this XML file.

In the comics/src/pages/home/home.ts file implement the following TypeScript code:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import xml2js from 'xml2js';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

   public xmlItems : any;
   
   constructor(public navCtrl: NavController,
               public http   : Http) 
   {

   }



   ionViewWillEnter()
   {
      this.loadXML();
   }



   loadXML()
   {
      this.http.get('/assets/data/comics.xml')
      .map(res => res.text())
      .subscribe((data)=>
      {
         this.parseXML(data)
         .then((data)=>
         {
            this.xmlItems = data;
         });
      });
   }


   parseXML(data)
   {
      return new Promise(resolve =>
      {
         var k,
             arr    = [],
             parser = new xml2js.Parser(
             {
                trim: true,
                explicitArray: true
             });

         parser.parseString(data, function (err, result) 
         {
            var obj = result.comics;
            for(k in obj.publication)
            {
               var item = obj.publication[k];
               arr.push({  
                  id           : item.id[0],
                  title        : item.title[0],
                  publisher : item.publisher[0],
                  genre        : item.genre[0]
               });
            }
             
            resolve(arr);
         });
      });
   }


}

Breaking the above down we begin by importing the recently installed XML2JS node package:

import xml2js from 'xml2js';

As well as Angular 2's Http and Reactive Extensions for JavaScript modules which will allow us to load external files and make use of Promises and Observables to manage asynchronous data:

import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

The loadXML method, as the name implies, handles the loading of the comics.xml file, using a map to convert the imported data into text before passing that into the subscribe method of an observable.

Once the data has been successfully loaded and parsed we then pass that into the parseXML method, which using a Promise to handle the return of asynchronous data, converts the XML into JSON and assigns this to the xmlItems property (this will subsequently be used by our page template to render the comics listings to the screen of our browser):

loadXML()
{
   this.http.get('/assets/data/comics.xml')
   .map(res => res.text())
   .subscribe((data)=>
   {
      this.parseXML(data)
      .then((data)=>
      {
         this.xmlItems = data;
      });
   });
}

With our parseXML method we begin by creating a new xml2js object - supplying the following key/value pairings to set up how we want the XML to be processed:

  • trim: true (remove excess whitespace from the beginning and end of text nodes)
  • explicitArray: true (Always put child nodes into an array)

All of the supported configuration options for the xml2js library are available here.

Developing from this we then supply the XML object data as a parameter for xml2js's parseString method for conversion to JSON.

From here we simply drill down into the supplied data and then, using a loop to iterate through the list of publications, assign each item of data to custom objects that we push into an array which is subsequently returned through the resolve method of a Promise:

parseXML(data)
{
   return new Promise(resolve =>
   {
      var k,
          arr    = [],
          parser = new xml2js.Parser(
          {
             trim: true,
             explicitArray: true
          });

      parser.parseString(data, function (err, result) 
      {
         var obj = result.comics;
         for(k in obj.publication)
         {
            var item = obj.publication[k];
            arr.push({  
               id         : item.id[0],
               title      : item.title[0],
               publisher  : item.publisher[0],
               genre      : item.genre[0]
            });
         }
             
         resolve(arr);
      });
   });
}

The loadXML method is then triggered through use of the ionViewWillEnter navigation lifecycle event:

ionViewWillEnter()
{
   this.loadXML();
}

This is a useful 'hook' to use for any code that you want to be triggered when the page template is about to be loaded for display.

All that remains now is to craft the HTML so that we can render the parsed XML data into a list for the page view.

Open the comics/src/pages/home/home.html file and make the following changes:

<ion-header>
   <ion-navbar>
      <ion-title>
         British Comics of the 60's - 80's
      </ion-title>
   </ion-navbar>
</ion-header>

<ion-content padding>
  
   <ion-item-group>
      <ion-item-divider color="light">XML</ion-item-divider>
      <ion-item *ngFor="let item of xmlItems">
         {{ item.title }}
      </ion-item>
   </ion-item-group>

</ion-content>

Nothing complicated with the above HTML just a simple ngFor directive to loop through our parsed JSON data and create individual <ion-item> elements displaying the name of each comic title.

Rooting the application

Before we can preview the project in the desktop browser we need to import the Angular HttpModule within the application's root module. This allows the imported Http class to be used within the Home page component class.

Open the comics/src/app/app.module.ts file and import the HttpModule as shown:

import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

Serving the application

When we return to the command line and run the ionic serve command we should see the following being output to the browser window:

XML data rendered to a HTML list in Ionic

If so, then congratulations! You've successfully imported, parsed and converted XML data to JSON and rendered that to your page...and all within a couple of relatively small methods.

Good job!

As you can see, thanks to use of the xml2js library, loading & parsing XML with Ionic is quite simple and easy to accomplish.

Now let's turn our attention towards doing the same but with imported CSV files instead.

Loading and parsing CSV files

Admittedly CSV files aren't the most enjoyable of data formats to work with but there may be occasions where you just have no other option but to have to use them in your applications.

In your spreadsheet software create a table with the following structure/data :

id title publisher genre
1 Valiant Fleetway Action/Adventure
2 Victor IPC Magazines Action/Adventure/War
3 Lion IPC Magazines Action/Adventure/War/Science Fiction/Information
4 Battle IPC Magazines Action/Adventure/War
5 2000AD IPC Magazines Thriller/Science Fiction/Myth/History
6 Warlord IPC Magazines War
7 Eagle IPC Magazines Science Fiction/Thriller/Adventure
8 Transformers UK Marvel Science Fiction/Action

Save this file as comics.xls (or, if you're using Apple's Numbers software, comics.numbers) and then export the file as comics.csv to the following location in your project: comics/src/assets/data/.

With the comics.csv file stored where it can be accessed by the application we now need to write the logic to both import this file and parse the data it contains.

Modify your comics/src/pages/home/home.ts file to include the following code within the body of the HomePage class:

public csvItems : any;
   
constructor(public navCtrl: NavController,
            public http   : Http) 
{

}


ionViewWillEnter()
{
   this.loadCSV();
}


loadCSV()
{
   this.http.get('/assets/data/comics.csv')
   .map(res => res.text())
   .subscribe((data)=>
   {
      var csv         = this.parseCSVFile(data);
      this.csvItems  = csv;      
   });
}



parseCSVFile(str) 
{
   var arr  = [],
       obj  = [],
       row,
       col,
       c,
       quote   = false;  // true means we're inside a quoted field

   // iterate over each character, keep track of current row and column (of the returned array)
   for (row = col = c = 0; c < str.length; c++) 
   {
      var cc = str[c], 
          nc = str[c+1];        // current character, next character

      arr[row]           = arr[row] || [];    
      arr[row][col]  = arr[row][col] || '';  

      /* If the current character is a quotation mark, and we're inside a
    quoted field, and the next character is also a quotation mark,
    add a quotation mark to the current column and skip the next character
      */
      if (cc == '"' && quote && nc == '"') 
      { 
         arr[row][col] += cc; 
         ++c; 
         continue; 
      }  


      // If it's just one quotation mark, begin/end quoted field
      if (cc == '"') 
      { 
         quote = !quote; 
         continue; 
      }


      // If it's a comma and we're not in a quoted field, move on to the next column
      if (cc == ',' && !quote) 
      { 
         ++col; 
         continue; 
      }


      /* If it's a newline and we're not in a quoted field, move on to the next
         row and move to column 0 of that new row */
      if (cc == '\n' && !quote) 
      { 
         ++row; 
         col = 0; 
         continue; 
      }

      // Otherwise, append the current character to the current column
      arr[row][col] += cc;         
   }

   return this.formatParsedObject(arr, true);
}



formatParsedObject(arr, hasTitles)
{
   let id,
       title,
       publisher,
       genre,
       obj = [];

   for(var j = 0; j < arr.length; j++)
   {
      var items         = arr[j];         

      if(items.indexOf("") === -1)
      {  
         if(hasTitles === true && j === 0)
         {
            id            = items[0];
            title        = items[1];
            publisher    = items[2];
            genre         = items[3];
         }
         else 
         {
            obj.push({  
               id          : items[0],
               title       : items[1],
               publisher   : items[2],
               genre       : items[3]
            });
         }
      }
   }
   return obj;
}

There's a fair amount of logic at work here so let's step through this one method at a time.

The loadCSV method uses the get method of Angular 2's http class to call the requested URL (in this instance the comics.csv file), then assigns a map to convert the retrieved data into text before passing that data into the subscribe method of an observable.

Inside our observable we then supply the retrieved CSV data to the parseCSVFile method which, upon completion of parsing, will return a JSON object which is then assigned to the csvItems property for use in our HTML template.

loadCSV()
{
   this.http.get('/assets/data/comics.csv')
   .map(res => res.text())
   .subscribe((data)=>
   {
      var csv     = this.parseCSVFile(data);
      this.csvItems = csv;       
   });
}

The parseCSVFile method is a little more involved, handling the CSV supplied data on a character by character basis, determining its type (whether it's a quotation mark, comma or new line break for example) and what actions to take, if any, at each stage.

This subsequently builds a multidimensional array of content, based on the columns/rows of the supplied CSV data which, upon completion, is returned after being processed through the formatParsedObject method:

parseCSVFile(str) 
{
   var arr  = [],
       obj  = [],
       row,
       col,
       c,
       quote   = false;  // true means we're inside a quoted field

   // iterate over each character, keep track of current row and column (of the returned array)
   for (row = col = c = 0; c < str.length; c++) 
   {
      var cc = str[c], 
          nc = str[c+1];        // current character, next character

      arr[row]       = arr[row] || [];    
      arr[row][col] = arr[row][col] || '';  

      /* If the current character is a quotation mark, and we're inside a
         quoted field, and the next character is also a quotation mark,
         add a quotation mark to the current column and skip the next character
      */
      if (cc == '"' && quote && nc == '"') 
      { 
         arr[row][col] += cc; 
         ++c; 
         continue; 
      }  


      // If it's just one quotation mark, begin/end quoted field
      if (cc == '"') 
      { 
         quote = !quote; 
         continue; 
      }


      // If it's a comma and we're not in a quoted field, move on to the next column
      if (cc == ',' && !quote) 
      { 
         ++col; 
         continue; 
      }


      /* If it's a newline and we're not in a quoted field, move on to the next
         row and move to column 0 of that new row */
      if (cc == '\n' && !quote) 
      { 
         ++row; 
         col = 0; 
         continue; 
      }

      // Otherwise, append the current character to the current column
      arr[row][col] += cc;         
   }

   return this.formatParsedObject(arr, true);
}

The logic for the above method, except for a few minor amendments, is taken directly from the following Stack Overflow post and full credit must be given to Trevor Dixon for sharing his excellent solution.

Upon the parsing of the CSV file data into a multi-dimensional array this is then supplied to the formatParsedObject method to finally convert the processed data into JSON that can be returned back for use in the page HTML.

The method simply loops through the supplied data, checks to see whether any empty arrays have been created (this will happen if empty rows are exported in the original CSV file), determines whether or not the first item in the array is a list of field titles from the CSV file (courtesy of the value for the hasTitles method parameter) and subsequently creates individual objects - containing each publication entry - which are then pushed into an array which is, upon completion of processing, returned from the method:

formatParsedObject(arr, hasTitles)
{
   let id,
       title,
       publisher,
       genre,
       obj = [];

   for(var j = 0; j &lt; arr.length; j++)
   {
      var items   = arr[j];         

      if(items.indexOf("") === -1)
      {  
         if(hasTitles === true &amp;&amp; j === 0)
         {
            id           = items[0];
            title        = items[1];
            publisher   = items[2];
            genre        = items[3];
         }
         else 
         {
            obj.push({  
               id           : items[0],
               title       : items[1],
               publisher   : items[2],
               genre       : items[3]
            });
         }
      }
   }
   return obj;
}

Now all that remains is to add the necessary HTML to the comics/src/pages/home/home.html file:

<ion-item-group>
   <ion-item-divider color="light">CSV</ion-item-divider>
   <ion-item *ngFor="let item of csvItems">
      {{ item.title }}
   </ion-item>
</ion-item-group>

Underneath the HTML code block for the XML listing like so:

<ion-header>
   <ion-navbar>
      <ion-title>
         British Comics of the 60's - 80's
      </ion-title>
   </ion-navbar>
</ion-header>

<ion-content padding>
  
   <ion-item-group>
      <ion-item-divider color="light">XML</ion-item-divider>
      <ion-item *ngFor="let item of xmlItems">
         {{ item.title }}
      </ion-item>
   </ion-item-group>


   <ion-item-group>
      <ion-item-divider color="light">CSV</ion-item-divider>
      <ion-item *ngFor="let item of csvItems">
         {{ item.title }}
      </ion-item>
   </ion-item-group>

</ion-content>

Which, when we run the ionic serve utility from the command line, will output the following to the screen:

CSV parsed data being rendered from JSON to the screen

Compared to parsing XML working with imported CSV data and converting that to JSON requires a little more effort on our parts but is, thankfully, not overly difficult to accomplish.

Now we turn our attention to importing and parsing the third and final data format: TSV.

Loading and parsing TSV files

TSV is not a data format I enjoy working with and, truth be told, I really can't see the point in its existence BUT you never know when you might have to work with files supplied in that format (and believe me it's not a great deal of fun when you do have to!)

Thankfully, parsing such files in Ionic/Angular is relatively simple.

Before we can begin though we need a data source to work with!

To create a TSV file on Mac OS X simply select the comics.numbers spreadsheet columns/rows that contain data, copy this selection and paste that into the TextEdit software application. Once you've done this save the file as comics.tsv.

If you are working on a Windows platform you should just be able to export the comics.csv file directly to TSV from Microsoft Excel.

As in the previous XML and CSV sections of this tutorial you'll need to save the comics.tsv file to the following directory in your comics project: comics/src/assets/data/.

Now modify your comics/src/pages/home/home.ts file to include the following code within the body of the HomePage class:

public tsvItems : any;
   
constructor(public navCtrl: NavController,
            public http   : Http) 
{

}


ionViewWillEnter()
{
   this.loadTSV();
}


loadTSV()
{
   this.http.get('/assets/data/comics.tsv')
   .map(res => res.text())
   .subscribe((data)=>
   {
      var tsv         = this.parseTSVFile(data); 
      this.tsvItems  = tsv;               
   });
}


parseTSVFile(str) 
{
   var arr  = [],
       obj  = [],
       row,
       col,
       c;  

       
   // iterate over each character, keep track of current row and column (of the returned array)
   for (row = col = c = 0; c < str.length; c++) 
   {
      var cc            = str[c]; 

      arr[row]           = arr[row] || [];        
      arr[row][col]  = arr[row][col] || '';  

      // If it's a tab move on to the next column
      if (cc == '\t') 
      { 
         ++col; 
         continue; 
      }


      /* If it's a newline move on to the next
         row and move to column 0 of that new row */
      if (cc == '\n') 
      { 
         ++row; 
         col = 0; 
         continue; 
      }


      // Otherwise, append the current character to the current column
      arr[row][col] += cc;            
   }

   return this.formatParsedObject(arr, false);
}

As you can see the above code is almost identical to the loadCSV and parseCSVFile methods for the Loading and parsing CSV files section.

Aside from the slight difference in the naming of our methods (and obviously loading the comics.tsv file instead) our parseTSVFile method is much more simplified, performing 2 conditional checks to see if the current character is a Tab (in which case we move onto the next column) or a New Line (in which case we reset the column count and move onto the next row).

Once again the data is pushed into a multi-dimensional array which is passed into the formatParseObject method - this time with the hasTitles parameter set to false.

We of course need to add the following modification to the comics/src/pages/home/home.html file so we can actually output the parsed TSV data to the screen:

<ion-item-group>
   <ion-item-divider color="light">TSV</ion-item-divider>
   <ion-item *ngFor="let item of tsvItems">
      {{ item.title }}
   </ion-item>
</ion-item-group>

Now when we publish our application to the browser using ionic serve we should be greeted with the following for the TSV section of the HTML:

TSV parsed data displayed as JSON listings in Ionic

And that concludes our work with loading, parsing and converting TSV files to JSON!

As you can see the logic is fairly simple (having borrowed heavily from the previous CSV parsing methods) and makes use of the formatParsedObject method to take the parsed data and transform this into an array of JSON objects which can then be passed back to the HTML template for rendering.

Completed Code

home.ts

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import xml2js from 'xml2js';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

   public xmlItems : any;
   public csvItems : any;
   public tsvItems : any;
   
   constructor(public navCtrl: NavController,
               public http   : Http) 
   {

   }



   ionViewWillEnter()
   {
      this.loadXML();
      this.loadCSV();
      this.loadTSV();
   }



   loadXML()
   {
      this.http.get('/assets/data/comics.xml')
      .map(res => res.text())
      .subscribe((data)=>
      {
         this.parseXML(data)
         .then((data)=>
         {
            this.xmlItems = data;
         });
      });
   }



   loadCSV()
   {
      this.http.get('/assets/data/comics.csv')
      .map(res => res.text())
      .subscribe((data)=>
      {
         var csv         = this.parseCSVFile(data);
             this.csvItems    = csv;      
      });
   }



   loadTSV()
   {
      this.http.get('/assets/data/comics.tsv')
      .map(res => res.text())
      .subscribe((data)=>
      {
         var tsv     = this.parseTSVFile(data); 
         this.tsvItems  = tsv;               
      });
   }




   parseCSVFile(str) 
   {
      var arr  = [],
          obj  = [],
          row,
          col,
          c,
          quote = false;  // true means we're inside a quoted field

      // iterate over each character, keep track of current row and column (of the returned array)
      for (row = col = c = 0; c < str.length; c++) 
      {
         var cc = str[c], 
             nc = str[c+1];        // current character, next character

         arr[row]        = arr[row] || [];             // create a new row if necessary
         arr[row][col]  = arr[row][col] || '';   // create a new column (start with empty string) if necessary

         /* If the current character is a quotation mark, and we're inside a
            quoted field, and the next character is also a quotation mark,
            add a quotation mark to the current column and skip the next character */
         if (cc == '"' && quote && nc == '"') 
         { 
            arr[row][col] += cc; 
            ++c; 
            continue; 
         }  


         // If it's just one quotation mark, begin/end quoted field
         if (cc == '"') 
         { 
            quote = !quote; 
            continue; 
         }


         // If it's a comma and we're not in a quoted field, move on to the next column
         if (cc == ',' && !quote) 
         { 
            ++col; 
            continue; 
         }


         /* If it's a newline and we're not in a quoted field, move on to the next
            row and move to column 0 of that new row */
         if (cc == '\n' && !quote) 
         { 
            ++row; 
            col = 0; 
            continue; 
         }


         // Otherwise, append the current character to the current column
         arr[row][col] += cc;         
      }

      return this.formatParsedObject(arr, true);
   }



   parseTSVFile(str) 
   {
      var arr  = [],
          obj  = [],
          row,
          col,
          c;  

       
      // iterate over each character, keep track of current row and column (of the returned array)
      for (row = col = c = 0; c < str.length; c++) 
      {
         var cc         = str[c]; 

         arr[row]        = arr[row] || [];             // create a new row if necessary
         arr[row][col]  = arr[row][col] || '';   // create a new column (start with empty string) if necessary


         // If it's a tab move on to the next column
         if (cc == '\t') 
         { 
            ++col; 
            continue; 
         }


         /* If it's a newline move on to the next
            row and move to column 0 of that new row */
         if (cc == '\n') 
         { 
            ++row; 
            col = 0; 
            continue; 
         }


         // Otherwise, append the current character to the current column
         arr[row][col] += cc;            
      }

      return this.formatParsedObject(arr, false);
   }




   parseXML(data)
   {
      return new Promise(resolve =>
      {
         var k,
             arr    = [],
             parser = new xml2js.Parser({
                         trim: true,
                         explicitArray: true
                      });

         parser.parseString(data, function (err, result) 
         {
            var obj = result.comics;
            for(k in obj.publication)
            {
               var item = obj.publication[k];
               arr.push({  
                  id       : item.id[0],
                  title    : item.title[0],
                  publisher : item.publisher[0],
                  genre    : item.genre[0]
               });
            }
             
            resolve(arr);
         });
      });
   }



   formatParsedObject(arr, hasTitles)
   {
      let id,
          title,
          publisher,
          genre,
          obj = [];


      for(var j = 0; j < arr.length; j++)
      {
         var items   = arr[j];         

         if(items.indexOf("") === -1)
         {  
            if(hasTitles === true && j === 0)
            {
               id           = items[0];
               title       = items[1];
               publisher   = items[2];
               genre       = items[3];
            }
            else 
            {
               obj.push({  
                  id       : items[0],
                  title    : items[1],
                  publisher : items[2],
                  genre    : items[3]
               });
            }
         }
      }

      return obj;
   }


}

home.html

<ion-header>
   <ion-navbar>
      <ion-title>
         British Comics of the 60's - 80's
      </ion-title>
   </ion-navbar>
</ion-header>

<ion-content padding>
  
   <ion-item-group>
      <ion-item-divider color="light">XML</ion-item-divider>
      <ion-item *ngFor="let item of xmlItems">
         {{ item.title }}
      </ion-item>
   </ion-item-group>


   <ion-item-group>
      <ion-item-divider color="light">CSV</ion-item-divider>
      <ion-item *ngFor="let item of csvItems">
         {{ item.title }}
      </ion-item>
   </ion-item-group>


   <ion-item-group>
      <ion-item-divider color="light">TSV</ion-item-divider>
      <ion-item *ngFor="let item of tsvItems">
         {{ item.title }}
      </ion-item>
   </ion-item-group>

</ion-content>

In summary

Parsing XML, CSV and TSV formatted data is relatively simple in Ionic, thanks to the underlying Angular framework methods (and the inclusion of the xml2js library for XML parsing).

Wherever possible it's always preferable to work directly with JSON as that format is so much quicker and easier to parse but, as we all know, sometimes the system(s) you're integrating with don't always provide that option. Where that's the case, such as with XML or CSV files (and, in very rare cases, even TSV files), you can at least take heart in the knowledge that with a little coding Ionic can make short work of such integration!

Feel free to use the above code in your own projects as you see fit and, if you're feeling generous, leave a comment on this tutorial using the form below.

If you enjoyed what you've read here then please consider signing up to my mailing list and, if you haven't done so already, take a look at my e-book: Mastering Ionic for further projects and tutorials based on the Ionic framework.

Tags

Categories

Post a comment

All comments are welcome and the rules are simple - be nice and do NOT engage in trolling, spamming, abusiveness or illegal behaviour. If you fail to observe these rules you will be permanently banned from being able to comment.

Top