DonutProcessor token2json too slow
Author: michaelnation26
Pull request: no

### System Info

- `transformers` version: 4.25.0.dev0
- Platform: Linux-5.4.0-1094-azure-x86_64-with-glibc2.2.5
- Python version: 3.8.15
- Huggingface_hub version: 0.11.0.dev0
- PyTorch version (GPU?): 1.13.0+cu117 (True)
- Tensorflow version (GPU?): not installed (NA)
- Flax version (CPU?/GPU?/TPU?): not installed (NA)
- Jax version: not installed
- JaxLib version: not installed
- Using GPU in script?: Yes
- Using distributed or parallel set-up in script?: No

### Who can help?

@NielsRogge 

### Information

- [ ] The official example scripts
- [X] My own modified scripts

### Tasks

- [ ] An officially supported task in the `examples` folder (such as GLUE/SQuAD, ...)
- [X] My own task or dataset (give details below)

### Reproduction

```
processor = DonutProcessor.from_pretrained("nielsr/donut-base")
sequence = (
    "<s_name>John Doe</s_name><s_age>99</s_age><s_city>Atlanta</s_city>"
    "<s_state>GA</s_state><s_zip>30301</s_zip><s_phone>123-4567</s_phone>"
)
# If you're not using a jupyter notebook, delete the %timeit
%timeit processor.token2json(sequence)
```
The `token2json` method does not scale well with the number of xml tags. It takes ~70ms for each tag in the sequence str. If there are 10 tags, it will take ~700ms, 20 tags ~1400ms, etc. The bottleneck in `token2json` is `self.tokenizer.get_added_vocab()`. It's called once for every tag. `get_added_vocab` takes ~70ms to run each time.

### Expected behavior

Since `get_added_vocab` isn't changing during the `token2json` call, `get_added_vocab` should only be called once and the results be reused. Here's one potential solution:
```
def token2json(self, tokens, is_inner_value=False, added_vocab=None):
    """
    Convert a (generated) token sequence into an ordered JSON format.
    """
    if added_vocab is None:
        added_vocab = self.tokenizer.get_added_vocab()
        
    output = dict()

    while tokens:
        start_token = re.search(r"<s_(.*?)>", tokens, re.IGNORECASE)
        if start_token is None:
            break
        key = start_token.group(1)
        end_token = re.search(rf"</s_{key}>", tokens, re.IGNORECASE)
        start_token = start_token.group()
        if end_token is None:
            tokens = tokens.replace(start_token, "")
        else:
            end_token = end_token.group()
            start_token_escaped = re.escape(start_token)
            end_token_escaped = re.escape(end_token)
            content = re.search(f"{start_token_escaped}(.*?){end_token_escaped}", tokens, re.IGNORECASE)
            if content is not None:
                content = content.group(1).strip()
                if r"<s_" in content and r"</s_" in content:  # non-leaf node
                    value = self.token2json(content, is_inner_value=True, added_vocab=added_vocab)
                    if value:
                        if len(value) == 1:
                            value = value[0]
                        output[key] = value
                else:  # leaf nodes
                    output[key] = []
                    for leaf in content.split(r"<sep/>"):
                        leaf = leaf.strip()
                        if leaf in added_vocab and leaf[0] == "<" and leaf[-2:] == "/>":
                            leaf = leaf[1:-2]  # for categorical special tokens
                        output[key].append(leaf)
                    if len(output[key]) == 1:
                        output[key] = output[key][0]

            tokens = tokens[tokens.find(end_token) + len(end_token):].strip()
            if tokens[:6] == r"<sep/>":  # non-leaf nodes
                return [output] + self.token2json(tokens[6:], is_inner_value=True, added_vocab=added_vocab)

    if len(output):
        return [output] if is_inner_value else output
    else:
        return [] if is_inner_value else {"text_sequence": tokens}
```

Comments:
Hi,

Thanks for looking into this and finding the bottleneck. Would you be available to contribute this to the community by opening a PR? Thanks!

> Hi,
> 
> Thanks for looking into this and finding the bottleneck. Would you be available to contribute this to the community by opening a PR? Thanks!

Yes. I just opened the [PR](https://github.com/huggingface/transformers/pull/20283).