有条件合并观测值

时间:2018-12-04 07:50:24

标签: javascript angular xsd rxjs observable

我正在通过http请求文件。该文件包含有关其他文件的信息,这些信息需要包含在主文件中。就我而言,这些是带有导入的xsd文件,但我认为可以是任何东西。

您可以在此处查看代码:https://stackblitz.com/edit/angular-ugtaka
我提供了一些控制台输出,这些输出显示将执行每个请求,但最后,我的可观察对象没有发出任何值。

共有3种xsd文件,其结构如下:main.xsd导入sub1.xsd和sub2.xsd,sub2.xsd导入sub1.xsd。
每个文件都应该有一个请求,并且sub1.xsd将被请求两次。

myObservable
.pipe(mergeMap((data) => {

    if (data.import !== undefined) {
      const requests: Observable<any>[] = [];
      if (data.import instanceof Array) {
        for (const xmlImport of data.import) {
          const localPath = `/assets/${xmlImport.path}`;
          requests.push(this.getXsdSchema(localPath));
        }
        const forked = combineLatest(requests);
        return forked;
      } else {
        const localPath = `/assets/${import.path}`;
        return this.getXsdSchema(localPath);
      }
    }
    const myEmpty = never();
    return myEmpty;
  }))

这不是演示中的实际代码。我尝试将其缩短一些,只包括可能出现问题的部分。
myObservable 是已解析的xsd文件,其中包含应合并到observable的import语句。如果导入次数不止,我尝试将它们与 combineLatest 合并,然后使用 mergeMap 运算符将其合并到主要的可观察对象中。
如果只有一个,则可以跳过 combineLatest 部分,然后直接将其返回;如果没有,则尝试使用 never empty 。< / p>

最后,我想拥有一个包含所有可观察物的流。我想使用reduce运算符将它们组合成一个包含所有文件信息的对象。

如果我将第68行中的 never()替换为 of(“ nothing”)之类的东西,则可观察对象将发出值,但不包括实际信息。

>

2 个答案:

答案 0 :(得分:2)

问题在于路径为xs_schema.xs_schema.xs_import。可以通过使用explicitRoot: false选项来避免这种情况。我为您的stackblitz分叉了,基本上它是替换xs_import属性中文件的数据:

public getXsdSchema(path: string): Observable<any> {
  //console.log(path);
  return this.http.get(path, {
    responseType: 'text',
    headers: new HttpHeaders({
      Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
    })
  })
    .pipe(concatMap(data => {
      return new Promise((resolve, reject) => {
        xml2js.parseString(data, {
          explicitArray: false,
          explicitRoot: false,
          tagNameProcessors: [function (name) {
            // replace ":" with "_" for all tag names to make sure that they can be accessed within javascript
            return name.replace(/:/g, '_');
          }]
        }, (err, xmlObject) => {
          err ? reject(err) : resolve(xmlObject);
        });
      });
    }),
    concatMap((data: any) => {
      if (data.xs_import) {
        if (!Array.isArray(data.xs_import)) {
          data.xs_import = data.xs_import ? [data.xs_import] : [];
        }

        return zip(...data.xs_import.map((xmlImport) => 
          this.getXsdSchema(`/assets/${xmlImport.$.schemaLocation}`)
        )).pipe(
          map((imports) => {
            data.xs_import = imports;
            return data;
          })
        );

      } else {
        return of(data);
      }
    }))
}

仅供参考:您可以通过在管道调用中用逗号分隔运算符来组合运算符。

结果是:

$: Object
xs_element: Object
xs_import: Array[2]
  0: Object
    $: Object
    xs_simpleType: Object
  1: Object
    $: Object
    xs_complexType: Object
    xs_import: Array[1]
      0: Object
        $: Object
        xs_simpleType: Object

答案 1 :(得分:1)

PierreDuc的答案很酷,因为它产生了一个已解析的XML树。我的另一种方法与原始代码相似,不同之处在于,我使用expand进行递归调用,最后将数据缩减为包含四个项目的平面数组。顺便说一句,xml2js.parseString需要包装在Observable中

import { Component } from '@angular/core';
import { HttpHeaders, HttpClient } from '@angular/common/http';
import * as xml2js from 'xml2js';
import { Observable, forkJoin, empty, never, combineLatest, of, merge,zip ,concat} from 'rxjs';
import { switchMap, expand,mergeMap, map, catchError, tap,last ,scan,reduce} from 'rxjs/operators';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  name = 'Angular';

  constructor(private http: HttpClient) {
    this.getXsdSchema('/assets/main.xsd').subscribe((data) => {
      console.log('data',data);
    });
  }


  public getXsdSchema(path: string): Observable<any> {
    return this.http.get(path, {
      responseType: 'text',
      headers: new HttpHeaders({
        Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
      })
    })
      .pipe(
        mergeMap(data => {
        let rtrn: any;
        return new Observable(obs=>{

        xml2js.parseString(data, {
          explicitArray: false,
          tagNameProcessors: [function (name) {
            // replace ":" with "_" for all tag names to make sure that they can be accessed within javascript
            return name.replace(/:/g, '_');
          }]
        }, (err, xmlObject) => {
          if (err) {
            obs.error(err)
          } else {
            obs.next(xmlObject)
            obs.complete()
          }
        });

        })
      })
      ,expand((data)=>{
      if (data.xs_schema&&data.xs_schema.xs_import !== undefined) {
          const requests: Observable<any>[] = [];

          if (data.xs_schema.xs_import instanceof Array) {
            for (const xmlImport of data.xs_schema.xs_import) {
              const localPath = `/assets/${xmlImport.$.schemaLocation}`;
              requests.push(this.getXsdSchema(localPath));
              //return this.getXsdSchema(localPath)
            }
            const forked = merge(...requests);
            return forked;
          } else {
            const localPath = `/assets/${data.xs_schema.xs_import.$.schemaLocation}`;
            return this.getXsdSchema(localPath);
          }
        }
        return empty()
      })
      ,reduce((acc,curr)=>acc.concat(curr),[])

      )
  }

}