[API接口文档]: ./api/index.md
[设计与机制]: ./设计与机制.md

[对鉴定者列表执行识别]: ./assets/对鉴定者列表执行识别.png
[执行串行鉴定者]: ./assets/执行串行鉴定者.png
[执行串行解析器]: ./assets/执行串行解析器.png
[识别]: ./assets/识别.png
[执行鉴定者]: ./assets/执行鉴定者.png
[解决]: ./assets/解决.png


**目录：**  


<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=true} -->

<!-- code_chunk_output -->

1. [入门](#入门)
2. [深入](#深入)
    1. [鉴定者的顺序](#鉴定者的顺序)
    2. [鉴定者名字](#鉴定者名字)
    3. [一进多出](#一进多出)
3. [提高复用性](#提高复用性)
    1. [串行鉴定者](#串行鉴定者)
    2. [串行解析器](#串行解析器)
    3. [前置鉴定者、后置鉴定者](#前置鉴定者-后置鉴定者)
    4. [前置解析器、后置解析器](#前置解析器-后置解析器)

<!-- /code_chunk_output -->


-------------

> 如果想要更深入的了解，你可查看 [设计与机制][] 和 [API接口文档][]



# 入门
以用 `recogn-parse` 实现预览文件为例：

1. 创建 RecognParse 实例
    首先需要创建一个 RecognParse 的实例：
    ```ts
    const recognParse = new RecognParse();
    ```

2. 添加识别器  
    为了识别出不同的文件格式，我们需要添加一个用于识别格式的鉴定者，它可以是一个函数，也称为 鉴定函数：

    ```ts
    // 扩展名鉴定者
    function extnameJudger(target: any, targetOptions: Options, preResult: FormatJudgeResult){
        // 获取文件路径
        const path = getPath(target);
        // 获取扩展名
        const type = getExtname(path);
        return {
            type: type,
            content:target
        }
    }

    recognParse.recognizer.add(extnameJudger);
    ```

3. 添加解析器  
    我们需要为支持的文件类型添加解析器，它可以是一个函数，也称为 解析函数：

    ```ts
    // pdf 解析器
    async function pdfParser(target: any, targetOptions: Options, preResult: any, recognResult: JudgeInfo) {
        // 请求文件内容
        const data = await request(target);
        // 查看
        viewPdf(data);
        return data
    }

    recognParse.resolver.add("pdf",pdfParser);


    // json 解析器
    async function jsonParser(target: any, targetOptions: Options, preResult: any, recognResult: JudgeInfo){
        // 请求文件内容
        const data = await request(target);

        const jsonObj = JSON.parse(data);
        // 查看
        viewTxt(jsonObj);
        
        // 可以返回外部需要使用的数据，也可以不返回
        return jsonObj
    }

    recognParse.resolver.add("json",jsonParser);
    ```

4. 使用  
    当添加好 鉴定者 和 解析器后，就可以如下使用了：
    ```ts
    recognParse.resolve("https://guo.binyou.com/recogn-parse.pdf").then((recognParseInfo:RecognParseInfo)=>{
        // recognParseInfo 对象的 键 是识别出的类型，值 是 对应的识别结果（保存在 `recogn` 属性上） 和 解析结果（保存在 `parse` 属性上）
        const data = recognParseInfo.pdf?.parse;
        console.log("pdf的文件内容是:",data);
    })
    ```



# 深入

截止目前，我们只添加了一个鉴定者，该鉴定者的作用就是提取出文件的扩展名，然后把扩展名解析使用的类型。大部分文件可以这样做，但也有一些例外，比如下面这些格式：
+ `GeoJSON`：这是一种使用 json 格式来描述的 地理失量图形格式。
+ `gltf`：这是一种使用 json 格式来描述的 3D 场景格式。

那我们就需要为这两种格式添加鉴定者了，以 `GeoJSON` 为例

```ts
// GeoJSON 鉴定者
async function geoJSONJudger(target: any, targetOptions: Options, preResult: FormatJudgeResult){
    // 获取文件路径
    const path = getPath(target);
    // 获取扩展名
    const type = getExtname(path);
    if (type !== "json") return null;

    const const data = await request(target);
    const jsonObj = JSON.parse(data);

    if (!isGeoJSON(jsonObj)) return null;

    return {
        type: "GeoJSON",
        content:jsonObj
    }
}

recognParse.recognizer.add(geoJSONJudger);
```

因为这个鉴定者需要获取到具体的数据才能识别文件内容的类型，所以这个鉴定者是异步函数，返回的是 `Promise`。


还需要为 `GeoJSON` 类型添加对应的解析器，如下：

```ts
// GeoJSON 解析器
function geoJsonParser(target: any, targetOptions: Options, preResult: any, recognResult: JudgeInfo) {
    // 因为鉴定者已经获获取并解析了 json 文件的崆，所以这里不必重新获取，直接拿来用即可。
    const data = recognResult.content;
    // 查看
    viewGeoJson(data);
    return data
}

recognParse.resolver.add("GeoJSON",geoJsonParser);
```


## 鉴定者的顺序

因为鉴定者是从前往后依次执行，直到第一个成功鉴定的鉴定者为止，如下图所示：
    ![对鉴定者列表执行识别][]
又因为 `extnameJudger` 鉴定者一定会成功识别，所以，如果将这两个鉴定者是添加在 `extnameJudger` 之后，鉴定就不会走到 `geoJSONJudger`，所以，应该把更具体的子类的鉴定者放在 范围更广的鉴定者前面，在这里是把 `geoJSONJudger` 放在 `extnameJudger` 的前面，调整后的代码如下：

```ts
recognParse.recognizer.add(geoJSONJudger);

recognParse.recognizer.add(extnameJudger);
```


## 鉴定者名字
在添加鉴定者时，还可以给鉴定者指定一个可选的名字，示例如下：

```ts
recognParse.recognizer.add(gltfJudger,"GeoJSON");
recognParse.recognizer.add(extnameJudger,"extname");
```

在使用 `resolve` 时可以明确指定鉴定者的名字，这样可以直接命中对应的监定者，提高执行效率，如下：
```ts
recognParse.resolve("https://guo.binyou.com/GeoJSON.json",null,{recogn:"GeoJson"})
```

如果鉴定品的名字 和 解析器的 类型 相同，则可以使用使用 `RecognParse` 来添加，如下：
```ts
recognParse.add("GeoJSON",{
    judger:geoJSONJudger,
    parser:geoJsonParser,
});
```

这时 `resolve` 仍然可以通过 `recogn` 选项来指定鉴定鉴定的名字，也可以通过 `type` 字段同时指定 鉴定者的名字 和 解析器的类型，如下：
```ts
recognParse.resolve("https://guo.binyou.com/gltf.json",null,{type:"gltf"})
```

如果想批量添加，则可以使用如下方法：
```ts
recognParse.addRecognParsers({
    GeoJSON:{
        judger:geoJSONJudger,
        parser:geoJsonParser,
    },
    gltf:{
        judger:gltfJudger,
        parser:gltfParser,
    },
    ...
})
```


## 一进多出

因为 `GeoJSON` 文件也是 json 格式的文件，所以，我们也可以展示 GeoJSON 图形的同时，展示 json 文件格式的内容。

简单的思路是分别调用 `resolve` 再次，如下：
```ts
// 以 GeoJSON 展示 
recognParse.resolve("https://guo.binyou.com/GeoJSON.json");
// 强制以 json 格式展示
recognParse.resolve("https://guo.binyou.com/GeoJSON.json",null,{recogn:"extname"});
// 或者
// recognParse.resolve("https://guo.binyou.com/GeoJSON.json",null,{type:"json",noRecogn:true});
```

还有一种性能更高的方案：鉴定者 可以返回多个鉴定信息，如下：
```ts
// GeoJSON 鉴定者
async function geoJSONJudger(target: any, targetOptions: Options, preResult: FormatJudgeResult){
    // 获取文件路径
    const path = getPath(target);
    // 获取扩展名
    const type = getExtname(path);
    if (type !== "json") return null;

    const const data = await request(target);
    const jsonObj = JSON.parse(data);

    if (!isGeoJSON(jsonObj)) return null;

    return [
        {
            type: "GeoJSON",
            content:jsonObj
        },{
            type: "json",
            content:jsonObj
        }
    ];
}

recognParse.recognizer.add(geoJSONJudger);
```

这样，当命中该鉴定者时，该鉴定者会返回两个类型的鉴定信息，它会分成调用这两个类型对应的解析器。

# 提高复用性
recogn-parse 也做了一些能够增强复用性、利于提高性能等相关的设计，如下：
- 串行的鉴定者 和 解析器
- 前置、后置鉴定者
- 前置、后置解析器

## 串行鉴定者
鉴定者 `geoJSONJudger` 中前面的逻辑是获取扩展名，其实这就是 `extnameJudger` 所做的工具，这部分完全可以复用。

鉴定者也可以是个函数数组，称为 串行鉴定者，如下：
```ts
// GeoJSON 鉴定者
async function geoJSONJudger(target: any, targetOptions: Options, preResult: FormatJudgeResult){
    // 获取前面鉴定函数识别的结果
    const type = preResult[0]?.type;
    if (type !== "json") return null;

    const const data = await request(target);
    const jsonObj = JSON.parse(data);

    if (!isGltf(jsonObj)) return null;

    return {
        type: "GeoJSON",
        content:jsonObj
    }
}
// 鉴定者 可以是一个 鉴定函数 数组
recognParse.recognizer.add([extnameJudger,geoJSONJudger],"GeoJSON");
```


* 串行鉴定者就是一组鉴定函数，所有的鉴定函数都会依次执行，不论中间有没有失败，都会依次执行完所有的鉴定函数，并会以最后一个鉴定函数返回的结果为最终的鉴定结果。
* 串行鉴定者序列中的任何项识别失败都不会中止 串行鉴定者序列 的执行。即：即使 串行鉴定者序列 `SerialJudger` 中的某一个 `Judge` 返回 null 或者 抛出错误，则仍会执行此 串行鉴定者序列 `SerialJudger` 中的下一个 `Judge`

它的执行逻辑图如下：
   ![执行串行鉴定者][]


## 串行解析器
与 串行鉴定者 的作用类似，都是为了解耦合 和 增强复用性。

串行解析器就是一组解析函数，这些解析函数会依次执行，并会以最后一个解析函数返回的结果为最终的解析结果。需要注意的是：这组解析函数中如果有解析函数抛出错误，则会终止解决流程。

它的执行逻辑如下图：

![执行串行解析器][]




## 前置鉴定者、后置鉴定者
如果 `获取扩展名` 是很多鉴定者首先要处理的逻辑，那么也可以把这部分逻辑做成前置鉴定者，这样可以让所有的鉴定者都能共用，方法如下：
```ts
recognParse.recognizer.preJudgers.push(extnameJudger)
```

`recognParse.recognizer.preJudgers` 是一个数组，它里面放置的是前置的鉴定者，同样也有后置鉴定者 `recognParse.recognizer.postJudgers`，它们的执行逻辑如下图：
![识别][]
![对鉴定者列表执行识别][]
![执行鉴定者][]



## 前置解析器、后置解析器
与 前置鉴定者、后置鉴定者的作用 和 使用方式类似，都是为了解耦合 和 增强复用性。

```ts
recognParse.recognizer.preParses.push(parser)
recognParse.recognizer.postParses.push(parser)
```

它的执行逻辑图如下：
![解决][]