├── tests ├── __init__.py ├── data │ ├── test.1c │ ├── test.obj.1c │ ├── test.json │ ├── apam.cf │ ├── apam_old.cf │ ├── json_decode_src │ │ ├── raw_text_json_object_no_format │ │ ├── 83927322-865e-4e4b-a52e-002afeb3767d.0 │ │ ├── raw_end_string │ │ ├── raw_text_json_array │ │ ├── raw_text_json_object │ │ └── raw_b64_and_string │ └── form18-0.json ├── Configuration803 │ ├── __init__.py │ ├── 1Cv8.cf │ ├── 1Cv8-16.cf │ └── test_decode_c803.py ├── ConfigurationExtension803 │ ├── __init__.py │ ├── Расширение1.cfe │ ├── test.py │ └── test_decode_ce803.py ├── ExternalDataProcessor803 │ ├── __init__.py │ ├── ВнешняяОбработка803.epf │ ├── index.json │ ├── product.json │ └── test_decode_edt803.py ├── BadCodePage │ └── fb5c657e-db29-4ff2-bef9-fed0ded785dc.0 ├── ExternalDataProcessor802 │ ├── ВнешняяОбработка802.epf │ └── test_decode_edt802.py ├── metadata │ ├── test_template.py │ └── test_configuration802.py ├── test_decoder.py ├── Configuration802 │ └── test_decode_c802.py ├── test_helper.py ├── test_brace_file_decoder.py ├── test_container_reader.py ├── BadBraceExamples │ ├── test_brace_file_decoder.py │ └── bad_example │ │ └── ce802DataProcessorInvalidStartByte ├── test_form_decoder.py ├── test_form_elem_803_decoder.py ├── test_file_organizer_ce.py └── test_form_elem_802_decoder.py ├── src └── v8unpack │ ├── MetaDataObject │ ├── core │ │ ├── __init__.py │ │ ├── IncludeEmpty.py │ │ ├── Simple.py │ │ ├── SimpleWithInfo.py │ │ ├── IncludeSimple.py │ │ └── Container.py │ ├── Form │ │ ├── FormElements4 │ │ │ ├── __init__.py │ │ │ ├── ItemAddition.py │ │ │ ├── Button.py │ │ │ ├── Field.py │ │ │ ├── Group.py │ │ │ ├── Table.py │ │ │ ├── Decoration.py │ │ │ └── FormElements4.py │ │ ├── FormElements26 │ │ │ ├── __init__.py │ │ │ ├── Panel.py │ │ │ ├── FormElement.py │ │ │ └── FormElements26.py │ │ ├── FormElements27 │ │ │ ├── __init__.py │ │ │ ├── FormElements27.py │ │ │ └── FormElement.py │ │ └── __init__.py │ ├── EnumForm.py │ ├── Language.py │ ├── ReportForm.py │ ├── TaskForm.py │ ├── CatalogForm.py │ ├── DocumentForm.py │ ├── ExchangePlanForm.py │ ├── Role.py │ ├── SettingsStorageForm.py │ ├── BusinessProcessForm.py │ ├── ChartOfAccountsForm.py │ ├── DocumentJournalForm.py │ ├── FilterCriterionForm.py │ ├── Style.py │ ├── AccountingRegisterForm.py │ ├── AccumulationRegisterForm.py │ ├── CalculationRegisterForm.py │ ├── EnumCommand.py │ ├── InformationRegisterForm.py │ ├── TaskCommand.py │ ├── CatalogCommand.py │ ├── ChartOfCalculationTypesForm.py │ ├── DocumentCommand.py │ ├── ExternalDataSourceCubeForm.py │ ├── ExternalDataSourceTableForm.py │ ├── ScheduledJob.py │ ├── CommonForm.py │ ├── EventSubscription.py │ ├── ExchangePlanCommand.py │ ├── Bot.py │ ├── BusinessProcessCommand.py │ ├── ChartOfAccountsCommand.py │ ├── FilterCriterionCommand.py │ ├── CommonModule.py │ ├── ChartOfCalculationTypesCommand.py │ ├── ExternalDataSourceCubeCommand.py │ ├── ExternalDataSourceTableCommand.py │ ├── ChartOfCharacteristicTypeCommand.py │ ├── Enum.py │ ├── DefinedType.py │ ├── CommonTemplate.py │ ├── FunctionalOption.py │ ├── SessionParameter.py │ ├── FilterCriterion.py │ ├── IntegrationService.py │ ├── Interface.py │ ├── ExternalDataSource.py │ ├── HTTPService.py │ ├── ReportCommand.py │ ├── Sequences.py │ ├── StyleItem.py │ ├── WebService.py │ ├── CommandGroup.py │ ├── DocumentNumerators.py │ ├── ExternalDataSourceTable.py │ ├── DocumentJournalCommand.py │ ├── AccountingRegisterCommand.py │ ├── AccumulationRegisterCommand.py │ ├── CalculationRegisterCommand.py │ ├── DataProcessorCommand.py │ ├── InformationRegisterCommand.py │ ├── CalculationRegisterRecalculations.py │ ├── CommonCommand.py │ ├── ExternalDataSourceCube.py │ ├── DocumentJournal.py │ ├── SettingsStorage.py │ ├── Task.py │ ├── CommonTemplate4.py │ ├── CalculationRegister.py │ ├── Document.py │ ├── Constant.py │ ├── Report.py │ ├── ChartOfCalculationTypes.py │ ├── CommonAttribute.py │ ├── InformationRegister.py │ ├── FunctionalOptionsParameter.py │ ├── ChartOfCharacteristicTypeForm.py │ ├── Catalog.py │ ├── AccumulationRegister.py │ ├── AccountingRegister.py │ ├── ChartOfCharacteristicType.py │ ├── ChartOfAccounts.py │ ├── BusinessProcess.py │ ├── WSReference.py │ ├── XDTOPackage.py │ ├── Subsystem.py │ ├── ExchangePlan.py │ ├── DataProcessor.py │ └── CommonPicture.py │ ├── version.py │ ├── __main__.py │ ├── __init__.py │ ├── organizer_file_ce.py │ ├── index.py │ ├── container_writer.py │ ├── container_reader.py │ ├── MetaObject │ ├── ConfigurationExtension.py │ ├── ExternalDataProcessor.py │ └── Configuration.py │ ├── organizer_code.py │ ├── container_doc.py │ ├── metadata_types.py │ └── decoder.py ├── requirements.txt ├── docs ├── stage.png ├── Структура управляемой формы.md ├── Структура обычной формы.md ├── develop.md └── transition.md ├── exe ├── source.py └── build_exe.py ├── .gitignore ├── LICENSE ├── pyproject.toml └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/data/test.1c: -------------------------------------------------------------------------------- 1 | test.1c -------------------------------------------------------------------------------- /tests/data/test.obj.1c: -------------------------------------------------------------------------------- 1 | test.obj.1c -------------------------------------------------------------------------------- /tests/Configuration803/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/ConfigurationExtension803/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/ExternalDataProcessor803/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/data/test.json: -------------------------------------------------------------------------------- 1 | {"test.json": "test.json"} -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | twine 2 | pyinstaller 3 | tqdm 4 | build -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements4/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/v8unpack/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.2.6' 2 | 3 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements26/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements27/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/stage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saby-integration/v8unpack/HEAD/docs/stage.png -------------------------------------------------------------------------------- /tests/data/apam.cf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saby-integration/v8unpack/HEAD/tests/data/apam.cf -------------------------------------------------------------------------------- /tests/data/apam_old.cf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saby-integration/v8unpack/HEAD/tests/data/apam_old.cf -------------------------------------------------------------------------------- /tests/Configuration803/1Cv8.cf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saby-integration/v8unpack/HEAD/tests/Configuration803/1Cv8.cf -------------------------------------------------------------------------------- /tests/Configuration803/1Cv8-16.cf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saby-integration/v8unpack/HEAD/tests/Configuration803/1Cv8-16.cf -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/EnumForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form1 2 | 3 | 4 | class EnumForm(Form1): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Language.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import Simple 2 | 3 | 4 | class Language(Simple): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ReportForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form 2 | 3 | 4 | class ReportForm(Form): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/TaskForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form0 2 | 3 | 4 | class TaskForm(Form0): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CatalogForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form1 2 | 3 | 4 | class CatalogForm(Form1): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/DocumentForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form1 2 | 3 | 4 | class DocumentForm(Form1): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ExchangePlanForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form1 2 | 3 | 4 | class ExchangePlanForm(Form1): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Role.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.SimpleWithInfo import SimpleWithInfo 2 | 3 | 4 | class Role(SimpleWithInfo): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/SettingsStorageForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form1 2 | 3 | 4 | class SettingsStorageForm(Form1): 5 | pass -------------------------------------------------------------------------------- /tests/ConfigurationExtension803/Расширение1.cfe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saby-integration/v8unpack/HEAD/tests/ConfigurationExtension803/Расширение1.cfe -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/BusinessProcessForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form0 2 | 3 | 4 | class BusinessProcessForm(Form0): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ChartOfAccountsForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form0 2 | 3 | 4 | class ChartOfAccountsForm(Form0): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/DocumentJournalForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form1 2 | 3 | 4 | class DocumentJournalForm(Form1): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/FilterCriterionForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form1 2 | 3 | 4 | class FilterCriterionForm(Form1): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Style.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.SimpleWithInfo import SimpleWithInfo 2 | 3 | 4 | class Style(SimpleWithInfo): 5 | pass 6 | -------------------------------------------------------------------------------- /exe/source.py: -------------------------------------------------------------------------------- 1 | import v8unpack 2 | from multiprocessing import freeze_support 3 | 4 | if __name__ == '__main__': 5 | freeze_support() 6 | v8unpack.main() 7 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/AccountingRegisterForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form0 2 | 3 | 4 | class AccountingRegisterForm(Form0): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/AccumulationRegisterForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form1 2 | 3 | 4 | class AccumulationRegisterForm(Form1): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CalculationRegisterForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form1 2 | 3 | 4 | class CalculationRegisterForm(Form1): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/EnumCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class EnumCommand(IncludeSimple): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements26/Panel.py: -------------------------------------------------------------------------------- 1 | from ..FormElements27.Panel import Panel as Panel27 2 | 3 | 4 | class Panel(Panel27): 5 | ver = 26 6 | 7 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/InformationRegisterForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form1 2 | 3 | 4 | class InformationRegisterForm(Form1): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/TaskCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class TaskCommand(IncludeSimple): 5 | pass 6 | -------------------------------------------------------------------------------- /tests/BadCodePage/fb5c657e-db29-4ff2-bef9-fed0ded785dc.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saby-integration/v8unpack/HEAD/tests/BadCodePage/fb5c657e-db29-4ff2-bef9-fed0ded785dc.0 -------------------------------------------------------------------------------- /tests/ExternalDataProcessor802/ВнешняяОбработка802.epf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saby-integration/v8unpack/HEAD/tests/ExternalDataProcessor802/ВнешняяОбработка802.epf -------------------------------------------------------------------------------- /tests/ExternalDataProcessor803/ВнешняяОбработка803.epf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saby-integration/v8unpack/HEAD/tests/ExternalDataProcessor803/ВнешняяОбработка803.epf -------------------------------------------------------------------------------- /tests/data/json_decode_src/raw_text_json_object_no_format: -------------------------------------------------------------------------------- 1 | {"Major": 4, "Minor": 41, "Patch": 1, "MajorMinorPatch": "4.41.1", "PreReleaseTag": "", "PreReleaseLabel": ""} -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CatalogCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class CatalogCommand(IncludeSimple): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ChartOfCalculationTypesForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form1 2 | 3 | 4 | class ChartOfCalculationTypesForm(Form1): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/DocumentCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class DocumentCommand(IncludeSimple): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ExternalDataSourceCubeForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form0 2 | 3 | 4 | class ExternalDataSourceCubeForm(Form0): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ExternalDataSourceTableForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form0 2 | 3 | 4 | class ExternalDataSourceTableForm(Form0): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ScheduledJob.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.SimpleWithInfo import SimpleWithInfo 2 | 3 | 4 | class ScheduledJob(SimpleWithInfo): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CommonForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form1 2 | from .Form.Form9 import Form9 3 | 4 | 5 | class CommonForm(Form1): 6 | 7 | pass 8 | -------------------------------------------------------------------------------- /tests/ExternalDataProcessor803/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExternalDataProcessor.obj.1c": "core/test.1c", 3 | "Области include": { 4 | "core_Область1": "core_Область2" 5 | } 6 | } -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/EventSubscription.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.SimpleWithInfo import SimpleWithInfo 2 | 3 | 4 | class EventSubscription(SimpleWithInfo): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ExchangePlanCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class ExchangePlanCommand(IncludeSimple): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Bot.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import SimpleNameFolder 2 | 3 | 4 | class Bot(SimpleNameFolder): 5 | ext_code = { 6 | 'obj': 1, 7 | } 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/BusinessProcessCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class BusinessProcessCommand(IncludeSimple): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ChartOfAccountsCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class ChartOfAccountsCommand(IncludeSimple): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/FilterCriterionCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class FilterCriterionCommand(IncludeSimple): 5 | pass 6 | -------------------------------------------------------------------------------- /tests/data/json_decode_src/83927322-865e-4e4b-a52e-002afeb3767d.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saby-integration/v8unpack/HEAD/tests/data/json_decode_src/83927322-865e-4e4b-a52e-002afeb3767d.0 -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CommonModule.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import SimpleNameFolder 2 | 3 | 4 | class CommonModule(SimpleNameFolder): 5 | ext_code = {'obj': 0} 6 | pass 7 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ChartOfCalculationTypesCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class ChartOfCalculationTypesCommand(IncludeSimple): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ExternalDataSourceCubeCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class ExternalDataSourceCubeCommand(IncludeSimple): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ExternalDataSourceTableCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class ExternalDataSourceTableCommand(IncludeSimple): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from multiprocessing import freeze_support 3 | 4 | from v8unpack import main 5 | 6 | if __name__ == '__main__': 7 | freeze_support() 8 | sys.exit(main()) 9 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ChartOfCharacteristicTypeCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class ChartOfCharacteristicTypeCommand(IncludeSimple): 5 | pass 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements26/FormElement.py: -------------------------------------------------------------------------------- 1 | from ..FormElements27.FormElement import FormProps as FormProps27, FormElement as FormElement27, Anchor 2 | 3 | 4 | class FormProps(FormProps27): 5 | name_index = 3 6 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Enum.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class Enum(Container): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][5][1] 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/DefinedType.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import Simple 2 | 3 | 4 | class DefinedType(Simple): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][3] 8 | -------------------------------------------------------------------------------- /src/v8unpack/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from .index import update_index 4 | from .v8unpack import main, extract, extract_all, build, build_all, update_index, update_index_all 5 | 6 | if __name__ == '__main__': 7 | sys.exit(main()) 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CommonTemplate.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject import MetaDataObject 2 | from .CommonTemplate4 import CommonTemplate4 3 | 4 | 5 | class CommonTemplate(MetaDataObject): 6 | versions = { 7 | '4': CommonTemplate4 8 | } 9 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/FunctionalOption.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import Simple 2 | 3 | 4 | class FunctionalOption(Simple): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][1] 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/SessionParameter.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import Simple 2 | 3 | 4 | class SessionParameter(Simple): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][1][1] 8 | -------------------------------------------------------------------------------- /tests/ConfigurationExtension803/test.py: -------------------------------------------------------------------------------- 1 | from base64 import b64decode 2 | import hashlib 3 | 4 | with open('./tmp/decode_stage_1/54477ae0-e46e-466a-b7b2-629b261e2ff4', 'rb') as file: 5 | a = file.read() 6 | 7 | b = hashlib.sha1(a).digest().hex() 8 | pass -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/FilterCriterion.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class FilterCriterion(Container): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][5][1] 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/IntegrationService.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class IntegrationService(Container): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][1] 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Interface.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.SimpleWithInfo import SimpleWithInfo 2 | 3 | 4 | class Interface(SimpleWithInfo): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][2] 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ExternalDataSource.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class ExternalDataSource(Container): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][1][1] 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/HTTPService.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class HTTPService(Container): 5 | pass 6 | 7 | @classmethod 8 | def get_decode_header(cls, header): 9 | return header[0][1][2] 10 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ReportCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class ReportCommand(IncludeSimple): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][2][9] 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Sequences.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class Sequences(Container): 5 | pass 6 | 7 | @classmethod 8 | def get_decode_header(cls, header): 9 | return header[0][1][7][1] 10 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/StyleItem.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import Simple 2 | 3 | 4 | class StyleItem(Simple): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][3] 8 | 9 | pass 10 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/WebService.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class WebService(Container): 5 | pass 6 | 7 | @classmethod 8 | def get_decode_header(cls, header): 9 | return header[0][1][2] 10 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CommandGroup.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import Simple 2 | 3 | 4 | class CommandGroup(Simple): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][6] 8 | 9 | pass 10 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/DocumentNumerators.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import Simple 2 | 3 | 4 | class DocumentNumerators(Simple): 5 | pass 6 | # @classmethod 7 | # def get_decode_header(cls, header_data): 8 | # return header_data[0][1][1] 9 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ExternalDataSourceTable.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class ExternalDataSourceTable(Container): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][1][1] 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/DocumentJournalCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class DocumentJournalCommand(IncludeSimple): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][2][9] 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/AccountingRegisterCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class AccountingRegisterCommand(IncludeSimple): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][2][9] 8 | -------------------------------------------------------------------------------- /exe/build_exe.py: -------------------------------------------------------------------------------- 1 | import PyInstaller.__main__ 2 | 3 | PyInstaller.__main__.run([ 4 | 'source.py', 5 | '--onefile', 6 | '-n', 'v8unpack', 7 | '--collect-all', 'v8unpack', 8 | '--hidden-import', 'tqdm', 9 | '--hidden-import', 'os', 10 | '--distpath', '.' 11 | ]) 12 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/AccumulationRegisterCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class AccumulationRegisterCommand(IncludeSimple): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][2][9] 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CalculationRegisterCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class CalculationRegisterCommand(IncludeSimple): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][2][9] 8 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/DataProcessorCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class DataProcessorCommand(IncludeSimple): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][2][9] 8 | pass 9 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements26/FormElements26.py: -------------------------------------------------------------------------------- 1 | from .FormElement import FormProps 2 | from .Panel import Panel 3 | from ..FormElements27.FormElements27 import FormElements27 4 | 5 | 6 | class FormElements26(FormElements27): 7 | FormProps = FormProps 8 | Panel = Panel 9 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/InformationRegisterCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.IncludeSimple import IncludeSimple 2 | 3 | 4 | class InformationRegisterCommand(IncludeSimple): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][2][9] 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg 2 | *.py[cod] 3 | *.so 4 | *.egg-info/ 5 | 6 | .tox 7 | .idea 8 | v8unpack.exe 9 | 10 | __pycache__/ 11 | build/ 12 | dist/ 13 | docs/_build/ 14 | tmp/ 15 | venv/ 16 | temp/ 17 | tests_tmp/ 18 | temp2/ 19 | tests/*/src/ 20 | tests/*/core/ 21 | *.spec 22 | /tests/metadata/data/ 23 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CalculationRegisterRecalculations.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class CalculationRegisterRecalculations(Container): 5 | pass 6 | 7 | @classmethod 8 | def get_decode_header(cls, header): 9 | return header[0][1][7][1] 10 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CommonCommand.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import Simple 2 | 3 | 4 | class CommonCommand(Simple): 5 | ext_code = {'obj': 2} 6 | help_file_number = 1 7 | 8 | @classmethod 9 | def get_decode_header(cls, header_data): 10 | return header_data[0][1][1][2][9] 11 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ExternalDataSourceCube.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class ExternalDataSourceCube(Container): 5 | ext_code = {'obj': 2, 'mgr': 1} 6 | 7 | @classmethod 8 | def get_decode_header(cls, header_data): 9 | return header_data[0][1][1][1] 10 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements4/ItemAddition.py: -------------------------------------------------------------------------------- 1 | from .FormElement import FormElement, calc_offset 2 | from v8unpack.helper import calc_offset 3 | 4 | 5 | class ItemAddition(FormElement): 6 | @classmethod 7 | def get_name_node_offset(cls, raw_data): 8 | return calc_offset([(3, 1), (3, 0)], raw_data) 9 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/DocumentJournal.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class DocumentJournal(Container): 5 | help_file_number = 0 6 | ext_code = { 7 | 'mgr': 1 8 | } 9 | 10 | @classmethod 11 | def get_decode_header(cls, header): 12 | return header[0][1][3][1] 13 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/SettingsStorage.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class SettingsStorage(Container): 5 | ext_code = { 6 | 'mgr': 8, 7 | } 8 | # help_file_number = 5 9 | 10 | @classmethod 11 | def get_decode_header(cls, header): 12 | return header[0][1][1][1] 13 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Task.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class Task(Container): 5 | ext_code = { 6 | 'obj': 6, 7 | 'mgr': 7 8 | } 9 | help_file_number = 5 10 | # @classmethod 11 | # def get_decode_header(cls, header): 12 | # return header[0][1][9][1] 13 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CommonTemplate4.py: -------------------------------------------------------------------------------- 1 | from .Template import Template2 2 | 3 | 4 | class CommonTemplate4(Template2): 5 | @classmethod 6 | def get_decode_header(cls, header_data): 7 | return header_data[0][1][1] 8 | 9 | @classmethod 10 | def get_template_type(cls, header_data): 11 | return header_data[0][1][2] 12 | 13 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CalculationRegister.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class CalculationRegister(Container): 5 | ext_code = { 6 | 'obj': 1, 7 | 'mgr': 2 8 | } 9 | help_file_number = 0 10 | 11 | @classmethod 12 | def get_decode_header(cls, header): 13 | return header[0][1][15][1] 14 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Document.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import FormContainer 2 | 3 | 4 | class Document(FormContainer): 5 | help_file_number = 1 6 | ext_code = { 7 | 'obj': 0, 8 | 'mgr': 2 9 | } 10 | 11 | @classmethod 12 | def get_decode_header(cls, header_data): 13 | return header_data[0][1][9][1] 14 | 15 | -------------------------------------------------------------------------------- /tests/data/json_decode_src/raw_end_string: -------------------------------------------------------------------------------- 1 | { 2 | {"S1 3 | S2 4 | S2 5 | S3 6 | S4"},"MainTable", 7 | {#base64:AgFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdayG8=}, 8 | {#base64:iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAA 9 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Constant.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import Simple 2 | 3 | 4 | class Constant(Simple): 5 | ext_code = { 6 | 'mgr': '1', # модуль менеджера Константы 7 | 'obj': '0', # модуль менеджера значения Константы 8 | } 9 | 10 | @classmethod 11 | def get_decode_header(cls, header_data): 12 | return header_data[0][1][1][1][1] 13 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Report.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class Report(Container): 5 | ext_code = { 6 | 'mgr': '2', # модуль менеджера Отчета 7 | 'obj': '0', # модуль Отчета 8 | } 9 | help_file_number = 1 10 | 11 | @classmethod 12 | def get_decode_header(cls, header_data): 13 | return header_data[0][1][3][1] 14 | -------------------------------------------------------------------------------- /tests/ExternalDataProcessor803/product.json: -------------------------------------------------------------------------------- 1 | { 2 | "saby_vo3_83_uf": { 3 | "src": "src", 4 | "bin": "temp/ВнешняяОбработка803.epf", 5 | "temp": "temp", 6 | "index": "index.json", 7 | "disable": false, 8 | "options": { 9 | "version": "80314", 10 | "descent": null, 11 | "gui": null, 12 | "auto_include": true 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ChartOfCalculationTypes.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class ChartOfCalculationTypes(Container): 5 | ext_code = { 6 | 'obj': 0, 7 | 'mgr': 3 8 | } 9 | help_file_number = 1 10 | predefined_file_number = 2 11 | 12 | @classmethod 13 | def get_decode_header(cls, header): 14 | return header[0][1][1][1] 15 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CommonAttribute.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import Simple 2 | 3 | 4 | class CommonAttribute(Simple): 5 | # ext_code = { 6 | # 'mgr': '1', # модуль менеджера Константы 7 | # 'obj': '0', # модуль менеджера значения Константы 8 | # } 9 | 10 | @classmethod 11 | def get_decode_header(cls, header_data): 12 | return header_data[0][1][1][1][1] 13 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/InformationRegister.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class InformationRegister(Container): 5 | ext_code = { 6 | 'mgr': 2, # модуль менеджера 7 | 'obj': 1, # Модуль набора записей 8 | } 9 | help_file_number = 0 10 | 11 | @classmethod 12 | def get_decode_header(cls, header_data): 13 | return header_data[0][1][15][1] 14 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/FunctionalOptionsParameter.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Simple import Simple 2 | 3 | 4 | class FunctionalOptionsParameter(Simple): 5 | # ext_code = { 6 | # 'mgr': '1', # модуль менеджера Константы 7 | # 'obj': '0', # модуль менеджера значения Константы 8 | # } 9 | 10 | @classmethod 11 | def get_decode_header(cls, header_data): 12 | return header_data[0][1][1] 13 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ChartOfCharacteristicTypeForm.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.Form import Form0 2 | 3 | 4 | class ChartOfCharacteristicTypeForm(Form0): 5 | # @classmethod 6 | # def get_form_root(cls, header_data): 7 | # obj_version = header_data[0][1][0] 8 | # if obj_version == '9': 9 | # return header_data 10 | # else: 11 | # return header_data[0] 12 | 13 | pass 14 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements4/Button.py: -------------------------------------------------------------------------------- 1 | from .FormElement import FormElement 2 | from v8unpack.helper import calc_offset 3 | 4 | 5 | class Button(FormElement): 6 | @classmethod 7 | def get_name_node_offset(cls, raw_data): 8 | return calc_offset([(3, 1), (2, 0)], raw_data) 9 | 10 | @classmethod 11 | def get_command_link_offset(cls, raw_data): 12 | return calc_offset([(3, 1), (5, 0)], raw_data) 13 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements4/Field.py: -------------------------------------------------------------------------------- 1 | from .FormElement import FormElement 2 | from v8unpack.helper import calc_offset 3 | 4 | 5 | class Field(FormElement): 6 | @classmethod 7 | def get_name_node_offset(cls, raw_data): 8 | return calc_offset([(3, 1), (1, 1), (2, 0)], raw_data) 9 | 10 | @classmethod 11 | def get_prop_link_offset(cls, raw_data): 12 | return calc_offset([(3, 1), (1, 1), (7, 0)], raw_data) 13 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Catalog.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import FormContainer 2 | 3 | 4 | class Catalog(FormContainer): 5 | ext_code = { 6 | 'mgr': '3', # модуль менеджера Справочника 7 | 'obj': '0', # модуль объекта Справочника 8 | } 9 | help_file_number = 1 10 | predefined_file_number = '1c' 11 | 12 | @classmethod 13 | def get_decode_header(cls, header): 14 | return header[0][1][9][1] 15 | -------------------------------------------------------------------------------- /docs/Структура управляемой формы.md: -------------------------------------------------------------------------------- 1 | Для 27 версии 2 | 3 | 4 | # Расположение элементов 5 | 6 | 7 | # Привязка данных к элементам форм 8 | У каждого реквизита формы есть уникальный номер который указывается на элементе формы. 9 | 10 | #copyinfo 11 | 0. 4 12 | 1. список реквизитов, первый элемент сама обраобтка, потом реквизиты 13 | 0. uuid реквизита 14 | 1. uuid реквизита 15 | 2. 1?? 16 | 3.0. тип метаданных 17 | 3.1. имя реквизита 18 | 2. список 19 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/AccumulationRegister.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class AccumulationRegister(Container): 5 | ext_code = { 6 | 'mgr': 2, # модуль менеджера 7 | 'obj': 1, # Модуль набора записей 8 | } 9 | help_file_number = 0 10 | predefined_file_number = 3 # Агрегаты 11 | 12 | @classmethod 13 | def get_decode_header(cls, header_data): 14 | return header_data[0][1][13][1] 15 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/AccountingRegister.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class AccountingRegister(Container): 5 | ext_code = { 6 | 'obj': 6, 7 | 'mgr': 7 8 | } 9 | help_file_number = 5 10 | 11 | @classmethod 12 | def get_decode_header(cls, header): 13 | obj_version = int(header[0][1][0]) 14 | if obj_version > 21: 15 | return header[0][1][16][1] 16 | else: 17 | return header[0][1][15][1] 18 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ChartOfCharacteristicType.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | 3 | 4 | class ChartOfCharacteristicType(Container): 5 | ext_code = { 6 | 'mgr': '16', # модуль менеджера Плана видов характеристик 7 | 'obj': '15', # модуль менеджера Плана видов характеристик 8 | } 9 | help_file_number = 5 10 | predefined_file_number = 7 11 | 12 | @classmethod 13 | def get_decode_header(cls, header_data): 14 | return header_data[0][1][13][1] 15 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ChartOfAccounts.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | from .. import helper 3 | 4 | 5 | class ChartOfAccounts(Container): 6 | ext_code = { 7 | 'obj': 14, 8 | 'mgr': 15 9 | } 10 | help_file_number = 5 11 | predefined_file_number = 9 12 | 13 | @classmethod 14 | def get_decode_header(cls, header): 15 | return header[0][1][15][1] 16 | 17 | # @classmethod 18 | # def get_decode_includes(cls, header_data): 19 | # return super().get_decode_includes(header_data) -------------------------------------------------------------------------------- /tests/data/json_decode_src/raw_text_json_array: -------------------------------------------------------------------------------- 1 | [{ 2 | "Major": 4, 3 | "Minor": 41, 4 | "Patch": 1, 5 | "MajorMinorPatch": "4.41.1", 6 | "PreReleaseTag": "", 7 | "PreReleaseLabel": "", 8 | "PreReleaseNumber": "", 9 | "SemVer": "4.41.1", 10 | "FullSemVer": "4.41.1", 11 | "AssemblySemVer": "4.41.1.0", 12 | "AssemblySemFileVer": "4.41.1.Sha.d4b5d62", 13 | "ShortSha": "d4b5d62", 14 | "Sha": "d4b5d6279298b1ed10f1fd71f3156c339c205dc6", 15 | "VersionSourceSha": "d4b5d6279298b1ed10f1fd71f3156c339c205dc6", 16 | "BuildMetaData": "", 17 | "CommitsSinceVersionSource": 44, 18 | "CommitDate": "2021-10-27" 19 | }] -------------------------------------------------------------------------------- /tests/data/json_decode_src/raw_text_json_object: -------------------------------------------------------------------------------- 1 | { 2 | "Major": 4, 3 | "Minor": 41, 4 | "Patch": 1, 5 | "MajorMinorPatch": "4.41.1", 6 | "PreReleaseTag": "", 7 | "PreReleaseLabel": "", 8 | "PreReleaseNumber": "", 9 | "SemVer": "4.41.1", 10 | "FullSemVer": "4.41.1", 11 | "AssemblySemVer": "4.41.1.0", 12 | "AssemblySemFileVer": "4.41.1.Sha.d4b5d62", 13 | "ShortSha": "d4b5d62", 14 | "Sha": "d4b5d6279298b1ed10f1fd71f3156c339c205dc6", 15 | "VersionSourceSha": "d4b5d6279298b1ed10f1fd71f3156c339c205dc6", 16 | "BuildMetaData": "", 17 | "CommitsSinceVersionSource": 44, 18 | "CommitDate": "2021-10-27" 19 | } -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/core/IncludeEmpty.py: -------------------------------------------------------------------------------- 1 | from ...MetaDataObject import MetaDataObject 2 | 3 | 4 | class IncludeEmpty(MetaDataObject): 5 | 6 | @classmethod 7 | def decode(cls, src_dir: str, file_name: str, dest_dir: str, dest_path: str, options, *, parent_type=None, 8 | parent_container_uuid=None): 9 | raise Exception('Так быть не должно, этот класс обслуживает вложенные объекты') 10 | 11 | @classmethod 12 | def decode_internal_include(cls, parent, header_data, src_dir, dest_dir, dest_path, version): 13 | return 14 | 15 | def encode_object(self, src_dir, file_name, dest_dir): 16 | raise Exception('Так быть не должно, этот класс обслуживает вложенные объекты') 17 | -------------------------------------------------------------------------------- /tests/metadata/test_template.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.append("../../src/") 6 | # from v8unpack.MetaDataObject.versions.Template803 import Meta 7 | from v8unpack.decoder import Decoder 8 | from v8unpack.helper import clear_dir 9 | 10 | 11 | class TestTemplate(unittest.TestCase): 12 | def setUp(self) -> None: 13 | self.current_dir = os.path.dirname(__file__) 14 | self.data_dir = os.path.join(self.current_dir, 'data') 15 | self.temp_dir = os.path.join(self.current_dir, 'temp') 16 | clear_dir(self.temp_dir) 17 | 18 | def test_decode(self): 19 | Decoder.decode_include(['Template', [ 20 | self.data_dir, '02f0dbd6-4bb9-4fdb-9c02-116da924943f', self.temp_dir, '', '803']]) 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/test_decoder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.append("../../src/") 6 | from v8unpack.decoder import Decoder 7 | from v8unpack import helper 8 | 9 | 10 | class TestDecoder(unittest.TestCase): 11 | def setUp(self) -> None: 12 | self.current_dir = os.path.dirname(__file__) 13 | self.data_dir = os.path.join(self.current_dir, 'data', 'json_decode_src') 14 | self.temp_dir = os.path.join(self.data_dir, 'temp') 15 | 16 | def test_decode_include(self): 17 | params = [ 18 | 'AccountingRegister', 19 | ['C:\\project\\v8unpack\\tests_tmp\\erp2\\tmp\\decode_stage_1\\1', '7b248429-2107-4371-b371-a099028cd179', 20 | 'C:\\project\\v8unpack\\tests_tmp\\erp2\\tmp\\decode_stage_3', 'AccountingRegister', {'version': '803'}]] 21 | tasks = Decoder.decode_include(params) 22 | -------------------------------------------------------------------------------- /tests/metadata/test_configuration802.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.append("../../src/") 6 | from v8unpack.MetaObject.Configuration802 import Configuration802 7 | from v8unpack.decoder import Decoder 8 | from v8unpack.helper import clear_dir 9 | 10 | 11 | class TestTemplate(unittest.TestCase): 12 | def setUp(self) -> None: 13 | self.current_dir = os.path.dirname(__file__) 14 | self.data_dir = os.path.join(self.current_dir, 'data') 15 | self.temp_dir = os.path.join(self.current_dir, 'temp') 16 | self.temp_dir2 = os.path.join(self.current_dir, 'temp2') 17 | 18 | def test_decode(self): 19 | clear_dir(self.temp_dir) 20 | clear_dir(self.temp_dir2) 21 | Configuration802.decode(self.data_dir, self.temp_dir) 22 | Configuration802.encode(self.temp_dir, self.temp_dir2) 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 infactum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "v8unpack" 3 | dynamic = ["version"] 4 | dependencies = ["tqdm>=4"] 5 | authors = [ 6 | { name = "Razgovorov Mikhail", email = "1338833@gmail.com" }, 7 | ] 8 | description = "Unpacking binaries 1C to JSON for GIT" 9 | keywords = ["1c", "cf", "cfe", "epf", "v8unpack", "saby", "sbis", "сбис", "1с"] 10 | readme = "README.md" 11 | requires-python = ">=3.7" 12 | classifiers = [ 13 | 'Development Status :: 5 - Production/Stable', 14 | 'Natural Language :: Russian', 15 | 'Programming Language :: Python :: 3', 16 | 'Topic :: Software Development', 17 | "Operating System :: OS Independent" 18 | ] 19 | license = "MIT" 20 | 21 | [tool.setuptools.dynamic] 22 | version = { attr = "v8unpack.version.__version__" } 23 | 24 | [project.scripts] 25 | v8unpack = "v8unpack:main" 26 | 27 | [project.urls] 28 | Homepage = "https://github.com/saby-integration/v8unpack" 29 | Issues = "https://github.com/saby-integration/v8unpack/issues" 30 | 31 | [build-system] 32 | requires = ["setuptools", "setuptools-scm"] 33 | build-backend = "setuptools.build_meta" 34 | 35 | [tool.setuptools.packages.find] 36 | where = ["src"] 37 | include = ["v8unpack*"] 38 | -------------------------------------------------------------------------------- /tests/Configuration802/test_decode_c802.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.append("../../src/") 6 | from v8unpack.unittest_helper import HelperTestDecode 7 | 8 | 9 | class TestDecode(HelperTestDecode): 10 | # processes = 1 # uncomment for debug 11 | 12 | def setUp(self): 13 | super(TestDecode, self).setUp() 14 | self.src_dir = os.path.dirname(__file__) 15 | self.src_file = '1Cv8_103.cf' 16 | self.init( 17 | version='803' 18 | ) 19 | pass 20 | 21 | def test_01_decode_stage0(self): 22 | super(TestDecode, self).decode_stage0() 23 | 24 | def test_01_decode_stage1(self): 25 | super(TestDecode, self).decode_stage1() 26 | 27 | def test_03_decode_stage3(self): 28 | super(TestDecode, self).decode_stage3() 29 | 30 | def test_06_encode_stage3(self): 31 | super(TestDecode, self).encode_stage3() 32 | 33 | def test_08_encode_stage1(self): 34 | super(TestDecode, self).encode_stage1() 35 | 36 | def test_09_encode_stage0(self): 37 | super(TestDecode, self).encode_stage0() 38 | 39 | 40 | if __name__ == '__main__': 41 | unittest.main() 42 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/BusinessProcess.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | from .. import helper 3 | 4 | 5 | class BusinessProcess(Container): 6 | ext_code = { 7 | 'obj': 6, 8 | 'mgr': 8 9 | } 10 | help_file_number = 5 11 | pass 12 | 13 | def decode_object(self, src_dir, file_name, dest_dir, dest_path, version, header_data): 14 | super().decode_object(src_dir, file_name, dest_dir, dest_path, version, header_data) 15 | try: 16 | package = helper.bin_read(src_dir, f'{self.header["uuid"]}.7') 17 | helper.bin_write(package, self.new_dest_dir, 'Карта маршрута.bin') 18 | except FileNotFoundError: 19 | return 20 | 21 | def encode_object(self, src_dir, file_name, dest_dir): 22 | res = super().encode_object(src_dir, file_name, dest_dir) 23 | try: 24 | package = helper.bin_read(src_dir, 'Карта маршрута.bin') 25 | file_name = f'{self.header["uuid"]}.7' 26 | helper.bin_write(package, dest_dir, file_name) 27 | self.file_list.append(file_name) 28 | except FileNotFoundError: 29 | return 30 | return res -------------------------------------------------------------------------------- /tests/test_helper.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import unittest 3 | 4 | from v8unpack import helper 5 | 6 | sys.path.append("../../src/") 7 | 8 | 9 | class TestHelper(unittest.TestCase): 10 | def test_get_extension_from_comment(self): 11 | data = [ 12 | ("", "bin"), 13 | ("png", "png"), 14 | (" png ", "png"), 15 | ("Комментарий png", "png") 16 | ] 17 | for elem in data: 18 | self.assertEqual(elem[1], helper.get_extension_from_comment(elem[0])) 19 | 20 | def test_check_version(self): 21 | data = [ 22 | ("12.1.1", "12.1.0", False), 23 | ("12.1.1", "12.1.1", False), 24 | ("12.1.1", "12.1.2", False), 25 | ("12.1.1", "13.1.1", True), 26 | ("12.1.1", "12.2.1", True), 27 | ("", "", True), 28 | ("F", "A", True), 29 | ("12.1.1", "", True), 30 | ("12.1.1", "d", True) 31 | ] 32 | for elem in data: 33 | try: 34 | helper.check_version(elem[0], elem[1]) 35 | self.assertFalse(elem[2]) 36 | except AssertionError: 37 | self.assertTrue(elem[2]) 38 | -------------------------------------------------------------------------------- /tests/data/json_decode_src/raw_b64_and_string: -------------------------------------------------------------------------------- 1 | { 2 | {"Ь 3 | ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КонтурEDI_ДополнительныеРеквизиты КАК ТаблицаРеквизитПоставщик 4 | И (ТаблицаРеквизитПоставщик.Свойство = ""ПартнерКакПоставщик"") 5 | Наименование"},"MainTable", 6 | {#base64:AgFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdaFTS2/0iI3BT9l0HEdayG8=}, 7 | {#base64:iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAA 8 | 5pmwVl8WWoJxcb6oi3X2G1fYrADgBnxJWpPZtAFxAAAAAElFTkSuQmCC}, 9 | {1,"ru","Основной список объектов к выгрузке (только ссылочные объекты). 10 | Доступен для интерактивного и проограмного использования."}, 11 | {1, 12 | {"ru","из них 13 | не сформировано"}, 14 | {"en","из них 15 | не сформировано"} 16 | },4, 17 | { 18 | {#base64:AgFTS2/0iI3BTqDV67a9oKcNdU7LDcIwDIVrpS6RcyLFSdrGWyAhBijQKwfUW5XJ 19 | 20 | OLAFa7ACtU0KbdXYsvP8e++13/F7P54D6rIY1KHt++5+UwyOKpVFoq84WA2EvA6U 21 | 22 | bO4uca29HkfTGLjrKRnntubXWDaaZcHBr8JKDERiGY2roOWGAy5KzPaPgkhTJ5Wm 23 | 24 | rNRsiEms6KKeAXL4Mm21pt25qHw1y2fx2DZ18FdnADs0oeqciRG9cdGeLwhYtdjQ 25 | 26 | XFhJtSKWz30vMgcItzCmDw==},0} 27 | } 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/WSReference.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | from ..MetaDataObject.core.Simple import Simple 5 | 6 | 7 | class WSReference(Simple): 8 | ext_code = {} 9 | 10 | @classmethod 11 | def get_decode_header(cls, header): 12 | return header[0][1][2] 13 | 14 | def decode_object(self, src_dir, file_name, dest_dir, dest_path, version, header_data): 15 | super().decode_object(src_dir, file_name, dest_dir, dest_path, version, header_data) 16 | 17 | src = os.path.join(src_dir, f'{self.header["uuid"]}.0') 18 | dest = os.path.join(self.new_dest_dir, self.header["name"], self.__class__.__name__) 19 | if not os.path.isdir(src): 20 | return 21 | shutil.copytree(src, dest) 22 | 23 | def encode_object(self, src_dir, file_name, dest_dir): 24 | super().encode_object(src_dir, file_name, dest_dir) 25 | 26 | src = os.path.join(src_dir, self.header["name"], self.__class__.__name__) 27 | if not os.path.isdir(src): 28 | return 29 | dir_name = f'{self.header["uuid"]}.0' 30 | dest = os.path.join(dest_dir, dir_name) 31 | shutil.copytree(src, dest) 32 | self.file_list.append(dir_name) 33 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/XDTOPackage.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject import MetaDataObject 2 | import os 3 | from .. import helper 4 | 5 | 6 | class XDTOPackage(MetaDataObject): 7 | 8 | def decode_object(self, src_dir, file_name, dest_dir, dest_path, version, header_data): 9 | super().decode_object(src_dir, file_name, dest_dir, dest_path, version, header_data) 10 | try: 11 | package = helper.bin_read(src_dir, f'{self.header["uuid"]}.0') 12 | helper.bin_write(package, self.new_dest_dir, f'{self.new_dest_file_name}.bin') 13 | except FileNotFoundError: 14 | return 15 | 16 | def decode_includes(self, src_dir, dest_dir, dest_path, header): 17 | return [] 18 | 19 | def encode_includes(self, src_dir, file_name, dest_dir, parent_id): 20 | return [] 21 | 22 | def encode_object(self, src_dir, file_name, dest_dir): 23 | try: 24 | package = helper.bin_read(src_dir, f'{file_name}.bin') 25 | file_name = f'{self.header["uuid"]}.0' 26 | helper.bin_write(package, dest_dir, file_name) 27 | self.file_list.append(file_name) 28 | except FileNotFoundError: 29 | return 30 | return [] 31 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Subsystem.py: -------------------------------------------------------------------------------- 1 | from .. import helper 2 | from ..ext_exception import ExtException 3 | from ..MetaDataObject.core.Container import Container 4 | 5 | 6 | class Subsystem(Container): 7 | help_file_number = 0 8 | ext_code = {} 9 | 10 | # todo Subsystem не учтено в старых конфакх может быть папка 0 c image и info 11 | 12 | def decode_object(self, src_dir, file_name, dest_dir, dest_path, version, header_data): 13 | super(Subsystem, self).decode_object(src_dir, file_name, dest_dir, dest_path, version, header_data) 14 | try: 15 | self.header['info'] = helper.brace_file_read(src_dir, f'{self.header["uuid"]}.1') 16 | except FileNotFoundError: 17 | self.header['info'] = None 18 | return 19 | pass 20 | 21 | def write_encode_object(self, dest_dir): 22 | super().write_encode_object(dest_dir) 23 | try: 24 | if self.header['info']: 25 | file_name = f'{self.header["uuid"]}.1' 26 | helper.brace_file_write(self.header['info'], dest_dir, file_name) 27 | self.file_list.append(file_name) 28 | except Exception as err: 29 | raise ExtException(parent=err, 30 | detail=f'{self.__class__.__name__} {self.header["name"]} {self.header["uuid"]}') 31 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/ExchangePlan.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import Container 2 | from ..ext_exception import ExtException 3 | from .. import helper 4 | 5 | 6 | class ExchangePlan(Container): 7 | ext_code = {'obj': 2, 'mgr': 3} 8 | help_file_number = 0 9 | pass 10 | 11 | @classmethod 12 | def get_decode_header(cls, header): 13 | return header[0][1][12] 14 | 15 | def decode_object(self, src_dir, file_name, dest_dir, dest_path, version, header_data): 16 | super().decode_object(src_dir, file_name, dest_dir, dest_path, version, header_data) 17 | try: 18 | self.header['info'] = helper.brace_file_read(src_dir, f'{self.header["uuid"]}.1') 19 | except FileNotFoundError: 20 | self.header['info'] = None 21 | 22 | def write_encode_object(self, dest_dir): 23 | try: 24 | super().write_encode_object(dest_dir) 25 | if self.header['info']: 26 | file_name = f'{self.header["uuid"]}.1' 27 | helper.brace_file_write(self.header['info'], dest_dir, file_name) 28 | self.file_list.append(file_name) 29 | except Exception as err: 30 | raise ExtException(parent=err, 31 | detail=f'{self.__class__.__name__} {self.header["name"]} {self.header["uuid"]}') 32 | -------------------------------------------------------------------------------- /tests/Configuration803/test_decode_c803.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.append("../../src/") 6 | from v8unpack.unittest_helper import HelperTestDecode 7 | 8 | 9 | class TestDecode(HelperTestDecode): 10 | # processes = 1 # uncomment for debug 11 | 12 | def setUp(self): 13 | super(TestDecode, self).setUp() 14 | self.src_dir = os.path.dirname(__file__) 15 | self.src_file = '1Cv8.cf' 16 | self.init( 17 | # version='803', 18 | auto_include=True 19 | ) 20 | pass 21 | 22 | def test_01_decode_stage0(self): 23 | super(TestDecode, self).decode_stage0() 24 | 25 | def test_01_decode_stage1(self): 26 | super(TestDecode, self).decode_stage1() 27 | 28 | def test_03_decode_stage3(self): 29 | super(TestDecode, self).decode_stage3() 30 | 31 | def test_04_decode_stage4(self): 32 | # shutil.rmtree(os.path.join(sys.path[0], 'decodeCodeSubmodule'), ignore_errors=True) 33 | super(TestDecode, self).decode_stage4() 34 | 35 | def test_05_encode_stage4(self): 36 | super(TestDecode, self).encode_stage4() 37 | 38 | def test_06_encode_stage3(self): 39 | super(TestDecode, self).encode_stage3() 40 | 41 | def test_08_encode_stage1(self): 42 | super(TestDecode, self).encode_stage1() 43 | 44 | def test_09_encode_stage0(self): 45 | super(TestDecode, self).encode_stage0() 46 | 47 | 48 | if __name__ == '__main__': 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /tests/ConfigurationExtension803/test_decode_ce803.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.append("../../src/") 6 | from v8unpack.unittest_helper import HelperTestDecode 7 | 8 | 9 | class TestDecode(HelperTestDecode): 10 | # processes = 1 # uncomment for debug 11 | 12 | def setUp(self): 13 | super(TestDecode, self).setUp() 14 | self.src_dir = os.path.dirname(__file__) 15 | self.src_file = 'Расширение1.cfe' 16 | self.init( 17 | auto_include=True, 18 | version='80313' 19 | ) 20 | pass 21 | 22 | def test_01_decode_stage0(self): 23 | super(TestDecode, self).decode_stage0() 24 | 25 | def test_01_decode_stage1(self): 26 | super(TestDecode, self).decode_stage1() 27 | 28 | def test_03_decode_stage3(self): 29 | super(TestDecode, self).decode_stage3() 30 | 31 | def test_04_decode_stage4(self): 32 | # shutil.rmtree(os.path.join(sys.path[0], 'decodeCodeSubmodule'), ignore_errors=True) 33 | super(TestDecode, self).decode_stage4() 34 | 35 | def test_05_encode_stage4(self): 36 | super(TestDecode, self).encode_stage4() 37 | 38 | def test_06_encode_stage3(self): 39 | super(TestDecode, self).encode_stage3() 40 | 41 | def test_08_encode_stage1(self): 42 | super(TestDecode, self).encode_stage1() 43 | 44 | def test_09_encode_stage0(self): 45 | super(TestDecode, self).encode_stage0() 46 | 47 | 48 | if __name__ == '__main__': 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/__init__.py: -------------------------------------------------------------------------------- 1 | from v8unpack.MetaDataObject.core.Simple import SimpleNameFolder 2 | from .Form5 import Form5 3 | from .Form9 import Form9 4 | 5 | 6 | class Form(SimpleNameFolder): 7 | versions = { 8 | '5': Form5, 9 | '7': Form5, 10 | '9': Form9, 11 | '12': Form9, 12 | '13': Form9, 13 | '14': Form9, 14 | } 15 | # @classmethod 16 | # def read_header(cls, src_dir, src_file_name, data_id): 17 | # return super().read_header(src_dir, f"{data_id['type']}{src_file_name}", data_id) 18 | 19 | @classmethod 20 | def get_form_root(cls, header_data): 21 | # return header_data[0][1][1] 22 | obj_version = header_data[0][1][0] 23 | if obj_version == '0': 24 | return header_data[0][1] 25 | elif obj_version == '1': 26 | return header_data[0][1][1] 27 | raise NotImplementedError() 28 | 29 | @classmethod 30 | def get_decode_header(cls, header_data): 31 | return cls.get_form_root(header_data)[1][1] 32 | 33 | @classmethod 34 | def get_version(cls, header_data, options): 35 | form_root = cls.get_form_root(header_data) 36 | form_version = form_root[1][0] 37 | return form_version 38 | 39 | 40 | class Form1(Form): 41 | @classmethod 42 | def get_form_root(cls, header_data): 43 | return header_data[0][1] 44 | 45 | 46 | class Form0(Form): 47 | @classmethod 48 | def get_form_root(cls, header_data): 49 | return header_data[0] 50 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/core/Simple.py: -------------------------------------------------------------------------------- 1 | from ...MetaDataObject import MetaDataObject 2 | 3 | 4 | class Simple(MetaDataObject): 5 | 6 | def decode_object(self, src_dir, file_name, dest_dir, dest_path, version, header_data): 7 | super().decode_object(src_dir, file_name, dest_dir, dest_path, version, header_data) 8 | if self.help_file_number is not None: 9 | self._decode_html_data(src_dir, self.new_dest_dir, self.new_dest_file_name, header_field='help', 10 | file_number=self.help_file_number) 11 | self.decode_code(src_dir) 12 | 13 | def decode_includes(self, src_dir, dest_dir, dest_path, header): 14 | return [] 15 | 16 | def encode_includes(self, src_dir, file_name, dest_dir, parent_id): 17 | return [] 18 | 19 | def encode_object(self, src_dir, file_name, dest_dir): 20 | if self.help_file_number is not None: 21 | self._encode_html_data(src_dir, file_name, dest_dir, header_field='help', file_number=self.help_file_number) 22 | self.encode_code(src_dir, file_name) 23 | return [] 24 | 25 | 26 | class SimpleNameFolder(Simple): 27 | pass 28 | # def set_write_decode_mode(self, dest_dir, dest_path): 29 | # self.set_mode_decode_in_name_folder(dest_dir, dest_path) 30 | 31 | # @classmethod 32 | # def encode_get_include_obj(cls, src_dir, dest_dir, include, tasks, options, parent_id, include_index): 33 | # cls.encode_get_include_obj_from_named_folder(src_dir, dest_dir, include, tasks, options, parent_id, include_index) 34 | 35 | # def get_encode_file_name(self, file_name): 36 | # return self.get_obj_name() 37 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/core/SimpleWithInfo.py: -------------------------------------------------------------------------------- 1 | from .Simple import Simple 2 | from .. import helper 3 | import os 4 | import shutil 5 | 6 | 7 | class SimpleWithInfo(Simple): 8 | def decode_object(self, src_dir, file_name, dest_dir, dest_path, version, header_data): 9 | super(Simple, self).decode_object(src_dir, file_name, dest_dir, dest_path, version, header_data) 10 | try: 11 | _src = os.path.join(src_dir, f'{self.header["uuid"]}.0') 12 | _dest = os.path.join(dest_dir, self.new_dest_path, f'{self.new_dest_file_name}.0.c1brace') 13 | shutil.copy2(_src, _dest) 14 | # self.header['info'] = helper.brace_file_read(src_dir, f'{self.header["uuid"]}.0') 15 | except FileNotFoundError: 16 | return 17 | 18 | # def write_encode_object(self, dest_dir): 19 | # super().write_encode_object(dest_dir) 20 | # info = self.header.get('info') 21 | # if info: 22 | # file_name = f'{self.header["uuid"]}.0' 23 | # helper.brace_file_write(info, dest_dir, file_name) 24 | # self.file_list.append(file_name) 25 | 26 | def encode_object(self, src_dir, file_name, dest_dir): 27 | super().encode_object(src_dir, file_name, dest_dir) 28 | try: 29 | _src = os.path.join(src_dir, f'{file_name}.0.c1brace') 30 | _dest_file_name = f'{self.header["uuid"]}.0' 31 | _dest = os.path.join(dest_dir, _dest_file_name) 32 | shutil.copy2(_src, _dest) 33 | self.file_list.append(_dest_file_name) 34 | # self.header['info'] = helper.brace_file_read(src_dir, f'{self.header["uuid"]}.0') 35 | except FileNotFoundError: 36 | return 37 | -------------------------------------------------------------------------------- /tests/test_brace_file_decoder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.append("../../src/") 6 | from v8unpack import helper 7 | from v8unpack.json_container_decoder import JsonContainerDecoder 8 | from v8unpack.unittest_helper import compare_file, NotEqualLine 9 | 10 | 11 | class TestDecodeBraceFile(unittest.TestCase): 12 | def setUp(self) -> None: 13 | self.current_dir = os.path.dirname(__file__) 14 | self.data_dir = os.path.join(self.current_dir, 'data/json_decode_src') 15 | self.temp_dir = os.path.join(self.data_dir, 'temp') 16 | self.temp_dir2 = os.path.join(self.data_dir, 'temp2') 17 | 18 | def test_decode_all_raw_data(self): 19 | test_files = os.listdir(self.data_dir) 20 | result = '' 21 | for file_name in test_files: 22 | if file_name == 'temp': 23 | continue 24 | uuid, x = file_name.split('.') 25 | result += self.decode_raw_data(uuid) 26 | self.assertEqual('', result) 27 | 28 | def test_decode_file(self): 29 | uuid = 'raw_picture' 30 | result = self.decode_raw_data(uuid) 31 | self.assertEqual('', result) 32 | 33 | def decode_raw_data(self, file_name): 34 | helper.clear_dir(self.temp_dir) 35 | data = helper.brace_file_read(self.data_dir, file_name) 36 | helper.brace_file_write(data, self.temp_dir, file_name) 37 | problems = '' 38 | try: 39 | result = compare_file( 40 | os.path.join(self.data_dir, file_name), 41 | os.path.join(self.temp_dir, file_name), 42 | problems 43 | ) 44 | except NotEqualLine as err: 45 | result = err 46 | return result 47 | -------------------------------------------------------------------------------- /tests/test_container_reader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.append("../../src/") 6 | from v8unpack.container_reader import extract as container_extract, decompress_and_extract 7 | from v8unpack.container_writer import build as container_build, compress_and_build 8 | 9 | 10 | class TestFileOrganizerCE(unittest.TestCase): 11 | def setUp(self) -> None: 12 | self.current_dir = os.path.dirname(__file__) 13 | self.data_dir = os.path.join(self.current_dir, 'data') 14 | self.temp_dir = os.path.join(self.data_dir, 'temp') 15 | 16 | def test_extract_old(self): 17 | src_filename = os.path.join(self.data_dir, 'apam_old.cf') 18 | dest_dir0 = os.path.join(self.temp_dir, 'apam-0') 19 | dest_dir1 = os.path.join(self.temp_dir, 'apam-1') 20 | container_extract(src_filename, dest_dir0, False, False) 21 | decompress_and_extract(dest_dir0, dest_dir1) 22 | 23 | def test_extract_16(self): 24 | src_filename = os.path.join(self.data_dir, 'apam.cf') 25 | dst_filename = os.path.join(self.temp_dir, 'apam.cf') 26 | # src_filename = os.path.join(self.data_dir, 'apam_old.cf') 27 | # src_filename = os.path.join(self.data_dir, '1Cv8_8316.cf') 28 | dest_dir0 = os.path.join(self.temp_dir, 'apam16-0') 29 | dest_dir1 = os.path.join(self.temp_dir, 'apam16-1') 30 | dest_dir2 = os.path.join(self.temp_dir, 'apam16-2') 31 | container_extract(src_filename, dest_dir0, False, False) 32 | decompress_and_extract(dest_dir0, dest_dir1) 33 | compress_and_build(dest_dir1, dest_dir2) 34 | container_build(dest_dir2, dst_filename, True, version='80316') 35 | 36 | #[(559, 686), (2451, 2578), (3121, 3184), (3727, 3796), (4339, 4410)] -------------------------------------------------------------------------------- /tests/BadBraceExamples/test_brace_file_decoder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.append("../../src/") 6 | from v8unpack import helper 7 | from v8unpack.json_container_decoder import JsonContainerDecoder 8 | from v8unpack.unittest_helper import compare_file, NotEqualLine 9 | 10 | 11 | class TestDecodeBraceFile(unittest.TestCase): 12 | def setUp(self) -> None: 13 | self.current_dir = os.path.dirname(__file__) 14 | self.data_dir = os.path.join(self.current_dir, 'bad_example') 15 | self.temp_dir = os.path.join(self.data_dir, 'temp') 16 | self.temp_dir2 = os.path.join(self.data_dir, 'temp2') 17 | 18 | def test_decode_all_raw_data(self): 19 | test_files = os.listdir(self.data_dir) 20 | result = '' 21 | for file_name in test_files: 22 | if file_name == 'temp': 23 | continue 24 | file_name_part = file_name.split('.') 25 | result += self.decode_raw_data(file_name_part[0]) 26 | self.assertEqual('', result) 27 | 28 | def test_decode_file(self): 29 | uuid = 'raw_picture' 30 | result = self.decode_raw_data(uuid) 31 | self.assertEqual('', result) 32 | 33 | def decode_raw_data(self, file_name): 34 | helper.clear_dir(self.temp_dir) 35 | data = helper.brace_file_read(self.data_dir, file_name) 36 | helper.brace_file_write(data, self.temp_dir, file_name) 37 | problems = '' 38 | try: 39 | result = compare_file( 40 | os.path.join(self.data_dir, file_name), 41 | os.path.join(self.temp_dir, file_name), 42 | problems 43 | ) 44 | except NotEqualLine as err: 45 | result = err 46 | return result 47 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/DataProcessor.py: -------------------------------------------------------------------------------- 1 | from ..MetaDataObject.core.Container import FormContainer 2 | import os 3 | 4 | from .. import helper 5 | from ..MetaObject import MetaObject 6 | from ..ext_exception import ExtException 7 | 8 | 9 | class DataProcessor(FormContainer): 10 | ext_code = { 11 | 'mgr': '2', # модуль менеджера 12 | 'obj': '0', # модуль объекта 13 | } 14 | help_file_number = 1 15 | 16 | @classmethod 17 | def get_decode_header(cls, header_data): 18 | return header_data[0][1][3][1] 19 | 20 | def decode_ids(self): 21 | data_id = super().decode_ids() 22 | 23 | if self.obj_version.startswith('803'): 24 | manager_data = self.header['header'][0][1] 25 | data_id['manager_uuid1'] = manager_data[1] 26 | data_id['manager_uuid2'] = manager_data[2] 27 | data_id['manager_uuid3'] = manager_data[7] 28 | data_id['manager_uuid4'] = manager_data[8] 29 | manager_data[1] = 'manager_uuid1 в файле id' 30 | manager_data[2] = 'manager_uuid2 в файле id' 31 | manager_data[7] = 'manager_uuid3 в файле id' 32 | manager_data[8] = 'manager_uuid4 в файле id' 33 | 34 | return data_id 35 | 36 | def encode_ids(self, data_id): 37 | super().encode_ids(data_id) 38 | if self.obj_version.startswith('803'): 39 | manager_data = self.header['header'][0][1] 40 | manager_data[1] = data_id['manager_uuid1'] 41 | manager_data[2] = data_id['manager_uuid2'] 42 | manager_data[7] = data_id['manager_uuid3'] 43 | manager_data[8] = data_id['manager_uuid4'] 44 | 45 | def encode_header(self): 46 | super().encode_header() 47 | self.set_product_comment(self.options.get('product_version')) 48 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/core/IncludeSimple.py: -------------------------------------------------------------------------------- 1 | from ...MetaDataObject import MetaDataObject 2 | from ...ext_exception import ExtException 3 | 4 | 5 | class IncludeSimple(MetaDataObject): 6 | ext_code = {'obj': 2} 7 | 8 | def __init__(self, *, meta_obj_class=None, obj_version=None, options=None): 9 | super().__init__(meta_obj_class=meta_obj_class, obj_version=obj_version, options=options) 10 | self.new_dest_path = None 11 | self.new_dest_dir = None 12 | 13 | @classmethod 14 | def decode(cls, src_dir: str, file_name: str, dest_dir: str, dest_path: str, options, *, parent_type=None, 15 | parent_container_uuid=None): 16 | raise Exception('Так быть не должно, этот класс обслуживает вложенные объекты') 17 | 18 | @classmethod 19 | def decode_internal_include(cls, parent, header_data, src_dir, dest_dir, dest_path, options): 20 | try: 21 | self = cls(options=options) 22 | self.decode_header(header_data) 23 | self.set_write_decode_mode(dest_dir, dest_path) 24 | self.decode_code(src_dir) 25 | self.write_decode_object(dest_dir, self.new_dest_path, self.new_dest_file_name) 26 | return self.uuid 27 | except Exception as err: 28 | raise ExtException( 29 | parent=err, 30 | action=f'{cls.__name__}.decode_internal_include' 31 | ) from err 32 | 33 | @classmethod 34 | def get_decode_header(cls, header_data): 35 | return header_data[0][1][3][2][9] 36 | 37 | def encode_object(self, src_dir, file_name, dest_dir): 38 | self.encode_code(src_dir, self.__class__.__name__) 39 | return [] 40 | 41 | def get_internal_data(self): 42 | return self.header['header'] 43 | 44 | def write_encode_object(self, dest_dir): 45 | self.encode_header() 46 | self.write_encode_code(dest_dir) 47 | 48 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements4/Group.py: -------------------------------------------------------------------------------- 1 | from .FormElement import FormElement, check_count_element 2 | from v8unpack.ext_exception import ExtException 3 | from v8unpack.helper import FuckingBrackets 4 | from v8unpack.helper import calc_offset 5 | 6 | 7 | class Group(FormElement): 8 | pass 9 | 10 | @classmethod 11 | def get_name_node_offset(cls, raw_data): 12 | return calc_offset([(3, 1), (1, 1), (2, 0)], raw_data) 13 | 14 | @classmethod 15 | def decode(cls, form, path, raw_data): 16 | try: 17 | size = check_count_element([ 18 | (3, 1), (1, 1), (17, 2) 19 | ], raw_data) 20 | except Exception as err: 21 | raise ExtException(parent=err) 22 | if raw_data[0] == '22' and size < 20: 23 | raise FuckingBrackets(detail=cls.__name__) 24 | 25 | data = super().decode(form, path, raw_data) 26 | index = calc_offset([(3, 1), (1, 1), (17, 0)], raw_data) 27 | new_path = f"{path}/{data['name']}" if path else data['name'] 28 | new_path = new_path.replace('includr_', 'include_') 29 | data['child'] = cls.decode_list(form, raw_data, index, new_path) 30 | return data 31 | 32 | @classmethod 33 | def encode(cls, form, path, data): 34 | try: 35 | try: 36 | child = data['child'] 37 | except KeyError: 38 | child = [] 39 | raw_data = super().encode(form, path, data) 40 | if not child: 41 | return raw_data 42 | index = calc_offset([(3, 1), (1, 1), (17, 0)], raw_data) 43 | name_for_child = f"include_{data['name'][8:]}" if data['name'][:8] == 'includr_' else data['name'] 44 | new_path = f"{path}/{name_for_child}" if path else name_for_child 45 | cls.encode_list(form, child, raw_data, index, new_path) 46 | return raw_data 47 | except Exception as err: 48 | raise ExtException(parent=err) 49 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/CommonPicture.py: -------------------------------------------------------------------------------- 1 | import os 2 | from base64 import b64decode, b64encode 3 | 4 | from .. import helper 5 | from ..MetaDataObject.core.Simple import Simple 6 | from ..ext_exception import ExtException 7 | 8 | 9 | class CommonPicture(Simple): 10 | def __init__(self, *, meta_obj_class=None, obj_version=None, options=None): 11 | super().__init__(meta_obj_class=meta_obj_class, obj_version=obj_version, options=options) 12 | self.ext_code = {} 13 | self.data = None 14 | self.raw_data = None 15 | 16 | def decode_object(self, src_dir, file_name, dest_dir, dest_path, version, header_data): 17 | try: 18 | super().decode_object(src_dir, file_name, dest_dir, dest_path, version, header_data) 19 | try: 20 | self.header['info'] = helper.brace_file_read(src_dir, f'{self.header["uuid"]}.0') 21 | except FileNotFoundError: 22 | return 23 | if self.header['info'][0][2] and self.header['info'][0][2][0] and self.header['info'][0][2][0][0]: 24 | bin_data = self._extract_b64_data(self.header['info'][0][2][0]) 25 | 26 | extension = helper.get_extension_from_comment(self.header['comment']) 27 | if dest_dir: 28 | helper.bin_write(bin_data, self.new_dest_dir, f'{self.new_dest_file_name}.{extension}') 29 | except Exception as err: 30 | raise ExtException(parent=err) 31 | 32 | def encode_object(self, src_dir, file_name, dest_dir): 33 | super().encode_object(src_dir, file_name, dest_dir) 34 | 35 | extension = helper.get_extension_from_comment(self.header['comment']) 36 | try: 37 | bin_data = helper.bin_read(src_dir, f'{file_name}.{extension}') 38 | self.header['info'][0][2][0][0] += b64encode(bin_data).decode(encoding='utf-8') 39 | file_name = f'{self.header["uuid"]}.0' 40 | helper.brace_file_write(self.header['info'], dest_dir, file_name) 41 | self.file_list.append(file_name) 42 | except FileNotFoundError: 43 | pass 44 | -------------------------------------------------------------------------------- /docs/Структура обычной формы.md: -------------------------------------------------------------------------------- 1 | Для 27 версии 2 | 3 | корневые страницы формы 4 | 0.0.1.2.1.1.20 5 | 0.1.10 версия форм, инкрементируется при каждом изменении 6 | 7 | # Расположение элементов 8 | Элементы разбиты по панеляем. 9 | 10 | Все элементы со всех страниц панели идут плоским списком. 11 | 12 | Привязка к странице указана внутри элемента (у деволтной метки это 3.20), нумерация страниц с 0. 13 | 14 | Каждый элемент в зависимости от типа может быть указан в 4 "оглавлениях" которые идут перед списком страниц. 15 | Их структура не понятна. 4 списка которые содержат id элемента и какое то число. 16 | 17 | 18 | Структура вложенной панели в части описания страниц и элементов практически идентична. 19 | 20 | #Привязки 21 | Привязки описываются на самом элементе, кроме этого на элеменете к которому 22 | они прявзяны так же дублируется информация кто к нему привязан по 23 | каждой из сторон отдельно 24 | 25 | 6 групп элементов для описания стороны которая привязана 26 | 27 | * 0 - хз 28 | * 2 - хз 29 | * id элемента к чему привязан или -1 30 | * как привязан 31 | * 0 - верхяя 32 | * 1 - нижняя 33 | * 2 - левая 34 | * 3 - правая 35 | * 4 - центр по вертикали 36 | * 5 - центр по горизонтали 37 | * 6 - нет 38 | * хз - похоже какая координата 39 | 40 | 41 | Привязи хранятся в рамках одной панели 42 | ##Информация кто привязан к элементу 43 | 3.12 для элемента 44 | 2 для панели 45 | 46 | 4-6 массивов друг за другом 47 | 48 | * 0 верхняя 49 | * 1 нижняя или центр 50 | * 2 левой 51 | * 3 правая 52 | структура привязки 53 | * 0 - ? 54 | * ид элемента 55 | * по какой границе 56 | * 0 верхняя 57 | * 1 нижняя или центр 58 | * 2 левой 59 | * 3 правая 60 | 61 | 62 | 63 | 3 - лево 64 | 4 - право 65 | 66 | 67 | 68 | # Порядок следования страниц 69 | 70 | Список страниц не меняется, их порядковый номер используется в описании элемента. 71 | 72 | Порядок следования страниц описан отдельно - отступ 4 элемента от списка страниц. 73 | 74 | На каждую страницу по 4 записи в порядке страниц (хз). 75 | 76 | Порядок следования страницы в 5 элементе. 77 | 78 | 79 | 80 | 81 | элементы ввода описываются в трех местах 82 | 0.2.2 83 | * описание элемент 84 | * список элементов вввода 85 | * какой то их перечень по порядку -------------------------------------------------------------------------------- /tests/test_form_decoder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.append("../../src/") 6 | from v8unpack import helper 7 | from v8unpack.MetaDataObject.versions.Form803 import Form803 as Form 8 | from v8unpack.MetaDataObject.CatalogForm import CatalogForm 9 | from v8unpack.unittest_helper import compare_file, NotEqualLine 10 | 11 | 12 | class TestDecodeBraceFile(unittest.TestCase): 13 | def setUp(self) -> None: 14 | self.current_dir = os.path.dirname(__file__) 15 | self.data_dir = os.path.join(self.current_dir, 'data', 'form') 16 | self.temp_dir = os.path.join(self.data_dir, 'temp') 17 | self.temp_dir2 = os.path.join(self.data_dir, 'temp2') 18 | 19 | def test_decode_all_raw_data(self): 20 | test_files = os.listdir(self.data_dir) 21 | result = '' 22 | for file_name in test_files: 23 | if file_name == 'temp': 24 | continue 25 | uuid, x = file_name.split('.') 26 | result += self.decode_raw_data(uuid) 27 | self.assertEqual('', result) 28 | 29 | def test_decode_file(self): 30 | uuid = '01f67911-4ca7-4668-b970-62c299b98e56' 31 | result = self.decode_raw_data(uuid) 32 | self.assertEqual('', result) 33 | 34 | def decode_raw_data(self, file_name): 35 | helper.clear_dir(self.temp_dir) 36 | helper.clear_dir(self.temp_dir2) 37 | options = dict(version='803') 38 | # helper.clear_dir(self.temp_dir2) 39 | CatalogForm.decode(self.data_dir, file_name, self.temp_dir, '', options=options) 40 | temp_dir = os.path.join(self.temp_dir, 'ФормаСписка') 41 | form = Form(options=options) 42 | form.encode(temp_dir, file_name, self.temp_dir2, None, [{}]) 43 | problems = '' 44 | try: 45 | result = compare_file( 46 | os.path.join(self.data_dir, file_name), 47 | os.path.join(self.temp_dir2, file_name), 48 | problems 49 | ) 50 | except NotEqualLine as err: 51 | result = err 52 | return result 53 | 54 | def test_decode_by_scheme(self): 55 | from v8unpack.MetaDataObject.schemes.Form import Form 56 | 57 | file_name = '9c80a4da-8f1f-48ae-915c-4227fef8f364' 58 | data = Form(self.data_dir, file_name).decode() 59 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements4/Table.py: -------------------------------------------------------------------------------- 1 | from .FormElement import FormElement, check_count_element 2 | from v8unpack.helper import FuckingBrackets 3 | from v8unpack.ext_exception import ExtException 4 | from v8unpack.helper import calc_offset 5 | 6 | 7 | class Table(FormElement): 8 | 9 | @classmethod 10 | def get_name_node_offset(cls, raw_data): 11 | return calc_offset([(4, 1), (1, 0)], raw_data) 12 | 13 | @classmethod 14 | def get_prop_link_offset(cls, raw_data): 15 | return calc_offset([(4, 1), (7, 0)], raw_data) 16 | 17 | @classmethod 18 | def decode(cls, form, path, raw_data): 19 | try: 20 | size = check_count_element([ 21 | (4, 1), (50, 2), (7, 2) 22 | ], raw_data) 23 | except Exception as err: 24 | raise ExtException(parent=err) 25 | if raw_data[0] == '55' and size != 99: 26 | raise FuckingBrackets() 27 | data = super().decode(form, path, raw_data) 28 | cls.decode_columns(form, path, raw_data, data) 29 | return data 30 | 31 | @classmethod 32 | def decode_columns(cls, form, path, raw_data, data): 33 | try: 34 | index = calc_offset([ 35 | (4, 1), (50, 2), (7, 0) 36 | ], raw_data) 37 | data['child'] = cls.decode_list(form, raw_data, index, f"{path}/{data['name']}" if path else data['name']) 38 | except Exception as err: 39 | raise ExtException(parent=err) 40 | 41 | @classmethod 42 | def encode(cls, form, path, data): 43 | try: 44 | try: 45 | child = data['child'] 46 | except KeyError: 47 | child = [] 48 | raw_data = super().encode(form, path, data) 49 | if not child: 50 | return raw_data 51 | cls.encode_columns(form, child, raw_data, data, f"{path}") 52 | return raw_data 53 | except Exception as err: 54 | raise ExtException(parent=err) 55 | 56 | @classmethod 57 | def encode_columns(cls, form, child, raw_data, data, path): 58 | try: 59 | index = calc_offset([ 60 | (4, 1), (50, 2), (7, 0) 61 | ], raw_data) 62 | cls.encode_list(form, child, raw_data, index, f"{path}/{data['name']}" if path else data['name']) 63 | except Exception as err: 64 | raise ExtException(parent=err) -------------------------------------------------------------------------------- /tests/ExternalDataProcessor802/test_decode_edt802.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import sys 4 | import unittest 5 | 6 | sys.path.append("../../src/") 7 | from v8unpack.unittest_helper import HelperTestDecode 8 | 9 | 10 | class TestDecode(HelperTestDecode): 11 | processes = 1 # uncomment for debug 12 | 13 | def setUp(self): 14 | super(TestDecode, self).setUp() 15 | self.src_dir = os.path.dirname(__file__) 16 | self.dest_dir = os.path.join(self.src_dir, 'src') 17 | self.src_file = 'ВнешняяОбработка802.epf' 18 | self.result = { 19 | # 'count_root_files_stage1': 14, 20 | # 'count_root_files_stage3': 5, 21 | # 'count_root_files_stage4': 4, 22 | # 'count_forms_files': 3, 23 | # 'count_templates_files': 2 24 | } 25 | self.init( 26 | index_file_name='index.json', 27 | auto_include=True, 28 | version='802' 29 | ) 30 | pass 31 | 32 | def test_01_decode_stage0(self): 33 | super(TestDecode, self).decode_stage0() 34 | 35 | def test_01_decode_stage1(self): 36 | super(TestDecode, self).decode_stage1() 37 | 38 | def test_03_decode_stage3(self): 39 | super(TestDecode, self).decode_stage3() 40 | self.assert_external_data_processor_decode_stage3() 41 | 42 | def test_04_decode_stage4(self): 43 | shutil.rmtree(os.path.join(self.test_dir, 'decodeCodeSubmodule'), ignore_errors=True) 44 | super(TestDecode, self).decode_stage4() 45 | 46 | def test_05_encode_stage4(self): 47 | super(TestDecode, self).encode_stage4() 48 | 49 | def test_06_encode_stage3(self): 50 | super(TestDecode, self).encode_stage3() 51 | 52 | def test_08_encode_stage1(self): 53 | super(TestDecode, self).encode_stage1() 54 | 55 | def test_09_encode_stage0(self): 56 | super(TestDecode, self).encode_stage0() 57 | 58 | @unittest.skip 59 | def test_extract(self): 60 | from v8unpack.v8unpack import extract, build 61 | extract('ВнешняяОбработка803.epf', 'src') 62 | build('src', 'ВнешняяОбработка.build.epf') 63 | 64 | @unittest.skip 65 | def test_create_index(self): 66 | from v8unpack.index import update_index 67 | update_index(self.decode_dir_stage4, os.path.join(sys.path[0], 'tmp', 'index.json'), 68 | 'decodeCodeSubmodule') 69 | 70 | 71 | if __name__ == '__main__': 72 | unittest.main() 73 | -------------------------------------------------------------------------------- /tests/ExternalDataProcessor803/test_decode_edt803.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import sys 4 | import unittest 5 | 6 | sys.path.append("../../src/") 7 | from v8unpack.unittest_helper import HelperTestDecode 8 | 9 | 10 | class TestDecode(HelperTestDecode): 11 | processes = 1 # uncomment for debug 12 | 13 | def setUp(self): 14 | super(TestDecode, self).setUp() 15 | self.src_dir = os.path.dirname(__file__) 16 | self.dest_dir = os.path.join(self.src_dir, 'src') 17 | self.src_file = 'ВнешняяОбработка803.epf' 18 | 19 | self.init( 20 | index_file_name='index.json', 21 | version='803' 22 | ) 23 | pass 24 | 25 | def test_01_decode_stage0(self): 26 | super(TestDecode, self).decode_stage0() 27 | 28 | def test_01_decode_stage1(self): 29 | super(TestDecode, self).decode_stage1() 30 | 31 | def test_03_decode_stage3(self): 32 | super(TestDecode, self).decode_stage3() 33 | self.assert_external_data_processor_decode_stage3() 34 | 35 | def test_04_decode_stage4(self): 36 | # shutil.rmtree(os.path.join(self.test_dir, 'decodeCodeSubmodule'), ignore_errors=True) 37 | super(TestDecode, self).decode_stage4() 38 | 39 | def test_05_encode_stage4(self): 40 | super(TestDecode, self).encode_stage4() 41 | 42 | def test_06_encode_stage3(self): 43 | super(TestDecode, self).encode_stage3() 44 | 45 | def test_08_encode_stage1(self): 46 | super(TestDecode, self).encode_stage1() 47 | 48 | def test_09_encode_stage0(self): 49 | super(TestDecode, self).encode_stage0() 50 | 51 | @unittest.skip 52 | def test_extract_all(self): 53 | super(TestDecode, self).extract_all('./product.json', 'ВнешняяОбработка803.epf') 54 | 55 | @unittest.skip 56 | def test_build_all(self): 57 | super(TestDecode, self).build_all('./product.json', 'ВнешняяОбработка803.epf') 58 | 59 | @unittest.skip 60 | def test_extract(self): 61 | from v8unpack.v8unpack import extract, build 62 | extract('ВнешняяОбработка803.epf', 'src') 63 | build('src', 'ВнешняяОбработка.build.epf') 64 | 65 | @unittest.skip 66 | def test_create_index(self): 67 | from v8unpack.index import update_index 68 | update_index(self.decode_dir_stage4, os.path.join(sys.path[0], 'tmp', 'index.json'), 69 | 'decodeCodeSubmodule') 70 | 71 | 72 | 73 | if __name__ == '__main__': 74 | unittest.main() 75 | -------------------------------------------------------------------------------- /tests/test_form_elem_803_decoder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.append("../../src/") 6 | from v8unpack import helper 7 | from v8unpack.MetaDataObject.versions.Form803 import Form803 8 | from v8unpack.MetaDataObject.versions.Form802 import Form802 9 | from v8unpack.unittest_helper import compare_file, NotEqualLine 10 | 11 | 12 | class TestFormElem803(unittest.TestCase): 13 | def setUp(self) -> None: 14 | self.current_dir = os.path.dirname(__file__) 15 | self.data_dir = os.path.join(self.current_dir, 'data', 'form_elem_803') 16 | self.temp_dir = os.path.join(self.data_dir, 'temp') 17 | self.temp_dir2 = os.path.join(self.data_dir, 'temp2') 18 | 19 | def test_decode_all_form_elem(self): 20 | test_files = os.listdir(self.data_dir) 21 | result = '' 22 | for file_name in test_files: 23 | if file_name == 'temp': 24 | continue 25 | item = file_name.split('.') 26 | result += self.decode_form_elem(item[0]) 27 | self.assertEqual('', result) 28 | 29 | def test_decode_form_elem(self): 30 | uuid = 'c611d632-eadf-4989-9e2e-f6042148db62' 31 | result = self.decode_form_elem(uuid) 32 | self.assertEqual('', result) 33 | 34 | def decode_form_elem(self, uuid): 35 | file_name = f'{uuid}.0' 36 | json_file_name = f'{file_name}.json' 37 | helper.clear_dir(self.temp_dir) 38 | if not os.path.isfile(os.path.join(self.data_dir, json_file_name)): 39 | raw_data = helper.brace_file_read(self.data_dir, file_name) 40 | helper.json_write(raw_data, self.data_dir, json_file_name) 41 | os.remove(os.path.join(self.data_dir, file_name)) 42 | else: 43 | raw_data = helper.json_read(self.data_dir, json_file_name) 44 | form = Form802() if raw_data[0][0] == '2' else Form803() 45 | form.new_dest_dir = self.temp_dir 46 | form.form = [raw_data] 47 | form.header['Тип формы'] = '1' 48 | form.decode_includes(None, self.temp_dir, '', None) 49 | 50 | form.write_decode_object(self.temp_dir, '', uuid) 51 | form.encode_nested_includes(self.temp_dir, uuid, self.temp_dir, '') 52 | helper.json_write(form.form[0], self.temp_dir, json_file_name) 53 | problems = '' 54 | try: 55 | result = compare_file( 56 | os.path.join(self.data_dir, json_file_name), 57 | os.path.join(self.temp_dir, json_file_name), 58 | problems 59 | ) 60 | except NotEqualLine as err: 61 | result = str(err) 62 | if result: 63 | print(result) 64 | return result 65 | -------------------------------------------------------------------------------- /src/v8unpack/organizer_file_ce.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from . import helper 4 | from .organizer_file import OrganizerFile 5 | 6 | 7 | class OrganizerFileCE(OrganizerFile): 8 | 9 | @classmethod 10 | def unpack_get_descent_filename(cls, src_path, src_file_name, src_data, dest_path, dest_file_name, descent, 11 | comparer): 12 | # если есть файл нашей версии или новый файл отличается от старого, или старого файла нет 13 | # сохраняем файл с номером текущей версии 14 | # считаем, что общие изменения должны делаться на минимальной версии 15 | descent_file_name = helper.get_descent_file_name(dest_file_name, descent) 16 | descent_full_dest_path = os.path.join(dest_path, descent_file_name) 17 | 18 | if not os.path.isfile(descent_full_dest_path): # если нет файла нужной версии 19 | near_descent_path, near_descent_file_name = helper.get_near_descent_file_name(dest_path, dest_file_name, 20 | descent) 21 | if near_descent_file_name: # нашли файл младшей версии 22 | if comparer(src_path, src_file_name, src_data, near_descent_path, near_descent_file_name): 23 | return '', '' 24 | return dest_path, descent_file_name 25 | 26 | @classmethod 27 | def pack_get_descent_filename(cls, src_path, src_file_name, descent): 28 | descent_path, descent_file_name = helper.get_near_descent_file_name(src_path, src_file_name, descent) 29 | if not descent_file_name: 30 | raise FileNotFoundError(f'{src_path}/{src_file_name} ({descent})') 31 | return descent_path, descent_file_name 32 | 33 | @classmethod 34 | def list_descent_dir(cls, src_dir, path, descent): 35 | def check_descent_name(_name): 36 | try: 37 | if len(_name) < 3: 38 | return False 39 | _descent = _name[-2] 40 | if str(int(_descent)) == _descent: 41 | return True 42 | except Exception: 43 | return False 44 | 45 | _index = {} 46 | result = [] 47 | _dir = os.path.join(src_dir, path) 48 | try: 49 | entries = os.listdir(_dir) 50 | except FileNotFoundError: 51 | entries = [] 52 | for entry in entries: 53 | full_path = os.path.join(_dir, entry) 54 | if os.path.isdir(full_path): 55 | result.append(entry) 56 | else: 57 | entry = helper.remove_descent_from_filename(entry) 58 | if entry not in _index: 59 | _index[entry] = 1 60 | result.append(entry) 61 | return result 62 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements4/Decoration.py: -------------------------------------------------------------------------------- 1 | from .FormElement import FormElement 2 | from v8unpack.helper import calc_offset 3 | 4 | 5 | class Decoration(FormElement): 6 | @classmethod 7 | def get_name_node_offset(cls, raw_data): 8 | return calc_offset([(3, 1), (1, 1), (2, 0)], raw_data) 9 | 10 | # @classmethod 11 | # def decode(cls, form, path, raw_data): 12 | # # _version = raw_data[0] 13 | # result = super().decode(form, path, raw_data) 14 | # return result 15 | 16 | # @classmethod 17 | # def decode_5(cls, form, raw_data): 18 | # 19 | # try: 20 | # size = check_count_element([ 21 | # (3, 1), (1, 1), (15, 1) 22 | # ], raw_data) 23 | # except TypeError: 24 | # raise FuckingBrackets(detail=cls.__name__) 25 | # except Exception as err: 26 | # raise ExtException(parent=err) 27 | # if size != 34: 28 | # raise FuckingBrackets(detail=cls.__name__) 29 | # result = super().decode(form, raw_data) 30 | # return result 31 | # 32 | # @classmethod 33 | # def decode_11(cls, form, raw_data): 34 | # try: 35 | # size = check_count_element([ 36 | # (3, 1), (1, 1), (15, 1), (5, 1) 37 | # ], raw_data) 38 | # except TypeError: 39 | # raise FuckingBrackets(detail=cls.__name__) 40 | # except Exception as err: 41 | # raise ExtException(parent=err) 42 | # if size != 33: 43 | # raise FuckingBrackets(detail=cls.__name__) 44 | # result = super().decode(form, raw_data) 45 | # return result 46 | # 47 | # @classmethod 48 | # def decode_12(cls, form, raw_data): 49 | # try: 50 | # size = check_count_element([ 51 | # (3, 1), (1, 1), (15, 1), (5, 1) 52 | # ], raw_data) 53 | # except TypeError: 54 | # raise FuckingBrackets(detail=cls.__name__) 55 | # except Exception as err: 56 | # raise ExtException(parent=err) 57 | # if size != 34: 58 | # raise FuckingBrackets(detail=cls.__name__) 59 | # result = super().decode(form, raw_data) 60 | # return result 61 | # 62 | # @classmethod 63 | # def _decode(cls, form, raw_data): 64 | # try: 65 | # size = check_count_element([ 66 | # (3, 1), (1, 1), (15, 1), (5, 1) 67 | # ], raw_data) 68 | # except TypeError: 69 | # raise FuckingBrackets(detail=cls.__name__) 70 | # except Exception as err: 71 | # raise ExtException(parent=err) 72 | # if size != 34: 73 | # raise FuckingBrackets(detail=cls.__name__) 74 | # result = super().decode(form, raw_data) 75 | # return result 76 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/core/Container.py: -------------------------------------------------------------------------------- 1 | from ... import helper 2 | from ...MetaDataObject import MetaDataObject 3 | from ...ext_exception import ExtException 4 | 5 | 6 | class Container(MetaDataObject): 7 | predefined_file_number = None 8 | 9 | @classmethod 10 | def get_decode_header(cls, header): 11 | return header[0][1][1] 12 | 13 | @classmethod 14 | def get_decode_includes(cls, header_data): 15 | try: 16 | return [header_data[0]] 17 | except IndexError: 18 | raise ExtException(message='Include types not found', detail=cls.__name__) 19 | 20 | def decode_object(self, src_dir, file_name, dest_dir, dest_path, version, header_data): 21 | super().decode_object(src_dir, file_name, dest_dir, dest_path, version, header_data) 22 | if self.help_file_number is not None: 23 | self._decode_html_data(src_dir, self.new_dest_dir, self.new_dest_file_name, header_field='help', 24 | file_number=self.help_file_number) 25 | if self.predefined_file_number is not None: 26 | self._decode_predefined(src_dir, self.new_dest_dir) 27 | self.decode_code(src_dir) 28 | 29 | def _decode_predefined(self, src_dir, dest_dir): 30 | try: 31 | data = helper.bin_read(src_dir, f'{self.header["uuid"]}.{self.predefined_file_number}') 32 | helper.bin_write(data, dest_dir, 'Предустановленные данные.bin') 33 | except FileNotFoundError: 34 | return 35 | 36 | # def set_write_decode_mode(self, dest_dir, dest_path): 37 | # self.set_mode_decode_in_name_folder(dest_dir, dest_path) 38 | 39 | def encode_object(self, src_dir, file_name, dest_dir): 40 | if self.help_file_number is not None: 41 | self._encode_html_data(src_dir, file_name, dest_dir, header_field='help', file_number=self.help_file_number) 42 | if self.predefined_file_number is not None: 43 | self._encode_predefined(src_dir, dest_dir) 44 | self.encode_code(src_dir, self.__class__.__name__) 45 | return [] 46 | 47 | def _encode_predefined(self, src_dir, dest_dir): 48 | try: 49 | package = helper.bin_read(src_dir, 'Предустановленные данные.bin') 50 | file_name = f'{self.header["uuid"]}.{self.predefined_file_number}' 51 | helper.bin_write(package, dest_dir, file_name) 52 | self.file_list.append(file_name) 53 | except FileNotFoundError: 54 | return 55 | 56 | # @classmethod 57 | # def encode_get_include_obj(cls, src_dir, dest_dir, include, tasks, options, parent_id, include_index): 58 | # cls.encode_get_include_obj_from_named_folder(src_dir, dest_dir, include, tasks, options, parent_id, include_index) 59 | 60 | # def get_encode_file_name(self, file_name): 61 | # return self.get_obj_name() 62 | 63 | 64 | class FormContainer(Container): 65 | @classmethod 66 | def get_container_uuid(cls, header_data): 67 | return header_data[0][1][1] -------------------------------------------------------------------------------- /docs/develop.md: -------------------------------------------------------------------------------- 1 | # Участие 2 | Типов объектов метаданных в 1С много, свойств у них ещё больше. Можно бесконечно улучшать 3 | качество и читаемость хранимых данных, до тех пор пока все объекты не будут расшифрованы. 4 | 5 | На текущий момент в модуле реализованы только необходимые автору объекты. Если Вам не хватает 6 | каких либо типов Вы можете самостоятельно их реализовать и добросить в этот репозиторий. 7 | 8 | Для этого необходимо: 9 | 10 | 1. В тестовую конфигурацию приложенную к данному репозиторию добавить нужный вам объект в 11 | небольшом количестве (обычно по количеству его вариантов). В качестве название объекта 12 | необходимо использовать название типа и варианта, например РеквизитДокументаСтрока. 13 | 14 | 2. Добавить реализацию для сборки разборки нужного типа объект и добиться чтобы тест сборки 15 | разборки конфигурации проходил без ошибок (test_decode менять не нужно, можно добавить 16 | свои тесты конкретного объекта рядом). 17 | 18 | 2.1. Определить uuid типа и прописать соответствие в metadata_types по аналогии 19 | с другими записаями. uuid типа метаданных будет виден тексте ошибки. Так же в ошибке вы 20 | увидите файл в котором описан объект этого типа. В содержимом файла вы сможете найти название 21 | объекта и по нему понять что за тип вам попался. Английское название типа для metadata_types 22 | нужно взять из Синтаксис-помощника 1С (строка для поиска "ОбъектМетаданных: ХХХХ.YYYY"). 23 | 24 | Например видим ошибку неизвестный тип вложенных метаданных: 13134204-f60b-11d5-a3c7-0050bae0a776 25 | в файле 79b41fb6-cf3d-433c-83f2-a97b226178c4. 26 | 27 | Открываем файл 79b41fb6-cf3d-433c-83f2-a97b226178c4 (файл будет где-то 28 | в папке temp\decode_stage_2, используйте параметр temp при разборе). 29 | 30 | Открыв файл Вы в начале файла увидите название типа метаданных, например ФормаЗаписи. 31 | 32 | Далее ищем в конфигураторе данный объект, и понимаем какой у него тип. Например в нашем случае это 33 | Регистры сведений -> Форма. В синтаксис помощнике ищем ОбъектМетаданных: РегистрСведений.Формы. 34 | 35 | На найденной странице смотрим как называется это у 1С по английски - MetadataObject:InformationRegister.Forms 36 | 37 | Под каждый тип метаданных в папке MetadataObject создается одноименный файл / класс, в нашем случае 38 | InformationRegisterForm (имя переводим в динственное число). Для форм достаточно копирования 39 | какой нибуть другой формы, они очень похожи. 40 | 41 | добавляем в metadata_types InformationRegisterForm = '13134204-f60b-11d5-a3c7-0050bae0a776' 42 | 43 | 2.2. Если объект сложный и его свойства хранятся в отдельном файле нужно будет добавить 44 | парсер объекта в MetaDataObject. Для простых объектов типа Рексизит Документа достаточно 45 | добавить описание. 46 | 47 | 3. Создать под свой пример юниттест по аналогии с теми что есть в проекте, эти тесты 48 | проверяют какая значимая информация теряется при сборке разборке. Кроме этого нужно убедиться, 49 | что при разборке + сборке получается валидный файл который открывается в 1С. 50 | 51 | 52 | 4. Добросить изменения 53 | 54 | 4.1. Если вы просто добавили новый класс достаточно подтять версию path. 55 | 56 | 4.2. Запись в истории версий модуля обязательна. 57 | -------------------------------------------------------------------------------- /src/v8unpack/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | from .helper import remove_descent_from_filename 5 | from .ext_exception import ExtException 6 | from . import helper 7 | 8 | 9 | def get_from_index(index: dict, path: str, file_name: str): 10 | try: 11 | _index = index 12 | if path: 13 | _path = os.path.normpath(path) 14 | _path = path.split(os.sep) 15 | for _dir in _path: 16 | _index = _index[_dir] 17 | try: 18 | return _index[file_name] 19 | except KeyError: 20 | return os.path.join(_index['*'], file_name) 21 | except TypeError: 22 | raise ExtException( 23 | message="Ошибка c уровнями вложенности в Index.json", 24 | detail=f'{path}/{file_name}', 25 | action='index.get' 26 | ) 27 | 28 | 29 | def update_index(src_dir: str, index_file_name: str, dest_dir: str): 30 | def _update_index(_src_dir, _index, _dest_dir, _path): 31 | entries = os.listdir(os.path.join(_src_dir, _path)) 32 | for entry in entries: 33 | entry = remove_descent_from_filename(entry) 34 | new_path = os.path.join(_path, entry) 35 | if os.path.isdir(os.path.join(_src_dir, new_path)): 36 | if entry not in _index: 37 | _index[entry] = {} 38 | _update_index(_src_dir, _index[entry], _dest_dir, new_path) 39 | else: 40 | if entry not in _index: 41 | _index[entry] = os.path.join(_dest_dir, _path, entry) if _dest_dir else '' 42 | pass 43 | 44 | try: 45 | with open(index_file_name, 'r', encoding='utf-8') as f: 46 | index = json.load(f) 47 | except FileNotFoundError: 48 | index = {} 49 | 50 | _update_index(src_dir, index, dest_dir, '') 51 | with open(index_file_name, 'w+', encoding='utf-8') as f: 52 | json.dump(index, f, ensure_ascii=False, indent=2) 53 | 54 | 55 | def get_dest_path(dest_dir: str, path: str, file_name: str, index: dict, descent: int = None): 56 | try: 57 | if index: 58 | try: 59 | _res = get_from_index(index, path, file_name) 60 | except KeyError: 61 | _res = None 62 | 63 | if _res: 64 | _path = os.path.dirname(_res) 65 | _file = os.path.basename(_res) 66 | _path = os.path.join( 67 | '..', 68 | '' if descent is None else '..', # в режиме с descent корень находится на уровень выше 69 | _path 70 | ) 71 | 72 | try: 73 | helper.makedirs(os.path.join(dest_dir, _path), exist_ok=True) 74 | except FileExistsError: 75 | pass 76 | return _path, _file 77 | 78 | return path, file_name 79 | except Exception as err: 80 | raise ExtException( 81 | parent=err, 82 | message='Ошибка получения пути из index.json', 83 | detail=f'{path}\{file_name}', 84 | action='CodeOrganizer.get_dest_path', 85 | ) from err 86 | -------------------------------------------------------------------------------- /tests/test_file_organizer_ce.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import sys 4 | import unittest 5 | 6 | sys.path.append("../../src/") 7 | from v8unpack import helper 8 | from v8unpack.organizer_file_ce import OrganizerFileCE 9 | 10 | 11 | class TestFileOrganizerCE(unittest.TestCase): 12 | def setUp(self) -> None: 13 | self.current_dir = os.path.dirname(__file__) 14 | self.data_dir = os.path.join(self.current_dir, 'data') 15 | self.temp_dir = os.path.join(self.data_dir, 'temp') 16 | 17 | def test_near(self): 18 | helper.clear_dir(self.temp_dir) 19 | self.copy_data_to_temp('test.obj.1c', ['3000070060', '3000075100', '3000088001']) 20 | 21 | _path, _file_name = helper.get_near_descent_file_name(self.temp_dir, 'test.obj.1c', 3000075100) 22 | self.assertEqual(self.temp_dir, _path) 23 | self.assertEqual('test.obj.3000075100.1c', _file_name) 24 | _path, _file_name = helper.get_near_descent_file_name(self.temp_dir, 'test.obj.1c', 3000088003) 25 | self.assertEqual(self.temp_dir, _path) 26 | self.assertEqual('test.obj.3000088001.1c', _file_name) 27 | _path, _file_name = helper.get_near_descent_file_name(self.temp_dir, 'test.obj.1c', 1) 28 | self.assertEqual('', _path) 29 | self.assertEqual('', _file_name) 30 | _path, _file_name = helper.get_near_descent_file_name(self.temp_dir, 'test.2.obj.1c', 1) 31 | self.assertEqual('', _path) 32 | self.assertEqual('', _file_name) 33 | 34 | def test_1(self): 35 | helper.clear_dir(self.temp_dir) 36 | self.copy_data_to_temp('test.1c', ['3000070060', '3000075100', '3000088001'], '') 37 | 38 | OrganizerFileCE.unpack_file(self.data_dir, 'test.1c', self.temp_dir, '', 'test.1c', {}, 3000088001) 39 | 40 | self.assertEqual( 41 | os.path.getsize(os.path.join(self.data_dir, 'test.1c')), 42 | os.path.getsize(os.path.join(self.temp_dir, 'test.3000088001.1c')) 43 | ) 44 | 45 | OrganizerFileCE.unpack_file(self.data_dir, 'test.1c', self.temp_dir, '', 'test.1c', {}, 3000075101) 46 | self.assertEqual( 47 | os.path.getsize(os.path.join(self.data_dir, 'test.1c')), 48 | os.path.getsize(os.path.join(self.temp_dir, 'test.3000075101.1c')) 49 | ) 50 | 51 | self.copy_data_to_temp('test.obj.1c', ['3000070060']) 52 | OrganizerFileCE.unpack_file(self.data_dir, 'test.obj.1c', self.temp_dir, '', 'test.obj.1c', {}, 3000070061) 53 | self.assertRaises( 54 | FileNotFoundError, 55 | lambda: os.path.getsize(os.path.join(self.temp_dir, 'test.obj.3000070061.1c')) 56 | ) 57 | self.assertEqual( 58 | os.path.getsize(os.path.join(self.data_dir, 'test.obj.1c')), 59 | os.path.getsize(os.path.join(self.temp_dir, 'test.obj.3000070060.1c')) 60 | ) 61 | 62 | def copy_data_to_temp(self, src_file_name, descents: list, value=None): 63 | for descent in descents: 64 | dest_file_name = helper.get_descent_file_name(src_file_name, descent) 65 | dest_full_path = os.path.join(self.temp_dir, dest_file_name) 66 | shutil.copy( 67 | os.path.join(self.data_dir, src_file_name), 68 | dest_full_path 69 | ) 70 | if value is not None: 71 | with open(dest_full_path, 'w', encoding='utf-8') as file: 72 | file.write('') 73 | -------------------------------------------------------------------------------- /tests/test_form_elem_802_decoder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.append("../../src/") 6 | from v8unpack import helper 7 | from v8unpack.MetaDataObject.versions.Form803 import Form803 8 | from v8unpack.MetaDataObject.versions.Form802 import Form802 9 | from v8unpack.unittest_helper import compare_file, NotEqualLine 10 | 11 | 12 | class TestFormElem802(unittest.TestCase): 13 | def setUp(self) -> None: 14 | self.current_dir = os.path.dirname(__file__) 15 | self.data_dir = os.path.join(self.current_dir, 'data', 'form82') 16 | self.temp_dir = os.path.join(self.data_dir, 'temp') 17 | self.temp_dir2 = os.path.join(self.data_dir, 'temp2') 18 | 19 | def test_convert_to_json(self): 20 | test_files = os.listdir(self.data_dir) 21 | for file_name in test_files: 22 | if os.path.isdir(os.path.join(self.data_dir,file_name)): 23 | continue 24 | json_file_name = f'{file_name}.json' 25 | raw_data = helper.brace_file_read(self.data_dir, file_name) 26 | helper.json_write(raw_data, self.temp_dir, json_file_name) 27 | 28 | def test_decode_all_form_elem(self): 29 | test_files = os.listdir(self.data_dir) 30 | result = '' 31 | for file_name in test_files: 32 | if file_name == 'temp': 33 | continue 34 | item = file_name.split('.') 35 | result += self.decode_form_elem(item[0]) 36 | self.assertEqual('', result) 37 | 38 | def test_decode_form_elem(self): 39 | # file_name = 'СтраницыИПанели' 40 | # file_name = 'Фильтр' 41 | # file_name = '2 добавлена страница' 42 | # file_name = '4 страница 1 добавлена надпись' 43 | file_name = 'ДоПереносаПоля' 44 | file_name = 'ПослеПереносаПоля' 45 | file_name = 'ДоПереноса2' 46 | file_name = 'ПослеПереноса2' 47 | result = self.decode_form_elem(file_name) 48 | self.assertEqual('', result) 49 | 50 | def test_decode_form_elem7(self): 51 | file_name = 'form7' # не панель страницы 01 поставлен ручной порядок обхода и изменен порядок 2 и 3 флагов 52 | result = self.decode_form_elem(file_name) 53 | 54 | def decode_form_elem(self, file_name): 55 | helper.clear_dir(self.temp_dir) 56 | json_origin_file_name = f'{file_name}.origin.json' 57 | json_result_file_name = f'{file_name}.result.json' 58 | raw_data = helper.brace_file_read(self.data_dir, file_name) 59 | helper.json_write(raw_data, self.temp_dir, json_origin_file_name) 60 | form = Form802(options=dict(auto_include=True)) 61 | form.new_dest_dir = self.temp_dir 62 | form.form = [raw_data] 63 | form.decode_includes(None, self.temp_dir, '', None) 64 | form.write_decode_object(self.temp_dir, '', file_name) 65 | form.encode_nested_includes(self.temp_dir, file_name, self.temp_dir, '') 66 | helper.json_write(form.form[0], self.temp_dir, json_result_file_name) 67 | helper.brace_file_write(helper.json_read(self.temp_dir, json_result_file_name), self.temp_dir, file_name) 68 | problems = '' 69 | try: 70 | result = compare_file( 71 | os.path.join(self.temp_dir, json_origin_file_name), 72 | os.path.join(self.temp_dir, json_result_file_name), 73 | problems 74 | ) 75 | except NotEqualLine as err: 76 | result = str(err) 77 | return result 78 | -------------------------------------------------------------------------------- /tests/data/form18-0.json: -------------------------------------------------------------------------------- 1 | [ 2 | "49", 3 | "0", 4 | "0", 5 | "0", 6 | "0", 7 | "1", 8 | "0", 9 | "0", 10 | "00000000-0000-0000-0000-000000000000", 11 | "1", 12 | [ 13 | "1", 14 | "0" 15 | ], 16 | "0", 17 | "0", 18 | "1", 19 | "1", 20 | "1", 21 | "0", 22 | "1", 23 | "0", 24 | [ 25 | "0", 26 | "1", 27 | "0" 28 | ], 29 | [ 30 | "0" 31 | ], 32 | "1", 33 | [ 34 | "22", 35 | [ 36 | "-1", 37 | "02023637-7868-4a5f-8576-835a76e0c9ba" 38 | ], 39 | "0", 40 | "0", 41 | "0", 42 | "9", 43 | "\"ФормаКоманднаяПанель\"", 44 | [ 45 | "1", 46 | "0" 47 | ], 48 | [ 49 | "1", 50 | "0" 51 | ], 52 | "0", 53 | "1", 54 | "0", 55 | "0", 56 | "0", 57 | "2", 58 | "2", 59 | [ 60 | "3", 61 | "4", 62 | [ 63 | "0" 64 | ] 65 | ], 66 | [ 67 | "7", 68 | "3", 69 | "0", 70 | "1", 71 | "100" 72 | ], 73 | [ 74 | "0", 75 | "0", 76 | "0" 77 | ], 78 | "1", 79 | [ 80 | "0", 81 | "0", 82 | "1" 83 | ], 84 | "0", 85 | "1", 86 | "0", 87 | "0", 88 | "0", 89 | "3", 90 | "3", 91 | "0" 92 | ], 93 | "0", 94 | "\"\"", 95 | "\"\"", 96 | "1", 97 | [ 98 | "22", 99 | [ 100 | "0" 101 | ], 102 | "0", 103 | "0", 104 | "0", 105 | "7", 106 | "\"Navigator\"", 107 | [ 108 | "1", 109 | "0" 110 | ], 111 | [ 112 | "1", 113 | "0" 114 | ], 115 | "0", 116 | "1", 117 | "0", 118 | "0", 119 | "0", 120 | "2", 121 | "2", 122 | [ 123 | "3", 124 | "4", 125 | [ 126 | "0" 127 | ] 128 | ], 129 | [ 130 | "7", 131 | "3", 132 | "0", 133 | "1", 134 | "100" 135 | ], 136 | [ 137 | "0", 138 | "0", 139 | "0" 140 | ], 141 | "0", 142 | "0", 143 | "1", 144 | "0", 145 | "1", 146 | [ 147 | "11", 148 | [ 149 | "0" 150 | ], 151 | "0", 152 | "0", 153 | "0", 154 | "0", 155 | "\"NavigatorExtendedTooltip\"", 156 | [ 157 | "1", 158 | "0" 159 | ], 160 | [ 161 | "1", 162 | "0" 163 | ], 164 | "1", 165 | "0", 166 | "0", 167 | "2", 168 | "2", 169 | [ 170 | "3", 171 | "4", 172 | [ 173 | "0" 174 | ] 175 | ], 176 | [ 177 | "7", 178 | "3", 179 | "0", 180 | "1", 181 | "100" 182 | ], 183 | [ 184 | "0", 185 | "0", 186 | "0" 187 | ], 188 | "1", 189 | [ 190 | "5", 191 | "0", 192 | "0", 193 | "3", 194 | "0", 195 | [ 196 | "0", 197 | "1", 198 | "0" 199 | ], 200 | [ 201 | "3", 202 | "4", 203 | [ 204 | "0" 205 | ] 206 | ], 207 | [ 208 | "3", 209 | "4", 210 | [ 211 | "0" 212 | ] 213 | ], 214 | [ 215 | "3", 216 | "0", 217 | [ 218 | "0" 219 | ], 220 | "0", 221 | "1", 222 | "0", 223 | "48312c09-257f-4b29-b280-284dd89efc1e" 224 | ] 225 | ], 226 | "0", 227 | "1", 228 | "2", 229 | [ 230 | "1", 231 | [ 232 | "1", 233 | "0" 234 | ], 235 | "0" 236 | ], 237 | "0", 238 | "0", 239 | "1", 240 | "0", 241 | "0", 242 | "1", 243 | "0", 244 | "3", 245 | "3", 246 | "0" 247 | ], 248 | "0", 249 | "3", 250 | "3", 251 | "0" 252 | ], 253 | "1", 254 | "\"\"", 255 | "0", 256 | "0", 257 | "0", 258 | "0", 259 | "0", 260 | "0", 261 | "3", 262 | "3", 263 | "0", 264 | "0", 265 | "0", 266 | "100", 267 | "1", 268 | "1", 269 | "0", 270 | "0", 271 | "0", 272 | [ 273 | "49", 274 | "0" 275 | ] 276 | ] -------------------------------------------------------------------------------- /src/v8unpack/container_writer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | import os 4 | import tempfile 5 | from base64 import b64encode 6 | from datetime import datetime 7 | from hashlib import sha1 8 | 9 | from tqdm.auto import tqdm 10 | 11 | from . import helper 12 | from .container import Container, Container64 13 | from .container_doc import Document 14 | from .json_container_decoder import JsonContainerDecoder 15 | 16 | 17 | def build(src_dir, filename, nested=False): 18 | """ 19 | Запаковывает каталог в контейнер включая вложенные каталоги. 20 | Сахар для ContainerWriter. 21 | 22 | :param src_dir: каталог с данными, запаковываемыми в контейнер 23 | :type src_dir: string 24 | :param filename: имя файла контейнера 25 | :type filename: string 26 | :param nested: 27 | :type nested: bool 28 | :param version: версия под которую собирается продукт 29 | :type version: string 30 | """ 31 | begin = datetime.now() 32 | print(f'{"Запаковываем бинарник":30}:', end="") 33 | helper.makedirs(os.path.dirname(filename), exist_ok=True) 34 | 35 | containers = os.listdir(src_dir) 36 | _src_dir = containers[-1] 37 | containers_count = len(containers) 38 | if containers_count not in [1, 2]: 39 | raise NotImplementedError(f'Количество контейнеров {containers_count}') 40 | 41 | with open(filename, 'w+b') as f: 42 | _src_dir = os.path.join(src_dir, '0') 43 | container = Container() 44 | container.build(f, _src_dir, nested, offset=0) 45 | if containers_count == 2: 46 | _src_dir = os.path.join(src_dir, '1') 47 | container = Container64() 48 | container.build(f, _src_dir, nested, offset=f.seek(0, os.SEEK_END)) 49 | print(f" - {datetime.now() - begin}") 50 | 51 | 52 | def calc_sha1(src_folder, dest_folder): 53 | entries = sorted(os.listdir(dest_folder)) 54 | versions = [] 55 | versions_file = '' 56 | for filename in entries: 57 | dest_path = os.path.join(dest_folder, filename) 58 | if filename == 'configinfo': 59 | versions_file = 'configinfo' 60 | continue 61 | 62 | with open(dest_path, 'rb') as file: 63 | data = file.read() 64 | versions.append(f'"{filename}"') 65 | versions.append(b64encode(sha1(data).digest()).decode()) 66 | if not versions_file: 67 | return 68 | versions.insert(0, str(int(len(versions) / 2))) 69 | _versions = JsonContainerDecoder().encode_root_object([versions]) 70 | 71 | src_path = os.path.join(src_folder, versions_file) 72 | dest_path = os.path.join(dest_folder, versions_file) 73 | 74 | with open(src_path, 'r+', encoding='utf-8') as file: 75 | data = file.read() 76 | data = data.replace('{____versions____}', _versions) 77 | file.seek(0) 78 | file.write(data) 79 | 80 | compress_and_build_simple_file(src_path, dest_path) 81 | pass 82 | 83 | 84 | def compress_and_build_simple_file(src_path, dest_path): 85 | with open(dest_path, 'w+b') as dest_fd: 86 | with open(src_path, 'rb') as src_fd: 87 | Document.compress(src_fd, dest_fd) 88 | 89 | 90 | def compress_and_build(src_dir, dest_dir, *, pool=None, nested=False): 91 | containers = os.listdir(src_dir) 92 | helper.clear_dir(dest_dir) 93 | for dir_name in containers: 94 | _src_dir = os.path.join(src_dir, dir_name) 95 | _dest_dir = os.path.join(dest_dir, dir_name) 96 | helper.clear_dir(_dest_dir) 97 | 98 | entries = sorted(os.listdir(_src_dir)) 99 | 100 | with tqdm(desc=f'{"Архивируем контейнеры":30}', total=len(entries)) as pbar: 101 | for filename in entries: 102 | src_path = os.path.join(_src_dir, filename) 103 | dest_path = os.path.join(_dest_dir, filename) 104 | # add_entry(container, src_dir, filename) 105 | if os.path.isdir(src_path): 106 | with open(dest_path, 'w+b') as dest_fd: 107 | with tempfile.TemporaryFile() as tmp: 108 | container = Container() 109 | container.build(tmp, src_path, nested=True) 110 | Document.compress(tmp, dest_fd) 111 | else: 112 | compress_and_build_simple_file(src_path, dest_path) 113 | pbar.update() 114 | calc_sha1(_src_dir, _dest_dir) 115 | -------------------------------------------------------------------------------- /docs/transition.md: -------------------------------------------------------------------------------- 1 | # Переход на сборку из одних исходников 2 | 3 | ## Сборка внешней обработки для разных платформ из одних исходников 4 | 5 | Предположим на входе есть три обработки сделанные когда-то для для 8.1, 8.2 и 8.3. 6 | Эти обработки по сути делают одно и то же, просто сделаны для разных платформ. 7 | Код в этих обработках писался под 8.3, и затем переносился и адаптировался под старые 8 | версии платформ. 9 | 10 | В основном он отличается наличием директив и незначительными особенностями платформ. 11 | 12 | Под 8.2 и 8.3 здесь и далее имеется ввиду обычные (82) и управляемые (83+) формы. 13 | 14 | Основной сложностью объединения являются разные идентификаторы объектов (самой обработки, 15 | форм и макетов. 16 | 17 | На выходе мы хотим получить четыре репозитория с исходным кодом, 3 для каждой из версий 18 | платформы, четвертый для субмодуля который будет использоваться в версиях и содержащем 19 | общий код, макеты и саму структуру обработки. 20 | 21 | Порядок действий: 22 | 23 | ### Причесываем обработку 24 | 25 | В комментариях макетов и картинок содержащих файлы, в комментарии последним словом пишем 26 | расширение файла. 27 | 28 | В код добавляем [области](https://github.com/saby-integration/v8unpack/blob/main/docs/usage.md) которые 29 | хотим хранить в отдельных файлах. 30 | 31 | ### Создаем репозиторий для 83 32 | 33 | Создаем репозиторий для 83 и для core. 34 | 35 | Клонируем репозиторий 83 на диск, и добавляем в него субмодуль core 36 | 37 | В папку bin кладем обработку. 38 | 39 | В корне создаем командные файлых для облегчения себе жизни и index.json, где будем описывать какие файлы 40 | у нас общие. 41 | 42 | extract.cmd 43 | 44 | v8unpack.exe -E bin\Sbis1C_UF.epf src --index index.json 45 | 46 | build.cmd 47 | 48 | v8unpack.exe -B src bin\Sbis1C_UF.epf --index index.json --version=83 49 | 50 | update_index.cmd - для формирования и обновления индекса 51 | 52 | v8unpack.exe -I src --index index.json -core core 53 | 54 | index.json 55 | 56 | {} 57 | 58 | В итоге имеем: 59 | 60 | >core - папка подмодуля для общих файлов 61 | >bin - папка для собранных бинарников 62 | Sbis1C_UF.epf 63 | >src - папка где будут исходники 64 | тут красота 65 | build.cmd - запускалка сборки 66 | extract.cmd - запускалка разборки 67 | update_index.cmd - формирования и обновления индекса 68 | index.json - словарь общих файлов 69 | 70 | 71 | ### 2.Распаковываем 72 | Запускаем extract.cmd в результате в src появились исходники. 73 | 74 | Запускаем update_index.cmd в результате заполнился index.json и в него попали все файлы, и по умолчанию 75 | они все выставились как будто они общие и должны лежать в core. Если сейчас ещё раз запустить разборку 76 | то они там все и окажутся. 77 | 78 | Редактируем index.json - у того, что не должно быть в core меняем значение на пустое "". Удалять лишние 79 | ключи смысла нет. При последующих запусках index_update он добавит все чего нет, а то что уже есть 80 | трогать не будет. Как альтернатива, формировать index.json руками или как то ещё. 81 | 82 | В нашем случае в core уезжает почти всё (код, формы, макеты), в 83 остаются только разметка 83 - файлы 83 | оканчивающиеся на *83.json 84 | 85 | Итого определились где что должно лежать, распаковали бинарник ещё раз - теперь имеем исходники в нужном виде. 86 | 87 | Пробуем собрать и проверить открыв собранный файл в 1С (на всякий случай имеем копию бинарника). 88 | 89 | Если все получилось - коммитим. Ура! Самое простое позади. 90 | 91 | ### 3. Объединяем с 82 92 | 93 | Как мы помним изначальной задачей является переиспользование максимально возможного кода, в нашем случае 94 | у нас полностью одинаковое количество, название и тип ресурсов (макеты, формы). 95 | 96 | На текущий момент уровень разбора метаданных достаточно низкий, по сути каждый объект метаданных состоит из 97 | заголовка, ссылок на вложенные объекты, значений свойств (в т.ч. разметка форм) и данных (двоичные данные 98 | макета или программный код). На текущий момент парсер лишь разделяет файлы на эти участки. 99 | 100 | Повторяем все тоже самое как делали для 83, только не забываем в командном файле сборки 101 | поменять номер собираемой версии на 82. 102 | 103 | Отдельные типы объектов генерятся полностью, например макеты и их можно объединить. Для этого нужно 104 | заменить uuid макета 82 на uuid от макета 83 везде где он встречается в исходниках. 105 | 106 | Повторяем для 81. 107 | 108 | Тадам и через пару недель стало жить чуть легче. 109 | 110 | -------------------------------------------------------------------------------- /src/v8unpack/container_reader.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import zlib 4 | from datetime import datetime 5 | 6 | from . import helper 7 | from .container import Container, Container64 8 | from .ext_exception import ExtException 9 | 10 | 11 | def extract(filename, folder, deflate=True, recursive=True): 12 | """ 13 | Распаковка контейнера. Сахар для ContainerReader 14 | 15 | :param filename: полное имя файла-контейнера 16 | :type filename: string 17 | :param folder: каталог назначения 18 | :type folder: string 19 | :param deflate: паспаковка 20 | :type deflate: boolean 21 | :param recursive: рекурсивно достаем все контейнеры 22 | :type recursive: boolean 23 | """ 24 | begin = datetime.now() 25 | print(f'{"Распаковываем бинарник":30}:', end="") 26 | helper.clear_dir(folder) 27 | with open(filename, 'rb') as f: 28 | offset = 0 29 | container_index = 0 30 | while True: 31 | try: 32 | container = detect_format(f, offset) 33 | container.read(f, offset) 34 | container.extract(os.path.join(folder, str(container_index)), deflate, recursive, 35 | progress=container_index) 36 | container_index += 1 37 | offset += container.size 38 | if offset == 0: 39 | raise NotImplementedError() 40 | except EOFError: 41 | return 42 | except Exception as err: 43 | raise err from err 44 | 45 | print(f"{datetime.now() - begin}") 46 | 47 | 48 | def detect_format(f, offset): 49 | f.seek(offset) 50 | first = f.read(8) 51 | if first[0:4] == b'\xFF\xFF\xFF\x7F': 52 | return Container() 53 | elif first == b'': 54 | raise EOFError() 55 | # elif first == b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF': 56 | else: 57 | return Container64() 58 | # raise Exception('Файл не является контейнером') 59 | 60 | 61 | def decompress_and_extract(src_folder, dest_folder, *, pool=None): 62 | helper.clear_dir(dest_folder) 63 | containers = os.listdir(src_folder) 64 | helper.clear_dir(dest_folder) 65 | tasks = [] 66 | for container in containers: 67 | _src_folder = os.path.join(src_folder, container) 68 | _dest_folder = os.path.join(dest_folder, container) 69 | helper.clear_dir(_dest_folder) 70 | entries = os.listdir(_src_folder) 71 | for filename in entries: 72 | tasks.append([_src_folder, filename, _dest_folder]) 73 | 74 | helper.run_in_pool(decompress_file_and_extract, tasks, pool=pool, title=f'{"Распаковываем контейнеры":30}') 75 | 76 | 77 | def decompress_file_and_extract(params): 78 | src_folder, filename, dest_folder = params 79 | src_path = os.path.join(src_folder, filename) 80 | dest_path = os.path.join(dest_folder, filename) 81 | file_is_container = None 82 | 83 | # wbits = -15 т.к. у архивированных файлов нет заголовков 84 | decompressor = zlib.decompressobj(-15) 85 | try: 86 | with open(dest_path, 'wb') as dest: 87 | with open(src_path, 'rb') as src: 88 | while True: 89 | buf = decompressor.unconsumed_tail 90 | if buf == b'': 91 | buf = src.read(8192) 92 | if buf == b'': 93 | break 94 | data = decompressor.decompress(buf) 95 | if file_is_container is None: 96 | file_is_container = data[0:4] == b'\xFF\xFF\xFF\x7F' 97 | if data == b'': 98 | break 99 | dest.write(data) 100 | 101 | # Каждый файл внутри контейнера может быть контейнером 102 | # Для проверки является ли файл контейнером проверим первые 4 бита 103 | # Способ проверки ненадежный - нужно придумать что-то другое 104 | 105 | if file_is_container: 106 | temp_filename = dest_path + ".temp" 107 | os.rename(dest_path, temp_filename) 108 | with open(temp_filename, 'rb') as f: 109 | container = Container() 110 | container.read(f) 111 | container.extract(dest_path, recursive=True) 112 | os.remove(temp_filename) 113 | 114 | except Exception as err: 115 | raise ExtException( 116 | parent=err, message="Ошибка при разархифировании контейнера", 117 | detail=f'{filename} ({err})') 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # saby v8unpack 2 | 3 | [![img lib ver](https://img.shields.io/pypi/v/v8unpack.svg "")](https://pypi.python.org/pypi/v8unpack) 4 | [![img python ver](https://img.shields.io/pypi/pyversions/v8unpack.svg "")](https://pypi.python.org/pypi/v8unpack) 5 | [![img license](https://img.shields.io/pypi/l/v8unpack.svg "")](https://pypi.python.org/pypi/v8unpack) 6 | [![img coverage](https://img.shields.io/coveralls/saby/v8unpack.svg "")](https://coveralls.io/github/saby/v8unpack) 7 | 8 | **v8unpack** - консольная утилита для сборки и распаковки бинарных файлов 9 | 1С:Предприятие 8.х (cf, cfe, epf) без использования технологической платформы. 10 | 11 | В какой-то момент жить без системы контроля версий на уровне исходников стало совсем не выносимо и обозрев все 12 | варианты выбор пал на v8unpack. Однако, без устранения основных недостатков его использование было бы крайне 13 | не удобным (плоский список из нечеловекочитаемых файлов, скрытый где-то в дебрях программный код управляемых форм). 14 | Сразу скажу, что мы с глубоким уважением относимся к труду авторов v8unpack, данная утилита отлично выполняет все 15 | свои функции и без неё создание этого решения было бы не возможным. Кроме этого её Python реализация от [Infactum](https://github.com/Infactum/onec_dtools) 16 | была взята за основу без каких либо изменений. 17 | 18 | ## Ключевые отличия от аналогичных утилит: 19 | * Структура хранения максимально приближена к структуре метаданных, человеко-читаемые имена файлов 20 | * Программный код всегда хранится в отдельных файлах и может быть разделен на несколько файлов 21 | * Общие для разных решений объекты метаданных могут автоматически браться из субмодулей 22 | * Двоичные данные макетов и картинки хранятся в исходном виде 23 | * При сборке под 8.2 и 8.1. автоматически комментируются директивы 8.3 24 | * Файлы хранятся в формате json 25 | * Видны изменения элементов форм 26 | 27 | ## Основным назначением утилиты являются: 28 | 1. Автоматическая сборка приложений 1С (расширения конфигураций, внешние обработки) 29 | для различных платформ и конфигураций из одних и тех же исходников 30 | 2. Удобное и человекочитаемое хранение исходников в системах контроля версий. 31 | 32 | ## Алгоритм работы 33 | Утилита распаковывает и запаковывет бинарник 1С в 4 этапа: 34 | 35 | 1. Распаковка стандартным v8unpack – на выходе текстовые файлы 36 | 2. Конвертация в json 37 | 3. Декодирование заголовков и разбивка по типам метаданных 38 | 4. Организация кода и структуры хранения 39 | * вынос областей кода и панелей форм в отдельные файлы 40 | * сборка кода и форм из нескольких файлов 41 | * разделение объектов метаданных, панелей форм, файлов или областей кода под сабмодулям 42 | * версионирование кода расширения по версиям без применения областей 43 | 44 | ![Алгоритм работы](https://github.com/saby-integration/v8unpack/blob/main/docs/stage.png?raw=true) 45 | 46 | 47 | ## Установка 48 | 49 | pip install v8unpack 50 | 51 | или [скачайте exe файл](https://github.com/saby-integration/v8unpack/releases/) 52 | 53 | собрать exe файл актуальной версии можно запустив build_local.cmd из корня проекта. 54 | 55 | Если работаете с тяжелыми конфигурациями используйте 64-bit питон последних версий. 56 | 57 | ## Распаковка файла 1С 58 | 59 | из командной строки: 60 | 61 | v8unpack.exe -E d:/sample.cf d:/unpack 62 | 63 | из python: 64 | ```python 65 | import v8unpack 66 | 67 | if __name__ == '__main__': 68 | v8unpack.extract('d:/sample.cf', 'd:/unpack') 69 | ``` 70 | 71 | ## Сборка исходников 72 | 73 | из командной строки: 74 | 75 | v8unpack.exe -B d:/unpack d:/repacked.cf 76 | 77 | из python: 78 | 79 | ```python 80 | import v8unpack 81 | 82 | if __name__ == '__main__': 83 | v8unpack.build('d:/unpack', 'd:/repacked.cf') 84 | ``` 85 | 86 | ## Документация 87 | 88 | [Переход на сборку из одних исходников](https://github.com/saby-integration/v8unpack/blob/main/docs/transition.md) 89 | 90 | [Использование](https://github.com/saby-integration/v8unpack/blob/main/docs/usage.md) 91 | 92 | [История изменений](https://github.com/saby-integration/v8unpack/blob/main/docs/history.md) 93 | 94 | [Участие](https://github.com/saby-integration/v8unpack/blob/main/docs/develop.md) 95 | 96 | ## Отладка 97 | 98 | ## Ограничения 99 | 100 | Разметка форм и свойства объектов по прежнему является не читаемыми, но в этом виде проще проводить их 101 | анализ и при желании дополнить парсер. 102 | 103 | На текущий момент [утилита покрывает все типы метаданных в нужном нам объеме](https://github.com/saby-integration/v8unpack/blob/main/src/v8unpack/metadata_types.py), 104 | если Вам нужно более детальная распаковка каких то объектов напишите здесь обращение 105 | или сделайте самостоятельно, мы будем рады [любому участию в проекте](https://github.com/saby-integration/v8unpack/blob/main/docs/develop.md). 106 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements27/FormElements27.py: -------------------------------------------------------------------------------- 1 | from .FormElement import FormProps 2 | from .Panel import Panel 3 | from v8unpack import helper 4 | from v8unpack.ext_exception import ExtException 5 | 6 | 7 | class FormElements27: 8 | FormProps = FormProps 9 | Panel = Panel 10 | 11 | def __init__(self, form): 12 | self.form = form 13 | self.props_index = {} 14 | self.last_elem_id = 1 15 | self.field_data_source = [] 16 | 17 | @property 18 | def options(self): 19 | return self.form.options 20 | 21 | @property 22 | def auto_include(self): 23 | if self.form.options: 24 | return self.form.options.get('auto_include') 25 | return False 26 | 27 | @property 28 | def elements_data(self): 29 | return self.form.elements_data 30 | 31 | @property 32 | def elements_tree(self): 33 | return self.form.elements_tree 34 | 35 | def decode(self, src_dir, dest_dir, dest_path, raw_data): 36 | try: 37 | self.form.props = self.FormProps.decode_list(self.form, raw_data[2][2]) 38 | self.form.elements_tree, self.form.elements_data = self.decode_elements(raw_data) 39 | except Exception as err: 40 | raise ExtException(parent=err) 41 | 42 | def decode_elements(self, form_data): 43 | try: 44 | meta_type = form_data[1][2][0] 45 | if meta_type != '09ccdc77-ea1a-4a6d-ab1c-3435eada2433': 46 | raise ExtException(message=f"Неизвестный формат элементов формы", 47 | detail=f"Новый тип элементов формы {meta_type}, " 48 | f"просьба передать файл формы {self.form.header.get('name')} разработчикам") 49 | self.create_prop_index_by_elem_id(form_data[2][3]) 50 | 51 | elements_tree, elements_data, elements_id = self.Panel.decode(self, '', form_data[1][2]) 52 | return elements_tree, elements_data 53 | except Exception as err: 54 | raise ExtException(parent=err) 55 | 56 | def create_prop_index_by_elem_id(self, raw_data): 57 | try: 58 | self.props_index = {} 59 | _props = {} 60 | if not self.form.props: 61 | return 62 | for prop in self.form.props: 63 | _props[prop['id']] = prop 64 | 65 | element_count = int(raw_data[0]) 66 | if not element_count: 67 | return 68 | 69 | for i in range(element_count): 70 | elem_raw_data = raw_data[i + 1] 71 | elem_id = elem_raw_data[0] 72 | # if elem_raw_data[1][0] == '1': 73 | prop_id = elem_raw_data[1][1][0] 74 | try: 75 | self.props_index[elem_id] = {'name': _props[prop_id]['name'], 'index': elem_raw_data[1]} 76 | except KeyError: 77 | pass 78 | # else: 79 | # raise NotImplementedError('prop index > 1') 80 | except Exception as err: 81 | raise ExtException(parent=err) 82 | 83 | def create_prop_index_by_name(self): 84 | try: 85 | self.props_index = {} 86 | if self.form.props: 87 | for prop in self.form.props: 88 | self.props_index[prop['name']] = prop['id'] 89 | except Exception as err: 90 | raise ExtException(parent=err) 91 | 92 | def fill_datasource(self, raw_data): 93 | raw_data.append(str(len(self.field_data_source))) 94 | for elem_id, prop_id in self.field_data_source: 95 | raw_data.append( 96 | [str(elem_id), ['1', [str(prop_id)]]] 97 | ) 98 | 99 | def encode(self, src_dir, file_name, version, raw_data, path=''): 100 | try: 101 | # index_element_count = 0 102 | # if raw_data[index_element_count] == 'Дочерние элементы отдельно': 103 | # elements = helper.json_read(src_dir, f'{file_name}.elements802.json') 104 | elements = helper.json_read(src_dir, f'{file_name}.elem.json') 105 | self.form.props = elements['props'] 106 | self.form.elements_data = elements['data'] 107 | self.form.elements_tree = elements['tree'] 108 | self.FormProps.encode_list(self.form, raw_data[2][2]) 109 | self.create_prop_index_by_name() 110 | self.Panel.encode(self, '', None, dict(raw=raw_data[1][2])) 111 | if self.auto_include: 112 | raw_data[2][3] = [] 113 | self.fill_datasource(raw_data[2][3]) 114 | return raw_data 115 | except Exception as err: 116 | raise ExtException(parent=err) 117 | -------------------------------------------------------------------------------- /src/v8unpack/MetaObject/ConfigurationExtension.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .. import helper 4 | from ..ext_exception import ExtException 5 | from ..MetaObject.Configuration import Configuration 6 | from ..version import __version__ 7 | 8 | 9 | class ConfigurationExtension(Configuration): 10 | info = ['6', '8', '9'] 11 | ext_code = { 12 | 'con': '5', 13 | 'app': '6', 14 | 'ssn': '7' 15 | } 16 | _obj_info = { 17 | 'a': 'a', 18 | } 19 | 20 | def decode(self, src_dir, dest_dir, *, version=None, **kwargs): 21 | self.header = {} 22 | root = helper.brace_file_read(src_dir, 'configinfo') 23 | self.header['configinfo'] = root 24 | self.header['v8unpack'] = __version__ 25 | self.header['file_uuid'] = root[1][1] 26 | self.header['version'] = root[0][1] 27 | # self.header['versions'] = root[2] 28 | self.header['copyinfo'] = root[1] 29 | # self.header['copyinfo'][2] = b64decode(self.header['copyinfo'][2])[:-16].hex() 30 | self.header['header'] = helper.brace_file_read(src_dir, f'{self.header["file_uuid"]}') 31 | _form_header = self.get_decode_header(self.header['header']) 32 | helper.decode_header(self, _form_header, id_in_separate_file=False) 33 | product_version = self.header['header'][0][3][1][1][15] 34 | if version is None: 35 | self.header['compatibility_version'] = self.header['header'][0][3][1][1][43] 36 | else: 37 | self.header['compatibility_version'] = version 38 | self.header['header'][0][3][1][1][43] = version 39 | 40 | self.decode_code(src_dir) 41 | 42 | for i in self.info: # хз что это 43 | file_name = f'{self.header["uuid"]}.{i}' 44 | if os.path.isdir(os.path.join(src_dir, file_name)): 45 | continue 46 | try: 47 | self.header[f'info{i}'] = helper.brace_file_read(src_dir, file_name) 48 | except FileNotFoundError: 49 | pass 50 | file_name = self.get_class_name_without_version() 51 | self._decode_info(src_dir, dest_dir, file_name) 52 | tasks = self.decode_includes(src_dir, dest_dir, '', self.header['header']) 53 | 54 | helper.txt_write(helper.str_decode(product_version), dest_dir, 'version.bin', encoding='utf-8') 55 | self.header['obj_version'] = self.obj_version 56 | helper.json_write(self.header, dest_dir, f'{self.get_class_name_without_version()}.json') 57 | self.write_decode_code(dest_dir, self.__class__.__name__) 58 | 59 | return tasks 60 | 61 | def encode(self, src_dir, dest_dir, *, file_name=None, include_index=None, file_list=None): 62 | try: 63 | self.header = helper.json_read(src_dir, f'{self.get_class_name_without_version()}.json') 64 | 65 | self.set_product_info(src_dir, file_name) 66 | 67 | # установка режима совместимости 68 | version = self.get_options('version') 69 | if version is not None: 70 | self.header['header'][0][3][1][1][43] = version 71 | prefix = self.get_options('prefix') 72 | if prefix is not None: 73 | self.header['header'][0][3][1][1][42] = helper.str_encode(prefix) 74 | 75 | # gui = self.get_options('gui') 76 | # if gui is not None: 77 | # self.header['header'][0][3][1][1][38] = gui 78 | 79 | helper.check_version(__version__, self.header.get('v8unpack', '')) 80 | 81 | if include_index and self.get_options('auto_include'): 82 | self.fill_header_includes(include_index) 83 | 84 | # self.header['copyinfo'][2] = b64encode(bytes.fromhex(self.header['copyinfo'][2]+md5().digest().hex())).decode() 85 | root = [ 86 | ["0", self.encode_version()], 87 | self.header['copyinfo'], 88 | # self.header['versions'] 89 | ["____versions____"] 90 | ] 91 | self.encode_code(src_dir, self.__class__.__name__) 92 | self.write_encode_code(dest_dir) 93 | _file_name = self.get_class_name_without_version() 94 | self._encode_info(src_dir, _file_name, dest_dir) 95 | helper.brace_file_write(root, dest_dir, 'configinfo') 96 | helper.brace_file_write(self.header['header'], dest_dir, self.header["file_uuid"]) 97 | for i in self.info: # хз что это 98 | try: 99 | helper.brace_file_write(self.header[f'info{i}'], dest_dir, f'{self.header["uuid"]}.{i}') 100 | except KeyError: 101 | pass 102 | 103 | return None 104 | except Exception as err: 105 | raise ExtException(parent=err) 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/v8unpack/MetaObject/ExternalDataProcessor.py: -------------------------------------------------------------------------------- 1 | from ..version import __version__ 2 | from .. import helper 3 | from ..MetaObject import MetaObject 4 | from ..ext_exception import ExtException 5 | 6 | 7 | class ExternalDataProcessor(MetaObject): 8 | 9 | def __init__(self, *, meta_obj_class=None, obj_version=None, options=None): 10 | super().__init__(meta_obj_class=meta_obj_class, obj_version=obj_version, options=options) 11 | self.data = None 12 | 13 | def decode(self, src_dir, dest_dir): 14 | self.header = {} 15 | self.data = {} 16 | root = helper.brace_file_read(src_dir, 'root') 17 | self.header['root'] = True 18 | self.header["file_uuid"] = root[0][1] 19 | _header_data = helper.brace_file_read(src_dir, f'{self.header["file_uuid"]}') 20 | self.decode_header(_header_data, id_in_separate_file=False) 21 | 22 | root = helper.brace_file_read(src_dir, 'root') 23 | self.header['v8unpack'] = __version__ 24 | self.header['file_uuid'] = root[0][1] 25 | self.header['version'] = helper.brace_file_read(src_dir, 'version') 26 | # self.header['versions'] = helper.brace_file_read(src_dir, 'versions') 27 | self.header['copyinfo'] = helper.brace_file_read(src_dir, 'copyinfo') 28 | 29 | try: 30 | form1 = helper.brace_file_read(src_dir, f'{self.header["uuid"]}.1') 31 | except FileNotFoundError: 32 | form1 = None 33 | 34 | self.header['form1'] = form1 35 | 36 | self.decode_code(src_dir, uncomment_directive=self.obj_version in ['802', '801']) 37 | pass 38 | _file_name = self.get_class_name_without_version() 39 | self.container_uuid = self.get_container_uuid(self.header['header']) 40 | tasks = self.decode_includes(src_dir, dest_dir, '', self.header['header']) 41 | 42 | self.header['obj_version'] = self.obj_version 43 | helper.json_write(self.header, dest_dir, f'{_file_name}.json') 44 | # helper.json_write(self.data, dest_dir, f'{_file_name}.data{self.obj_version}.json') 45 | self.write_decode_code(dest_dir, 'ExternalDataProcessor') 46 | 47 | return tasks 48 | # helper.run_in_pool(self.decode_include, tasks, pool) 49 | pass 50 | 51 | @classmethod 52 | def get_container_uuid(cls, header_data): 53 | return header_data[0][3][1][1][1] 54 | 55 | @classmethod 56 | def get_decode_includes(cls, header_data): 57 | return [header_data[0][3][1]] 58 | 59 | @classmethod 60 | def get_decode_header(cls, header_data): 61 | return header_data[0][3][1][1][3][1] 62 | 63 | def encode(self, src_dir, dest_dir, *, file_name=None, include_index=None, file_list=None, **kwargs): 64 | try: 65 | _file_name = self.get_class_name_without_version() 66 | self.header = helper.json_read(src_dir, f'{_file_name}.json') 67 | helper.check_version(__version__, self.header.get('v8unpack', '')) 68 | 69 | # try: 70 | # self.data = helper.json_read(src_dir, f'{_file_name}.data{self.obj_version}.json') 71 | # except FileNotFoundError: 72 | # self.data = self.encode_empty_data() 73 | 74 | self.set_product_info(src_dir, file_name) 75 | 76 | if include_index and self.get_options('auto_include'): 77 | self.fill_header_includes(include_index) 78 | 79 | helper.brace_file_write(self.encode_root(), dest_dir, 'root') 80 | file_list.append('root') 81 | helper.brace_file_write(self.header['version'], dest_dir, 'version') 82 | file_list.append('version') 83 | helper.brace_file_write(self.header['copyinfo'], dest_dir, 'copyinfo') 84 | file_list.append('copyinfo') 85 | # helper.brace_file_write(self.header['versions'], dest_dir, 'versions') 86 | helper.brace_file_write(self.header['header'], dest_dir, self.header["file_uuid"]) 87 | file_list.append(self.header["file_uuid"]) 88 | if self.header.get('form1'): 89 | file_name = f'{self.header["uuid"]}.1' 90 | helper.brace_file_write(self.header['form1'], dest_dir, file_name) 91 | file_list.append(file_name) 92 | self.encode_code(src_dir, 'ExternalDataProcessor') 93 | self.write_encode_code(dest_dir, comment_directive=self.obj_version in ['802', '801']) 94 | 95 | file_list.append('versions') 96 | file_list.extend(self.file_list) 97 | versions = self.encode_versions(file_list) 98 | helper.brace_file_write(versions, dest_dir, 'versions') 99 | return None 100 | except Exception as err: 101 | raise ExtException(parent=err) 102 | 103 | def encode_root(self): 104 | return [[ 105 | "2", 106 | self.header["file_uuid"], 107 | "" 108 | ]] 109 | 110 | def encode_empty_data(self): 111 | return { 112 | "copyinfo": [ 113 | [ 114 | "4", 115 | [ 116 | "0" 117 | ], 118 | [ 119 | "0" 120 | ], 121 | [ 122 | "0" 123 | ], 124 | [ 125 | "0", 126 | "0" 127 | ], 128 | [ 129 | "0" 130 | ] 131 | ] 132 | ] 133 | } 134 | -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements4/FormElements4.py: -------------------------------------------------------------------------------- 1 | from .FormElement import FormProps, FormParams, FormCommands, FormElement 2 | from v8unpack.MetaDataObject import helper 3 | from v8unpack.ext_exception import ExtException 4 | from v8unpack.helper import calc_offset 5 | 6 | 7 | class FormElements4: 8 | 9 | def __init__(self, form): 10 | self.form = form 11 | self.props_index = None 12 | self.commands_index = None 13 | 14 | @property 15 | def elements_data(self): 16 | return self.form.elements_data 17 | 18 | @property 19 | def elements_tree(self): 20 | return self.form.elements_tree 21 | 22 | def decode(self, src_dir, dest_dir, dest_path, raw_data): 23 | try: 24 | self.form.props = FormProps.decode_list(self, raw_data) 25 | self.form.commands = FormCommands.decode_list(self, raw_data) 26 | self.decode_elements(raw_data) 27 | self.form.params = FormParams.decode_list(self, raw_data) 28 | except Exception as err: 29 | raise ExtException(parent=err) 30 | 31 | def create_prop_index_by_id(self): 32 | self.props_index = {} 33 | if self.form.props: 34 | for prop in self.form.props: 35 | self.props_index[prop['id']] = {'name': prop['name'], 'child': {}} 36 | childs = prop.get('child', []) 37 | for child in childs: 38 | self.props_index[prop['id']]['child'][child['id']] = {'name': child['name'], 'child': {}} 39 | 40 | def create_commands_index_by_id(self): 41 | self.commands_index = {} 42 | if self.form.commands: 43 | for command in self.form.commands: 44 | self.commands_index[command['id']] = command['name'] 45 | 46 | def create_prop_index_by_name(self): 47 | self.props_index = {} 48 | if self.form.props: 49 | for prop in self.form.props: 50 | self.props_index[prop['name']] = prop['id'] 51 | childs = prop.get('child', []) 52 | for child in childs: 53 | self.props_index[f"{prop['name']}.{child['name']}"] = prop['id'], child['id'] 54 | 55 | def create_commands_index_by_name(self): 56 | self.commands_index = {} 57 | if self.form.commands: 58 | for command in self.form.commands: 59 | self.commands_index[command['name']] = command['raw'][1] 60 | 61 | def decode_elements(self, raw_data): 62 | try: 63 | index = self.get_form_elem_index(raw_data) 64 | root_data = raw_data[1] 65 | self.create_prop_index_by_id() 66 | self.create_commands_index_by_id() 67 | # index_panel_count = index[1] 68 | # form_panels_count = int(root_data[index_panel_count]) 69 | # if form_panels_count: 70 | # self.command_panels = [root_data[index_panel_count + 1]] 71 | # root_data[index_panel_count] = 'В отдельном файле' 72 | # del root_data[index_panel_count + 1] 73 | 74 | index_root_element_count = index[0] 75 | form_items_count = int(root_data[index_root_element_count]) 76 | if form_items_count: 77 | self.form.elements_tree = FormElement.decode_list(self, root_data, index_root_element_count) 78 | self.form.elements_data = dict(sorted(self.form.elements_data.items())) 79 | pass 80 | except Exception as err: 81 | raise ExtException(parent=err) 82 | 83 | def get_form_elem_index(self, raw_data): 84 | try: 85 | root_data = raw_data[1] 86 | index_command_panel_count = calc_offset([(18, 2), (3, 0)], root_data) 87 | command_panel_count = int(root_data[index_command_panel_count]) 88 | index_root_elem_count = index_command_panel_count + command_panel_count + 1 89 | return index_root_elem_count, index_command_panel_count 90 | except Exception as err: 91 | raise ExtException( 92 | message='случай требующий анализа, предоставьте образец формы разработчикам', 93 | detail=f'{self.form.name}, {err}') 94 | 95 | def encode(self, src_dir, file_name, dest_dir, raw_data): 96 | try: 97 | # elements = helper.json_read(src_dir, f'{file_name}.elements{self.version}.json') 98 | elements = helper.json_read(src_dir, f'{file_name}.elem.json') 99 | try: 100 | self.form.elements_tree = elements.get('tree') 101 | self.form.elements_data = elements.get('data') 102 | self.form.props = elements.get('props') 103 | self.form.params = elements.get('params') 104 | self.form.commands = elements.get('commands') 105 | except (KeyError, TypeError): 106 | raise ExtException(message='Форма разобрана старым сборщиком (<0.16), разберите её новым сборщиком', 107 | action='Form9of.encode_elements') 108 | 109 | self.form.commands = FormCommands.encode_list(self, src_dir, file_name, None, raw_data) 110 | self.form.props = FormProps.encode_list(self, src_dir, file_name, None, raw_data) 111 | 112 | self.create_prop_index_by_name() 113 | self.create_commands_index_by_name() 114 | 115 | self.encode_elements(src_dir, file_name, dest_dir, raw_data) 116 | FormParams.encode_list(self, src_dir, file_name, None, raw_data) 117 | except Exception as err: 118 | raise ExtException(parent=err) 119 | 120 | def encode_elements(self, src_dir, file_name, dest_dir, raw_data): 121 | try: 122 | index = self.get_form_elem_index(raw_data) 123 | root_data = raw_data[1] 124 | 125 | # index_panel_count = index[1] 126 | # form_panels_count = int(root_data[index_panel_count]) 127 | # if form_panels_count == 'В отдельном файле': 128 | # self.command_panels = helper.json_read(src_dir, f'{file_name}.panels{version}.json') 129 | 130 | index_root_element_count = index[0] 131 | if root_data[index_root_element_count] == 'Дочерние элементы отдельно': 132 | # root_data[index_root_element_count] = str(len(self.elements_tree)) 133 | FormElement.encode_list(self, self.form.elements_tree, root_data, index_root_element_count) 134 | except Exception as err: 135 | raise ExtException(parent=err) 136 | -------------------------------------------------------------------------------- /src/v8unpack/organizer_code.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from . import helper 4 | from .ext_exception import ExtException 5 | 6 | 7 | class OrganizerCode: 8 | def __init__(self): 9 | self.code_areas = None 10 | self.data = None 11 | 12 | @staticmethod 13 | def is_area(name): 14 | area_type = name[:17] 15 | is_area = area_type in ('#Область include_', '#Область includr_') 16 | if is_area: 17 | return area_type[9:-1] 18 | return None 19 | 20 | @classmethod 21 | def unpack(cls, params): 22 | src_dir, path, file_name, dest_dir, index, descent = params 23 | self = cls() 24 | self.code_areas = {'root': dict(data='')} 25 | _path = [''] 26 | _include_path = ['root'] 27 | with open(os.path.join(src_dir, path, file_name), 'r', encoding='utf-8-sig') as file: 28 | line = file.readline() 29 | while line: 30 | try: 31 | _line = line.strip() 32 | dynamic_directive_index = line.rfind(' //DynamicDirective') 33 | if dynamic_directive_index >= 0: 34 | if line.find('&') >= 0: 35 | line = line[dynamic_directive_index + 1:] 36 | if _line and _line[0] == '#': 37 | if _line.startswith('#Область'): 38 | area_type = cls.is_area(_line) 39 | if area_type: 40 | area_name_parts = _line[17:].strip().split(' //') 41 | key = area_name_parts[0] 42 | 43 | # if key in self.code_areas: 44 | # raise Exception( 45 | # f'В {path}{file_name} ссылка на один и тот же файл у разных областей {key}') 46 | self.code_areas[_include_path[-1]]['data'] += line 47 | self.code_areas[key] = dict(data='', area_type=area_type) 48 | _include_path.append(key) 49 | _path.append(key) 50 | else: 51 | self.code_areas[_include_path[-1]]['data'] += line 52 | _path.append('') 53 | 54 | elif _line.startswith('#КонецОбласти'): 55 | if _path[-1]: # кончилась include область 56 | _include_path.pop() 57 | _path.pop() 58 | self.code_areas[_include_path[-1]]['data'] += line 59 | else: 60 | self.code_areas[_include_path[-1]]['data'] += line 61 | else: 62 | self.code_areas[_include_path[-1]]['data'] += line 63 | line = file.readline() 64 | except Exception as err: 65 | raise ExtException(parent=err, detail=f'in file {file_name} line {line}') from err 66 | return self.code_areas 67 | 68 | @classmethod 69 | def pack(cls, params): 70 | src_dir, src_path, src_file_name, dest_dir, dest_path, dest_file_name, index_code_areas, \ 71 | descent, pack_get_descent_filename = params 72 | data = cls.pack_file(src_dir, src_path, src_file_name, index_code_areas, descent, pack_get_descent_filename) 73 | helper.txt_write(data, os.path.join(dest_dir, dest_path), dest_file_name) 74 | 75 | @classmethod 76 | def pack_file(cls, src_dir, path, file_name, index_code_areas, descent, pack_get_descent_filename, 77 | dynamic_directive=None): 78 | normcase_src = os.path.normcase('\\src\\') 79 | try: 80 | data = '' 81 | _src_abs_path = os.path.abspath(os.path.join(src_dir, path)) 82 | if _src_abs_path.startswith(src_dir) or os.path.normcase(path).find(normcase_src) >= 0: 83 | _src_abs_path, file_name = pack_get_descent_filename(_src_abs_path, file_name, descent) 84 | 85 | with open(os.path.join(_src_abs_path, file_name), 'r', encoding='utf-8') as file: 86 | line = file.readline() 87 | while line: 88 | _line = line.strip() 89 | if _line: 90 | if _line.startswith('//DynamicDirective') and dynamic_directive: 91 | line = f'{dynamic_directive} {line}' 92 | data += line 93 | if _line and _line[0] == '#': 94 | if cls.is_area(_line): 95 | area_name_parts = _line[17:].strip().split(' //') 96 | include_path = area_name_parts[0] 97 | 98 | current_dynamic_directive = area_name_parts[1] if len(area_name_parts) > 1 else dynamic_directive 99 | 100 | _path, _file_name = cls.parse_include_path(include_path, path, file_name, index_code_areas, 101 | descent) 102 | _src_abs_path = os.path.abspath(os.path.join(src_dir, _path)) 103 | if _src_abs_path.startswith(src_dir) or os.path.normcase(_path).find(normcase_src) >= 0: 104 | _path, _file_name = pack_get_descent_filename(_src_abs_path, _file_name, descent) 105 | data += cls.pack_file(src_dir, _path, _file_name, index_code_areas, descent, 106 | pack_get_descent_filename, current_dynamic_directive) 107 | line = file.readline() 108 | return data 109 | except ExtException as err: 110 | raise ExtException( 111 | parent=err, 112 | action=f'{cls.__name__}.pack_file {file_name}') from err 113 | except Exception as err: 114 | raise ExtException( 115 | parent=err, 116 | action=f'{cls.__name__}.pack_file {file_name}', 117 | message='Ошибка упаковки файла', detail=f'{os.path.join(path, file_name)}: {err}') from err 118 | 119 | @staticmethod 120 | def parse_include_path(include_path, path, file_name, index_code_areas, descent=None, *, file_extension='bsl'): 121 | if index_code_areas and include_path in index_code_areas: 122 | include_path = index_code_areas[include_path] 123 | tmp = include_path.split('_') 124 | size_tmp = len(tmp) 125 | if size_tmp == 0: 126 | raise Exception(f'{path} {file_name} в include не указан путь') 127 | _file_name = f'{tmp[-1]}.{file_extension}' 128 | _path = '..' # include не должен лежать внутри папки с исходниками 129 | if descent is not None: 130 | _path = os.path.join(_path, '..') 131 | if size_tmp > 1: 132 | tmp = ['..' if elem == '' else elem for elem in tmp[:-1]] 133 | _path = os.path.join(_path, *tmp) 134 | if _path == '..': 135 | _path = path 136 | return _path, _file_name 137 | -------------------------------------------------------------------------------- /tests/BadBraceExamples/bad_example/ce802DataProcessorInvalidStartByte: -------------------------------------------------------------------------------- 1 | {1, 2 | {16,f19ca7f7-0910-4819-ac4f-4aff29fb6393,98e5092c-4a2e-4565-864f-d0c6de4e1f1f, 3 | {0, 4 | {0, 5 | {0,0,9d85a8d0-5e73-4c6a-8f1c-90534632e07b},"ВыгрузкаДанныхВ1СБухгалтерию77", 6 | {1,"ru","Выгрузка данных в ""1С:Бухгалтерию 7.7"""},""} 7 | },e277a104-e829-41e0-b81d-a76f3f092159,0,1,0b3f5a76-f45d-44f5-ab9c-6ce8243cab04,bd058632-aec4-46e1-8103-49f99f8d0b12,00000000-0000-0000-0000-000000000000, 8 | {0}, 9 | {0} 10 | },5, 11 | {2bcef0d1-0981-11d6-b9b8-0050bae0a95d,1, 12 | { 13 | {0, 14 | {11,02f52a24-b6e0-4443-8fb8-4bbdaf961d46,7e5a08c2-45b3-458d-943f-0d3052ddc33c,a8812006-a3cf-4c1e-8dbb-9e0b2e20e5ed,2d0dcfe1-7312-4bdc-ba67-e8c829e99eb8, 15 | {0, 16 | {0, 17 | {0,0,41670c40-dfce-47b9-89f5-68b2fa7f8be5},"ФильтрыОтчета", 18 | {1,"ru","Фильтры отчета"},""} 19 | },0, 20 | {0}, 21 | {0} 22 | } 23 | },1, 24 | {5d24a9d1-098e-11d6-b9b8-0050bae0a95d,5, 25 | { 26 | {0, 27 | {25, 28 | {2, 29 | {0, 30 | {0,0,61d6c629-74bf-43df-9416-b175271e2dc6},"ИмяФильтра", 31 | {1,"ru","Имя фильтра"},""}, 32 | {"Pattern", 33 | {"S",50,1} 34 | } 35 | },0, 36 | {0}, 37 | {0},0,"",0, 38 | {"U"}, 39 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 40 | {5004,0}, 41 | {3,0,0}, 42 | {0,0},0, 43 | {0}, 44 | {"U"},0} 45 | },0}, 46 | { 47 | {0, 48 | {25, 49 | {2, 50 | {0, 51 | {0,0,de985b9d-f617-4792-863e-6f56d08093ee},"ЗначениеФильтра", 52 | {1,"ru","Значение фильтра"},""}, 53 | {"Pattern", 54 | {"#",280f5f0e-9c8a-49cc-bf6d-4d296cc17a63}, 55 | {"B"}, 56 | {"S",10,1}, 57 | {"D","D"}, 58 | {"N",10,0,0} 59 | } 60 | },0, 61 | {0}, 62 | {0},0,"",0, 63 | {"U"}, 64 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 65 | {5004,0}, 66 | {3,0,0}, 67 | {0,0},0, 68 | {0}, 69 | {"U"},0} 70 | },0}, 71 | { 72 | {0, 73 | {25, 74 | {2, 75 | {0, 76 | {0,0,92699c71-b62f-47f3-bc20-8308ee24ceeb},"ПредставлениеФильтра", 77 | {1,"ru","Представление фильтра"},""}, 78 | {"Pattern", 79 | {"S",50,1} 80 | } 81 | },0, 82 | {0}, 83 | {0},0,"",0, 84 | {"U"}, 85 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 86 | {5004,0}, 87 | {3,0,0}, 88 | {0,0},0, 89 | {0}, 90 | {"U"},0} 91 | },0}, 92 | { 93 | {0, 94 | {25, 95 | {2, 96 | {0, 97 | {0,0,d52baf0e-7d32-496e-bc92-e2c9ef8bffd9},"ОписаниеФильтра", 98 | {1,"ru","Описание фильтра"},""}, 99 | {"Pattern", 100 | {"S",100,1} 101 | } 102 | },0, 103 | {0}, 104 | {0},0,"",0, 105 | {"U"}, 106 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 107 | {5004,0}, 108 | {3,0,0}, 109 | {0,0},0, 110 | {0}, 111 | {"U"},0} 112 | },0}, 113 | { 114 | {0, 115 | {25, 116 | {2, 117 | {0, 118 | {0,0,d2a3c460-4a3a-4283-9e5d-b9bfa4c07cf7},"ТипФильтра", 119 | {1,"ru","Тип фильтра"},""}, 120 | {"Pattern", 121 | {"S",10,1} 122 | } 123 | },0, 124 | {0}, 125 | {0},0,"",0, 126 | {"U"}, 127 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 128 | {5004,0}, 129 | {3,0,0}, 130 | {0,0},0, 131 | {0}, 132 | {"U"},0} 133 | },0} 134 | } 135 | } 136 | }, 137 | {3daea016-69b7-4ed4-9453-127911372fe6,1,67840a1d-2826-4a7e-8542-8512c6dd246c}, 138 | {45556acb-826a-4f73-898a-6025fc9536e1,0}, 139 | {d5b0e5ed-256d-401c-9c36-f630cafd8a62,2,e277a104-e829-41e0-b81d-a76f3f092159,40c751cc-7eb2-445c-9eea-d026a096fe1e}, 140 | {ec6bb5e5-b7a8-4d75-bec9-658107a699cf,11, 141 | { 142 | {0, 143 | {25, 144 | {2, 145 | {0, 146 | {0,0,7637df1e-84c3-4393-bc13-904aecfc3e7f},"ДатаНач", 147 | {1,"ru","Дата Нач"},""}, 148 | {"Pattern", 149 | {"D","D"} 150 | } 151 | },0, 152 | {0}, 153 | {0},0,"",0, 154 | {"U"}, 155 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 156 | {5004,0}, 157 | {3,0,0}, 158 | {0,0},0, 159 | {0}, 160 | {"U"},0} 161 | },0}, 162 | { 163 | {0, 164 | {25, 165 | {2, 166 | {0, 167 | {0,0,6a61c499-0888-4511-9a9b-862e834cbb51},"ДатаКон", 168 | {1,"ru","Дата Кон"},""}, 169 | {"Pattern", 170 | {"D","D"} 171 | } 172 | },0, 173 | {0}, 174 | {0},0,"",0, 175 | {"U"}, 176 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 177 | {5004,0}, 178 | {3,0,0}, 179 | {0,0},0, 180 | {0}, 181 | {"U"},0} 182 | },0}, 183 | { 184 | {0, 185 | {25, 186 | {2, 187 | {0, 188 | {0,0,24b52e26-8a98-479a-adb4-298a00ffc11c},"НеЗамещатьДокументыПриЗагрузке", 189 | {1,"ru","Не замещать документы при загрузке"},""}, 190 | {"Pattern", 191 | {"B"} 192 | } 193 | },0, 194 | {0}, 195 | {0},0,"",0, 196 | {"U"}, 197 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 198 | {5004,0}, 199 | {3,0,0}, 200 | {0,0},0, 201 | {0}, 202 | {"U"},0} 203 | },0}, 204 | { 205 | {0, 206 | {25, 207 | {2, 208 | {0, 209 | {0,0,2c2f3e04-9a57-44c3-9a58-594dcdeb070c},"НеЗамещатьСправочникиПриЗагрузке", 210 | {1,"ru","Не замещать справочники при загрузке"},""}, 211 | {"Pattern", 212 | {"B"} 213 | } 214 | },0, 215 | {0}, 216 | {0},0,"",0, 217 | {"U"}, 218 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 219 | {5004,0}, 220 | {3,0,0}, 221 | {0,0},0, 222 | {0}, 223 | {"U"},0} 224 | },0}, 225 | { 226 | {0, 227 | {25, 228 | {2, 229 | {0, 230 | {0,0,490098db-eab7-49b1-a527-b0e38cb32f87},"ВыгружатьАналитикуПоСкладам", 231 | {1,"ru","Выгружать аналитику по складам"},""}, 232 | {"Pattern", 233 | {"B"} 234 | } 235 | },0, 236 | {0}, 237 | {0},0,"",0, 238 | {"U"}, 239 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 240 | {5004,0}, 241 | {3,0,0}, 242 | {0,0},0, 243 | {0}, 244 | {"U"},0} 245 | },0}, 246 | { 247 | {0, 248 | {25, 249 | {2, 250 | {0, 251 | {0,0,2300bcf8-7f6a-44eb-9cb4-2e21d5862a37},"Организация", 252 | {1,"ru","Организация"},""}, 253 | {"Pattern", 254 | {"#",0e283f00-16f5-4700-b0f6-5206e4fefcf3} 255 | } 256 | },0, 257 | {0}, 258 | {0},0,"",0, 259 | {"U"}, 260 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 261 | {5004,0}, 262 | {3,0,0}, 263 | {0,0},0, 264 | {0}, 265 | {"U"},0} 266 | },0}, 267 | { 268 | {0, 269 | {25, 270 | {2, 271 | {0, 272 | {0,0,4293ed95-fac9-41cc-a867-bb21527be1cd},"ФайлВыгрузки", 273 | {1,"ru","Файл выгрузки"},""}, 274 | {"Pattern", 275 | {"S"} 276 | } 277 | },0, 278 | {0}, 279 | {0},0,"",0, 280 | {"U"}, 281 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 282 | {5004,0}, 283 | {3,0,0}, 284 | {0,0},0, 285 | {0}, 286 | {"U"},0} 287 | },0}, 288 | { 289 | {0, 290 | {25, 291 | {2, 292 | {0, 293 | {0,0,2a624d9b-1711-424b-92f6-707012a2bcc2},"ТаблицаПравилВыгрузки", 294 | {1,"ru","Таблица правил выгрузки"},""}, 295 | {"Pattern", 296 | {"#",e603c0f2-92fb-4d47-8f38-a44a381cf235} 297 | } 298 | },0, 299 | {0}, 300 | {0},0,"",0, 301 | {"U"}, 302 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 303 | {5004,0}, 304 | {3,0,0}, 305 | {0,0},0, 306 | {0}, 307 | {"U"},0} 308 | },0}, 309 | { 310 | {0, 311 | {25, 312 | {2, 313 | {0, 314 | {0,0,03b86161-52b9-4c46-ab86-3e34f6a67994},"ТаблицаПравилКонвертации", 315 | {1,"ru","Таблица правил конвертации"},""}, 316 | {"Pattern", 317 | {"S",10,1} 318 | } 319 | },0, 320 | {0}, 321 | {0},0,"",0, 322 | {"U"}, 323 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 324 | {5004,0}, 325 | {3,0,0}, 326 | {0,0},0, 327 | {0}, 328 | {"U"},0} 329 | },0}, 330 | { 331 | {0, 332 | {25, 333 | {2, 334 | {0, 335 | {0,0,c79f286d-e19a-4c99-b40a-ead9c540b119},"ФлагКомментироватьОбработкуОбъектов", 336 | {1,"ru","Комментировать выгрузку объектов"},""}, 337 | {"Pattern", 338 | {"B"} 339 | } 340 | },0, 341 | {0}, 342 | {0},0,"",0, 343 | {"U"}, 344 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 345 | {5004,0}, 346 | {3,0,0}, 347 | {0,0},0, 348 | {0}, 349 | {"U"},0} 350 | },0}, 351 | { 352 | {0, 353 | {25, 354 | {2, 355 | {0, 356 | {0,0,fa336e05-7197-4c77-9da3-ffaae1d13b9f},"ВыгрузкаДанныхВ1СБухгалтерию77", 357 | {1,"ru","Выгрузка данных в ""1С:Бухгалтерию 7.7"""},""}, 358 | {"Pattern", 359 | {"S",10,1} 360 | } 361 | },0, 362 | {0}, 363 | {0},0,"",0, 364 | {"U"}, 365 | {"U"},0,00000000-0000-0000-0000-000000000000,2,0, 366 | {5004,0}, 367 | {3,0,0}, 368 | {0,0},0, 369 | {0}, 370 | {"U"},0} 371 | },0} 372 | } 373 | } -------------------------------------------------------------------------------- /src/v8unpack/MetaDataObject/Form/FormElements27/FormElement.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from v8unpack import helper 4 | from v8unpack.ext_exception import ExtException 5 | 6 | 7 | class FormItemTypes(Enum): 8 | Field = '381ed624-9217-4e63-85db-c4c3cb87daae' 9 | CheckBox = '35af3d93-d7c7-4a2e-a8eb-bac87a1a3f26' 10 | RadioBtn = '782e569a-79a7-4a4f-a936-b48d013936ec' 11 | SelectField = '64483e7f-3833-48e2-8c75-2c31aac49f6e' 12 | CommandPanel = 'e69bf21d-97b2-4f37-86db-675aea9ec2cb' 13 | Button = '6ff79819-710e-4145-97cd-1618da79e3e2' 14 | Image = '151ef23e-6bb2-4681-83d0-35bc2217230c' 15 | Group = '90db814a-c75f-4b54-bc96-df62e554d67d' 16 | Table = 'ea83fe3a-ac3c-4cce-8045-3dddf35b28b1' 17 | TableField = '236a17b3-7f44-46d9-a907-75f9cdc61ab5' 18 | Panel = '09ccdc77-ea1a-4a6d-ab1c-3435eada2433' 19 | Label = '0fc7e20d-f241-460c-bdf4-5ad88e5474a5' 20 | ListField = '19f8b798-314e-4b4e-8121-905b2a7a03f5' 21 | Separator = '36e52348-5d60-4770-8e89-a16ed50a2006' 22 | FieldHtml = 'd92a805c-98ae-4750-9158-d9ce7cec2f20' 23 | Indicator = 'b1db1f86-abbb-4cf0-8852-fe6ae21650c2' 24 | CalendarBox = 'e3c063d8-ef92-41be-9c89-b70290b5368b' 25 | TrackBar = '6c06cd5d-8481-4b6f-a90a-7a97a8bb8bef' 26 | TextDocumentField = '14c4a229-bfc3-42fe-9ce1-2da049fd0109' 27 | GraphicalSchemaField = '42248403-7748-49da-b782-e4438fd7bff3' 28 | GeographicalSchemaField = 'ad37194e-555e-4305-b718-5dca84baf145' 29 | Chart = 'a8b97779-1a4b-4059-b09c-807f86d2a461' 30 | GanttChart = 'e5fdc112-5c84-4a16-9728-72b85692b6e2' 31 | PivotChart = 'a26da99e-184a-4823-b0d6-62816d38dc4e' 32 | Dendrogram = '984981b1-622d-4ebc-94f7-885f0cdfb59a' 33 | 34 | 35 | class Anchor(Enum): 36 | top = '0' 37 | bottom_center = '1' 38 | left = '2' 39 | right = '3' 40 | 41 | 42 | class FormElement: 43 | ver = 27 44 | name = 'elements' 45 | 46 | def __init__(self): 47 | self.anchored = {} 48 | 49 | @classmethod 50 | def get_class_form_elem(cls, name): 51 | index = ['Panel'] 52 | if name in index: 53 | return helper.get_class(f'v8unpack.MetaDataObject.Form.FormElements{cls.ver}.{name}.{name}') 54 | else: 55 | return FormElement 56 | 57 | @classmethod 58 | def set_name(cls, name, raw_data): 59 | raw_data[-2][1] = helper.str_encode(name) 60 | 61 | @classmethod 62 | def decode(cls, form, path, elem_raw_data): 63 | metadata_type_uuid = elem_raw_data[0] 64 | name = helper.str_decode(elem_raw_data[-2][1]) 65 | try: 66 | metadata_type = FormItemTypes(metadata_type_uuid) 67 | except ValueError: 68 | raise ExtException( 69 | message='Неизвестный тип элемента формы', 70 | detail=f'{metadata_type_uuid} {name} - {form.__class__.__name__} {form.header["name"]}', 71 | action='Form802Element.decode' 72 | ) 73 | page = elem_raw_data[-3][-5] 74 | elem_id = elem_raw_data[1] 75 | elem_data = dict( 76 | name=name, 77 | type=metadata_type.name, 78 | id=elem_id, 79 | ver=cls.ver 80 | ) 81 | # cls.decode_anchored(elem_id, form.anchored, path, elem_raw_data[3], 12) 82 | return form.add_elem(page, path, name, elem_data, elem_raw_data) 83 | 84 | 85 | 86 | # @classmethod 87 | # def decode_anchored(cls, anchor_id, anchored, path, elem_raw_data, offset): 88 | # def add_anchor(): 89 | # if len(anchor_data) != 3 or anchor_data[0] != '0': 90 | # raise ExtException(message='Неизвестный формат данных привязки', detail=path, 91 | # dump=dict(anchor_data=anchor_data, anchor_id=anchor_id)) 92 | # 93 | # elem_id = anchor_data[1] 94 | # border = Anchor(anchor_data[2]).name 95 | # if elem_id not in anchored: 96 | # anchored[elem_id] = [] 97 | # anchored[elem_id].append(dict( 98 | # border=border, 99 | # anchor=anchor_id, 100 | # anchor_border=Anchor(str(anchor_border)).name, 101 | # )) 102 | # try: 103 | # for anchor_border in range(4): 104 | # count_anchor = int(elem_raw_data[offset]) 105 | # for j in range(count_anchor): 106 | # anchor_data = elem_raw_data[offset + j + 1] 107 | # add_anchor() 108 | # elem_raw_data[offset] = '0' 109 | # del elem_raw_data[offset + 1: offset + 1 + count_anchor] 110 | # offset += 1 111 | # except Exception as err: 112 | # raise ExtException(parent=err) 113 | 114 | @classmethod 115 | def encode(cls, form, path, elem_tree, elem_data): 116 | try: 117 | raw_data = elem_data['raw'] 118 | if form.auto_include: 119 | form.last_elem_id += 1 120 | raw_data[1] = str(form.last_elem_id) 121 | 122 | elem_id = raw_data[1] 123 | 124 | prop = elem_data.get('prop') 125 | if prop: 126 | prop_index = form.props_index.get(prop) 127 | if not prop_index: 128 | raise ExtException(message='Отсутствует свойство', detail=prop) 129 | form.field_data_source.append((elem_id, prop_index)) 130 | 131 | # type_index = elem_data.get('type_index') 132 | # if type_index: 133 | # for group in type_index: 134 | # for elem in type_index[group]: 135 | # form.anchored[group].append([elem[0], elem_id, elem[1]]) 136 | 137 | return elem_id, raw_data 138 | except Exception as err: 139 | raise ExtException(parent=err) 140 | 141 | 142 | class FormProps: 143 | name = 'props' 144 | name_index = 4 145 | 146 | 147 | @classmethod 148 | def decode(cls, form, elem_raw_data): 149 | try: 150 | elem_data = dict( 151 | name=helper.str_decode(elem_raw_data[cls.name_index]), 152 | id=elem_raw_data[0][0], 153 | raw=elem_raw_data 154 | ) 155 | return elem_data 156 | except Exception as err: 157 | raise ExtException(parent=err) 158 | 159 | @classmethod 160 | def decode_list(cls, form, raw_data, index_element_count=0): 161 | result = [] 162 | element_count = int(raw_data[index_element_count]) 163 | if not element_count: 164 | return 165 | 166 | for i in range(element_count): 167 | elem_raw_data = raw_data[index_element_count + i + 1] 168 | result.append(cls.decode(form, elem_raw_data)) 169 | 170 | raw_data[index_element_count] = 'Дочерние элементы отдельно' 171 | del raw_data[index_element_count + 1:index_element_count + 1 + element_count] 172 | return result 173 | 174 | @classmethod 175 | def encode_list(cls, form, raw_data, path=''): 176 | try: 177 | result = [] 178 | index_element_count = 0 179 | if raw_data[index_element_count] == 'Дочерние элементы отдельно': 180 | if form.props: 181 | for prop in form.props: 182 | result.append(cls.encode(form, path, prop)) 183 | raw_data[index_element_count] = str(len(form.props)) 184 | raw_data[index_element_count + 1:index_element_count + 1] = result 185 | else: 186 | raw_data[index_element_count] = '0' 187 | except Exception as err: 188 | raise ExtException(parent=err) 189 | 190 | @classmethod 191 | def encode(cls, form, path, data): 192 | return data['raw'] 193 | -------------------------------------------------------------------------------- /src/v8unpack/MetaObject/Configuration.py: -------------------------------------------------------------------------------- 1 | import os 2 | from base64 import b64encode 3 | 4 | from .. import helper 5 | from ..MetaObject import MetaObject 6 | from ..ext_exception import ExtException 7 | from ..metadata_types import MetaDataTypes, MetaDataGroup 8 | from ..version import __version__ 9 | 10 | 11 | class Configuration(MetaObject): 12 | ext_code = { 13 | 'seance': 7, 14 | 'app': 6, 15 | '802': 0, 16 | 'con': 5 17 | } 18 | help_file_number = 3 19 | 20 | _images = { 21 | 'Заставка': 2 22 | } 23 | 24 | _obj_info = { 25 | '4': '4', 26 | '9': '9', 27 | '8': '8', 28 | '10': '10', 29 | 'a': 'a', 30 | 'b': 'b', 31 | 'c': 'c', 32 | 'd': 'd', 33 | 'e': 'e', 34 | 'f': 'f', 35 | } 36 | 37 | def __init__(self, *, meta_obj_class=None, obj_version=None, options=None): 38 | super().__init__(meta_obj_class=meta_obj_class, obj_version=obj_version, options=options) 39 | self.counter = {} 40 | 41 | def decode(self, src_dir, dest_dir, *, version=None, **kwargs): 42 | self.header = {} 43 | root = helper.brace_file_read(src_dir, 'root') 44 | self.header['v8unpack'] = __version__ 45 | self.header["file_uuid"] = root[0][1] 46 | self.header["root"] = root 47 | 48 | _header_data = helper.brace_file_read(src_dir, f'{self.header["file_uuid"]}') 49 | self.decode_header(_header_data, id_in_separate_file=False) 50 | 51 | file_name = self.get_class_name_without_version() 52 | 53 | self.header['version'] = helper.brace_file_read(src_dir, 'version') 54 | self.header['versions'] = helper.brace_file_read(src_dir, 'versions') 55 | # shutil.copy2(os.path.join(src_dir, 'versions.json'), os.path.join(dest_dir, 'versions.json')) 56 | 57 | self.decode_code(src_dir, uncomment_directive=self.obj_version in ['802', '801']) 58 | self._decode_html_data(src_dir, dest_dir, 'help', header_field='help', file_number=self.help_file_number) 59 | self._decode_images(src_dir, dest_dir) 60 | self._decode_info(src_dir, dest_dir, file_name) 61 | # self._decode_unknown(src_dir, dest_dir, file_name) 62 | 63 | tasks = self.decode_includes(src_dir, dest_dir, '', self.header['header']) 64 | self.header['obj_version'] = self.obj_version 65 | helper.json_write(self.header, dest_dir, f'{file_name}.json') 66 | self.write_decode_code(dest_dir, file_name) 67 | return tasks 68 | 69 | @classmethod 70 | def get_decode_header(cls, header): 71 | return header[0][3][1][1][1][1] 72 | 73 | def set_product_version(self, product_version): 74 | _version = product_version 75 | product = self.options.get('product') 76 | if product: 77 | _version = f'{product}_{_version}' 78 | self.header['header'][0][3][1][1][15] = helper.str_encode(_version) 79 | 80 | def decode_includes(self, src_dir, dest_dir, dest_path, header_data): 81 | tasks = [] 82 | index_includes_group = 2 83 | count_includes_group = int(header_data[0][index_includes_group]) 84 | for index_group in range(count_includes_group): 85 | group = header_data[0][index_includes_group + index_group + 1] 86 | group_uuid = group[0] 87 | group_version = group[1][0] 88 | try: 89 | metadata_group = MetaDataGroup(group_uuid) 90 | except ValueError: 91 | raise ExtException(message='Неизвестная группа метаданных', detail=group_uuid) 92 | include = group[1][1] if group_version == '6' else group[1] 93 | self.decode_include(src_dir, dest_dir, dest_path, tasks, include) 94 | return tasks 95 | 96 | def encode(self, src_dir, dest_dir, *, file_name=None, include_index=None, file_list=None): 97 | _file_name = self.get_class_name_without_version() 98 | helper.check_version(__version__, self.header.get('v8unpack', '')) 99 | 100 | if include_index and self.get_options('auto_include'): 101 | self.fill_header_includes(include_index) # todo dynamic index 102 | 103 | helper.brace_file_write(self.header["root"], dest_dir, 'root') 104 | file_list.append('root') 105 | helper.brace_file_write(self.encode_version(), dest_dir, 'version') 106 | file_list.append('version') 107 | # shutil.copy2(os.path.join(src_dir, 'versions.json'), os.path.join(dest_dir, 'versions.json')) 108 | 109 | self._encode_html_data(src_dir, 'help', dest_dir, header_field='help', file_number=self.help_file_number) 110 | self._encode_images(src_dir, dest_dir) 111 | self.encode_code(src_dir, _file_name) 112 | self._encode_info(src_dir, _file_name, dest_dir) 113 | self.write_encode_code(dest_dir, comment_directive=self.obj_version in ['802', '801']) 114 | helper.brace_file_write(self.header['header'], dest_dir, self.header["file_uuid"]) 115 | file_list.append(self.header["file_uuid"]) 116 | 117 | file_list.append('versions') 118 | file_list.extend(self.file_list) 119 | helper.brace_file_write(self.encode_versions(file_list), dest_dir, 'versions') 120 | return None 121 | 122 | def encode_version(self): 123 | return self.header['version'] 124 | 125 | def _decode_images(self, src_dir, dest_dir): 126 | if self._images: 127 | for elem in self._images: 128 | try: 129 | data = helper.brace_file_read(src_dir, f'{self.header["uuid"]}.{self._images[elem]}') 130 | except FileNotFoundError: 131 | return 132 | try: 133 | if data[0][2] and data[0][2][0] and data[0][2][0][0]: 134 | bin_data = self._extract_b64_data(data[0][2][0]) 135 | helper.bin_write(bin_data, dest_dir, f'{elem}') 136 | except IndexError: 137 | pass 138 | self.header[f'image_{elem}'] = data 139 | 140 | def _encode_images(self, src_dir, dest_dir): 141 | if self._images: 142 | for elem in self._images: 143 | try: 144 | bin_data = helper.bin_read(src_dir, f'{elem}') 145 | except FileNotFoundError: 146 | bin_data = None 147 | header = self.header.get(f'image_{elem}') 148 | if header and len(header[0]) > 1 and bin_data: 149 | header[0][2][0][0] += b64encode(bin_data).decode(encoding='utf-8') 150 | if header: 151 | file_name = f'{self.header["uuid"]}.{self._images[elem]}' 152 | helper.brace_file_write(header, dest_dir, file_name) 153 | self.file_list.append(file_name) 154 | 155 | def fill_header_includes(self, include_index): 156 | header_data = self.header['header'] 157 | index_includes_group = 2 158 | count_includes_group = int(header_data[0][index_includes_group]) 159 | for index_group in range(count_includes_group): 160 | group = header_data[0][index_includes_group + index_group + 1] 161 | group_uuid = group[0] 162 | group_version = group[1][0] 163 | try: 164 | metadata_group = MetaDataGroup(group_uuid) 165 | except ValueError: 166 | raise ExtException(message='Неизвестная группа метаданных', detail=group_uuid) 167 | include = group[1][1] if group_version == '6' else group[1] 168 | try: 169 | count_include_types = int(include[2]) 170 | except IndexError: 171 | raise ExtException(message='Include types not found', detail=self.__class__.__name__) 172 | for i in range(count_include_types): 173 | self.fill_header_include(i, include, include_index) 174 | -------------------------------------------------------------------------------- /src/v8unpack/container_doc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import collections 3 | import datetime 4 | import io 5 | import math 6 | import os 7 | import tempfile 8 | import zlib 9 | from datetime import datetime, timedelta 10 | from struct import pack 11 | # -*- coding: utf-8 -*- 12 | from struct import unpack 13 | 14 | from .helper import file_size 15 | 16 | Header = collections.namedtuple('Header', 'first_empty_block_offset, default_block_size, count_files') 17 | Block = collections.namedtuple('Block', 'doc_size, current_block_size, next_block_offset, data') 18 | File = collections.namedtuple('File', 'name, size, created, modified, data') 19 | DocumentData = collections.namedtuple('DocumentData', 'size, data') 20 | 21 | # Размер буффера передачи данных из потока в поток 22 | BUFFER_CHUNK_SIZE = 512 23 | 24 | 25 | class Document: 26 | def __init__(self, container): 27 | self.container = container 28 | self.full_size = 0 # include all header size 29 | self.data_size = 0 30 | 31 | def read(self, file, offset, min_block_size=0): 32 | document_data = self.read_chunk(file, offset, min_block_size) 33 | return b''.join([chunk for chunk in document_data]) 34 | 35 | def read_chunk(self, file, offset, min_block_size=0): 36 | """ 37 | Считывает документ из контейнера. В качестве данных документа возвращается генератор. 38 | 39 | :param file: объект файла контейнера 40 | :type file: BufferedReader 41 | :param offset: смещение документа в контейнере 42 | :type offset: int 43 | :return: данные документа 44 | :rtype: 45 | """ 46 | gen = self._read_gen(file, offset, min_block_size) 47 | 48 | try: 49 | self.data_size = next(gen) 50 | except StopIteration: 51 | self.data_size = 0 52 | 53 | return gen 54 | 55 | def _read_gen(self, file, offset, min_block_size=0): 56 | """ 57 | Создает генератор чтения данных документа в контейнере. 58 | Первое значение генератора - размер документа (байт). 59 | Остальные значения - данные блоков, составляющих документ 60 | 61 | :param file: объект файла контейнера 62 | :type file: BufferedReader 63 | :param offset: смещение документа в контейнере (байт) 64 | :type offset: int 65 | :return: генератор чтения данных документа 66 | """ 67 | header_block = self.read_block(file, offset) 68 | doc_size = max(header_block.doc_size, min_block_size) 69 | if doc_size > header_block.current_block_size: 70 | self.full_size = doc_size + math.ceil( 71 | header_block.doc_size / header_block.current_block_size) * min_block_size 72 | if min_block_size == self.container.index_block_size: 73 | self.full_size += self.container.index_block_size 74 | else: 75 | self.full_size = doc_size + self.container.block_header_size 76 | 77 | if header_block is None: 78 | return 79 | else: 80 | yield header_block.doc_size 81 | yield header_block.data 82 | 83 | left_bytes = header_block.doc_size - len(header_block.data) 84 | next_block_offset = header_block.next_block_offset 85 | 86 | while left_bytes > 0 and next_block_offset != self.container.end_marker: 87 | block = self.read_block(file, next_block_offset, left_bytes) 88 | left_bytes -= len(block.data) 89 | yield block.data 90 | next_block_offset = block.next_block_offset 91 | 92 | def read_block(self, file, offset, max_data_length=None): 93 | """ 94 | Считывает блок данных из контейнера. 95 | 96 | :param file: объект файла контейнера 97 | :type file: BufferedReader 98 | :param offset: смещение блока в файле контейнера (байт) 99 | :type offset: int 100 | :param max_data_length: максимальный размер считываемых данных из блока (байт) 101 | :type max_data_length: int 102 | :return: объект блока данных 103 | :rtype: Block 104 | """ 105 | file.seek(offset + self.container.offset) 106 | header_size = self.container.block_header_size 107 | buff = file.read(header_size) 108 | if not buff: 109 | return 110 | header = unpack(self.container.block_header_fmt, buff) 111 | 112 | doc_size = int(header[1], 16) 113 | current_block_size = int(header[3], 16) 114 | next_block_offset = int(header[5], 16) 115 | 116 | if max_data_length is None: 117 | max_data_length = min(current_block_size, doc_size) 118 | 119 | data_size = min(current_block_size, max_data_length) 120 | 121 | data = file.read(data_size) 122 | 123 | return Block(doc_size, current_block_size, next_block_offset, data) 124 | 125 | def write_header(self, file, file_name): 126 | modify_time = epoch2int(os.stat(file.fileno()).st_mtime) 127 | # В *nix это не время создания файла. 128 | creation_time = epoch2int(os.stat(file.fileno()).st_ctime) 129 | buffer = b''.join([pack('QQi', creation_time, modify_time, 0), file_name.encode('utf-16-le'), b'\x00' * 4]) 130 | attribute_doc_offset = self.write(io.BytesIO(buffer)) 131 | return attribute_doc_offset 132 | 133 | def write(self, data, *, min_block_size=0): 134 | return self.write_block(data, doc_size=file_size(data), min_block_size=min_block_size) 135 | 136 | def write_block(self, data, *, doc_size=0, min_block_size=0, offset=None, next_block_offset=None): 137 | """ 138 | Записывает блок данных в контейнер 139 | 140 | :param data: file-like объект 141 | :param kwargs: Опциональные параметры 142 | :return: смещение записанных данных (байт) 143 | :rtype: int 144 | """ 145 | if offset is not None: 146 | self.container.file.seek(offset) 147 | else: 148 | offset = file_size(self.container.file) 149 | block_size = file_size(data) 150 | min_block_size = max(min_block_size, block_size) 151 | if not next_block_offset: 152 | next_block_offset = self.container.end_marker 153 | 154 | header_data = ('\r\n', self.container.int2hex(doc_size), ' ', self.container.int2hex(min_block_size), ' ', 155 | self.container.int2hex(next_block_offset), ' ', '\r\n') 156 | header = pack(self.container.block_header_fmt, *[x.encode() for x in header_data]) 157 | 158 | self.container.file.write(header) 159 | self.write_block_data(data, self.container.file) 160 | 161 | self.container.file.write(b'\x00' * (min_block_size - data.tell())) 162 | 163 | return offset 164 | 165 | @classmethod 166 | def compress(cls, src_fd, dest_fd): 167 | with tempfile.TemporaryFile() as f: 168 | compressor = zlib.compressobj(wbits=-15) 169 | src_fd.seek(0) 170 | while True: 171 | chunk = src_fd.read(BUFFER_CHUNK_SIZE) 172 | if not chunk: 173 | f.write(compressor.flush()) 174 | break 175 | f.write(compressor.compress(chunk)) 176 | cls.write_block_data(f, dest_fd) 177 | # return data_doc_offset 178 | 179 | @staticmethod 180 | def write_block_data(data, dest_file): 181 | data.seek(0) 182 | while True: 183 | buffer = data.read(BUFFER_CHUNK_SIZE) 184 | if not buffer: 185 | break 186 | dest_file.write(buffer) 187 | 188 | 189 | def epoch2int(epoch_time): 190 | """ 191 | Преобразует время в формате "количество секунд с начала эпохи" в количество сотых микросекундных интервалов 192 | с 0001.01.01 193 | 194 | :param epoch_time: время в формате Python 195 | :type epoch_time: real 196 | :return: количество сотых микросекундных интервалов 197 | :rtype: int 198 | """ 199 | # Начало эпохи на разных системах - разная дата 200 | # Поэтому явно вычисляем разницу между указанной датой и 0001.01.01 201 | return (datetime.fromtimestamp(epoch_time) - datetime(1, 1, 1)) // timedelta( 202 | microseconds=100) 203 | -------------------------------------------------------------------------------- /src/v8unpack/metadata_types.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class MetaDataGroup(Enum): 5 | General = '9cd510cd-abfc-11d4-9434-004095e12fc7' 6 | Main = '9fcd25a0-4822-11d4-9414-008048da11f9' 7 | Accounting = 'e3687481-0a87-462c-a166-9f34594f9bba' 8 | Calculation = '9de14907-ec23-4a07-96f0-85521cb6b53b' 9 | DocFlow = '51f2d5d8-ea4d-4064-8892-82951750031e' 10 | DataSource = 'e68182ea-4237-4383-967f-90c1e3370bc7' 11 | Integration = 'fb282519-d103-4dd3-bc12-cb271d631dfc' 12 | 13 | 14 | class MetaDataTypes(Enum): 15 | # todo в синтаксис помощнике найти все английские названия искать строкой "ОбъектМетаданных: [хxx...]" 16 | # DocumentAttribute = "45e46cbc-3e24-4165-8b7b-cc98a6f80211" # Документ.Реквизиты 17 | # ExternalDataSource = 'e68182ea-4237-4383-967f-90c1e3370bc7' # Внешние источники данных (не точно...) 18 | AccountingRegister = '2deed9b8-0056-4ffe-a473-c20a6c32a0bc' # Регистры бухгалтерии 19 | AccountingRegisterCommand = "7162da60-f7fe-4d78-ad5d-e31700f9af18" 20 | AccountingRegisterForm = 'd3b5d6eb-4ea2-4610-a3e2-624d4e815934' # Регистры бухгалтерии 21 | AccumulationRegister = 'b64d9a40-1642-11d6-a3c7-0050bae0a776' # Регистры накопления 22 | AccumulationRegisterCommand = "99f328af-a77f-4572-a2d8-80ed20c81890" 23 | AccumulationRegisterForm = 'b64d9a44-1642-11d6-a3c7-0050bae0a776' # Регистры накопления форма 24 | Bot = '6e6dc072-b7ac-41e7-8f88-278d25b6da2a' # Бот 25 | BusinessProcess = 'fcd3404e-1523-48ce-9bc0-ecdb822684a1' # Бизнес-процессы 26 | BusinessProcessCommand = '7a3e533c-f232-40d5-a932-6a311d2480bf' 27 | BusinessProcessForm = '3f7a8120-b71a-4265-98bf-4d9bc09b7719' 28 | CalculationRegister = 'f2de87a8-64e5-45eb-a22d-b3aedab050e7' # Регистры расчета 29 | CalculationRegisterCommand = "acdf0f11-2d59-4e37-9945-c6721871a8fe" 30 | CalculationRegisterForm = 'a2cb086c-db98-43e4-a1a9-0760ab048f8d' # Регистры расчета 31 | CalculationRegisterRecalculations = '274bf899-db0e-4df6-8ab5-67bf6371ec0b' # Перерасчеты 32 | Catalog = "cf4abea6-37b2-11d4-940f-008048da11f9" # Справочники 33 | CatalogCommand = '4fe87c89-9ad4-43f6-9fdb-9dc83b3879c6' # Команда справочника 34 | CatalogForm = 'fdf816d2-1ead-11d5-b975-0050bae0a95d' # Форма элемента справочника 35 | ChartOfAccounts = '238e7e88-3c5f-48b2-8a3b-81ebbecb20ed' # План счетов 36 | ChartOfAccountsCommand = '0df30176-6865-4787-9fc8-609eb144174f' # План счетов команды include 37 | ChartOfAccountsForm = '5372e285-03db-4f8c-8565-fe56f1aea40e' # План счетов 38 | ChartOfCalculationTypes = '30b100d6-b29f-47ac-aec7-cb8ca8a54767' # План видов расчета 39 | ChartOfCalculationTypesCommand = "2e90c75b-2f0c-4899-a7d4-5426eaefc96e" 40 | ChartOfCalculationTypesForm = 'a7f8f92a-7a4b-484b-937e-42d242e64144' 41 | ChartOfCharacteristicType = '82a1b659-b220-4d94-a9bd-14d757b95a48' # План видов характеристик 42 | ChartOfCharacteristicTypeCommand = "95b5e1d4-abfa-4a16-818d-a5b07b7d3f73" 43 | ChartOfCharacteristicTypeForm = 'eb2b78a8-40a6-4b7e-b1b3-6ca9966cbc94' # План видов характеристик 44 | CommandGroup = '1c57eabe-7349-44b3-b1de-ebfeab67b47d' # Группа команд 45 | CommonAttribute = '15794563-ccec-41f6-a83c-ec5f7b9a5bc1' # реквизиты 46 | CommonCommand = '2f1a5187-fb0e-4b05-9489-dc5dd6412348' # Общая команда 47 | CommonForm = "07ee8426-87f1-11d5-b99c-0050bae0a95d" # Общие формы 48 | CommonModule = '0fe48980-252d-11d6-a3c7-0050bae0a776' # Общие модули 49 | CommonPicture = '7dcd43d9-aca5-4926-b549-1842e6a4e8cf' # Общие картинки 50 | CommonTemplate = '0c89c792-16c3-11d5-b96b-0050bae0a95d' # Общие макеты 51 | Configuration = '9cd510cd-abfc-11d4-9434-004095e12fc7' # Идентифицирует род. узел описания конфигурации 52 | Constant = '0195e80c-b157-11d4-9435-004095e12fc7' # Константы 53 | DataProcessor = "bf845118-327b-4682-b5c6-285d2a0eb296" # Обработки 54 | DataProcessorCommand = '45556acb-826a-4f73-898a-6025fc9536e1' 55 | DefinedType = 'c045099e-13b9-4fb6-9d50-fca00202971e' # Определяемые типы 56 | Document = "061d872a-5787-460e-95ac-ed74ea3a3e84" # Документы 57 | DocumentCommand = 'b544fc6a-2ba3-4885-8fb2-cb289fb6d65e' # Команда документа 58 | DocumentForm = "fb880e93-47d7-4127-9357-a20e69c17545" # Форма документа 59 | DocumentJournal = '4612bd75-71b7-4a5c-8cc5-2b0b65f9fa0d' # Журналы документов 60 | DocumentJournalCommand = "a49a35ce-120a-4c80-8eea-b0618479cd70" 61 | DocumentJournalForm = 'ec81ad10-ca07-11d5-b9a5-0050bae0a95d' 62 | DocumentNumerators = '36a8e346-9aaa-4af9-bdbd-83be3c177977' # Нумератор документа 63 | Enum = "f6a80749-5ad7-400b-8519-39dc5dff2542" # Перечисления 64 | EnumCommand = "6d8d73a7-ba29-401d-9032-3872ec2d6433" 65 | EnumForm = "33f2e54b-37ce-4a7a-a569-b648d7aa4634" # Перечисления 66 | EventSubscription = '4e828da6-0f44-4b5b-b1c0-a2b3cfe7bdcc' # Подписки на события 67 | ExchangePlan = '857c4a91-e5f4-4fac-86ec-787626f1c108' # План обмена 68 | ExchangePlanCommand = 'd5207c64-11d5-4d46-bba2-55b7b07ff4eb' 69 | ExchangePlanForm = '87c509ab-3d38-4d67-b379-aca796298578' # План обмена Form 70 | ExternalDataProcessor = 'c3831ec8-d8d5-4f93-8a22-f9bfae07327f' # Внешняя обработка 71 | ExternalDataSource = '5274d9fc-9c3a-4a71-8f5e-a0db8ab23de5' # ВнешнийИсточникДанных 72 | ExternalDataSourceCube = '2bb208ca-e441-4023-9ab9-32a7807a85d0' # ВнешнийИсточникДанных Куб 73 | ExternalDataSourceCubeForm = '3448e506-5add-4fce-a604-7305466b2d8e' # ВнешнийИсточникДанных Куб 74 | ExternalDataSourceCubeCommand = '4ee40ec7-3469-439f-adb4-aa26ce2d3ec3' # ВнешнийИсточникДанных Куб 75 | ExternalDataSourceTable = 'e3403acd-1c95-421b-87e4-4dfa29d38b52' # ВнешнийИсточникДанных Таблица 76 | ExternalDataSourceTableForm = '17816ebc-4068-496e-adc4-8879945a832f' # ВнешнийИсточникДанных Таблица 77 | ExternalDataSourceTableCommand = '5bb6f09e-5d80-41f6-8070-9faa4d15b69b' # ВнешнийИсточникДанных Таблица 78 | FilterCriterion = '3e7bfcc0-067d-11d6-a3c7-0050bae0a776' # Критерии отбора 79 | FilterCriterionCommand = "23fa3b84-220a-40e9-8331-e588bed87f7d" 80 | FilterCriterionForm = '00867c40-06b1-11d6-a3c7-0050bae0a776' # Критерии отбора Form 81 | Form = 'd5b0e5ed-256d-401c-9c36-f630cafd8a62' # Форма внешней обработки 82 | FormAttribute = 'ec6bb5e5-b7a8-4d75-bec9-658107a699cf' # Атрибут Внешней обработки 83 | FunctionalOption = 'af547940-3268-434f-a3e7-e47d6d2638c3' # Функциональные опции 84 | FunctionalOptionsParameter = '30d554db-541e-4f62-8970-a1c6dcfeb2bc' # Параметры функциональных опций 85 | HTTPService = '0fffc09c-8f4c-47cc-b41c-8d5c5a221d79' # HTTPСервис 86 | InformationRegister = '13134201-f60b-11d5-a3c7-0050bae0a776' # Регистры сведений 87 | InformationRegisterCommand = 'b44ba719-945c-445c-8aab-1088fa4df16e' 88 | InformationRegisterForm = '13134204-f60b-11d5-a3c7-0050bae0a776' # Form регистра сведений 89 | IntegrationService = 'bf3420b0-f6f9-41a0-b83a-fe9d4ab0b65d'#e3403acd-1c95 -421b-87e4-4dfa29d38b52' # Сервис интеграции 90 | Interface = '39bddf6a-0c3c-452b-921c-d99cfa1c2f1b' # Интерфейсы 91 | Language = '9cd510ce-abfc-11d4-9434-004095e12fc7' # Языки 92 | Report = '631b75a0-29e2-11d6-a3c7-0050bae0a776' # Отчеты 93 | ReportCommand = 'e7ff38c0-ec3c-47a0-ae90-20c73ca72246' # Отчеты 94 | ReportForm = 'a3b368c0-29e2-11d6-a3c7-0050bae0a776' # Отчеты форма 95 | Role = '09736b02-9cac-4e3f-b4f7-d3e9576ab948' # Роли 96 | ScheduledJob = '11bdaf85-d5ad-4d91-bb24-aa0eee139052' # Регламентные задания 97 | Sequences = 'bc587f20-35d9-11d6-a3c7-0050bae0a776' # Последовательность, 1 ШТ 98 | SessionParameter = '24c43748-c938-45d0-8d14-01424a72b11e' # Параметры сеанса 99 | SettingsStorage = '46b4cd97-fd13-4eaa-aba2-3bddd7699218' # Хранилища настроек 100 | SettingsStorageForm = 'b8533c0c-2342-4db3-91a2-c2b08cbf6b23' 101 | Style = '3e5404af-6ef8-4c73-ad11-91bd2dfac4c8' # Стиль 102 | StyleItem = '58848766-36ea-4076-8800-e91eb49590d7' # Элемены стиля 103 | Subsystem = '37f2fa9a-b276-11d4-9435-004095e12fc7' # Подсистемы 104 | TabularSectionAttribute = '2bcef0d1-0981-11d6-b9b8-0050bae0a95d' # Атрибут Табличной части Внешней обработки 105 | Task = '3e63355c-1378-4953-be9b-1deb5fb6bec5' # Задачи 106 | TaskCommand = 'f27c2152-a2c9-4c30-adb1-130f5eb2590f' 107 | TaskForm = '3f58cbfb-4172-4e54-be49-561a579bb38b' 108 | Template = '3daea016-69b7-4ed4-9453-127911372fe6' # Макет внешней обработки 109 | WebService = '8657032e-7740-4e1d-a3ba-5dd6e8afb78f' # Веб-сервис 110 | WSReference = 'd26096fb-7a5d-4df9-af63-47d04771fa9b' # WS-ссылки 111 | XDTOPackage = 'cc9df798-7c94-4616-97d2-7aa0b7bc515e' # ПакетXDTO 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/v8unpack/decoder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | from datetime import datetime 4 | 5 | from . import helper 6 | from .MetaObject.Configuration import Configuration 7 | from .MetaObject.ConfigurationExtension import ConfigurationExtension 8 | from .MetaObject.ExternalDataProcessor import ExternalDataProcessor 9 | from .ext_exception import ExtException 10 | from .metadata_types import MetaDataTypes 11 | 12 | available_types = { 13 | 'ExternalDataProcessor': ExternalDataProcessor, 14 | 'Configuration': Configuration, 15 | 'ConfigurationExtension': ConfigurationExtension 16 | } 17 | 18 | 19 | class Decoder: 20 | @staticmethod 21 | def get_handler_by_version_file(*, version=None, root=None, header=None, options=None, configinfo=None, **kwargs): 22 | if root: 23 | if int(version[0][0][0]) >= 216: 24 | _tmp = len(version[0][0]) 25 | obj_type = MetaDataTypes(header[0][3][0]) 26 | if _tmp == 2: 27 | obj_version = '802' 28 | elif _tmp == 3: 29 | obj_version = version[0][0][2][0][:3] 30 | if len(obj_version) == 3: 31 | pass 32 | elif obj_version in ['1', '2']: 33 | obj_version = '802' 34 | else: 35 | raise Exception(f'Not supported version {obj_version}') 36 | else: 37 | raise Exception(f'Not supported version {_tmp}') 38 | try: 39 | # if not options.get('version'): 40 | options['obj_version'] = obj_version 41 | return available_types[obj_type.name](options=options, obj_version=options['obj_version']) 42 | except KeyError: 43 | raise Exception(f'Not supported type {obj_type.name}') 44 | elif version[0][0][0] == "106": 45 | options['obj_version'] = '801' 46 | return ExternalDataProcessor(options=options, obj_version=options['obj_version']) 47 | if configinfo: 48 | if int(configinfo[0][1][0]) >= 216: 49 | if len(configinfo[0][1]) == 3: 50 | obj_version = configinfo[0][1][2][0] 51 | if obj_version.startswith('803'): 52 | options['obj_version'] = obj_version 53 | return ConfigurationExtension(options=options, obj_version=options['obj_version']) 54 | raise Exception('Не удалось определить парсер') 55 | 56 | @classmethod 57 | def detect_version(cls, src_dir, options=None): 58 | version = None 59 | root = None 60 | header = None 61 | configinfo = None 62 | if os.path.isfile(os.path.join(src_dir, 'root')): 63 | version = helper.brace_file_read(src_dir, 'version') 64 | root = helper.brace_file_read(src_dir, 'root') 65 | header = helper.brace_file_read(src_dir, root[0][1]) 66 | if os.path.isfile(os.path.join(src_dir, 'configinfo')): 67 | configinfo = helper.brace_file_read(src_dir, 'configinfo') 68 | 69 | return cls.get_handler_by_version_file( 70 | version=version, 71 | root=root, 72 | header=header, 73 | options=options, 74 | configinfo=configinfo 75 | ) 76 | 77 | @classmethod 78 | def decode(cls, src_dir, dest_dir, *, pool=None, options=None): 79 | begin = datetime.now() 80 | print(f'{"Разбираем объект":30}') 81 | decoder = cls.detect_version(src_dir, options=options) 82 | helper.clear_dir(dest_dir) 83 | tasks = decoder.decode(src_dir, dest_dir) # возвращает список вложенных объектов MetaDataObject 84 | while tasks: # многопоточно рекурсивно декодируем вложенные объекты MetaDataObject 85 | tasks = helper.run_in_pool(cls.decode_include, tasks, pool, title=f'{"Разбираем вложенные объекты":30}', 86 | need_result=True) 87 | # tasks = helper.list_merge(*tasks_list) 88 | print(f'{"Разбор объекта закончен":30}: {datetime.now() - begin}') 89 | 90 | @classmethod 91 | def decode_include(cls, params): 92 | include_type, (obj_uuid, src_dir, dest_dir, new_dest_path, parent_container_uuid, options) = params 93 | try: 94 | handler = helper.get_class_metadata_object(include_type) 95 | # handler = handler.get_version(decode_params[4]) 96 | tasks = handler.decode(obj_uuid, src_dir, dest_dir, new_dest_path, options, 97 | parent_container_uuid=parent_container_uuid, 98 | parent_type=include_type) 99 | return tasks 100 | except ExtException as err: 101 | raise ExtException( 102 | parent=err, 103 | action=f'{cls.__name__}.decode_include {include_type}' 104 | ) 105 | except Exception as err: 106 | raise ExtException(parent=err, action=f'{cls.__name__}.decode_include') 107 | 108 | @classmethod 109 | def encode(cls, src_dir, dest_dir, *, pool=None, file_name=None, options=None): 110 | begin = datetime.now() 111 | print(f'{"Собираем объект":30}') 112 | helper.clear_dir(dest_dir) 113 | _type = cls.get_encoder(src_dir, options) 114 | header = helper.json_read(src_dir, f'{_type}.json') 115 | encoder = cls.get_handler_by_version_file(options=options, **header) 116 | encoder.header = header 117 | encoder.container_uuid = encoder.get_container_uuid(header['header']) 118 | 119 | # возвращает список вложенных объектов MetaDataObject 120 | helper.clear_dir(dest_dir) 121 | 122 | parent_id = encoder.get_class_name_without_version() 123 | child_tasks = encoder.encode_includes(src_dir, file_name, dest_dir, parent_id) 124 | include_index = {} 125 | file_list = [] 126 | object_task = [] 127 | title = f'{"Собираем вложенные объекты":30}' 128 | while object_task or child_tasks: # многопоточно рекурсивно декодируем вложенные объекты MetaDataObject 129 | _file_list, _include_index, _object_task, child_tasks = helper.run_in_pool_encode_include( 130 | cls.encode_include, child_tasks, pool, 131 | title=title 132 | ) 133 | if _file_list: 134 | file_list.extend(_file_list) 135 | if _include_index: 136 | helper.update_dict(include_index, _include_index) 137 | if _object_task: 138 | object_task.append(_object_task) 139 | if not child_tasks and object_task: 140 | _object_task = object_task.pop() 141 | for elem in _object_task: 142 | elem[1][6] = include_index.pop(f"{elem[1][4]}/{elem[0]}/{elem[1][1]}") 143 | child_tasks = _object_task 144 | title = f'{"Собираем вложенные объекты":30}' 145 | a = 1 146 | 147 | encoder.encode(src_dir, dest_dir, file_name=file_name, include_index=include_index.pop(parent_id, None), 148 | file_list=file_list) 149 | 150 | print(f'{"Сборка объекта закончена":30}: {datetime.now() - begin}') 151 | 152 | @classmethod 153 | def get_encoder(cls, src_dir, options=None): 154 | _type = None 155 | for _type in available_types: 156 | if os.path.isfile(os.path.join(src_dir, f'{_type}.json')): 157 | return _type 158 | raise FileNotFoundError(f'Не найдены файлы для сборки версии ({src_dir})') 159 | 160 | @classmethod 161 | def encode_include(cls, params): 162 | include_type, (new_src_dir, entry, dest_dir, options, parent_id, parent_container_uuid, include_index) = params 163 | try: 164 | handler = helper.get_class_metadata_object(include_type) 165 | 166 | # handler = handler.get_version(options.get('version', '803')[:3])(options=options) 167 | # handler.title = include_type 168 | object_task, child_tasks = handler.encode(new_src_dir, entry, dest_dir, parent_id, parent_container_uuid, 169 | include_index, options=options) 170 | return object_task, child_tasks 171 | except Exception as err: 172 | raise ExtException(parent=err, action=f'Decoder.encode_include({include_type})') from None 173 | 174 | 175 | def encode(src_dir, dest_dir, *, pool=None, options=None, file_name=None): 176 | def unpack_dummy(): 177 | helper.clear_dir(dest_dir) 178 | dummy_path = os.path.join(src_dir, 'dummy.zip') 179 | if os.path.isfile(dummy_path): 180 | helper.clear_dir(container_dest_dir) 181 | shutil.unpack_archive(dummy_path, container_dest_dir) 182 | return os.path.join(dest_dir, '1') 183 | return container_dest_dir 184 | 185 | try: 186 | product_version = helper.txt_read(src_dir, 'version.bin', encoding='utf-8') 187 | options['product_version'] = product_version 188 | except FileNotFoundError: 189 | pass 190 | 191 | container_dest_dir = os.path.join(dest_dir, '0') 192 | container_dest_dir = unpack_dummy() 193 | # options = helper.set_options_param(options, 'version', version[:3]) 194 | Decoder.encode(src_dir, container_dest_dir, pool=pool, options=options, file_name=file_name) 195 | 196 | 197 | def decode(src_dir, dest_dir, *, pool=None, options=None): 198 | containers = os.listdir(src_dir) 199 | containers_count = len(containers) 200 | if containers_count not in [1, 2]: 201 | raise NotImplementedError(f'Количество контейнеров {containers_count}') 202 | 203 | _src_dir = os.path.join(src_dir, containers[-1]) 204 | Decoder.decode(_src_dir, dest_dir, pool=pool, options=options) 205 | if containers_count == 2: 206 | shutil.make_archive(os.path.join(dest_dir, 'dummy'), 'zip', os.path.join(src_dir, '0')) 207 | --------------------------------------------------------------------------------