# Integration Toolkit Solutions Wiki

**URL:** <https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080>\
**Category:** 3rd Party Modules\
**Tags:** ignition81, scripting\
**Created:** [April 14, 2025, 9:02pm UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080 "2025-04-14T21:02:36Z")\
**Posts on this page:** 1\
**Showing post:** 17

<div class="post-metadata">

**Author:** ![hunterdg](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/hunterdg/32/67774_2.png) [@hunterdg](https://forum.inductiveautomation.com/u/hunterdg)\
**Post date:** [April 28, 2025, 9:19am UTC](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080/17 "2025-04-28T09:19:58Z")

</div>

## parsePath().toString() equivalent? 😬

How do i

> [@Automation Professionals' Integration Toolkit Module](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/153):
>
> produce proper historical paths

from `parsePath()`?

Forgive my lack of familiarity if there’s an easy way - I try to avoid datasets unless absolutely necessary. (e.g. Table `props.columns` derivation).

Perhaps a "ds vs object" blurb might be useful if my ignorance is showing.

> **here's my uuugly method (first try)**
>
> ```json
> {
> "type": "expr",
> "config": {
> "expression": "parsePath(\r\n\tconcat(\r\n\t\t{view.custom.node.meta_node.Tag_Path}, // concat(\u0027[\u0027, {session.custom.project.tag.provider}, \u0027]\u0027), \u0027\u0027),\r\n\t\t\"/Analog Tags/\",\r\n\t\tif(\r\n\t\t\t{view.custom.Type_ID.meta} \u003d \u0027Wl\u0027,\r\n\t\t\t\u0027TEMPERATURE\u0027,\r\n\t\t\t\u0027METER_TEMP\u0027\r\n\t\t)\r\n\t)\r\n)"
> },
> "transforms": [
> {
> "expression": "flatten(forEach(\r\n\t{value},\r\n\tasPairs(it())\r\n))",
> "type": "expression"
> },
> {
> "expression": "where(\r\n\t{value},\r\n\t!isNull(it()[1])\r\n)",
> "type": "expression"
> },
> {
> "expression": "forEach(\r\n\t{value},\r\n\tlower(groupConcat(it(), \u0027:\u0027))\r\n)",
> "type": "expression"
> },
> {
> "expression": "groupConcat({value}, \u0027:/\u0027)",
> "type": "expression"
> }
> ]
> }
> 
> ```

TL/DR: Use `parsePath()` in conjunction with `tags()` reading the storage provider property of the tags. Tags will return a fully de-relativized path, that can then be combined with the value returned from `tags` and a separately provided system name to construct the historical path.

> **Expression**
>
> ```python
> transform(
> {path.to.gateway.name},
> forEach(
> tags(
> forEach(
> {path.to.list.of.tag.paths},
> concat(it(),'.historyProvider')
> )
> ),
> transform(
> parsePath(it()[0]),
> concat(
> 'histprov:',it()[1], //gets value returned from tags.
> ':/drv:',coalesce(value()['drv'],value(1)), //if tags is supplied a path with a driver then use that, otherwise use the provided value
> ':',value()['prov'],
> ':/tag:',value()['tag']
> )
> )
> )	
> )
> 
> ```

To produce a proper Historical Path given a simple tag path, you need to know a few things.

1. The Historical Provider that is configured for the given tag path.
2. The name of the gateway the Historical Provider is on. (If a single gateway system, then it's just the gateway name. For remote providers you need the gateway name and the tag provider on the remote gateway.

A historical tag path looks like this:

```python
histprov:hist_prov_name:/drv:gateway_name:tag_provider_name:/tag:tag_path

```

This then of course begs the question of how you can dynamically retrieve that information.

In a single gateway system it's pretty simple. There is a System Tag `SystemName` that will give you the gateway name, and you can actually read the `historyProvider` property from the tag, given the tag path. So the first is a simple tag binding, and the second is a simple Indirect Tag Binding.

For a multiple gateway system it is not that simple. For reference see: [Remote Tag Provider History Queries](https://forum.inductiveautomation.com/t/remote-tag-provider-history-queries/93632)

Once you have those pieces of information then it's just a matter of parsing the given tag path into it's parts and reconstructing them into the historical tag path.

So, given the following path `[default]_Simulator_/Ramp/Ramp0`, in a single gateway system where the System Name is "Ignition\_Gateway", and the historical provider is "History\_Provider". This expression will return the correct historical tag path.

> **Expression for Single Tag Path**
>
> ```python
> transform(
> parsePath({this.custom.tagPath}),
> lower(
> concat(
> 'histprov:', coalesce(value()['histprov'],{this.custom.histProv}),':/',
> 'drv:',coalesce(value()['drv'],{this.custom.gatewayName}),':',coalesce(value()['prov'],'default'),':/',
> 'tag:',value()['tag']
> )
> )
> )
> 
> ```
> 
> NOTE: I don't believe that paths are case sensitive, but if you want this to match the output from say the Power Chart, then the `lower()` does that.

> **Output**
>
> ```python
> histprov:history_provider:/drv:ignition_gateway:default:/tag:_Simulator_/Ramp/Ramp0
> 
> ```

If you provide `parsePath` with multiple strings, then it returns a row for each string. Modifying the above expression with `forEach` will allow you to return a list of multiple historical tag paths.

> **Expression for Multiple Tag Paths**
>
> ```python
> transform(
> parsePath({this.custom.tagPath},{this.custom.tagPath1}),
> forEach(
> value(),
> lower(
> concat(
> 'histprov:', coalesce(it()['histprov'],{this.custom.histProv}),':/',
> 'drv:',coalesce(it()['drv'],{this.custom.gatewayName}),':',coalesce(it()['prov'],'default'),':/',
> 'tag:',it()['tag']
> )
> )
> )
> )
> 
> ```

> **Features employed**
>
> - [Iterables](https://www.automation-pros.com/toolkit/doc/iteration.html#iterables) General behavior of all iterators.
> - [transform](https://www.automation-pros.com/toolkit/doc/utility.html#transform): Makes a snapshot of the dataset returned from parsePath.
> - [parsePath](https://www.automation-pros.com/toolkit/doc/datasets.html#parsepath "parsePath()"): returns `QualifiedPath` parts as a dataset
> - see also [QualifiedPathUtils](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.47/com/inductiveautomation/ignition/common/QualifiedPathUtils.html#parsePossibleTagpathNew(java.lang.String)), [QualifiedPath](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.47/com/inductiveautomation/ignition/common/QualifiedPath.html#parse(java.lang.String))
> 
> - [coalesce](https://www.docs.inductiveautomation.com/docs/8.1/appendix/expression-functions/logic/coalesce): returns the first non-null argument
> - [concat](https://www.docs.inductiveautomation.com/docs/8.1/appendix/expression-functions/string/concat): returns the concatenation of all provided arguments
> - [lower](https://www.docs.inductiveautomation.com/docs/8.1/appendix/expression-functions/string/lower): returns the provided string in all lower case
> - [forEach](https://www.automation-pros.com/toolkit/doc/iteration.html#foreach): loops through each row in the dataset.
> - [it](https://www.automation-pros.com/toolkit/doc/iteration.html#it): Delivers the row from this iteration through the dataset.

@lrose - surely the intent of parsePath() was to make this process purely dynamic (as opposed to hardcoding the path part names as your examples do)?

@hunterdg - My examples are dynamic. Given a tag path, this will output a historical tag path. There are a couple of other bindings involved as explained, but it will change dynamically with the path.

The intent of `parsePath` is to break a string path down into its constituent parts. A simple tag path by itself does not contain the needed information to build a corresponding historical tag path. Information must be retrieved and inserted. `parsePath` on its own does not do any extra look up.

@hunterdg I suppose, maybe this is what you're looking for, although, this will not provide a valid historical tag path, unless it is handed a valid historical tag path.

> **Expression**
>
> ```python
> transform(
> parsePath({this.custom.tagPath},{this.custom.tagPath1}),
> forEach(
> value(),
> groupConcat(
> flatten(
> forEach(
> columnsOf(it()), // columns of dataset returned from parsePath
> forEach(
> where(
> it(1), // rows of dataset returned from parsePath
> !isNull(it()[it(1)[0]])
> ), // all non-null values in this row
> concat(it(1)[0],':',it()[it(1)[0]]) // columnName + : + columnValue
> )
> )
> ),
> ':/'
> )
> )
> )
> 
> ```

@lrose - This does indeed work for a _non-historical_ (but PowerChart-compatible) QualifiedPath as you point out, but I’m somewhat disappointed as it seems to be basically the consolidated version of my original method (which is still very much appreciated). I assumed there must be an easier way to concat dataset rows interpolated with their column names. ~~Fairly certain `QualifiedPath.toString()` performs the equivalent and is much more approachable, despite the overhead (and regardless, I believe a scripting context is needed to build a “proper” historical QualifiedPath object anyway).~~

~~EDIT - @pturmel’s original post mentions “(I've been frustrated with the need for scripts to produce proper _ **historical** _ paths from simple tag paths.)”…~~

I now see that `parsePath()` requires passing a _string_ (with `histprov` + `drv`) to return a _historical_ path - guess I missed that critical (and obvious in hindsight) piece - so i might as well just pass the historical paths strings straight to PowerChart. It is still useful to derive _non-historical_ stringified qualified paths from a QualifiedPath object though.

Feel free to cleanup this post. I consider my question “mostly” answered - but I’m still uncertain as to the rationale for `parsePath()` - as it most certainly does not seem to "produce proper historical paths from simple tag paths” as the original post suggests.

@hunterdg `parsePath` is best used when using `tags()` to read the Storage Provider property of tags. If you use parsepath on the returned full tag path, you can use its parts with the returned value, and separately provided system name, to construct the history qualified path from a basic tag path. A big `concat()` operation.

@pturmel 🙏

---

_[View the full topic](https://forum.inductiveautomation.com/t/integration-toolkit-solutions-wiki/105080)._
