1

I have this file named "test.json"

"AFMU repairs": {
  "enabled": true,
  "priority": 3,
  "responder": true,
  "script": "{event.item} \r\n{if event.repairedfully:\r\n    fully repaired\r\n|else:\r\n    partially repaired \r\n    {Occasionally(2, cat(\r\n        OneOf(\"to \", \"at \"),\r\n        Humanise(event.health * 100),\r\n        \" percent functionality\"\r\n    ))}\r\n}\r\n\r\n{Occasionally(2, \r\n    cat(OneOf(\", \", \"and is\"), \" ready for re-activation\")\r\n)}.",
  "default": true,
  "name": "AFMU repairs",
  "description": "Triggered when repairing modules using the Auto Field Maintenance Unit (AFMU)"
},
"Asteroid cracked": {
  "enabled": true,
  "priority": 3,
  "responder": true,
  "script": null,
  "default": true,
  "name": "Asteroid cracked",
  "description": "Triggered when you break up a 'Motherlode' asteroid for mining"
},
"Asteroid prospected": {
  "enabled": true,
  "priority": 3,
  "responder": true,
  "script": "{set minimumPercent to 10} {_ The minimum percentage surface mineral concentration to report _}\r\n{set spokenCores to [\r\n    \"Alexandrite\": false,\r\n    \"Benitoite\": false,\r\n    \"Grandidierite\": false,\r\n    \"Low Temperature Diamonds\": true,\r\n    \"Monazite\": false,\r\n    \"Musgravite\": false,\r\n    \"Rhodplumsite\": false,\r\n    \"Serendibite\": false,\r\n    \"Void Opals\": true,\r\n]}\r\n{set spokenMinerals to [\r\n    \"Bauxite\": false,\r\n    \"Bertrandite\": false,\r\n    \"Bromellite\": false,\r\n    \"Cobalt\": false,\r\n    \"Coltan\": false,\r\n    \"Cryolite\": false,\r\n    \"Gallite\": false,\r\n    \"Gold\": false,\r\n    \"Goslarite\": false,\r\n    \"Hydrogen Peroxide\": false,\r\n    \"Indite\": false,\r\n    \"Jadeite\": false,\r\n    \"Lepidolite\": false,\r\n    \"Lithium Hydroxide\": false,\r\n    \"Liquid oxygen\": false,\r\n    \"Low Temperature Diamonds\": true,\r\n    \"Methane Clathrate\": false,\r\n    \"Methanol Monohydrate\": false,\r\n    \"Moissanite\": false,\r\n    \"Osmium\": false,\r\n    \"Painite\": true,\r\n    \"Platinum\": false,\r\n    \"Palladium\": false,\r\n    \"Praseodymium\": false,\r\n    \"Pyrophyllite\": false,\r\n    \"Rutile\": false,\r\n    \"Samarium\": false,\r\n    \"Silver\": false,\r\n    \"Taaffeite\": false,\r\n    \"Thorium\": false,\r\n    \"Tritium\": true,\r\n    \"Uraninite\": false,\r\n    \"Water\": false,\r\n]}\r\n\r\n{if len(event.motherlode) > 0 && spokenCores[event.motherlode]:\r\n   Motherlode detected: {event.motherlode}.\r\n}\r\n\r\n{set minerals to []}\r\n{for mineral in event.commodities:\r\n    {if mineral.percentage > minimumPercent && spokenMinerals[mineral.commodity]:\r\n        {set mineral_desc to: \r\n            {round(mineral.percentage)} percent {mineral.commodity}\r\n        }\r\n        {set minerals to cat(minerals, [mineral_desc])}\r\n    }\r\n}\r\n{if len(minerals) > 0:\r\n    Asteroid contains {List(minerals)}\r\n    {if event.materialcontent = \"High\":\r\n        and a high concentration of engineering materials\r\n    }.\r\n    {if event.remaining < 100:\r\n        It is {100 - event.remaining} percent depleted.\r\n    }\r\n}",
  "default": true,
  "name": "Asteroid prospected",
  "description": "Triggered when using a prospecting drone"
},

What i want is to "extract" the script string, make it readable, put the script name at the top and save a readable file "test.cottle".

-EDIT-

Below an example of the final result (converted only the first of the three script in test.json):

{_ AFMU repairs }
{event.item} 
{if event.repairedfully:
    fully repaired
|else:
    partially repaired 
    {Occasionally(2, cat(
        OneOf(\"to \", \"at \"),
        Humanise(event.health * 100),
        \" percent functionality\"
    ))}
}

{Occasionally(2, 
    cat(OneOf(\", \", \"and is\"), \" ready for re-activation\")
)}.

{_ Asteroid prospected }
{set minimumPercent to 10} {_ The minimum percentage surface mineral concentration to report _}
... 
... and so on

Here's a working sample of the first expression.

I already have done it in SublimeText using regex search/replace, but i want to make it automatically, so i tried with PowerShell 7, which is basically a totally new thing for me.

This is my test.ps1:

Write-Host "Reading $FileName..."
$content = [System.IO.File]::ReadAllText("C:\test.json")

Write-Host "..replacing..."
$content -replace '(?sm)(.|\n)*?\"script\"\: \"(?<TheScript>.*)\"\,\n.*\n.*\"name\"\: \"(?<TheScriptName>.*)\".*\n' , '\{_ ${TheScriptName} \}\n${TheScript}\n\n'
$content -replace '\\r\\n' , '\n'

Write-Host "...saving."
[System.IO.File]::WriteAllText("C:\test.json.cottle", $content)

Well, the save file is just like the original.

Why does my replace won't work? i tried it at https://regexr.com/ and there's working (altough without named capturing groups). I also added the (?sm) from this question but it does'nt work. What should i do?

0

2 Answers 2

1

You need to use the non-greedy *? instead of the greedy *. Also, the -replace operator does not update the content of the variable, so you need to capture the output of it with something like $content = $content -replace.... Then your output will be a little unexpected since you don't need to escape the replacement text, so '\{_ ${TheScriptName} \}\n${TheScript}\n\n' should simply be '{_ ${TheScriptName} }\n${TheScript}\n\n', and the special characters won't translate so you have to use "`n" instead of \n. The next line can be updated to '(\\r)?\\n', "`n". Finally you'll almost get your expected output.

Write-Host "Reading $FileName..."
$content = [System.IO.File]::ReadAllText("C:\test.json")

Write-Host "..replacing..."
$content = $content -replace '(?sm)(.|\n)*?\"script\"\: \"(?<TheScript>.*?)\"\,\n.*?\n.*?\"name\"\: \"(?<TheScriptName>.*?)\".*?\n' , '{_ ${TheScriptName} }\n${TheScript}\n\n'
$content = $content -replace '(\\r)?\\n' , "`n"

Write-Host "...saving."
[System.IO.File]::WriteAllText("C:\test.json.cottle", $content)

I did say "almost" since that includes some trailing data you don't expect due to how you replace things:

  "description": "Triggered when using a prospecting drone"
}

Since you parse this as a multi-line string you have to use the non-greedy or else you end up consuming all remaining data on the last .*. To get around that we'll have to split the file into individual records, and then parse each one individually. Totally doable, it just takes a little more doing. Here's what I would do if it were me:

Write-Host "Reading $FileName..."
$content = Get-Content $FileName -Raw

Write-Host "..replacing..."
$content = $content -split '(?sm)(?<=},)'|
    Where-Object{$_ -match '(?sm)(.|\n)*?\"script\"\: \"(?<TheScript>.*?)\"\,\n.*?\n.*?\"name\"\: \"(?<TheScriptName>.*?)\"'} |
    ForEach-Object{"`{_ $($Matches['TheScriptName']) }`n$($Matches['TheScript'])`n`n" -replace '(\\r)?\\n', "`n"}

Write-Host "...saving."
Set-Content -Value $content -Path "$filename.cottle"
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much. Your explanation made me see all the mistakes i've made, so i learnt something useful.
1

Don't try to parse JSON with regular expressions. Independently of the fact that you're in for a bag of hurt, JSON is too complex to be handled properly with regex.

Use a parser.

PowerShell's JSON parser is called ConvertFrom-JSON.

Using a helper function from an earlier answer of mine (Iterating through a JSON file PowerShell)

# https://stackoverflow.com/a/33521853/18771
function Get-ObjectMember {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
        [PSCustomObject]$obj
    )
    $obj | Get-Member -MemberType NoteProperty | ForEach-Object {
        $key = $_.Name
        [PSCustomObject]@{Key = $key; Value = $obj."$key"}
    }
}

Things become easy (I'm assuming $data.events here, use the real path to your events list):

$data = Get-Content "test.json" -Raw -Encoding UTF8 | ConvertFrom-Json

$data.events | Get-ObjectMember | ForEach-Object {
    # $_.key will be "AFMU repairs", $_.value will be the associated object, etc.
    "{_ $($_.key) }"
    $_.value.script
    ""
}

Outputs

{_ AFMU repairs }
{event.item} 
{if event.repairedfully:
    fully repaired
|else:
    partially repaired 
    {Occasionally(2, cat(
        OneOf("to ", "at "),
        Humanise(event.health * 100),
        " percent functionality"
    ))}
}

{Occasionally(2, 
    cat(OneOf(", ", "and is"), " ready for re-activation")
)}.

{_ Asteroid cracked }

{_ Asteroid prospected }
{set minimumPercent to 10} {_ The minimum percentage surface mineral concentration to report _}
{set spokenCores to [
    "Alexandrite": false,
    "Benitoite": false,
    "Grandidierite": false,
    "Low Temperature Diamonds": true,
    "Monazite": false,
    "Musgravite": false,
    "Rhodplumsite": false,
    "Serendibite": false,
    "Void Opals": true,
]}
{set spokenMinerals to [
    "Bauxite": false,
    "Bertrandite": false,
    "Bromellite": false,
    "Cobalt": false,
    "Coltan": false,
    "Cryolite": false,
    "Gallite": false,
    "Gold": false,
    "Goslarite": false,
    "Hydrogen Peroxide": false,
    "Indite": false,
    "Jadeite": false,
    "Lepidolite": false,
    "Lithium Hydroxide": false,
    "Liquid oxygen": false,
    "Low Temperature Diamonds": true,
    "Methane Clathrate": false,
    "Methanol Monohydrate": false,
    "Moissanite": false,
    "Osmium": false,
    "Painite": true,
    "Platinum": false,
    "Palladium": false,
    "Praseodymium": false,
    "Pyrophyllite": false,
    "Rutile": false,
    "Samarium": false,
    "Silver": false,
    "Taaffeite": false,
    "Thorium": false,
    "Tritium": true,
    "Uraninite": false,
    "Water": false,
]}

{if len(event.motherlode) > 0 && spokenCores[event.motherlode]:
   Motherlode detected: {event.motherlode}.
}

{set minerals to []}
{for mineral in event.commodities:
    {if mineral.percentage > minimumPercent && spokenMinerals[mineral.commodity]:
        {set mineral_desc to: 
            {round(mineral.percentage)} percent {mineral.commodity}
        }
        {set minerals to cat(minerals, [mineral_desc])}
    }
}
{if len(minerals) > 0:
    Asteroid contains {List(minerals)}
    {if event.materialcontent = "High":
        and a high concentration of engineering materials
    }.
    {if event.remaining < 100:
        It is {100 - event.remaining} percent depleted.

You can pipe this directly into a file with | Set-Content test.cottle -Encoding UTF8

1 Comment

Thank you so much! You solved my problem and made me learn something too! I'll give the answer to @TheMadTechnician 'cause he actually answered the question i posed, but in the end i'll use your solution. Thanks again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.