Замена текста в файле ворда

A

anna

Не работает замена текста в файле ворда из лотусскрипта, ошибок при этом не выдает. Что это может быть?
Код:
Dim wObj As Variant
Dim rngToSearch As Variant
Dim rngResult As Variant
Dim worddoc As Variant
Dim WordApp As Variant	  '  Word
Set WordApp = CreateObject ("Word.Application")
WordApp.Visible=True
Set worddoc = WordApp.Documents.Open( "c:\test.docx",False)
Set wObj = worddoc.Application
Set rngToSearch = worddoc.Content
rngToSearch.Find.Text = "a"
rngToSearch.Find.Forward = True 'rngResult
rngToSearch.Find.Wrap = wdFindStop
rngToSearch.Find.MatchCase = False
rngToSearch.Find.MatchWildcards = False
rngToSearch.Find.Replacement.Text="f"
rngToSearch.Find.Execute ,,,,,,,,,,wdReplaceAll
If rngToSearch.Find.Found = True Then
Print "Найдено совпадение"
Else
Print "Не найдено совпадений"
End If
 
Последнее редактирование:

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
472
оно не должно менять части слова! по замыслу моему ;) да и мсдн по этому написано
occurrence of the word "Hello."
обратить внимание на word , а не letter
[DOUBLEPOST=1463651263,1463651150][/DOUBLEPOST]и все-таки - почему бы не использовать POI?
 
A

anna

оно не должно менять части слова! по замыслу моему ;) да и мсдн по этому написано обратить внимание на word , а не letter
[DOUBLEPOST=1463651263,1463651150][/DOUBLEPOST]и все-таки - почему бы не использовать POI?
Не меняет оно, если даже по словам!
Хорошо, сейчас посмотрю POI.
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
472
дык выкладывайте - я посмотрю, может с файлом что...
и да - в корень, на современных виндях (выше глисты) - может по правам не пройти изменение
 
A

anna

дык выкладывайте - я посмотрю, может с файлом что...
и да - в корень, на современных виндях (выше глисты) - может по правам не пройти изменение
С файлом все ок, этот же скрипт в него успешно пишет. Переложила из корня в каталог, никакой разницы. Файл обычный, с набором бессвязных символов и слов, тестовый
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
472
С файлом все ок, этот же скрипт в него успешно пишет
учитывая что этот скрипт у меня работает на разных файлах контрактов и виндях - странно что у вас он не заменяет (много лет)
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
472
как я уже отмечал - у меня код несколько др. , там есть дублирование региона
Код:
	On Error GoTo ErrorHandler
	Dim wObj As Variant
	Dim rngToSearch As Variant
	Dim rngResult As Variant
	Dim i As Long
	Dim s As String, txt As String
	
	Set wObj = worddoc.Application
	Set rngToSearch = worddoc.Content
	ForAll m In replacementList
		Set rngResult = rngToSearch.Duplicate
		'	   s=defDelim.GetEscLeft() & Listtag(m) & defDelim.GetEscRight()
		'	   s={\<} & Listtag(m) & {\>}
		s=ListTag(m)
		rngResult.Find.Text = s
		rngResult.Find.Forward = True
		rngResult.Find.Wrap = wdFindStop
		'не действует MachCase=False для шаблона \<fld_name\>
		rngResult.Find.MatchCase = False'MSWord - индусское поделие
		'	   rngResult.Find.MatchWholeWord = False
		rngResult.Find.MatchWildcards = True
		txt=CStr(m)
		rngResult.Find.Replacement.Text=txt
		rngResult.Find.Execute ,,,,,,,,,,wdReplaceAll
'	   Do While rngResult.Find.Execute()
'		   i = i + 1
'		   rngResult.Select
'		   worddoc.Application.Selection.Text=m
'		   rngResult.Collapse 0 'wdCollapseEnd
'	   Loop
		i=i+1
		DbgMsg({Значение:} & ListTag(m) & ", #" & CStr(i))
	End ForAll
	
	ReplaceByTemplate=True
ExitFunctoin:
	Exit Function
ErrorHandler:
	Call RaiseError()
	Print {#} & Format(i, {000000}) & { ;replace value>} & s & { ;by value>} & txt
	Resume ExitFunctoin
 
A

anna

Я максимально упростила, чтобы добиться замены, хотя бы единичной. С дублированием контента тоже не работает.
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
472
винда какая, установка нотусни какая? Просто из VB работает?
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
472
и если не получется с COM (что не удивительно) может не терять время и сделать на POI
мой код есть, список токенов из LS можно передавать через бридж (LS2J) как массив String
вот только какой тип массива - я не помню, те, кот. через Redim - точно не передаются
а вот типа Dim v:v=Split({},{}) и ArrayAppend или статичные Dim sArr(num) As String вполне
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
472
рабочий пример для LS (в моих предыдущих примерах входной файл мог не освобождаться), не относящиеся в замене (напрямую) либы не прикладываю
агент
Код:
%REM
*********************************************
	Agent test\XWPF
	Created Feb 11, 2016 by Mikhail Cholokov/CRUINTERNET
	Description: Comments for Agent
%END REM
Option Public
Option Declare
Use "XWPFUtils.LS2J"
Sub Initialize
	On Error GoTo ErrH
	Dim xwpf As New XWPFUtils()
	Dim doc As NotesDocument, ses As New NotesSession
	Set doc=ses.Documentcontext
	Dim fname As String, fList List As String
	fname=DetachDocFiles(doc, "*.docx", fList)
	Dim arrFrom, arrTo
	arrFrom=Split({SPA_POSITION;SPA_WHOM},{;})
	arrTo=Split({Генерал;Кузнецов},{;})
	If Len(fname)<1 Then Error 1024, Chr(10)&{файл не выгружен из документа:} &doc.Universalid &Chr(10)
	Print {File:} fname
	Call xwpf.FindAndReplace(Arrfrom, Arrto, fname, fname & {.docx})
	Dim run As String
	run={cmd.exe /c ""}&fname &{.docx""}'for long path suppport - double quotes
	Print {Executing>} run
	Dim status As Integer:status=Shell(run)
Quit:
	Exit Sub
ErrH:
	Error Err, RaiseError
End Sub
либа бриджа
Код:
%REM
*********************************************
	Library XPFUtils.LS2J
	Created Feb 11, 2016 by Mikhail Cholokov/CRUINTERNET
	Description: Comments for Library
%END REM
Option Public
Option Declare
Use "Files.LS2J"
Use "XWPFUtils"
 
%REM
*********************************************
	Class XWPFUtils
	Description: Comments for Class
%END REM
Class XWPFUtils As ErrorHandlerWJ
	Private XWPFClass As Javaclass
	Private XWPFobj As Javaobject
 
	Sub New()
		On Error GoTo ErrH
		Set me.XWPFClass=jSession.Getclass({XWPFUtils})
		Set me.XWPFobj=me.XWPFClass.Createobject()
Quit:
		Exit Sub
ErrH:
		Error Err, Me.RaiseError
	End Sub
	Sub Delete()
		If Not me.XWPFobj Is Nothing Then Delete me.XWPFobj 
	End Sub
	%REM
	*--------------------------------------------
	 Function FindAndReplace
	 Description: Comments for Function
	%END REM
	Function FindAndReplace(arrFrom, arrTo, fFrom As String, fTo As String)
		Dim routineName As String
		routineName="FindAndReplace"
		On Error GoTo ErrH
		'your code here
		Call me.XWPFobj.replaceFromArray(arrFrom, arrTo, fFrom, fTo)
		'FindAndReplace=
Quit:
		Exit Function
ErrH:
		Error Err, Chr(10) &{path From>}&fFrom &Chr(10) _
		&{path To>}&fFrom &Chr(10) _
		&RaiseError
		Resume Quit
	End Function
End Class
либа java
Java:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
 
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
//http://stackoverflow.com/questions/22268898/replacing-a-text-in-apache-poi-xwpf
public class XWPFUtils {
	public void replaceFromArray(String []arrFrom, String[] arrTo, String fnameIN, String fnameOUT) {
		XWPFDocument doc=null;
		FileInputStream fis=null;
		try {
			fis=new FileInputStream(fnameIN);
			doc = new XWPFDocument(OPCPackage.open(fis));
		} catch (InvalidFormatException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		for (XWPFParagraph p : doc.getParagraphs()) {
			List<XWPFRun> runs = p.getRuns();
			if (runs != null) {
				for (XWPFRun r : runs) {
					paragraphReplace(arrFrom,arrTo,r);
				}
			}
			for (XWPFTable tbl : doc.getTables()) {
				for (XWPFTableRow row : tbl.getRows()) {
					for (XWPFTableCell cell : row.getTableCells()) {
						for (XWPFParagraph p1 : cell.getParagraphs()) {
							for (XWPFRun r : p1.getRuns()) {
								paragraphReplace(arrFrom,arrTo,r);
							}
						}
					}
				}
			}
		}
		try {
			doc.write(new FileOutputStream(fnameOUT));
			System.out.println("Done");
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		finally {
			try {
				System.out.println("Closing input doc and stream...");
				if (doc!=null)doc.close();
				if (fis!= null)fis.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			/*				if (pkg!=null)
				try {
					pkg.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			 */			}
 
	}
	private void paragraphReplace( String[] arrFrom, String[] arrTo, XWPFRun r) {
		String text = r.getText(0);
		if (text != null) {
			for(int i=0; i<arrFrom.length;i++) {
				if (text.contains(arrFrom[i])) {
					text = text.replace(arrFrom[i], arrTo[i]);
					System.out.println("Replacement:"+arrFrom[i]+"|"+arrTo[i]);
					r.setText(text, 0);
				}
			}
		}
	}
}
[DOUBLEPOST=1463661737,1463661666][/DOUBLEPOST]работает быстрее в разы чем COM вордени
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
472
забыл сказать ;)...
у бриджа (LS2J) есть указанные особенности, но я вспомнил детали...
Dim v:v=Split({первый;второй},{;}) и передача в бридж v - все работать будет
а вот после v=ArrayAppend(v,{третий}) все испортится и бридж уже v не примет :)
а потому написал измененные классы и агент:
Код:
%REM
*********************************************
	Agent test\XWPF
	Created Feb 11, 2016 by Mikhail Cholokov/CRUINTERNET
	Description: Comments for Agent
%END REM
Option Public
Option Declare
Use "XWPFUtils.LS2J"
Sub Initialize
	On Error GoTo ErrH
	Dim xwpf As New XWPFUtils()
	Dim doc As NotesDocument, ses As New NotesSession
	Set doc=ses.Documentcontext
	Dim fname As String, fList List As String
	fname=DetachDocFiles(doc, "*.docx", fList)
	Dim arrFrom, arrTo, lst List As String
	arrFrom=Split({SPA_POSITION;SPA_WHOM},{;})
	arrTo=Split({Генерал;Кузнецов},{;})
	lst({SPA_POSITION})={Генерал}
	lst({SPA_WHOM})={Кузнецов}
	If Len(fname)<1 Then Error 1024, Chr(10)&{файл не выгружен из документа:} &doc.Universalid &Chr(10)
	Print {File:} fname
	'Call xwpf.FindAndReplace(Arrfrom, Arrto, fname, fname & {.docx})
	Call xwpf.FindAndReplaceList(lst, fname, fname & {.docx})
	Dim run As String
	run={cmd.exe /c ""}&fname &{.docx""}'for long path suppport - double quotes
	Print {Executing>} run
	Dim status As Integer:status=Shell(run)
Quit:
	Exit Sub
ErrH:
	Error Err, RaiseError
End Sub
агент поменял для демонстрации работы листом
Код:
%REM
*********************************************
	Library XPFUtils.LS2J
	Created Feb 11, 2016 by Mikhail Cholokov/CRUINTERNET
	Description: Comments for Library
%END REM
Option Public
Option Declare
Use "Files.LS2J"
Use "XWPFUtils"
 
%REM
*********************************************
	Class XWPFUtils
	Description: Comments for Class
%END REM
Class XWPFUtils As ErrorHandlerWJ
	Private XWPFClass As Javaclass
	Private XWPFobj As Javaobject
 
	Sub New()
		On Error GoTo ErrH
		Set me.XWPFClass=jSession.Getclass({XWPFUtils})
		Set me.XWPFobj=me.XWPFClass.Createobject()
Quit:
		Exit Sub
ErrH:
		Error Err, Me.RaiseError
	End Sub
	Sub Delete()
		If Not me.XWPFobj Is Nothing Then Delete me.XWPFobj
	End Sub
	%REM
	*--------------------------------------------
	 Function FindAndReplace
	 Description: Comments for Function
	%END REM
	Function FindAndReplace(arrFrom, arrTo, fFrom As String, fTo As String)As String
		Dim routineName As String
		routineName="FindAndReplace"
		On Error GoTo ErrH
		'your code here
		Print {Replacing From String arrays...}
		Call me.XWPFobj.replaceFromArray(arrFrom, arrTo, fFrom, fTo)
		FindAndReplace=fTo
Quit:
		Exit Function
ErrH:
		Error Err, Chr(10) &{path From>}&fFrom &Chr(10) _
		&{path To>}&fFrom &Chr(10) _
		&RaiseError
		Resume Quit
	End Function
	%REM
	*--------------------------------------------
		Function FindAndReplaceList
		Description: Comments for Function
	%END REM
	Function FindAndReplaceList(lst List As String, fFrom As String, fTo As String) As String
		Dim routineName As String
		routineName="FindAndReplaceList"
		On Error GoTo ErrH
		'your code here
		Print {Replacing From list...}
		'Dim arrFrom() As String, arrTo() As String':arrFrom=Split({},{}):arrTo=Split({},{})
		Dim cnt As Long
		Call me.XWPFobj.clear()
		ForAll m In lst
			Call me.XWPFobj.add(ListTag(m),CStr(m))
		End ForAll
'		Dim arrFrom, arrTo
'		arrFrom=me.XWPFobj.getStringArray(cnt)
'		arrTo=me.XWPFobj.getStringArray(cnt)
		'		cnt=0
		'		ForAll m In lst
'			arrFrom(cnt)=ListTag(m)
'			arrTo(cnt)=CStr(m)
'			cnt=cnt+1
'		End ForAll
'		FindAndReplaceList=FindAndReplace(arrFrom, arrTo, fFrom, fTo)
		Call me.XWPFobj.replaceFromArray(fFrom, fTo)
		FindAndReplaceList=fTo
Quit:
		Exit Function
ErrH:
		Error Err, Me.RaiseError
		Resume Quit
	End Function
End Class
здесь уже веселее и еще одна особенность бриджа - просто вывести, из java, return new String[cnt]; - получим малопонятную ошибку Java Object ... null
мало того - вывод null из java, в таком варианте (когда ждет String[]) - появлялся nsd, а потому
Java:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
 
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
//http://stackoverflow.com/questions/22268898/replacing-a-text-in-apache-poi-xwpf
public class XWPFUtils {
	public static final HashMap<String,String> map=new HashMap<String, String>();
	public String[] getStringArray(int cnt) {
		List<String> arr= new ArrayList<String>(Collections.nCopies(cnt, ""));
		return (String[]) arr.toArray(new String[] {});
//		return new String[cnt];
	}
	public static void clear() {
		map.clear();
	}
	public static String add(String key, String value) {
		return map.put(key,value);
	}
	public void replaceFromArray(String fnameIN, String fnameOUT) {
		XWPFDocument doc=null;
		FileInputStream fis=null;
		try {
			fis=new FileInputStream(fnameIN);
			doc = new XWPFDocument(OPCPackage.open(fis));
		} catch (InvalidFormatException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		for (XWPFParagraph p : doc.getParagraphs()) {
			List<XWPFRun> runs = p.getRuns();
			if (runs != null) {
				for (XWPFRun r : runs) {
					paragraphReplace(r);
				}
			}
			for (XWPFTable tbl : doc.getTables()) {
				for (XWPFTableRow row : tbl.getRows()) {
					for (XWPFTableCell cell : row.getTableCells()) {
						for (XWPFParagraph p1 : cell.getParagraphs()) {
							for (XWPFRun r : p1.getRuns()) {
								paragraphReplace(r);
							}
						}
					}
				}
			}
		}
		try {
			doc.write(new FileOutputStream(fnameOUT));
			System.out.println("Done");
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		finally {
			try {
				System.out.println("Closing input doc and stream...");
				if (doc!=null)doc.close();
				if (fis!= null)fis.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public void replaceFromArray(String []arrFrom, String[] arrTo, String fnameIN, String fnameOUT) {
		XWPFDocument doc=null;
		FileInputStream fis=null;
		try {
			fis=new FileInputStream(fnameIN);
			doc = new XWPFDocument(OPCPackage.open(fis));
		} catch (InvalidFormatException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		for (XWPFParagraph p : doc.getParagraphs()) {
			List<XWPFRun> runs = p.getRuns();
			if (runs != null) {
				for (XWPFRun r : runs) {
					paragraphReplace(arrFrom,arrTo,r);
				}
			}
			for (XWPFTable tbl : doc.getTables()) {
				for (XWPFTableRow row : tbl.getRows()) {
					for (XWPFTableCell cell : row.getTableCells()) {
						for (XWPFParagraph p1 : cell.getParagraphs()) {
							for (XWPFRun r : p1.getRuns()) {
								paragraphReplace(arrFrom,arrTo,r);
							}
						}
					}
				}
			}
		}
		try {
			doc.write(new FileOutputStream(fnameOUT));
			System.out.println("Done");
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		finally {
			try {
				System.out.println("Closing input doc and stream...");
				if (doc!=null)doc.close();
				if (fis!= null)fis.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			/*				if (pkg!=null)
				try {
					pkg.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			 */			}
 
	}
	private void paragraphReplace( String[] arrFrom, String[] arrTo, XWPFRun r) {
		String text = r.getText(0);
		if (text != null) {
			for(int i=0; i<arrFrom.length;i++) {
				if (arrFrom[i].length()>0) {
					if (text.contains(arrFrom[i])) {
						text = text.replace(arrFrom[i], arrTo[i]);
						System.out.println("Replacement:"+arrFrom[i]+"|"+arrTo[i]);
						r.setText(text, 0);
					}
				}
			}
		}
	}
	private void paragraphReplace(XWPFRun r) {
		String text = r.getText(0);
		if (text != null) {
			for(Map.Entry<String, String> entry : map.entrySet()) {
				if(entry.getKey().length()>0) {
					if (text.contains(entry.getKey())) {
						text=text.replace(entry.getKey(), entry.getValue());
						System.out.println("Replacement:"+entry.getKey()+"|"+entry.getValue());
						r.setText(text, 0);
					}
				}
			}
		}
	}
}
и мы чисты перез нотусевым законом, костыль в виде возаврата массива пустых стрингов оставил как демонстрацию обхода LS2J факапов
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
472
дополню еще конвертацией а ПДФ...

из того что я сам юзал (в порядке моего предпочтения):
-jodconverter - работает вместе с libre/openoffice. Для современной либры запуск
Bash:
libreoffice --headless --accept="socket,host=127.0.0.1,port=8100;urp;" --nofirststartwizard
запускал в убунте - результат лучше (практически идеальный), если сравнивать c xdocreport
-xdocreport - описывал траблы
-МСО - все как и по ссылке (100% , что не удивительно - java там как рапер), но, например, на сервере ябы такой вариант не запустил

и да - МСО понимает и работает с ODT (сохранял в либре, открывал в ворде - форматирование полностью совпало) хотя будет невнятно пугать юзера ;)
 
Последнее редактирование:
A

anna

В общем, сделала криво, зато без ворда - форма простая через диалог бокс, оттуда все равно печатать будут на принтер, так вот заодно с печатью экспортирую в ворд и получившийся файл аттачу к заявке.
В конце все равно бумагу подписывают руками, сканируют и вкладывают. Все равно бумагооборот же.
 
Последнее редактирование модератором:

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
472
Вы как-то имеете склонность все усложнять
я, честно, не вижу ничего сложного, все выложенное делалось в течении часа (каждое решение). Просто интересны варианты
Дольше занимает поиск/анализ/обдумывание, но для вас, в предложенных вариантах, этого не нужно
В либах и агентах - там кода-то на раз ...

В цикле разработки не должно быть понятия - быстро нафигачить, ибо дальше начинаются мучительные поиски и исправления.
Вот вопросы генерации, отображения, замены текста, конвертация - они "вечны" и закрыть, хотя бы частично эту тему, я лично, предпочитаю максимально возможно (исходя из ресурсов).

Вашей "девочке" возможно надоест - потому как "удобный" инструмент ей не предоставили.

Все выше сказанное можно счесть занудством...

Возможно, предлагая, в своей организации, или осуществляя похотелки - можно раскрыть задачу и заинтересовать сотрудников. Тогда всем будет профит и работы на корзину станет меньше
В конце все равно бумагу подписывают руками, сканируют и вкладывают. Все равно бумагооборот же.
зависит от вида бумаг, если внутренние - нет технических проблем от них отказаться
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!