dev4Agriculture/isoxml-js

Angular project with identical code to example yields empty error

Closed this issue · 11 comments

Hello!

I am trying to implement this in an angular application.

I have attempted to use both overloads of the parseISOXMLFile function like so;

onFileSelected(event: Event) {
  const files = (event.target as HTMLInputElement).files;
  const isoxmlManager = new ISOXMLManager();

  if (files && files.length > 0) {
    const file = files[0];

    jsZip.loadAsync(file).then((zip) => {
      zip.files["TASKDATA/TASKDATA.XML"].async('string').then((fileData) => {
        try {
          isoxmlManager.parseISOXMLFile(fileData, 'application/xml').then(() => {
            // getWarnings() method returns all the warnings from the last parsing
            console.log(isoxmlManager.getWarnings())

            // all global attributes of the parsed file
            console.log(isoxmlManager.options)

            // get all the Partfileds
            const partfields: Partfield[] = isoxmlManager.rootElement.attributes.Partfield || []

            // print designators of all the Partfields
            partfields.forEach(partfield => {
                console.log(`${partfield.attributes.PartfieldDesignator}`)
            })
          })
        } catch(e) {
          console.log(e)
        }
      });

    });
  }
}

And

onFileSelected(event: Event) {
    const files = (event.target as HTMLInputElement).files;
    const isoxmlManager = new ISOXMLManager();

    if (files && files.length > 0) {
      const file = files[0];

      const reader = new FileReader()

      reader.onload = async () => {
        const array = new Uint8Array(reader.result as ArrayBuffer)
        try {
          await isoxmlManager.parseISOXMLFile(array, 'application/zip').then(() => {
            // getWarnings() method returns all the warnings from the last parsing
            console.log(isoxmlManager.getWarnings())

            // all global attributes of the parsed file
            console.log(isoxmlManager.options)

            // get all the Partfileds
            const partfields: Partfield[] = isoxmlManager.rootElement.attributes.Partfield || []

            // print designators of all the Partfields
            partfields.forEach(partfield => {
                console.log(`${partfield.attributes.PartfieldDesignator}`)
            })
          })
        } catch(e) {
          console.log(e)
        }
      }

      reader.readAsArrayBuffer(file)
    }
 }

In both instances I am getting the following error:

image

The error indicates that it is essentially an empty file I am trying to look at

I have tried to modifiy the settings, but it is the same error no matter what I do

The file used, comes from a legitimate source, and works perfectly fine in other tools used to verify and showcase the content

Hi @alkrdev , thank you for reporting this issue!

The errors look weird, particularly part PDV[first]. Internally, XML is parsed to JSON, and then JSON is converted to JS classes. During that conversion, we iterate over arrays in the following way. My guess is that Angular or one of your dependencies adds additional methods to Array prototype (isEmpty, first, last), which breaks for..in loops in our code...

I'll try to fix this issue in our sources ASAP and will let you know.

@alkrdev could you check this fix, please. It takes into account Array prototype pollution, but not Object prototype pollution. I'm not sure if that's the case in AngularJS projects... I tried to cover Object's prototype as well, but discovered a bug in XML parser we are currently using...

Unfortunately still getting the same error;

image

Here's a slightly better image of the error. Do you need more context or information? Let me know and I will provide it.

I am not sure if it is relevant, but the isoxmlManager.getWarnings() function is placed in the catch, instead of inside the try - Which deviates from the examples.

@alkrdev Are you sure you tried the version from the PR #12 ? I didn't deploy the fix to npm. The warnings above indicate that the library still iterates over additional methods in Array.prototype, which is exactly what I fixed in that PR...

Yeah, I installed the branch;

image

And it looks like the 2 changes are present in my files

image
image

EDIT:
3rd change:
image

@alkrdev Thanks for checking the version! At this point, I don't have good ideas of what's happening in your environment. I can't reproduce this error locally...

I have the following ideas about how we can debug it:

  1. (the best option) Is it possible to create a standalone minimal reproducible example of this problem? Something that I can run locally and see that error.
  2. (just in case - I don't think that it's the source of the problem) Is it possible to send me the ISOXML file you are using for testing?
  3. Could you dump Array and Object prototypes from your environment (e.g. in browser with running Angular application)?
Object.keys(Object.prototype)
Object.keys(Array.prototype)
for ([idx, item] of ['a'].entries()) {console.log(idx, item)}
for (key of Object.keys({a: 1})) {console.log(key)}

From running the following:

image

I get this output:

image

And in trying to setup a minimal standalone example (Completely new and barebones angular application), it worked perfectly fine, and I decided to run the above there aswell, and this is the result:

image

I hunted down and removed the 3 pollutants which were apparently added as extensions to the Array.prototype, and now it is working correctly.

I am assuming the way to reproduce this, would be to undo the changes I have just made;

From a newly created Angular project, inset the following:

/src/app/core/extensions/array.extensions.ts
/src/app/core/extensions/index.ts

In index.ts, add the following:

export * from './array.extensions';

In array.extensions.ts, add the following:

declare global {
  interface Array<T> {
    /**
     * Returns true if the array is empty. False otherwise.
     */
    isEmpty(): boolean;

    /**
     * Returns the first element of the array
     */
    first(): T | undefined;

    /**
     * Returns the last element of the array
     */
    last(): T | undefined;
  }
}

if (!Array.prototype.isEmpty) {
  Array.prototype.isEmpty = function () {
    return this.length === 0;
  };
}

if (!Array.prototype.first) {
  Array.prototype.first = function () {
    return this[0];
  };
}

if (!Array.prototype.last) {
  Array.prototype.last = function () {
    return this[this.length - 1];
  };
}

export {};

in the following file:
/src/main.ts

Add the following to the imports:

import './app/core/extensions';

@alkrdev Thanks for the dumps of prototypes and description of minimal reproducible prototype. What I don't understand is why the fix I made doesn't work - it's supposed to handle issues with exactly this type of Array.prototype pollution... Your output of test for's above demonstrates that it should work in your environment... I'll try to reproduce it with Angular + files above tonight.

@aparshin Hey, different account, same person;

I will gladly concede that I somehow messed up on my end, and the changes you submitted probably didn't apply on my end, somehow..

If you want more information on the setup on my side, please let me know, as far as I am concerned it seems to work perfectly fine now, due to your changes.

@alexanderseges @alkrdev Glad to hear that! The fix is published in npm as v1.9.2. And thanks for reporting one more time!