8 Commits

8 changed files with 585 additions and 51 deletions
+420
View File
@@ -0,0 +1,420 @@
<?xml version="1.0" encoding="UTF-8"?>
<style xmlns="http://purl.org/net/xbiblio/csl"
class="in-text"
version="1.0"
default-locale="uk-UA"
demote-non-dropping-particle="never">
<info>
<title>ДСТУ 8302:2015 — бібліографічні посилання</title>
<title-short>ДСТУ 8302:2015</title-short>
<id>dstu-8302-2015-typst</id>
<link href="https://online.budstandart.com/ua/catalog/doc-page.html?id_doc=64411" rel="documentation"/>
<author>
<name>Custom Typst CSL</name>
</author>
<category citation-format="numeric"/>
<category field="generic-base"/>
<summary>CSL для оформлення переліку джерел за логікою ДСТУ 8302:2015 у Typst/Pandoc: числові посилання, українські скорочення, DOI/URL, дата звернення, коректні вебджерела, статті та нормативні акти.</summary>
<updated>2026-05-25T00:00:00+00:00</updated>
<rights license="http://creativecommons.org/licenses/by-sa/4.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 License</rights>
</info>
<locale xml:lang="uk-UA">
<terms>
<term name="accessed">дата звернення</term>
<term name="page" form="short">С.</term>
<term name="page" form="symbol">с.</term>
<term name="volume" form="short">Т.</term>
<term name="issue" form="short">№</term>
<term name="edition" form="short">вид.</term>
<term name="editor" form="short">ред.</term>
<term name="translator" form="short">пер.</term>
<term name="et-al">та ін.</term>
<term name="and">,</term>
</terms>
</locale>
<!-- Ініціали після прізвища: Vergara L. M., Park M. -->
<macro name="contributors">
<names variable="author">
<name name-as-sort-order="all"
sort-separator=" "
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"/>
<substitute>
<names variable="editor">
<name name-as-sort-order="all"
sort-separator=" "
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"/>
<label form="short" prefix=" (" suffix=")"/>
</names>
<names variable="translator">
<name name-as-sort-order="all"
sort-separator=" "
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"/>
<label form="short" prefix=" (" suffix=")"/>
</names>
</substitute>
</names>
</macro>
<macro name="title">
<text variable="title"/>
</macro>
<macro name="container">
<text variable="container-title"/>
</macro>
<macro name="site-or-publisher">
<choose>
<if variable="container-title">
<text variable="container-title"/>
</if>
<else>
<text variable="publisher"/>
</else>
</choose>
</macro>
<macro name="issued-year">
<date variable="issued">
<date-part name="year"/>
</date>
</macro>
<macro name="issued-date">
<date variable="issued">
<date-part name="day" form="numeric-leading-zeros" suffix="."/>
<date-part name="month" form="numeric-leading-zeros" suffix="."/>
<date-part name="year"/>
</date>
</macro>
<macro name="accessed-date">
<date variable="accessed">
<date-part name="day" form="numeric-leading-zeros" suffix="."/>
<date-part name="month" form="numeric-leading-zeros" suffix="."/>
<date-part name="year"/>
</date>
</macro>
<macro name="publisher-place">
<group delimiter=" : ">
<text variable="publisher-place"/>
<text variable="publisher"/>
</group>
</macro>
<macro name="edition">
<group delimiter=" ">
<text variable="edition"/>
<label variable="edition" form="short"/>
</group>
</macro>
<macro name="book-pages">
<choose>
<if variable="number-of-pages">
<group delimiter=" ">
<text variable="number-of-pages"/>
<text term="page" form="symbol"/>
</group>
</if>
<else-if variable="page">
<group delimiter=" ">
<text variable="page"/>
<text term="page" form="symbol"/>
</group>
</else-if>
</choose>
</macro>
<macro name="pages">
<choose>
<if variable="page">
<group delimiter=" ">
<text term="page" form="short"/>
<text variable="page"/>
</group>
</if>
</choose>
</macro>
<macro name="volume-issue">
<group delimiter=", ">
<choose>
<if variable="volume">
<group delimiter=" ">
<text value="Т."/>
<text variable="volume"/>
</group>
</if>
</choose>
<choose>
<if variable="issue">
<group delimiter=" ">
<text value="№"/>
<text variable="issue"/>
</group>
</if>
</choose>
</group>
</macro>
<macro name="url-access">
<choose>
<if variable="URL">
<group delimiter=" ">
<text variable="URL" prefix="URL: "/>
<choose>
<if variable="accessed">
<group delimiter=": " prefix="(" suffix=")">
<text term="accessed"/>
<text macro="accessed-date"/>
</group>
</if>
</choose>
</group>
</if>
</choose>
</macro>
<macro name="doi-or-url">
<choose>
<if variable="DOI">
<text variable="DOI" prefix="DOI: "/>
</if>
<else>
<text macro="url-access"/>
</else>
</choose>
</macro>
<macro name="legal-details">
<choose>
<if variable="genre">
<group delimiter=" : ">
<text macro="title"/>
<group delimiter=" ">
<text variable="genre"/>
<choose>
<if variable="issued">
<group delimiter=" ">
<text value="від"/>
<text macro="issued-date"/>
</group>
</if>
</choose>
<choose>
<if variable="number">
<group delimiter=" ">
<text value="№"/>
<text variable="number"/>
</group>
</if>
</choose>
</group>
</group>
</if>
<else>
<group delimiter=". ">
<text macro="title"/>
<group delimiter=" ">
<choose>
<if variable="issued">
<group delimiter=" ">
<text value="від"/>
<text macro="issued-date"/>
</group>
</if>
</choose>
<choose>
<if variable="number">
<group delimiter=" ">
<text value="№"/>
<text variable="number"/>
</group>
</if>
</choose>
</group>
</group>
</else>
</choose>
</macro>
<citation collapse="citation-number">
<sort>
<key variable="citation-number"/>
</sort>
<layout delimiter=", " prefix="[" suffix="]">
<text variable="citation-number"/>
</layout>
</citation>
<bibliography hanging-indent="false" entry-spacing="0" line-spacing="1">
<sort>
<key variable="citation-number"/>
</sort>
<layout>
<group display="block">
<text display="left-margin" variable="citation-number" suffix=". "/>
<choose>
<!-- Нормативні акти: Цивільний кодекс України : Закон України від 16.01.2003 № 435-IV. URL: ... -->
<if type="legislation bill legal_case treaty" match="any">
<group delimiter=". " suffix=".">
<text macro="legal-details"/>
<text macro="url-access"/>
</group>
</if>
<!-- Наукова стаття -->
<else-if type="article-journal">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text macro="container"/>
<text macro="issued-year"/>
<text macro="volume-issue"/>
<text macro="pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Газетна / журнальна стаття без DOI -->
<else-if type="article-newspaper article-magazine" match="any">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text variable="container-title" font-style="italic"/>
<text macro="issued-date"/>
<text macro="pages"/>
<text macro="url-access"/>
</group>
</else-if>
<!-- Вебсторінки: без дублювання назви замість автора -->
<else-if type="webpage post post-weblog" match="any">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text macro="site-or-publisher"/>
<text macro="issued-date"/>
<text macro="url-access"/>
</group>
</else-if>
<!-- Книга -->
<else-if type="book">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text macro="edition"/>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="book-pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Розділ у книзі -->
<else-if type="chapter">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<group delimiter=" ">
<text value="In:"/>
<text variable="container-title" font-style="italic"/>
</group>
<names variable="editor">
<name name-as-sort-order="all"
sort-separator=" "
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"/>
<label form="short" prefix=" (" suffix=")"/>
</names>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Матеріали конференції -->
<else-if type="paper-conference">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<group delimiter=" ">
<text value="In:"/>
<text variable="container-title"/>
</group>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Дисертації, звіти -->
<else-if type="thesis report" match="any">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text variable="genre"/>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="book-pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Патенти -->
<else-if type="patent">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<group delimiter=" ">
<text value="№"/>
<text variable="number"/>
</group>
<text macro="issued-date"/>
<text macro="url-access"/>
</group>
</else-if>
<!-- Універсальний fallback -->
<else>
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text variable="container-title" font-style="italic"/>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="pages"/>
<text macro="doi-or-url"/>
</group>
</else>
</choose>
</group>
</layout>
</bibliography>
</style>
+1 -1
View File
@@ -14,7 +14,7 @@
"грудня",
).at(month - 1)
#let is-cyr(c) = regex("[\p{Cyrillic}]") in c
#let is-cyr(c) = regex("^\p{Cyrillic}") in c
/// type-safe emptiness check
#let is-empty(val) = {
+7 -1
View File
@@ -65,6 +65,7 @@
/// - title (str): Work title
/// - authors (array): List of author dictionaries
/// - mentors (array): List of mentor dictionaries
/// - committee-members (array): Optional list of commission member dictionaries for the title page
/// - task-list (dict): Task metadata
/// - calendar-plan (dict): Calendar plan table
/// - abstract (dict): Keywords and abstract text
@@ -77,6 +78,7 @@
title: none,
authors: (),
mentors: (),
committee-members: none,
task-list: (),
calendar-plan: (),
abstract: (),
@@ -107,12 +109,14 @@
} else {
abstract
}
let committee_members = committee-members
tp.cw-v2.nure(
university,
title,
authors,
mentors,
committee_members,
task-list,
calendar-plan,
abstract,
@@ -125,10 +129,12 @@
doc
{
show regex("^\\d+\\."): it => [#it#h(0.5cm)]
show block: it => [#it.body#parbreak()]
bibliography(bib-path, title: [Перелік джерел посилання], style: "csl/dstu-3008-2015.csl", full: true)
bibliography(bib-path, title: [Перелік джерел посилання], style: "csl/dstu-8302-2015.csl", full: true)
}
style.appendices(appendices)
+74 -2
View File
@@ -49,6 +49,7 @@
caption: none,
columns: none,
header: none,
tag: none,
..args,
) = {
let caption = _required("caption", caption)
@@ -63,7 +64,6 @@
let named = args.named()
let body = args.pos()
context {
let h = counter(heading).get()
let section = if h.len() > 0 { h.at(0) } else { 0 }
@@ -85,6 +85,8 @@
{
set block(spacing: dstu-table-caption-gap)
[#metadata((kind: "dstu-table", number: num)) #if tag != none { label(tag) }]
block(sticky: true)[
#dstu-table-label[Таблиця #num -- #caption]
]
@@ -156,7 +158,10 @@
// Text and paragraph
set text(lang: "uk", size: 14pt, hyphenate: false, font: ("Times New Roman", "Liberation Serif"))
set par(justify: true, spacing: spacing, leading: spacing, first-line-indent: (amount: indent-size, all: true))
set par(justify: true, spacing: spacing, leading: spacing, first-line-indent: (
amount: indent-size,
all: true,
))
set block(spacing: spacing)
set underline(evade: false)
@@ -169,6 +174,63 @@
set list(indent: indent-size + 0.1cm, body-indent: 0.5cm, marker: [--])
// Figures
show ref: it => {
let target = it.element
if target == none {
return it
}
// dstu-table refs carry a number already resolved at the table location.
if (
target.func() == metadata
and type(target.value) == dictionary
and target.value.at("kind", default: none) == "dstu-table"
) {
return link(target.location())[#target.value.at("number")]
}
if target.func() != figure and target.func() != math.equation {
return it
}
// A figure or equation number must be read at the element itself, not at the citation site.
// The default ref display re-runs the numbering function here, where its inner `counter(heading)`
// context resolves to the citing chapter and prints the wrong section or appendix letter.
// We sample every counter at the target location instead.
let loc = target.location()
let appendix = dstu-table-appendix.at(loc)
let section = if appendix == none {
str(counter(heading).at(loc).first())
} else {
upper(ukr-enum.at(appendix - 1))
}
let number = if target.func() == figure {
let idx = counter(figure.where(kind: target.kind)).at(loc).first()
section + "." + str(idx)
} else {
let idx = counter(math.equation).at(loc).first()
"(" + section + "." + str(idx) + ")"
}
let supplement = if it.supplement == auto {
target.supplement
} else {
it.supplement
}
let body = if supplement != auto and supplement != none {
[#supplement #number]
} else {
[#number]
}
link(loc, body)
}
show figure: it => {
v(double-spacing, weak: true)
it
@@ -186,6 +248,7 @@
counter(figure.where(kind: raw)).update(0)
counter(figure.where(kind: image)).update(0)
counter(figure.where(kind: table)).update(0)
dstu-table-counter.update(0)
it
}
set figure(numbering: i => context numbering("1.1", counter(heading).get().at(0), i))
@@ -218,6 +281,15 @@
v(double-half-spacing, weak: true)
}
// blocks `like this` aren't welcome, so ` is replaced with "
show raw.where(block: false): it => text(
lang: "uk",
size: 14pt,
hyphenate: false,
weight: "regular",
font: ("Times New Roman", "Liberation Serif"),
)["#it.text"]
it
}
+68 -32
View File
@@ -1,7 +1,7 @@
#import "../../shared.typ": universities
#import "../../helpers.typ": *
#import "../../style.typ": spacing
#import "../../utils.typ": bold, uline, filled-lines
#import "../../utils.typ": bold, uline, filled-lines, hfill
#let note(content) = block(width: 100%, above: 5pt, below: 0pt)[
#set text(size: 10pt)
@@ -69,27 +69,53 @@
#let task-num(n) = box(str(n) + ".")
#let title-field(value) = {
uline(align: center, filled-lines(value))
uline(align: center, value.join())
uline(align: center, [])
note[(тема)]
}
#let commission-lines(members) = {
if members.len() > 0 {
for (i, member) in members.enumerate() {
if i > 0 {
linebreak()
}
let member-display-name = member.at("display-name", default: member.name)
let member-degree = member.at("degree", default: "")
uline(align: left, [#member-degree #member-display-name])
}
} else {
v(0.55em)
line(length: 100%, stroke: 0.5pt)
v(0.55em)
line(length: 100%, stroke: 0.5pt)
v(0.55em)
line(length: 100%, stroke: 0.5pt)
}
}
#let nure(
university,
title,
authors,
mentors,
committee_members,
task-list,
calendar-plan,
abstract,
bib-count,
faculty: "компютерних наук",
faculty: "комп'ютерних наук",
education-level: "перший (бакалаврський)",
program-type: "освітньо-професійна",
program-name: none,
) = {
let author = authors.first()
let head-mentor = mentors.first()
let commission-members = if committee_members == none {
mentors.slice(1)
} else {
committee_members
}
let uni = universities.at(university)
let edu-prog = uni.edu-programs.at(author.edu-program)
@@ -131,7 +157,10 @@
#note([(повна назва)])
#v(0.8em)
#inline-label-line([Кафедра], edu-prog.department-gen)
#inline-label-line(
[Кафедра],
lower(edu-prog.department-gen),
)
#note([(повна назва)])
#v(1.8em)
@@ -157,7 +186,7 @@
#executor-label\
здобувач #underline([#author.course]) курсу, групи #underline(group-name)\
#uline(align: center, author-display-name)
#note[(Власне імя, ПРІЗВИЩЕ)]
#note[(Власне ім'я, ПРІЗВИЩЕ)]
#v(0.2em)
#inline-label-line([Спеціальність], [#edu-prog.code -- #edu-prog.name-long])
@@ -168,18 +197,14 @@
#v(0.3em)
#inline-label-line([Керівник], [#mentor-degree #mentor-display-name])
#note[(посада, Власне імя, ПРІЗВИЩЕ)]
#note[(посада, Власне ім'я, ПРІЗВИЩЕ)]
#v(0.3em)
#pad(left: 75pt)[
#set par(first-line-indent: 0pt)
Члени комісії (#text(size: 10pt)[Власне імя, ПРІЗВИЩЕ, підпис])
#v(0.55em)
#line(length: 100%, stroke: 0.5pt)
#v(0.55em)
#line(length: 100%, stroke: 0.5pt)
#v(0.55em)
#line(length: 100%, stroke: 0.5pt)
Члени комісії (#text(size: 10pt)[Власне ім'я, ПРІЗВИЩЕ, підпис])
#v(0.15em)
#commission-lines(commission-members)
]
],
)
@@ -204,7 +229,7 @@
faculty,
),
),
([Кафедра], edu-prog.department-gen),
([Кафедра], lower(edu-prog.department-gen)),
([Рівень вищої освіти], education-level),
([Спеціальність], [#edu-prog.code -- #edu-prog.name-long]),
([Тип програми], program-type),
@@ -229,7 +254,7 @@
#v(1.0em)
#label-line([здобувачеві], author-full-name-dat, caption: [(прізвище, імя, по батькові)], label-width: 95pt)
#label-line([здобувачеві], author-full-name-dat, caption: [(прізвище, ім'я, по батькові)], label-width: 95pt)
#v(1.0em)
@@ -263,17 +288,17 @@
#v(5.0em)
Дата видачі завдання “#underline(task-list.initial-date.display("[day]"))” #underline(month-gen(task-list.initial-date.month())) #task-list.initial-date.display("[year]")р.
Дата видачі завдання “#underline(task-list.initial-date.display("[day]"))” #underline(month-gen(task-list.initial-date.month())) #task-list.initial-date.display("[year]") р.
#v(1.4em)
Здобувач #uline(align: center, [])
#note[(підпис)]
Здобувач #underline([#hfill(6cm)])
#note[(підпис) #h(8cm)]
#v(1.4em)
Керівник роботи #uline(align: center, []) #h(1cm) #underline[#mentor-degree #mentor-display-name]
#note[(підпис) #h(4.5cm) (посада, Власне імя, ПРІЗВИЩЕ)]
#note[(підпис) #h(4.5cm) (посада, Власне ім'я, ПРІЗВИЩЕ)]
#pagebreak()
]
@@ -289,7 +314,8 @@
#context [
#let pages = counter(page).final().at(0)
#let images = query(figure.where(kind: image)).len()
#let tables = query(figure.where(kind: table)).len()
#let dstu-tables = query(metadata).filter(it => type(it.value) == dictionary and it.value.at("kind", default: none) == "dstu-table").len()
#let tables = query(figure.where(kind: table)).len() + dstu-tables
#let bibs = bib-count.final().dedup().len()
#let counters = ()
@@ -303,23 +329,33 @@
\
#(
abstract
.keywords
.map(upper)
.sorted(by: (a, b) => {
if is-cyr(a) != is-cyr(b) { is-cyr(a) } else { a < b }
})
.join(", ")
)
#let keyword-pairs = if abstract.keywords.len() > 0 and type(abstract.keywords.first()) == array {
abstract.keywords.map(pair => (
uk: pair.at(0),
en: pair.at(1),
))
} else if abstract.at("en", default: none) != none and abstract.en.at("keywords", default: none) != none {
abstract.keywords.enumerate().map(((i, uk)) => (
uk: uk,
en: abstract.en.keywords.at(i),
))
} else {
abstract.keywords.map(uk => (uk: uk))
}
#let sorted-keyword-pairs = keyword-pairs.sorted(by: (a, b) => {
if is-cyr(a.uk) != is-cyr(b.uk) { is-cyr(a.uk) } else { a.uk < b.uk }
})
#(sorted-keyword-pairs.map(pair => upper(pair.uk)).join(", "))
\
#abstract.text
#if abstract.at("en", default: none) != none [
#if abstract.at("en", default: none) != none or (keyword-pairs.len() > 0 and keyword-pairs.first().at("en", default: none) != none) [
\
#(abstract.en.keywords.map(upper).join(", "))
#(sorted-keyword-pairs.map(pair => upper(pair.en)).join(", "))
\
@@ -337,7 +373,7 @@
} else {
block(width: 100%)[
#link(el.location())[
ДОДАТОК #it.prefix()#h(0.5em)#it.inner()
Додаток #it.prefix()#h(0.5em)#it.inner()
]
]
}
+2 -1
View File
@@ -210,7 +210,8 @@
#context [
#let pages = counter(page).final().at(0)
#let images = query(figure.where(kind: image)).len()
#let tables = query(figure.where(kind: table)).len()
#let dstu-tables = query(metadata).filter(it => type(it.value) == dictionary and it.value.at("kind", default: none) == "dstu-table").len()
#let tables = query(figure.where(kind: table)).len() + dstu-tables
#let bibs = bib-count.final().dedup().len()
/* TODO: why this stopped working?
#let tables = counter(figure.where(kind: table)).final().at(0)
+12 -13
View File
@@ -20,6 +20,12 @@
(name: "Сокорчук І. П.", display-name: "Ігор СОКОРЧУК", degree: "ст.викл. кафедри ПІ"),
)
#let committee_members = (
(name: "Груздо І.В.", degree: "Доц."),
(name: "Зибіна К.В.", degree: "Ст. викл."),
(name: "Гребенюк В.О.", degree: "Ст. викл."),
)
#let task-list = (
done-date: datetime(year: 2026, month: 12, day: 27),
initial-date: datetime(year: 2026, month: 9, day: 15),
@@ -53,11 +59,11 @@
#let abstract = (
keywords: (
"веб-застосунок",
"інформаційна система",
"курсова робота",
"програмна інженерія",
"тестовий приклад",
("веб-застосунок", "WEB APPLICATION"),
("інформаційна система", "INFORMATION SYSTEM"),
("курсова робота", "COURSEWORK"),
("програмна інженерія", "SOFTWARE ENGINEERING"),
("тестовий приклад", "DEMO SAMPLE"),
),
text: [
Мета даної роботи -- продемонструвати оформлення пояснювальної записки
@@ -74,15 +80,7 @@
шаблону без прив'язки до реального студента, викладача або завершеної роботи.
],
)
#let abstract_en = (
keywords: (
"WEB APPLICATION",
"INFORMATION SYSTEM",
"COURSEWORK",
"SOFTWARE ENGINEERING",
"DEMO SAMPLE",
),
text: [
The purpose of this work is to demonstrate the formatting of a coursework
explanatory note using the new template variant.
@@ -122,6 +120,7 @@
),
authors: authors,
mentors: mentors,
committee-members: committee_members,
task-list: task-list,
calendar-plan: calendar-plan,
abstract: abstract,
+1 -1
View File
@@ -19,7 +19,7 @@ work_output_file = "{{config_root}}/main.pdf"
nure_package_repo = "https://gitea.linerds.us/pencelheimer/typst_nure_template.git"
nure_package_ref = "0.1.1"
nure_package_rev = "f60fefc7b6f4df5b6fcb835bfc9742dc92dd9abf"
nure_package_rev = "d6037872ddb6391afec147a679fa05f08cba6bb0"
nure_package_name = "vendor/nure"
nure_package_ver = "{{vars.nure_package_ref}}"