Описание


Инструмент предназначен для создания файлов со статической информацией из базы данных, которая:
PGHtml реализован как служба, которая создает HTML,JS,JSON и другие типы файлы с использованием данных, получаемых из базы данных PostgreSQL.
Файл создается из файла шаблона, в котором проводится подстановка(замена) SQL-запросов, тегов и переменных.
Шаблон имееет префикс расширения pg, т.е. на основе файла index.pghtml будет создан index.html

Подстановки



$$ [SQL query] $$результат выполнения SQL запроса
<pghtml-include> [file] <>содержимое файла
${[variable]}значение переменной


Пример замены


<!-- единичное значение из базы данных -->
$$
select user
$$

<!-- множественые значения -->
$$
select tablename from pg_tables
  where schemaname='pg_catalog' limit 5
$$

<!-- включение файла example_include_ru.html -->
<pghtml-include>include-simple-ru.html</pghtml-include>



<!-- единичное значение из базы данных -->
postgres

<!-- множественые значения -->
pg_statistic
pg_type
pg_foreign_server
pg_authid
pg_statistic_ext_data

<!-- включение файла example_include_ru.html -->
[ файл example-header-ru.html ]


Пример шаблона


Шаблон

<!DOCTYPE html>
<head>
<title>${title}</title>
</head>

<body>
<h2>${caption}</h2>
${text}
</body>

</html>

Исходная и результирующая страницы

<pghtml-var name="title">Example</pghtml-var>
<pghtml-var name="caption">Multiline text</pghtml-var>
<pghtml-var name="text">
Text line 1
<br>
<i>Text line 2</i>
</pghtml-var>
<pghtml-include>/template/template-common.html</pghtml-include>





<!DOCTYPE html>
<head>
<title>Example</title>
</head>

<body>
<h2>Multiline text</h2>

Text line 1
<br>
<i>Text line 2</i>

</body>

</html>


Ключевые особенности



Полный пример


Вызов из командной строки



Преобразование файла example.pghtml в example.html

<!DOCTYPE html>
<html>

<head>

<!-- inclusion of styles for acceleration -->
<style type="text/css">
<pghtml-include>/css/common.css</pghtml-include>
</style>

<!-- inclusion of title with two variables "G_SITE" and "description" -->
<pghtml-include description="Example">/include/title.html</pghtml-include>

</head>
<body>


<!-- single value from database --> 
<p>Database uptime</p>

$$
select now() - pg_postmaster_start_time()
$$

<br><br>


<!-- dataset --> 
<p>Top 10 indexes by size, table</p>

<table>

<tr><th>Schema</th><th>Index</th><th>Size</th></tr>

$$
select '<tr><td>'||schemaname||'</td><td>'||indexname||'</td><td class="td-number">'||size||'</td></tr>' from (
  select schemaname,indexname,pg_total_relation_size((schemaname||'.'||tablename)::regclass) size from pg_indexes order by 3 desc limit 10
) t 
$$

</table>

<br><br>

<!-- dataset from json --> 
<p>Top 10 indexes by scans, json</p>

<div id="top_indexes_scans" style="width: 600px; height: 200px">

<button onclick="loadFile('top_indexes_scans', 'data/top_indexes_scans.json')">
Load file 'data/top_indexes_scans.json'
</button>


</div>


<script>

function loadFile(element_id, filename) {
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
      if (xhr.status == 200) {
        document.getElementById(element_id).innerHTML = "<pre>"+xhr.responseText+"</pre>";
        document.getElementById(element_id).style.border = "1px solid black";
      } else {
        document.getElementById(element_id).innerHTML =
          "Page must be opened via web server,<br>view file '"+filename+"' manually";
      }
    }
  }
  xhr.open('GET', filename, true);
  xhr.send();
}

</script>

<br><br>

<!-- variables --> 
<p>Variables</p>

<table>

<tr><th>Name</th><th>Value</th></tr>

<tr> <td>${directory_root}</td>  <td>${directory_root}</td>  </tr>
<tr> <td>${directory}</td>       <td>${directory}</td>       </tr>
<tr> <td>${filename}</td>        <td>${filename}</td>        </tr>
<tr> <td>${filepath_source}</td> <td>${filepath_source}</td> </tr>
<tr> <td>${filepath_dest}</td>   <td>${filepath_dest}</td>   </tr>
<tr> <td>${G_SITE}</td>          <td>${G_SITE}</td>          </tr>

</table>

<br><br>


<pghtml-include>include/footer.html</pghtml-include>

</body>
</html>







<!DOCTYPE html>
<html>

<head>

<!-- inclusion of styles for acceleration -->
<style type="text/css">
html { font: 12px Verdana, Arial, Helvetica, sans-serif; }

p { font-size: 16px; font-weight: bold; }

table { border-collapse: collapse; }

td, th { padding: 3px; border: 1px solid black; }

th { background: silver; }

.td-number { text-align: right; }
</style>

<!-- inclusion of title with two variables "G_SITE" and "description" -->
<title>Example</title>
                     

</head>
<body>


<!-- single value from database --> 
<p>Database uptime</p>

00:01:03.749699

<br><br>


<!-- dataset --> 
<p>Top 10 indexes by size, table</p>

<table>

<tr><th>Schema</th><th>Index</th><th>Size</th></tr>

<tr><td>pg_catalog</td><td>pg_depend_reference_index</td><td class="td-number">1130496</td></tr>
<tr><td>pg_catalog</td><td>pg_depend_depender_index</td><td class="td-number">1130496</td></tr>
<tr><td>pg_catalog</td><td>pg_proc_proname_args_nsp_index</td><td class="td-number">1015808</td></tr>
<tr><td>pg_catalog</td><td>pg_proc_oid_index</td><td class="td-number">1015808</td></tr>
<tr><td>pg_catalog</td><td>pg_attribute_relid_attnum_index</td><td class="td-number">688128</td></tr>
<tr><td>pg_catalog</td><td>pg_attribute_relid_attnam_index</td><td class="td-number">688128</td></tr>
<tr><td>pg_catalog</td><td>pg_rewrite_oid_index</td><td class="td-number">679936</td></tr>
<tr><td>pg_catalog</td><td>pg_rewrite_rel_rulename_index</td><td class="td-number">679936</td></tr>
<tr><td>pg_catalog</td><td>pg_collation_name_enc_nsp_index</td><td class="td-number">581632</td></tr>
<tr><td>pg_catalog</td><td>pg_collation_oid_index</td><td class="td-number">581632</td></tr>

</table>

<br><br>

<!-- dataset from json --> 
<p>Top 10 indexes by scans, json</p>

<div id="top_indexes_scans" style="width: 600px; height: 200px">

<button onclick="loadFile('top_indexes_scans', 'data/top_indexes_scans.json')">
Load file 'data/top_indexes_scans.json'
</button>


</div>


<script>

function loadFile(element_id, filename) {
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
      if (xhr.status == 200) {
        document.getElementById(element_id).innerHTML = "<pre>"+xhr.responseText+"</pre>";
        document.getElementById(element_id).style.border = "1px solid black";
      } else {
        document.getElementById(element_id).innerHTML =
          "Page must be opened via web server,<br>view file '"+filename+"' manually";
      }
    }
  }
  xhr.open('GET', filename, true);
  xhr.send();
}

</script>

<br><br>

<!-- variables --> 
<p>Variables</p>

<table>

<tr><th>Name</th><th>Value</th></tr>

<tr> <td>${directory_root}</td>  <td>${directory_root}</td>  </tr>
<tr> <td>${directory}</td>       <td></td>       </tr>
<tr> <td>${filename}</td>        <td>example-full.pghtml</td>        </tr>
<tr> <td>${filepath_source}</td> <td>/tmp/build/example/example-full.pghtml</td> </tr>
<tr> <td>${filepath_dest}</td>   <td>/tmp/build/example/example-full.html</td>   </tr>
<tr> <td>${G_SITE}</td>          <td>${G_SITE}</td>          </tr>

</table>

<br><br>


-----
<br>
<span style="font-size: 75%">
info updated at 2025-12-09 12:13:39.25283-05
</span>


</body>
</html>


Преобразование файла top_indexes_scans.pgjson в top_indexes_scans.json




[{"schema":"pg_catalog","index":"pg_class_oid_index","scans":103}, 
 {"schema":"pg_catalog","index":"pg_attribute_relid_attnum_index","scans":99}, 
 {"schema":"pg_catalog","index":"pg_index_indexrelid_index","scans":63}, 
 {"schema":"pg_catalog","index":"pg_db_role_setting_databaseid_rol_index","scans":32}, 
 {"schema":"pg_catalog","index":"pg_database_oid_index","scans":19}, 
 {"schema":"pg_catalog","index":"pg_database_datname_index","scans":11}, 
 {"schema":"pg_catalog","index":"pg_opclass_oid_index","scans":7}, 
 {"schema":"pg_catalog","index":"pg_amproc_fam_proc_index","scans":7}, 
 {"schema":"pg_catalog","index":"pg_authid_oid_index","scans":3}, 
 {"schema":"pg_catalog","index":"pg_authid_rolname_index","scans":3}]


Файлы примера находятся вместе с исходным кодом в общем архиве, который можно загрузить со страницы Загрузка



Видео


Обзорное видео размещено на YouTube