css compression/decompression

Share your advanced PureBasic knowledge/code with the community.
AZJIO
Addict
Addict
Posts: 2187
Joined: Sun May 14, 2017 1:48 am

css compression/decompression

Post by AZJIO »

The uncompression code is primitive, as it works only after compression by my algorithm. At the moment, decompression does not correct decompressed code and does not process comments, since they are considered to be deleted after compression.
It started with a question from here
Download: upload.ee, yandex (the files are always newer)

ini file (must have the same name as the exe file):
source = path to source file
destination = path to the resulting file being created
flag = 6 - css formatting flags (see below)
open = 1 - open the resulting file for viewing in the associated program
msg = 1 - messages

Flags of the ini file (can be a combination-sum of flags):
1 - CSS compression. Flags 2, 4, 8 do not work in this mode.
2 - opening curly brace "{" on a new line
4 - after the comma "," in the class listing, there will be a line break instead of a space. This makes the classes a vertical list and is easier to read.
8 - (the option is disabled for now) there is no space inside the curly braces {...} after the colon ":" (separation of parameter and value)
16 - Convert rgb(x,x,x) to #hex
128 - disables deleting comments. Works with flag 1.

Command line: The "flag source_path destination_path" is passed on the command line. If the file is not transferred, a file selection window opens. If a file is selected, a save dialog is created in the same folder as a file with index "_1". The command line takes precedence over the ini file.

css_tidy

Code: Select all

;- TOP
; AZJIO     2024.09.02 - 2024.09.24

EnableExplicit

;- ● Global
Global NewList TextList.s()
Global Dim Tab.s(1)
Tab(1) = #TAB$
Global g_Format, flag, Error, open, msg, StartTime
Global source$, destination$, current$, ext$, Message$

;- ● Define
Define Len, *Point
Define Result.string
Define CountPP
Define tmp$

; https://www.purebasic.fr/english/viewtopic.php?p=478874
XIncludeFile "AutoDetectTextEncoding_Trim.pbi"

Procedure.s RTrimChar(String$, TrimChar$ = " " + #TAB$ + #CRLF$ + #FF$ + #VT$)
    Protected Len2, Blen, i
    Protected *jc0, *c.Character, *jc.Character

    Len2 = Len(String$)
    Blen = StringByteLength(String$)

    If Not Asc(String$)
        ProcedureReturn ""
    EndIf

    *c = @String$ + Blen - SizeOf(Character)
    *jc0 = @TrimChar$

    For i = Len2 To 1 Step - 1
        *jc = *jc0

        While *jc\c
            If *c\c = *jc\c
                *c\c = 0
                Break
            EndIf
            *jc + SizeOf(Character)
        Wend

        If *c\c
            Break
        EndIf
        *c - SizeOf(Character)
    Next

    ProcedureReturn String$
EndProcedure

Procedure ForceDirectories(Dir.s)
	Static tmpDir.s, Init
	Protected result
	
	If Asc(Dir) ; если что-то есть
		If Not Init
			tmpDir = Dir
			Init   = 1
		EndIf
		Dir = RTrim(Dir, #PS$) ; убираем последний слеш, не делая лишние проверки
		; если папка существует или дошли до корневой, то
		If FileSize(Dir) = -2 Or (Len(Dir) < 3) Or GetPathPart(Dir) = Dir
			If FileSize(tmpDir) = -2
				result = -1 ; попробуем использовать -1 в качестве "путь уже создан"
			EndIf
			tmpDir = ""
			Init   = 0
			ProcedureReturn result ; если это папка, то возврат будет 1
		EndIf
		ForceDirectories(GetPathPart(Dir))
		ProcedureReturn CreateDirectory(Dir)
	Else
		ProcedureReturn 0
	EndIf
EndProcedure

; Чтение файла в гаджет
Procedure.s OpenFileToGadget(FilePath$)
	Protected length, oFile, bytes, *mem, Text$
	oFile = ReadFile(#PB_Any, FilePath$)
	If oFile
		g_Format = ReadStringFormat(oFile)
		length = Lof(oFile)
		*mem = AllocateMemory(length)
		If *mem
			bytes = ReadData(oFile, *mem, length)
			If bytes
				If g_Format = #PB_Ascii
					g_Format = dte::detectTextEncodingInBuffer(*mem, bytes, 0)
					If g_Format = #PB_Ascii
						Text$ = PeekS(*mem, bytes, #PB_Ascii)
					Else
						Text$ = PeekS(*mem, bytes, #PB_UTF8) ; если UTF8 без BOM
					EndIf
				Else
					; тут не уверен, PeekS() поддерживает #PB_Unicode,
					; а ReadStringFormat() может дать #PB_UTF16BE, #PB_UTF32, #PB_UTF32BE
					; хотя эти форматы не популярны скорее не встретятся, и надо сделать на них игнор
					Text$ = PeekS(*mem, bytes, g_Format)
				EndIf
			EndIf
			FreeMemory(*mem)
		EndIf
		CloseFile(oFile)
	EndIf
	ProcedureReturn Text$
EndProcedure


Procedure cssCompression(*c.Character)
	Protected sz = SizeOf(Character)
	Protected OpenBrace, OpenSquareBracket
	Protected *tmp, *start
	
	If *c = 0 Or *c\c = 0
		MessageRequester("Ошибка", "Данные пусты")
		ProcedureReturn
; 	ElseIf *c\c = '{' ; защита уже сделана
; 		MessageRequester("Ошибка", "{ - фигурная скобка не может быть первым символом соответсвенно невозможно осуществить просмотр назад")
; 		ProcedureReturn
	EndIf
	
	*start = *c
	
; 	Удаление стартовых пробелов
	While *c\c = 32 Or (*c\c > 8 And *c\c < 14)
		*c\c = 1
		*c + sz
	Wend

	While *c\c
		Select *c\c
;- /
			Case '/' ; проверка комментариев, если найдено то затираем псевдо-пусто-символом
				If flag & 128 ; если флаг 128, то не удаляем комментарии
					*c + sz
					If *c\c = '*'
						*c + sz
						While *c\c
							If *c\c = '*'
								*c + sz
								If *c\c = '/'
									Break
								EndIf
								*c - sz
							EndIf
							*c + sz
						Wend
						If *c\c = 0 ; проверка если комент не закрыт в конце файла
							Break
						EndIf
					Else
						Continue
					EndIf
				Else
					*c + sz
					If *c\c = '*'
						*c - sz
						*c\c = 1
						*c + sz
						*c\c = 1
						*c + sz
						While *c\c
							If *c\c = '*'
								*c + sz
								If *c\c = '/'
									*c - sz
									*c\c = 1
									*c + sz
									*c\c = 1
									
									*c + sz
									While *c\c = 32 Or (*c\c > 8 And *c\c < 14)
										*c\c = 1
										*c + sz
									Wend
									*c - sz
									Break
								EndIf
								*c - sz
							EndIf
							*c\c = 1
							*c + sz
						Wend
					Else
						*c - sz
					EndIf
				EndIf
;- '
			Case 39 ; проверка апострофа, идём до закрывающего апострофа
					; *tmp = *c
				*c + sz
				While *c\c
					If *c\c = 39
						; Debug PeekS(*tmp, (*c - *tmp) / 2 + 1, #PB_Unicode)
						Break
					EndIf
					If *c\c > 8 And *c\c < 14
						; Debug PeekS(*tmp, (*c - *tmp) / 2, #PB_Unicode)
						*c\c = 32
						Error + 1
						Break
					EndIf
					*c + sz
				Wend
;- "
			Case 34 ; проверка кавычки, идём до закрывающей кавычки
					; *tmp = *c
				*c + sz
				While *c\c
					If *c\c = 34
						; Debug PeekS(*tmp, (*c - *tmp) / 2 + 1, #PB_Unicode)
						Break
					ElseIf *c\c > 8 And *c\c < 14
						; Debug PeekS(*tmp, (*c - *tmp) / 2, #PB_Unicode)
						*c\c = 32
						Error + 1
						Break
					Else
						*c + sz
					EndIf
				Wend
;- [ ]
			Case '['
				OpenSquareBracket = 1
			Case ']'
				OpenSquareBracket = 0
;- !
			Case '!'
				If OpenBrace And CompareMemoryString(*c , @"!important", #PB_String_NoCaseAscii, 10) = #PB_String_Equal
					; 				проверка пробелов назад
					*tmp = *c
					*c - sz
					While *c>=*start And (*c\c = 32 Or (*c\c > 8 And *c\c < 14))
						*c\c = 1
						*c - sz
					Wend
					*c = *tmp + (10 * sz)
					Continue
				EndIf
;- ,:
			Case ',', ':';, ')'
				If OpenSquareBracket = 0; And OpenBrace = 0
					*c + sz
					While *c\c = 32 Or (*c\c > 8 And *c\c < 14)
						*c\c = 1
						*c + sz
					Wend
					*c - sz
				EndIf
;- {
			Case '{'
				OpenBrace = 1
; 				проверка пробелов назад
				*tmp = *c
				*c - sz
				While *c>=*start And (*c\c = 32 Or *c\c = 1 Or (*c\c > 8 And *c\c < 14))
					*c\c = 1
					*c - sz
				Wend
				*c = *tmp
; 				проверка пробелов вперёд
				*c + sz
				While *c\c = 32 Or (*c\c > 8 And *c\c < 14)
					*c\c = 1
					*c + sz
				Wend
				*c - sz
;- }
			Case '}'
				OpenBrace = 0
; 				проверка пробелов назад
				*tmp = *c
				*c - sz
				While *c>=*start And (*c\c = 32 Or *c\c = ';' Or *c\c = 1 Or (*c\c > 8 And *c\c < 14))
					*c\c = 1
					*c - sz
				Wend
				*c = *tmp
; 				проверка пробелов вперёд
				*c + sz
				While *c\c = 32 Or (*c\c > 8 And *c\c < 14)
					*c\c = 1
					*c + sz
				Wend
				*c - sz
;- ;
			Case ';' ; разделитель параметров в css
				If OpenBrace ; внутри фигурных скобок
					*c + sz
					While *c\c = 32 Or *c\c = ';' Or (*c\c > 8 And *c\c < 14)
						*c\c = 1
						*c + sz
					Wend
					*c - sz
				EndIf
;- Space, Tab, CR
			Case 32, 9 To 13 ; пробел и табуляция и переносы строк
				If *c\c <> 32
					OpenSquareBracket = 0
				EndIf
				*c\c = 32 ; заменяем любой перенос пробелом
; 				If OpenBrace
; 					*c\c = 1
; 				Else
					*c + sz
					While *c\c = 32 Or (*c\c > 8 And *c\c < 14) ; удаляем повтор пробельных символов
						*c\c = 1
						*c + sz
					Wend
					*c - sz
; 				EndIf
		EndSelect
		*c + sz
	Wend
EndProcedure



Procedure cssTidy(*c.Character)
	Protected sz = SizeOf(Character)
	Protected OpenBrace, OpenSquareBracket, CountBrace
	Protected *tmp, *beginning
	
	If *c = 0 Or *c\c = 0
		MessageRequester("Ошибка", "Данные пусты")
		ProcedureReturn
; 	ElseIf *c\c = '{' ; защита уже сделана
; 		MessageRequester("Ошибка", "{ - фигурная скобка не может быть первым символом соответсвенно невозможно осуществить просмотр назад")
; 		ProcedureReturn
	EndIf
	
	*beginning = *c
	
; 	Удаление стартовых пробелов
; 	While *c\c = 32 Or (*c\c > 8 And *c\c < 14)
; 		*c\c = 1
; 		*c + sz
; 	Wend

	While *c\c
		Select *c\c
;- comment
			Case '/' ; проверка комментариев, если найдено то ищем закрытие
				*c + sz
				If *c\c = '*'
					*c + sz
					While *c\c
						If *c\c = '*'
							*c + sz
							If *c\c = '/'
								Break
							EndIf
							*c - sz
						EndIf
						*c + sz
					Wend
					If *c\c = 0 ; проверка если комент не закрыт в конце файла
						Break
					EndIf
				Else
					Continue
				EndIf
;- '
			Case 39 ; проверка апострофа, идём до закрывающего апострофа
					; *tmp = *c
				*c + sz
				While *c\c
					If *c\c = 39
						; Debug PeekS(*tmp, (*c - *tmp) / 2 + 1, #PB_Unicode)
						Break
					EndIf
					If *c\c > 8 And *c\c < 14
						; Debug PeekS(*tmp, (*c - *tmp) / 2, #PB_Unicode)
						Error + 1
						Break
					EndIf
					*c + sz
				Wend
;- "
			Case 34 ; проверка кавычки, идём до закрывающей кавычки
					; *tmp = *c
				*c + sz
				While *c\c
					If *c\c = 34
						; Debug PeekS(*tmp, (*c - *tmp) / 2 + 1, #PB_Unicode)
						Break
					ElseIf *c\c > 8 And *c\c < 14
						; Debug PeekS(*tmp, (*c - *tmp) / 2, #PB_Unicode)
						Error + 1
						Break
					Else
						*c + sz
					EndIf
				Wend
; 			Case '['
; 				OpenSquareBracket = 1
; 			Case ']'
; 				OpenSquareBracket = 0
;- ,
			Case ','
				*c\c = 0
				*c + sz
				AddElement(TextList())
				TextList() = PeekS(*beginning)
				AddElement(TextList())
				While *c\c = 32 Or (*c\c > 8 And *c\c < 14)
					*c + sz
				Wend
				; без этого OpenBrace перечисление шрифтов делает столбиком. Исправить.
				; С другой стороны вложенные классы перестанет обрабатывать
				If flag & 4 And Not OpenBrace
					TextList() = "," + #CRLF$
				ElseIf *c\c = ' '
					TextList() = ","
				Else
					TextList() = ", "
				EndIf
				*beginning = *c
				Continue
;- :
			Case ':'
				If OpenBrace
					*c + sz
					If *c\c =  ':' Or *c\c =  ' '
						*c + sz
						Continue
					EndIf
					*c - sz
					*c - sz
					; 				If *c\c = ':' Or *c\c = '{' Or *c\c = '}' Or *c\c = ';' Or *c\c = ')' Or *c\c = ' ' Or (*c\c > 8 And *c\c < 14)
					; 					*c + sz
					; ; 					*c + sz
					; ; 					Continue
					; 				Else
					; 					*c + sz
					; 					*c\c = 0
					; 					*c + sz
					; 					AddElement(TextList())
					; 					TextList() = PeekS(*beginning)
					; 					AddElement(TextList())
					; 					TextList() = ": "
					; 					*beginning = *c
					; 					Continue
					; 				EndIf
					If (*c\c > 96 And *c\c < 123) And CompareMemoryString(*c + sz * 2, @"not", #PB_String_NoCaseAscii, 3) <> #PB_String_Equal
						*c + sz
						*c\c = 0
						*c + sz
						AddElement(TextList())
						TextList() = PeekS(*beginning)
						AddElement(TextList())
						TextList() = ": "
						*beginning = *c
						Continue
					Else
						*c + sz
					EndIf
				EndIf
; 				If OpenBrace And flag ! 8 ; внутри фигурных скобок
; 					*c + sz
; 					If *c\c = ':'
; 						*c + sz
; 						Continue
; 					ElseIf CompareMemoryString(*c , @"not", #PB_String_NoCaseAscii, 3) = #PB_String_Equal
; 						*c + sz * 3
; 						Continue
; 					ElseIf CompareMemoryString(*c , @"root", #PB_String_NoCaseAscii, 4) = #PB_String_Equal
; 						*c + sz * 4
; 						Continue
; 						; 				ElseIf PeekS(*c, 3) = "not"
; 						; 					*c + sz * 3
; 						; 					Continue
; 						; 				ElseIf PeekS(*c, 3) = "root"
; 						; 					*c + sz * 4
; 						; 					Continue
; 						; 				ElseIf *c\c = 'n'
; 						; 					*c + sz
; 					Else
; 						*c - sz
; 					EndIf
; 				EndIf
;- ;
			Case ';'
				*c\c = 0
				*c + sz
				AddElement(TextList())
				TextList() = PeekS(*beginning)
				AddElement(TextList())
				If OpenBrace
					TextList() = ";" + #CRLF$ + Tab(OpenBrace)
				Else
					TextList() = ";" + #CRLF$ + Tab(OpenBrace)
				EndIf
				While *c\c = 32 Or *c\c = ';' Or (*c\c > 8 And *c\c < 14)
					*c + sz
				Wend
				*beginning = *c
				Continue
				;- !
			Case '!'
				If OpenBrace And CompareMemoryString(*c , @"!important", #PB_String_NoCaseAscii, 10) = #PB_String_Equal
					*c - sz
					If *c\c <> 32
						*c + sz
						*c\c = 0
						AddElement(TextList())
						TextList() = PeekS(*beginning)
						AddElement(TextList())
						TextList() = " !important"
						; 				проверка пробелов назад
						*c + 10 * sz
						*beginning = *c
						Continue
					Else
						*c + sz
					EndIf
				EndIf
;- (
			Case '(' ; при открывающей скобке ищем закрывающую
				*c + sz
				While *c\c
					Select *c\c
						Case '(' ; если опять открывается, то используем счётчик, чтобы искать последнюю закрывющуюся.
							CountBrace + 1
						Case ')'
							If CountBrace
								CountBrace - 1
							Else
								If OpenBrace
									*c\c = 0
									*c + sz
									AddElement(TextList())
									TextList() = PeekS(*beginning)
									AddElement(TextList())
									If *c\c = ';' Or *c\c = ':'  Or *c\c = ' ' ; Or *c\c = '{'
										TextList() = ")"
									Else
										TextList() = ") "
									EndIf
									*beginning = *c
									*c - sz
								EndIf
								Break
							EndIf
						Case 9 To 13
							Error + 1
							Break
					EndSelect
					*c + sz
				Wend
; )
; 			Case ')'
;- {
			Case '{'
				OpenBrace + 1
				If OpenBrace > ArraySize(Tab())
					ReDim Tab(OpenBrace)
					Tab(OpenBrace) = LSet("", OpenBrace, #TAB$)
				EndIf
				*c\c = 0
				*c + sz
				AddElement(TextList())
				TextList() = PeekS(*beginning)
				TextList() = RTrimChar(TextList()) ; не оптимизировано, это можно сделать тут же без функции
				AddElement(TextList())
				If flag & 2
					TextList() = #CRLF$ + "{" + #CRLF$ + Tab(OpenBrace)
				Else
					TextList() = " {" + #CRLF$ + Tab(OpenBrace)
				EndIf
				While *c\c = 32 Or *c\c = ';' Or (*c\c > 8 And *c\c < 14)
					*c + sz
				Wend
				*beginning = *c
				Continue
;- }
			Case '}'
				OpenBrace - 1
				If OpenBrace < 0
					OpenBrace = 0
				EndIf
				*c\c = 0
				*c + sz
				TextList() = RTrimChar(TextList()) ; так как здесь добавление отступов, то проверяем что у предыдущего не было добавлено
				AddElement(TextList())
				TextList() = PeekS(*beginning)
				TextList() = RTrimChar(TextList())
				AddElement(TextList())
				TextList() = #CRLF$ + Tab(OpenBrace) + "}"
				While *c\c = 32 Or (*c\c > 8 And *c\c < 14)
					*c + sz
				Wend
				While *c\c = '}'
					OpenBrace - 1
					If OpenBrace < 0
						OpenBrace = 0
					EndIf
					TextList() + #CRLF$ + Tab(OpenBrace) + "}"
					*c + sz
					While *c\c = 32 Or (*c\c > 8 And *c\c < 14)
						*c + sz
					Wend
				Wend
				TextList() + #CRLF$ + Tab(OpenBrace); + #CRLF$
				*beginning = *c
				Continue
		EndSelect
		*c + sz
	Wend
EndProcedure

Structure RGBA
	StructureUnion
		color.l
		b.a[4]
	EndStructureUnion
EndStructure

Procedure RGBtoHEX(*text.String)
	Protected *c.Character, Color.RGBA
	Protected *StartNum, *Start0, *Start00
	Protected i, count, tmp$
	Protected sz = SizeOf(Character)
	
; 	Debug *text\s
	
	*c = @*text\s
	*Start00 = *c
	While *c\c
		If *c\c = 'r'
			*Start0 = *c
			*c - sz
			If *c >= *Start00 And (*c\c = ' ' Or *c\c = ':' Or *c\c = #TAB)
				*c + sz * 2
			Else
				*c + sz * 2
				Continue
			EndIf
			If *c\c = 'g'
				*c + sz
			Else
				Continue
			EndIf
			If *c\c = 'b'
				*c + sz
			Else
				Continue
			EndIf
			While *c\c = 32
				*c + sz
			Wend
			If *c\c = 0
				Break
			EndIf
			If *c\c <> '('
				*c + sz
				Continue
			EndIf
			*c + sz
			While *c\c = 32
				*c + sz
			Wend
			If *c\c = 0
				Break
			EndIf
			*StartNum = *c
			While *c\c >= '0' And *c\c <= '9'
				*c + sz
			Wend
			; 		Debug *StartNum
			; 		Debug *c
			If *StartNum <> *c
				; 			Debug PeekS(*StartNum, (*c - *StartNum) / sz)
				; 			red0 = Val(PeekS(*StartNum, (*c - *StartNum) / sz))
				Color\b.a[2] = Val(PeekS(*StartNum, (*c - *StartNum) / sz))
				; 			Debug red0
			Else
				*c + sz
				Continue
			EndIf
			While *c\c = 32
				*c + sz
			Wend
			If *c\c = 0
				Break
			EndIf
			If *c\c <> ','
				*c + sz
				Continue
			EndIf
			*c + sz
			While *c\c = 32
				*c + sz
			Wend
			If *c\c = 0
				Break
			EndIf
			*StartNum = *c
			While *c\c >= '0' And *c\c <= '9'
				*c + sz
			Wend
			; 		Debug *StartNum
			; 		Debug *c
			If *StartNum <> *c
				; 			Debug PeekS(*StartNum, (*c - *StartNum) / sz)
				; 			green0 = Val(PeekS(*StartNum, (*c - *StartNum) / sz))
				Color\b.a[1] = Val(PeekS(*StartNum, (*c - *StartNum) / sz))
				; 			Debug green0
			Else
				*c + sz
				Continue
			EndIf
			While *c\c = 32
				*c + sz
			Wend
			If *c\c = 0
				Break
			EndIf
			If *c\c <> ','
				*c + sz
				Continue
			EndIf
			*c + sz
			While *c\c = 32
				*c + sz
			Wend
			If *c\c = 0
				Break
			EndIf
			*StartNum = *c
			While *c\c >= '0' And *c\c <= '9'
				*c + sz
			Wend
			; 		Debug *StartNum
			; 		Debug *c
			If *StartNum <> *c
				; 			Debug PeekS(*StartNum, (*c - *StartNum) / sz)
				; 			blue0 = Val(PeekS(*StartNum, (*c - *StartNum) / sz))
				Color\b.a[0] = Val(PeekS(*StartNum, (*c - *StartNum) / sz))
				; 			Debug blue0
			Else
				*c + sz
				Continue
			EndIf
			While *c\c = 32
				*c + sz
			Wend
			If *c\c = 0
				Break
			EndIf
			If *c\c <> ')'
				*c + sz
				Continue
			EndIf
			For i = *Start0 To *c Step SizeOf(Character)
				PokeA(i, 1)
			Next
			count = 0
			For i = 0 To 2
				If Hex(Color\b[i] >> 4) = Hex(Color\b[i] & $F)
					count + 1
				EndIf
			Next
			If count = 3
				tmp$ = "#" + Hex(Color\b[0] >> 4) + Hex(Color\b[1] >> 4) + Hex(Color\b[2] >> 4)
			Else
				tmp$ = "#" + RSet(Hex(Color\color), 6, "0")
			EndIf
			CopyMemory(@tmp$, *Start0, StringByteLength(tmp$))
		EndIf
		*c + sz
	Wend
	*text\s = ReplaceString(*text\s, Chr(1), "")
; 	ProcedureReturn text$
EndProcedure


current$ = GetPathPart(ProgramFilename())

; Global ini$ = current$ + "css_parser_decompression.ini"
Global ini$ = Left(ProgramFilename(), Len(ProgramFilename()) - 3) + "ini"

; чтение параметров ком-строки
CountPP = CountProgramParameters()
If CountPP
	flag = Val(ProgramParameter())
	If CountPP > 1
		source$ = ProgramParameter()
		If CountPP > 2
			destination$ = ProgramParameter()
		EndIf
	EndIf
Else
; 	если нет параметров, то читаем ini-файл
	;- ● ini
	If OpenPreferences(ini$)
		source$ = ReadPreferenceString("source", "")
		destination$ = ReadPreferenceString("destination", "")
		flag = ReadPreferenceInteger("flag", 0)
		open = ReadPreferenceInteger("open", 0)
		msg = ReadPreferenceInteger("msg", 0)
		ClosePreferences()
	EndIf
EndIf


If flag & 1024
	 msg | 1
EndIf
If flag & 2048
	msg | 2
EndIf
If flag & 4096
	msg | 4
EndIf
	
If flag & 8192
	open = 1
EndIf

; Если не получили пути, то открываем диалоговое окно выбора.
If Not Asc(source$) Or FileSize(source$) < 0
	Debug "source$ = " + source$
	source$ = OpenFileRequester("", current$, "*.css|*.css|*.*|*.*", 0)
	If Not Asc(source$) Or FileSize(source$) < 0
		End ; если выбор отменён, то закрываем программу
	EndIf
EndIf
ext$ = GetExtensionPart(source$)


; Если нет пути сохранения или не создан путь к файлу или имя файла содержит некорректные символы, то пытаемся получить путь через диалог открытия файла
If Not Asc(destination$) Or Not ForceDirectories(GetPathPart(destination$)) Or Not CheckFilename(GetFilePart(destination$))
	Debug "destination$ = " + destination$
	destination$ = Left(source$, Len(source$) - Len(ext$) - 1) + "_2." + ext$ ; формируем путь к файлу для сохранения данных на основе исходного
	destination$ = SaveFileRequester("", destination$, "*." + ext$ + "|*." + ext$ + "|*.*|*.*", 0)
	If Not Asc(destination$) Or FileSize(GetPathPart(destination$)) <> -2
		Debug "неудача-destination$ = " + destination$
		End ; если выбор отменён, то закрываем программу
	EndIf
EndIf
; Если исходный файл не существует или файл сохранения игнорирован, то завершение программы, дальнейшее выполнятся не будет

; ClearDebugOutput()
; Debug "source$ = " + source$
; Debug "destination$ = " + destination$

Enumeration
	#ch1
	#ch2
	#ch4
	#ch16
	#ch128
	#chmsg1
	#chmsg2
	#chmsg4
	#btnOK
	#btnOpen1
	#btnOpen2
	#fieldPath1
	#fieldPath2
	#chOpen
EndEnumeration

;-┌──GUI──┐
If flag & 512 And OpenWindow(0, 0, 0, 420, 430, "Настройки css tidy", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget)
	CheckBoxGadget(#ch1, 10, 10, 400, 30, "1 - Сжатие (удаление пробелов)")
	CheckBoxGadget(#ch2, 10, 40, 400, 30, "2 - Фигурная скобка '{' на отдельной строке")
	CheckBoxGadget(#ch4, 10, 70, 400, 30, "4 - Списки классов в столбик, перенос после ','")
	CheckBoxGadget(#ch16, 10, 100, 400, 30, "16 - Преобразовать rgb(x,x,x) в #hex")
	CheckBoxGadget(#ch128, 10, 130, 400, 30, "128 - не удалять комментарии при сжатии")
	
	CheckBoxGadget(#chmsg1, 10, 170, 400, 30, "1 - показать ошибки незавершённой кавычки")
	CheckBoxGadget(#chmsg2, 10, 200, 400, 30, "2 - показать время выполнения обработки css")
	CheckBoxGadget(#chmsg4, 10, 230, 400, 30, "4 - показать пути")
	
	CheckBoxGadget(#chOpen, 10, 270, 400, 30, "Открыть файл после обработки")
	
	If flag & 1
		SetGadgetState(#ch1, #PB_Checkbox_Checked)
	EndIf
	If flag & 2
		SetGadgetState(#ch2, #PB_Checkbox_Checked)
	EndIf
	If flag & 4
		SetGadgetState(#ch4, #PB_Checkbox_Checked)
	EndIf
	If flag & 16
		SetGadgetState(#ch16, #PB_Checkbox_Checked)
	EndIf
	If flag & 128
		SetGadgetState(#ch128, #PB_Checkbox_Checked)
	EndIf
	
	If msg & 1
		SetGadgetState(#chmsg1, #PB_Checkbox_Checked)
	EndIf
	If msg & 2
		SetGadgetState(#chmsg2, #PB_Checkbox_Checked)
	EndIf
	If msg & 4
		SetGadgetState(#chmsg4, #PB_Checkbox_Checked)
	EndIf
	
	If open
		SetGadgetState(#chOpen, #PB_Checkbox_Checked)
	EndIf
	
	StringGadget(#fieldPath1, 10, 310, 400 - 26, 26, source$)
	StringGadget(#fieldPath2, 10, 350, 400 - 26, 26, destination$)
	ButtonGadget(#btnOpen1, 420 - 36, 310, 26, 26, Chr($2026))	; … (обзор)
	ButtonGadget(#btnOpen2, 420 - 36, 350, 26, 26, Chr($2026))	; … (обзор)
	
	
	ButtonGadget(#btnOK, 300, 383, 111, 40, "OK")
		

;-┌──Loop──┐
	Repeat
		Select WaitWindowEvent()
			Case #PB_Event_Gadget
				Select EventGadget()
					Case #btnOK
						Break
					Case #btnOpen1
						tmp$ = OpenFileRequester("", current$, "*.css|*.css|*.*|*.*", 0)
						If Asc(tmp$)
							SetGadgetText(#fieldPath1, tmp$)
						EndIf
					Case #btnOpen2
						tmp$ = SaveFileRequester("", destination$, "*." + ext$ + "|*." + ext$ + "|*.*|*.*", 0)
						If Asc(tmp$)
							SetGadgetText(#fieldPath2, tmp$)
						EndIf
				EndSelect
			Case #PB_Event_CloseWindow ; тоже что отмена, закрытие окна ничего не делает
				CloseWindow(0)
				End
		EndSelect
	ForEver
;-└──Loop──┘
	
	source$ = GetGadgetText(#fieldPath1)
	If Not Asc(source$) Or FileSize(source$) < 0
		MessageRequester("", "Исходный файл не существует, завершаем программу")
		End ; если выбор отменён, то закрываем программу
	EndIf
	destination$ = GetGadgetText(#fieldPath2)
	If Not Asc(destination$) Or FileSize(GetPathPart(destination$)) <> -2
		MessageRequester("", "Не указан файл назначения или не существует каталог для файла, завершаем программу")
		End ; если выбор отменён, то закрываем программу
	EndIf
	
	flag = 0
	If GetGadgetState(#ch1)
		flag + 1
	EndIf
	If GetGadgetState(#ch2)
		flag + 2
	EndIf
	If GetGadgetState(#ch4)
		flag + 4
	EndIf
	If GetGadgetState(#ch16)
		flag + 16
	EndIf
	If GetGadgetState(#ch128)
		flag + 128
	EndIf
	
	msg = 0
	If GetGadgetState(#chmsg1)
		msg + 1
	EndIf
	If GetGadgetState(#chmsg2)
		msg + 2
	EndIf
	If GetGadgetState(#chmsg4)
		msg + 4
	EndIf
	
	
	If GetGadgetState(#chOpen)
		open = 1
	Else
		open = 0
	EndIf
EndIf


Result\s = OpenFileToGadget(source$) ; вызов функции чтения файла с учётом кодировки.

;- Обработка
If msg & 2
	StartTime = ElapsedMilliseconds()
EndIf
If flag & 1
	cssCompression(@Result\s)
	Result\s = ReplaceString(Result\s, Chr(1), "")
	Result\s = ReplaceString(Result\s, ":0 0 0 0;", ":0;")
	If flag & 16
		RGBtoHEX(@Result)
	EndIf
Else
	If flag & 16
		RGBtoHEX(@Result)
	EndIf
	cssTidy(@Result\s)
	; Вычисляем длину данных css
	; Len = 0
	ForEach TextList()
		Len + Len(TextList())
	Next
	; Экспортируем список в текстовые данные css
	Result\s = Space(Len)
	*Point = @Result\s
	ForEach TextList()
		CopyMemoryString(TextList(), @*Point)
	Next
EndIf
If msg & 2
	StartTime = ElapsedMilliseconds() - StartTime
EndIf

#File = 0
If CreateFile(#File, destination$, g_Format)
	WriteStringFormat(#File, g_Format)
	WriteString(#File, Result\s)
	CloseFile(#File)
	If open
		CompilerSelect #PB_Compiler_OS
			CompilerCase #PB_OS_Windows
				RunProgram(destination$) ; открывает файл
			CompilerCase #PB_OS_Linux
				RunProgram("xdg-open", destination$, "") ; открывает файл
		CompilerEndSelect
	EndIf
EndIf

;- Message
; Вывод сообщений
If msg & 1 And Error
	 ; реагируем на ошибку, когда открыта кавычка, но не найдена закрывающая кавычка, пришлось оборвать ожидание кавычки на конце строки
	Message$ + "Количество ошибок: " + Str(Error) + #CRLF$
EndIf
If msg & 2
	Message$ + "Выполнено за " + Str(StartTime) + " мсек" + #CRLF$
EndIf
If msg & 4
	Message$ + source$ + #CRLF$ + destination$ + #CRLF$
EndIf
If Asc(Message$)
	CompilerSelect #PB_Compiler_OS
		CompilerCase #PB_OS_Windows
			MessageRequester("info", Message$, #MB_SYSTEMMODAL)
		CompilerCase #PB_OS_Linux
			MessageRequester("info", Message$)
	CompilerEndSelect
EndIf
;- END
Last edited by AZJIO on Tue Sep 24, 2024 4:57 am, edited 2 times in total.
AZJIO
Addict
Addict
Posts: 2187
Joined: Sun May 14, 2017 1:48 am

Re: css compression/decompression

Post by AZJIO »

The main thing here is not to go beyond the limits of memory

Code: Select all

EnableExplicit

Structure RGBA
	StructureUnion
		color.l
		b.a[4]
	EndStructureUnion
EndStructure

Define Color.RGBA


Define text$, *c.Character, red0, green0, blue0, *StartNum, sz = SizeOf(Character), *Start0, i, tmp$
text$ = "some text  rgb(255, 128, 0) something else  rgb    ( 45   , 134  , 221 )  yeah"

Debug text$
*c = @text$
While *c\c
	If *c\c = 'r' And CompareMemoryString(*c , @"rgb", #PB_String_NoCaseAscii, 3) = #PB_String_Equal
		*Start0 = *c
		*c + sz * 3
		While *c\c = 32
			*c + sz
		Wend
		If *c\c = 0
			Break
		EndIf
		If *c\c <> '('
			*c + sz
			Continue
		EndIf
		*c + sz
		While *c\c = 32
			*c + sz
		Wend
		If *c\c = 0
			Break
		EndIf
		*StartNum = *c
		While *c\c >= '0' And *c\c <= '9'
			*c + sz
		Wend
; 		Debug *StartNum
; 		Debug *c
		If *StartNum <> *c
; 			Debug PeekS(*StartNum, (*c - *StartNum) / sz)
; 			red0 = Val(PeekS(*StartNum, (*c - *StartNum) / sz))
			Color\b.a[2] = Val(PeekS(*StartNum, (*c - *StartNum) / sz))
; 			Debug red0
		Else
			*c + sz
			Continue
		EndIf
		While *c\c = 32
			*c + sz
		Wend
		If *c\c = 0
			Break
		EndIf
		If *c\c <> ','
			*c + sz
			Continue
		EndIf
		*c + sz
		While *c\c = 32
			*c + sz
		Wend
		If *c\c = 0
			Break
		EndIf
		*StartNum = *c
		While *c\c >= '0' And *c\c <= '9'
			*c + sz
		Wend
; 		Debug *StartNum
; 		Debug *c
		If *StartNum <> *c
; 			Debug PeekS(*StartNum, (*c - *StartNum) / sz)
; 			green0 = Val(PeekS(*StartNum, (*c - *StartNum) / sz))
			Color\b.a[1] = Val(PeekS(*StartNum, (*c - *StartNum) / sz))
; 			Debug green0
		Else
			*c + sz
			Continue
		EndIf
		While *c\c = 32
			*c + sz
		Wend
		If *c\c = 0
			Break
		EndIf
		If *c\c <> ','
			*c + sz
			Continue
		EndIf
		*c + sz
		While *c\c = 32
			*c + sz
		Wend
		If *c\c = 0
			Break
		EndIf
		*StartNum = *c
		While *c\c >= '0' And *c\c <= '9'
			*c + sz
		Wend
; 		Debug *StartNum
; 		Debug *c
		If *StartNum <> *c
; 			Debug PeekS(*StartNum, (*c - *StartNum) / sz)
; 			blue0 = Val(PeekS(*StartNum, (*c - *StartNum) / sz))
			Color\b.a[0] = Val(PeekS(*StartNum, (*c - *StartNum) / sz))
; 			Debug blue0
		Else
			*c + sz
			Continue
		EndIf
		While *c\c = 32
			*c + sz
		Wend
		If *c\c = 0
			Break
		EndIf
		If *c\c <> ')'
			*c + sz
			Continue
		EndIf
		For i = *Start0 To *c Step SizeOf(Character)
			PokeA(i, 1)
		Next
		tmp$ = "#" + RSet(Hex(Color\color), 6, "0")
		CopyMemory(@tmp$, *Start0, StringByteLength(tmp$))
	EndIf
	*c + sz
Wend
text$ = ReplaceString(text$, Chr(1), "")
Debug text$
User avatar
jacdelad
Addict
Addict
Posts: 2009
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: css compression/decompression

Post by jacdelad »

I have no idea what this is for... :mrgreen:
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
Post Reply