Skip to content

Commit 5f1da20

Browse files
first commit
0 parents  commit 5f1da20

File tree

4 files changed

+598
-0
lines changed

4 files changed

+598
-0
lines changed

README.md

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
Special thanks to the author of the idea [akopyl](https://habr.com/ru/users/akopyl/).
2+
3+
## What is this?
4+
5+
It converts this:
6+
7+
```css
8+
section#some-id {
9+
--text: 'This is text!';
10+
--attr-title: 'Title';
11+
background: red;
12+
color: aliceblue;
13+
}
14+
section#some-id header[data-attribute='v'] {
15+
--text: 'This is the header text';
16+
color: blue;
17+
}
18+
section#some-id span {
19+
--text: 'Text of span';
20+
--text-after: 'Text after';
21+
color: peru;
22+
}
23+
```
24+
25+
To this:
26+
27+
```html
28+
<section id="some-id" title="Title">
29+
This is text!
30+
<header data-attribute="v">This is the header text</header>
31+
<span> Text of span </span>Text after
32+
</section>
33+
```
34+
35+
## How to use this?
36+
37+
### Elements
38+
39+
You can create an element through a selector:
40+
41+
```css
42+
div.classname#id[attr-1][attr-2='v'] {
43+
/* None of the parts of the selector are mandatory */
44+
/* But at least something needs to be left */
45+
}
46+
```
47+
48+
```html
49+
<!-- Result -->
50+
<div id="id" class="classname" attr-1 attr-2="v"></div>
51+
```
52+
53+
**Nesting** is supported:
54+
55+
```css
56+
div {
57+
}
58+
div span {
59+
}
60+
```
61+
62+
```html
63+
<div>
64+
<span></span>
65+
</div>
66+
```
67+
68+
If you want to **add styles** but **not add elements** (that is, so that some selectors are ignored), add one of the following to the selector:
69+
70+
- Pseudo-class
71+
- Pseudo-element
72+
- One of these selectors: `*`, `+`, `>`, `||`, `|`, `~`
73+
- Or wrap it in an [`@at-rule`](https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule)
74+
75+
Example - these selectors will be **ignored**:
76+
77+
```css
78+
> div.classname#id[attr-1][attr-2='v'] {
79+
}
80+
div::before {
81+
/* Yes, and this one too */
82+
}
83+
div:not(:has(span)) {
84+
/* And this one too! */
85+
}
86+
@container (width > 1440px) {
87+
div[data-a='This element will be ignored too'] {
88+
}
89+
}
90+
```
91+
92+
### Text and attributes
93+
94+
Attributes can be set via a selector (_it can be useful for styling_), or you can use a [custom property](https://developer.mozilla.org/en-US/docs/Web/CSS/--*):
95+
96+
```css
97+
/* In a selector */
98+
a[title='Title!'] {
99+
/* Specific attribute */
100+
--attr-href: './index.html';
101+
102+
/* And massively! */
103+
--attrs: 'target="_self" rel="noopener"';
104+
}
105+
```
106+
107+
```html
108+
<a title="Title!" href="./index.html" target="_self" rel="noopener"> </a>
109+
```
110+
111+
You can also add **text** to the tag via `--text` property:
112+
113+
```css
114+
div {
115+
--text: 'The battle continues again';
116+
}
117+
```
118+
119+
```html
120+
<div>The battle continues again</div>
121+
```
122+
123+
In order to insert a tag _between the text_, you will definitely need special properties that allow you to enter text before and after the tag `--text-before` and `--text-after`:
124+
125+
```css
126+
div {
127+
--text: 'The text inside the div';
128+
}
129+
div span {
130+
--text: 'The text inside span';
131+
--text-before: '| before';
132+
--text-after: '| after';
133+
}
134+
```
135+
136+
```html
137+
<div>
138+
The text inside the div | before<span> The text inside span </span>| after
139+
</div>
140+
```
141+
142+
## API
143+
144+
The very minimum to run looks like this:
145+
146+
```js
147+
// This code outputs to the terminal/console the result of processing the simplest CSS from the single tag.
148+
import { CssToHtml } from 'css-to-html';
149+
150+
let result = new CssToHtml({ css: 'div{}' });
151+
152+
console.log(result.outputHTML);
153+
```
154+
155+
### Writing to a file
156+
157+
To write the html that turned out to be directly in a file, add the `write` parameter:
158+
<br>
159+
(_Attention! The entire file will be **overwritten**_)
160+
161+
```js
162+
new CssToHtml({
163+
164+
write: {
165+
in: "your_path_to_html_file",
166+
},
167+
})
168+
```
169+
170+
#### Overwriting a part of a file
171+
172+
Using the `after` and/or `before` parameters, you will not overwrite the entire file, but **specify the area** to be overwritten.
173+
<br>
174+
You can omit one of these parameters or not specify them at all.
175+
176+
Without `after` and `before` parameters:
177+
178+
```js
179+
new CssToHtml({
180+
181+
write: {
182+
in: "your_path_to_html_file",
183+
},
184+
})
185+
```
186+
187+
```html
188+
<some-html-content>
189+
<div>Your content from CSS</div>
190+
</some-html-content>
191+
192+
<!-- to... -->
193+
194+
<div>Your content from CSS</div>
195+
```
196+
197+
With `after` and `before` parameters:
198+
199+
```js
200+
new CssToHtml({
201+
202+
write: {
203+
...,
204+
after: '<some-html-content>',
205+
before: '</some-html-content>',
206+
},
207+
})
208+
```
209+
210+
```html
211+
<some-html-content>
212+
<div>Your content from CSS</div>
213+
</some-html-content>
214+
215+
<!-- Without changes -->
216+
217+
<some-html-content>
218+
<div>Your content from CSS</div>
219+
</some-html-content>
220+
```
221+
222+
#### Formatting
223+
224+
Before giving you html, it is formatted by the [js-beatify](https://github.com/beautifier/js-beautify) library.
225+
If you want to change the formatting settings, pass them as a parameter:
226+
227+
```js
228+
new CssToHtml({
229+
230+
formatterOptions: {
231+
indent_size: 2,
232+
},
233+
})
234+
```
235+
236+
### If you find a bug, please create an issue [here](https://github.com/Ulyanov-programmer/css-to-html/issues).
237+
238+
### If this project was useful to you, you can give it a ★ in [repository](https://github.com/Ulyanov-programmer/css-to-html).

cssToHtml.js

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import fs from 'fs-extra'
2+
import path from 'path'
3+
import beautify from 'js-beautify'
4+
import { parse } from '@adobe/css-tools'
5+
import { createParser } from 'css-selector-parser'
6+
import { ElementOfHtml } from './elementOfHtml.js'
7+
8+
9+
export class CssToHtml {
10+
static ENCODING = 'utf8'
11+
static SELECTOR_PARSER = createParser({ syntax: 'progressive' })
12+
static UNACCEPTABLE_SELECTORS = [
13+
'WildcardTag',
14+
'PseudoElement',
15+
'PseudoClass',
16+
':',
17+
'*',
18+
'+',
19+
'>',
20+
'||',
21+
'~',
22+
'|',
23+
]
24+
#pathToHTML
25+
#html
26+
#css
27+
#astRules
28+
#elements = []
29+
#writeBefore
30+
#writeAfter
31+
#formatterOptions = {
32+
indent_size: 2,
33+
max_preserve_newlines: -1,
34+
preserve_newlines: false,
35+
}
36+
#writeInFile = false
37+
outputHTML
38+
39+
constructor({ css, formatterOptions, write, }) {
40+
this.#css = css
41+
42+
this.#formatterOptions = Object.assign(this.#formatterOptions, formatterOptions)
43+
44+
if (write?.in) {
45+
this.#pathToHTML = path.normalize(write.in)
46+
47+
if (!fs.existsSync(this.#pathToHTML)) {
48+
console.error(`The ${this.#pathToHTML} file was not found, so it will be created.`)
49+
fs.createFileSync(this.#pathToHTML)
50+
}
51+
52+
this.#writeInFile = true
53+
this.#html = fs.readFileSync(this.#pathToHTML, CssToHtml.ENCODING)
54+
this.#writeAfter = write?.after
55+
this.#writeBefore = write?.before
56+
}
57+
58+
let astRules = parse(this.#css).stylesheet.rules
59+
60+
if (!astRules.length)
61+
return
62+
63+
this.#filterAstRules(astRules)
64+
65+
this.#initHTMLElements()
66+
this.#createHTMLStructure()
67+
68+
if (!this.#elements.length) return
69+
70+
this.outputHTML = this.#prepareHtml()
71+
72+
if (this.#writeInFile) {
73+
fs.writeFileSync(write.in, this.outputHTML)
74+
}
75+
}
76+
77+
#filterAstRules(astRules) {
78+
this.#astRules = astRules.filter(
79+
rule => {
80+
if (
81+
rule.type != 'rule' ||
82+
this.#containsUnacceptableSelector(rule.selectors[0])
83+
)
84+
return false
85+
86+
return true
87+
}
88+
)
89+
}
90+
#initHTMLElements() {
91+
for (let rule of this.#astRules) {
92+
this.#elements.push(new ElementOfHtml(rule, rule.selectors[0]))
93+
}
94+
}
95+
#createHTMLStructure() {
96+
for (let i = 0; i < this.#elements.length; i++) {
97+
this.#elements[i].searchInnerElements(this.#elements, i)
98+
}
99+
100+
this.#elements = this.#elements.filter(el => el.parentSelector == '')
101+
}
102+
#prepareHtml() {
103+
let newContent = ''
104+
let [contentStartIndex, contentEndIndex] = this.#getWritingStartAndEndIndex()
105+
106+
if (this.#writeInFile) {
107+
newContent = this.#html.substring(0, contentStartIndex)
108+
109+
for (let element of this.#elements) {
110+
newContent += element.string + '\n'
111+
}
112+
113+
newContent += this.#html.substring(contentEndIndex)
114+
}
115+
else {
116+
for (let element of this.#elements) {
117+
newContent += element.string + '\n'
118+
}
119+
}
120+
121+
return beautify.html(newContent, this.#formatterOptions)
122+
}
123+
#containsUnacceptableSelector(selector) {
124+
return CssToHtml.UNACCEPTABLE_SELECTORS.some(
125+
unSelector => selector.includes(unSelector)
126+
)
127+
}
128+
#getWritingStartAndEndIndex() {
129+
if (!this.#writeInFile) return [0, 0]
130+
131+
132+
let contentStartIndex = 0, contentEndIndex = this.#html.length
133+
134+
if (this.#writeAfter) {
135+
contentStartIndex = this.#html.indexOf(this.#writeAfter)
136+
}
137+
if (this.#writeBefore) {
138+
contentEndIndex = this.#html.lastIndexOf(this.#writeBefore)
139+
}
140+
141+
if (contentStartIndex == -1 || contentEndIndex == -1) {
142+
throw new Error(`contentStartIndex or contentEndIndex was not found in the file ${this.#pathToHTML}!`)
143+
}
144+
145+
contentStartIndex += this.#writeAfter?.length ?? 0
146+
147+
return [contentStartIndex, contentEndIndex]
148+
}
149+
}

0 commit comments

Comments
 (0)