TreeStructInfo tester
TreeStructInfo → JSON, PHP, XML lub YAML w jednej chwili!
Potrzebujesz szybko sparsować plik w formacie TreeStructInfo i przekształcić go w inną czytelną strukturę? Skorzystaj z mojej lekkiej aplikacji do błyskawicznego przetwarzania tego formatu.
Wczytaj plik .tsinfo
lub wklej treść
Automatyczny parser formatu TreeStructInfo 2.0
Konwersja do JSON, PHP, XML lub YAML
Obsługa atrybutów, węzłów, typów i referencji
Gotowy kod w jednej z wybranych struktur – do użycia od ręki
Idealne narzędzie dla programistów pracujących z tym formatem lub integrujących dane z zewnętrznych źródeł.
Sprawdź, jak prosto można przejść z pliku tekstowego do innego gotowego formatu!
- w pliku konfiguracyjnym musi być zachowana odpowiednia kolejność definiowania danych
- atrybuty i węzły referencjonowane muszą być zdefiniowane po całym drzewie z danymi czyli po
end tree
- atrybuty referencjonowane muszą być zdefiniowane przed węzłami referencjonowanymi
- węzły referencjonowane występujące w innych węzłach referencjonowanych muszą być zdefiniowane przed nimi
- przy włączonym automatycznym rzutowaniu typów separatorem miejsc dziesiętnych musi być kropka
- maksymalny oczekiwany czas wykonania wynosi 10ms (do celów wizualizacji paskiem postępu i jego kolorami)
- dla parsera wcięcia i ich rozmiar nie mają znaczenia
Ta aplikacja służy wyłącznie do testowania parsera TreeStructInfo. Parser może konwertować dane dając nieprawidłowe wyniki, zwłaszcza przy ustawionym automatycznum rzutowaniu typów albo jednego znaku łączenia stringów który jest jeden dla wszystkich danych.
Strona formatu TreeStructInfo:
https://tsinfo.4programmers.net/pl/index.htm
Kody źródłowe parsera użyte w tej aplikacji:
http://www.dariuszrorat.ugu.pl/blog/wpis/96-biblioteka-treestructinfo-php
http://www.dariuszrorat.ugu.pl/blog/wpis/98-biblioteka-treestructinfo-javascript
Dokumentacja parsera:
http://www.dariuszrorat.ugu.pl/dokumenty/14-dokumentacja-treestructinfoparser-php
http://www.dariuszrorat.ugu.pl/dokumenty/16-dokumentacja-treestructinfoparserjs
Podświetlenie składni użyte w tej aplikacji:
http://www.dariuszrorat.ugu.pl/blog/wpis/97-podswietlenie-skladni-formatu-treestructinfo-codemirror-i-highlightjs
Przykład do testowania
:: Example TreeStructInfo 2.0
treestructinfo "2.0" name "Test Sample Tree"
:: Various root tree attributes
attr StringAttr "Example String Attribute"
attr BoolAttr "true"
attr IntBoolAttr "1"
attr IntAttr "42"
attr FloatAttr "3.1415"
attr EngineeringAttr "1.23E+03"
attr BinaryDataAttr "54726565537472756374496E666F202D"
attr Base64DataAttr "U29tZSBiYXNlNjQgZGF0YQ=="
attr MultilineDataAttr "First Line"
"Second Line"
node Coords
attr DecCoords "100,120"
attr HexCoords "0xA0,0x80"
attr OctCoords "0o200,0o100"
attr BinCoords "0b10110011,0b11001101"
end node
node Meta
attr Author "John Doe"
ref attr SharedNote
ref node ParentRefNode
end node
node Items
node ItemA
attr Price "12,50 zł"
attr Available "false"
end node
node ItemB
attr Price "20,00 zł"
attr Available "true"
end node
end node
end tree
:: Ref attributes and ref nodes must be defined after end tree
:: Ref attrubites must be defined before ref nodes
ref attr SharedNote "This is a shared note used across the tree."
:: Child ref node must by defined before parent ref node
ref node ChildRefNode
attr Name "This Is Child Ref Node"
node ChildNode
attr ExampleValue "123456"
end node
end ref node
ref node ParentRefNode
attr Name "This Is Parent Ref Node"
node Child
attr ChildExampleAttr "This is child node attr"
ref attr SharedNote
end node
ref node ChildRefNode
end ref node
Wynik
{{ result }}
Kod po stronie przeglądarki
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.7/codemirror.min.css">
<link rel="stylesheet" href="http://www.dariuszrorat.ugu.pl/assets/css/codemirror/theme/dark.css">
<link rel="stylesheet" href="http://www.dariuszrorat.ugu.pl/assets/css/codemirror/themes.css">
<div id="app">
<div class="row mb-3">
<div class="col-12">
<label for="tsinfo" class="form-label">Tekst TreeStructInfo</label>
<textarea id="tsinfo" class="form-control"></textarea>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<label for="engine" class="form-label">Silnik parsowania</label>
<select id="engine" class="form-select" v-model="engine" @change="engineChange">
<option value="php">PHP</option>
<option value="javascript">JavaScript</option>
</select>
</div>
<div class="col-md-4">
<label for="format" class="form-label">Format wyjściowy</label>
<select id="format" class="form-select" v-model="format" :disabled="engine === 'javascript'">
<option value="json">JSON</option>
<option value="php">PHP</option>
<option value="xml">XML</option>
<option value="yaml">YAML</option>
</select>
</div>
<div class="col-md-4">
<label for="joinChar" class="form-label">Łączenie stringów</label>
<select id="joinChar" class="form-select" v-model="joinChar">
<option value="empty">bez spacji</option>
<option value="space">spacja</option>
<option value="eol">nowa linia</option>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<div class="form-check form-switch">
<input type="checkbox" id="casting" name="casting" class="form-check-input" v-model="casting" true-value="on" false-value="off">
<label class="form-check-label" for="casting">automatyczne rzutowanie typów</label>
</div>
</div>
<div class="col-12">
<div class="form-check form-switch">
<input type="checkbox" id="val01asBool" name="val01asBool" class="form-check-input" v-model="val01asBool" true-value="on" false-value="off">
<label class="form-check-label" for="val01asBool">wartości [0, 1] jako boolean</label>
</div>
</div>
<div class="col-12">
<div class="form-check form-switch">
<input type="checkbox" id="rawdata" name="rawdata" class="form-check-input" v-model="rawdata" true-value="on" false-value="off">
<label class="form-check-label" for="rawdata">tylko surowe dane bez nazwy i wersji formatu</label>
</div>
</div>
</div>
<div class="mb-3">
<button class="btn btn-primary" @click="convert"><i class="bi bi-play-fill"></i> Wykonaj</button>
</div>
<div class="mb-3" v-if="success">
<h2>Wynik</h2>
<pre><code :class="'language-'+resultLng" id="output">{{ result }}</code></pre>
</div>
<div class="progress mb-3" v-if="success">
<div class="progress-bar" role="progressbar" :style="'width: '+ percent + '%'" :class="bgColor">
{{time}} ms
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.7/codemirror.min.js"></script>
<script src="http://www.dariuszrorat.ugu.pl/assets/js/codemirror/mode/tsinfo/tsinfo.js"></script>
<script src="http://www.dariuszrorat.ugu.pl/assets/js/highlightjs/languages/tsinfo.js" defer></script>
<script src="http://www.dariuszrorat.ugu.pl/assets/js/tsinfo/parser.js"></script>
<script>
// Max 10ms expected time
const MAX_EXPECTED_TIME = 10;
function escapeHTML(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
var vm = null;
document.addEventListener('DOMContentLoaded', function() {
vm = new Vue({
el: '#app',
data: {
tsinfo: '',
engine: 'php',
format: 'json',
joinChar: 'empty',
casting: 'on',
val01asBool: 'off',
rawdata: 'off',
result: '',
resultLng: 'json',
time: 0,
bgColor: 'text-bg-primary',
percent: 0,
success: false,
parser: null
},
mounted() {
var self = this;
this.$nextTick(function () {
self.parser = new TreeStructInfoParserJS();
});
},
updated() {
this.$nextTick(function () {
const el = document.getElementById('output');
if (el)
{
if (el.hasAttribute('data-highlighted'))
el.removeAttribute('data-highlighted');
if (el.hasAttribute('data-highlighter'))
el.removeAttribute('data-highlighter');
if (el.classList.contains('hljs'))
el.classList.remove(...Array.from(el.classList).filter(cls => cls.startsWith('hljs')));
el.innerHTML = escapeHTML(el.textContent);
hljs.highlightElement(el);
}
});
},
methods: {
engineChange() {
var self = this;
if (self.engine === 'javascript')
{
self.format = 'json';
}
},
convert() {
var self = this;
self.result = '';
self.success = false;
var engine = self.engine;
if (engine === 'javascript')
{
self.parser.setAutoCasting(self.casting === 'on');
switch (self.joinChar)
{
case 'empty': self.parser.setMultilineJoinChar(''); break;
case 'space': self.parser.setMultilineJoinChar(' '); break;
case 'eol': self.parser.setMultilineJoinChar('\n'); break;
}
let valuesTrue = ['true', 'yes', 'on', 't', 'y'];
let valuesFalse = ['false', 'no', 'off', 'f', 'n'];
if (self.val01asBool === 'on')
{
valuesTrue.push('1');
valuesFalse.push('0');
}
self.parser.setBoolValues(valuesTrue, valuesFalse);
setTimeout(function() {
const start = performance.now();
try
{
const result = self.parser.parse(self.tsinfo);
self.result = self.rawdata === 'on' ? result.data : result;
self.success = true;
self.resultLng = self.format;
}
catch (error)
{
self.success = false;
BootstrapToast.show({title: 'Błąd', message: 'Przepraszamy, ale wystąpił błąd parsowania składni: ' + error.message, when: 'teraz', type: 'text-bg-danger'});
}
const duration = performance.now() - start;
self.time = duration.toFixed(2);
self.percent = Math.min((duration / MAX_EXPECTED_TIME) * 100, 100).toFixed(0);
if (self.percent <= 25) self.bgColor = 'text-bg-success';
else if ((self.percent > 25) && (self.percent <= 50)) self.bgColor = 'text-bg-info';
else if ((self.percent > 50) && (self.percent <= 75)) self.bgColor = 'text-bg-warning';
else self.bgColor = 'text-bg-danger';
}, 0);
return;
}
var data = {
tsinfo: self.tsinfo,
format: self.format,
joinChar: self.joinChar,
casting: self.casting,
val01asBool: self.val01asBool,
rawdata: self.rawdata,
token: 'db40003a0be62f1a17782f05fb582d15245bbd58'
};
axios.post(urlSite('public/ajax/app/exec/28'), data, {headers: {'Content-Type': 'application/x-www-form-urlencoded'}})
.then(function (response) {
self.result = response.data.data;
const duration = response.data.time;
self.time = duration.toFixed(2);
self.percent = Math.min((duration / MAX_EXPECTED_TIME) * 100, 100).toFixed(0);
if (self.percent <= 25) self.bgColor = 'text-bg-success';
else if ((self.percent > 25) && (self.percent <= 50)) self.bgColor = 'text-bg-info';
else if ((self.percent > 50) && (self.percent <= 75)) self.bgColor = 'text-bg-warning';
else self.bgColor = 'text-bg-danger';
self.success = true;
self.resultLng = self.format;
}).catch(function(error) {
self.success = false;
BootstrapToast.show({title: 'Błąd', message: error.response.data.message, when: 'teraz', type: 'text-bg-danger'});
});
}
}
});
//CodeMirror for textarea
var myTextarea = document.getElementById('tsinfo');
var editor = CodeMirror.fromTextArea(myTextarea, {
lineNumbers: true,
mode: "treestructinfo",
indentUnit: 4,
indentWithTabs: false,
enterMode: "keep",
tabMode: "shift",
extraKeys: {Tab: false, "Shift-Tab": false}
});
editor.setSize(null, 500);
editor.getWrapperElement().style["font-size"] = "14px";
editor.refresh();
editor.on('change', (args) => { vm.tsinfo = editor.getValue() } );
//WCAG missing form label fix
codemirrorTextareaIdFix("tsinfo");
});
</script>
Kod po stronie serwera
$tsinfo = Input::post('tsinfo', '');
$format = Input::post('format', 'php');
$join_char = Input::post('joinChar', 'empty');
$casting = Input::post('casting', 'off') === 'on';
$val01_as_bool = Input::post('val01asBool', 'off') === 'on';
$rawdata = Input::post('rawdata', 'off') === 'on';
$values_true = ['true', 'yes', 'on', 't', 'y'];
$values_false = ['false', 'no', 'off', 'f', 'n'];
switch ($join_char)
{
case 'space': $join = ' '; break;
case 'eol': $join = PHP_EOL; break;
default: $join = ''; break;
}
if ($val01_as_bool)
{
$values_true[] = '1';
$values_false[] = '0';
}
$timediff = 0;
$start = microtime(true);
try
{
$array = Format::from_tsinfo($tsinfo, $casting, $join, [$values_true, $values_false]);
$result = $rawdata ? Arr::get($array, 'data', []) : $array;
}
catch (Exception $e)
{
Kohana_Exception::log($e);
throw HTTP_Exception::factory(500, json_encode(array('code' => 500, 'message' => 'Przepraszamy, ale wystąpił błąd parsowania składni: ' . $e->getMessage(), 'errors' => [])))
->as_json();
}
switch ($format)
{
case 'php': $dump = var_export($result, true); break;
case 'yaml': $dump = Format::to_yaml($result); break;
case 'xml':
$xml = Format::to_xml($result);
//dump as pretty XML
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = true;
$dom->formatOutput = true;
$dom->loadXML($xml);
$dump = $dom->saveXML();
break;
default: $dump = $result; break;
}
$end = microtime(true);
$timediff = ($end - $start) * 1000;
$output = [
'data' => $dump,
'time' => $timediff,
];
$response = Response::factory()
->headers('Content-Type', 'application/json')
->status(200)
->body(json_encode($output));
echo $response->send_headers()->body();