Цитата |
---|
Александр Солошенко написал: Я по крайней мере логики не нашел. |
1. в document.xml по r:id определяем ID = rId4
2. в файле: document.xml.rels находим блок: Id=rId4
3. заменяем в нем Target="link.html"
31.08.2020 18:58:40
1. в document.xml по r:id определяем ID = rId4 2. в файле: document.xml.rels находим блок: Id=rId4 3. заменяем в нем Target="link.html" |
|||
|
|
01.09.2020 09:48:10
Александр Солошенко,задача решаемая. Эту же задачу я решил при вставке в документ картинок / списков. Там такая же логика.
Делается это следующим образом (по крайней мере у меня) 1. Проходите по document.xml, где делаете все замены. При этом вам надо для каждой ссылки генерировать уникальынй rId (посмотрите \Bitrix\DocumentGenerator\Body\Xml::getRandomId()). Вставляйте тег с вашим идентификатором. Сохраните в отдельной переменной соотношение идентификатор => ссылка 2. Пройдитесь по вашему массиву идентификаторов и вставьте что надо в rels Ну и помните, что кроме document.xml там могут быть и другие .xml файлы, которые, по-хорошему, надо также обработать. |
|
|
|
02.09.2020 17:14:10
Антон, я изучил docx архив с картинками. Задача с гиперссылками действительно точно такая же. А где найти код php, которым вы редактируете document.xml и document.rels.xml?
|
|
|
|
02.09.2020 17:32:29
Я кажись понял, /home/bitrix/www/bitrix/modules/documentgenerator/lib/body/d
|
|
|
|
02.09.2020 18:18:53
|
|||
|
|
02.09.2020 18:27:30
Антон Горбылев,
у меня стоит следующая задача: необходимо подменять изображение подписи на КП в зависимости от ответственного за сделку. Думаю, сценарий будет следующим: 1. в карточке сотрудника загрузим файлы с росписями. 2. на событии: onBeforeProcessDocument достаю файл соответствующего изображения. 3. подменяю роспись на КП изображением полученным на шаге №2. ACHTUNG!!! вопрос: как сделать 3-й шаг: подменяю роспись на КП изображением полученным на шаге №2. |
|
|
|
03.09.2020 09:55:07
Ахат Баязи,передайте в качестве значения путь к файлу или ид из b_file.
|
|
|
|
03.09.2020 17:56:31
1. $rootActivity = $this->GetRootActivity(); 2. Открываю как zip-архив сгенерированный файл docx, который сформировался на основе ранее подгруженного шаблона и данных передаваемых из бп. 3. Открываю документ xml и делаю замены так, как Вы указали в пункте 1. 4. Вставляю то, что нужно в rels 5. Сохраняю архив и выдаю пользователю новый файл. Верно? В пункте 3 что нужно использовать? Если можно, то более детально расписать блоки кода (логику). Я не очень силен в php, но хорошо знаю английский и есть большое желание подтянуть php. |
|||
|
|
04.09.2020 10:03:21
Александр Солошенко, в общих чертах верно. По поводу работы с БП не подскажу, посмотрите существующие действия (папка activities).
|
|
|
|
05.09.2020 18:29:32
Антон, добрый день, я взял файл docxxml.php
И сделал свои правки, основываясь на коде, который используется для вставок картинок. Получилось что-то такое: /** * Get all hyperlink nodes marked with placeholders. * If $generateNewhyperlinkIds is true - will replace relation ids to new values. * * @param bool $generateNewHyperlinkIds * @param \DOMNode|null $contextNode * @return array */ public function findHyperlinks(bool $generateNewHyperlinkIds = false, \DOMNode $contextNode = null): array { $this->initDomDocument(); if($contextNode) $HyperlinkDescriptions = $this->xpath->query('//w:hyperlink w:history', $contextNode); } else { $HyperlinkDescriptions = $this->xpath->query('//w:hyperlink w:history'); } $placeholders = []; foreach($HyperlinkDescriptions as $description) { /** @var \DOMElement $description */ if($description->hasAttributes()) { $name = $description->attributes->getNamedItem('name'); $descr = $description->attributes->getNamedItem('descr'); $placeholder = null; if($descr) { $placeholder = static::getCodeFromPlaceholder($descr->nodeValue); } if(!$placeholder && $name) { $placeholder = static::getCodeFromPlaceholder($name->nodeValue); } if($placeholder) { if(!isset($placeholders[$placeholder])) { $placeholders[$placeholder] = [ 'hyperlinkNode' => [], 'innerIDs' => [], ]; } $placeholders[$placeholder]['hyperlinkNode'][] = $description->parentNode->parentNode; $embeds = $description->parentNode->getElementsByTagNameNS(static::getNamespaces()['a'], 'blip'); if($embeds->length > 0) { /** @var \DOMAttr $innerHyperlinkId */ $hyperlinkId = $innerhyperlinkId->value; if($generateNewHyperlinkIds && !isset($this->arrayHyperlinkValues['originalId'][$hyperlinkId])) { $newHyperlinkId = static::getRandomId('rId', true); $placeholders[$placeholder]['originalId'][$newHyperlinkId] = $HyperlinkId; $HyperlinkId = $innerHyperlinkId->value = $newHyperlinkId; } if(!in_array($HyperlinkId, $placeholders[$placeholder]['innerIDs'])) { $placeholders[$placeholder]['innerIDs'][] = $hyperlinkId; if(isset($this->arrayHyperlinkValues['values'][$hyperlinkId])) { $placeholders[$placeholder]['values'][$hyperlinkId] = $this->arrayHyperlinkValues['values'][$hyperlinkId]; $placeholders[$placeholder]['originalId'][$hyperlinkId] = $this->arrayHyperlinkValues['originalId'][$hyperlinkId]; } } } } } }
if(!empty($placeholders) && $generateNewHyperlinkIds) { $this->saveContent(); }
return $placeholders; }
/** * @param mixed $value * @param string $placeholder * @param string $modifier * @param array $params * @return string */ protected function printValue($value, $placeholder, $modifier = '', array $params = []): string { $value = parent::printValue($value, $placeholder, $modifier); if(empty($value)) { return (string) $value; } if (ToUpper(SITE_CHARSET) !== 'UTF-8') { if(is_array($value) || is_object($value)) { $value = ''; } elseif(!Encoding::detectUtf8($value)) { $value = Encoding::convertEncoding($value, SITE_CHARSET, 'UTF-8'); } } if(is_string($value)) { if($this->isHyperlinkValue($placeholder, $this->values)) { return ''; }
if($this->isHtml($value)) { $context = []; if(isset($params['currentNode']) && $params['currentNode'] instanceof \DOMElement) { $context['rowProperties'] = $this->getRowPropertyNodeValue($params['currentNode']); } $value = $this->htmlToXml($value, $context); } else { $value = $this->prepareTextValue($value); } }
return $value; }
/** * @param string $placeholder * @param array $values * @param array|null $fields * @return bool */ protected function isHyperlinkValue(string $placeholder, array $values, array $fields = null): bool { if(!$fields) { $fields = $this->fields; }
return ( array_key_exists($placeholder, $values) && isset($fields[$placeholder]['TYPE']) && ( $fields[$placeholder]['TYPE'] === DataProvider::FIELD_TYPE_HYPERLINK || $fields[$placeholder]['TYPE'] === DataProvider::FIELD_TYPE_STAMP ) ); }
/** * @return string */ protected function getBreakLineTag(): string { return '</w:t><w:br/><w:t>'; }
/** * @param $string * @return bool */ protected function isHtml($string): bool { return (preg_match('/<\s?[^\>]*\/?\s?>/i', $string) != false); }
/** * Converts html to xml with the same rendering. * * @param string $html * @param array $context * @return string */ protected function htmlToXml(string $html, array $context = []): string { $htmlDocument = new DOM\Document(); $htmlDocument->loadHTML($html); $result = $this->htmlNodeToXml($htmlDocument, $context); if(!empty($result)) { $result = '</w:t></w:r>'.$result.'<w:r><w:t>'; } return $result; }
/** * @param DOM\Node $node * @param array $properties * @return DOM\DisplayProperties */ protected function getDisplayProperties(DOM\Node $node, array $properties = []): DOM\DisplayProperties { return new DOM\DisplayProperties($node, $properties); }
/** * Recursively converts html node to xml. * * @param DOM\Node $node * @param array $context * @return string */ protected function htmlNodeToXml(DOM\Node $node, array &$context = []): string { $result = '';
$this->deleteLastBreakLineInBlockTag($node); $displayProperties = $this->getDisplayProperties($node);
if($displayProperties->isHidden()) { return $result; } $nodes = $node->getChildNodes(); $nodeName = mb_strtolower($node->getNodeName()); if($nodeName === 'ul') { $context['currentList'] = [ 'type' => static::NUMBERING_TYPE_UNORDERED, 'id' => $this->getRandomId('numberingValue', false), ]; } elseif($nodeName === 'ol') { $context['currentList'] = [ 'type' => static::NUMBERING_TYPE_ORDERED, 'id' => $this->getRandomId('numberingValue', false), ]; } elseif($nodeName === 'li') { $context['showNumber'] = true; }
if($displayProperties->isDisplayBlock()) { $context['display'] = DOM\DisplayProperties::DISPLAY_BLOCK; } if(!isset($context['font']) || !is_array($context['font'])) { $context['font'] = []; } $context['font'] = array_merge($context['font'], $displayProperties->getProperties()['font']); // The trick is in order we get tags. We have to carry $context all along. // First we have 'b' tag and then we have #text tag. But they are on the same level of hierarchy. // So we have to put 'bold font' into context and we need to know about it in the next tag. /** @var DOM\Node $childNode */ foreach($nodes as $childNode) { $nodeValue = str_replace("\n", '', $childNode->getNodeValue()); if($context['display'] === DOM\DisplayProperties::DISPLAY_BLOCK || $displayProperties->isDisplayBlock()) { $nodeValue = trim($nodeValue); } $childNodeName = mb_strtolower($childNode->getNodeName()); if($childNodeName === 'br') { $result .= '<w:r>'; $result .= '<w:br/>'; $result .= '</w:r>'; } elseif($childNode instanceof DOM\Text && !empty($nodeValue)) { if(isset($context['showNumber']) && isset($context['currentList'])) { $result .= '</w:p>'; $result .= '<w:p>'; $this->numberingIds[$context['currentList']['id']] = $context['currentList']; $result .= '<w:pPr>'; $result .= '<w:numPr>'; $result .= '<w:ilvl w:val="0" />'; $result .= '<w:numId w:val="'.$context['currentList']['id'].'" />'; $result .= '</w:numPr>'; $result .= '</w:pPr>'; unset($context['showNumber']); $context['display'] = $displayProperties->getProperties()[DOM\DisplayProperties::DISPLAY]; $result .= '<w:r>'; } elseif($context['display'] === DOM\DisplayProperties::DISPLAY_BLOCK) { $result .= '<w:r>'; $result .= '<w:br/>'; $context['display'] = $displayProperties->getProperties()[DOM\DisplayProperties::DISPLAY]; } else { $result .= '<w:r>'; } $result .= $this->addRowPropertiesTag($context); $result .= '<w:t xml:space="preserve">'; $result .= $this->prepareTextValue($nodeValue); $result .= '</w:t>'; $result .= '</w:r>'; } else { $result .= $this->htmlNodeToXml($childNode, $context); } }
if($nodeName === 'ul' || $nodeName === 'ol') { unset($context['currentList']); $result .= '</w:p>'; $result .= '<w:p>'; } elseif($nodeName === 'li') { unset($context['showNumber']); } $context['font'] = array_diff_assoc($context['font'], $displayProperties->getProperties()['font']);
return $result; } Все ли изменения я сделал и что еще нужно поменять в самом docxxml.php?И какой файл должен отвечать за передачу гиперссылок? БП мне генерирует ссылки вида: [url= |
|
|
|
15.09.2020 18:05:57
Александр Чувилёв, список поддерживаемых шрифтов небольшой. Какой-то дефолтный список, что входит в CentOS, подробнее не скажу.
Попробуйте поставить галочку "сохранить шрифт в файле" в шаблоне документа - возможно, поможет (а может и нет). |
|
|
|
11.10.2020 23:36:57
1. создаем пользовательское поле у сотрудника (тип файл): UF_CRM_SIGN 2. загрузить в него файл росписи сотрудника 3. в шаблоне использовать штатную картинку росписи директора 3. В событии onBeforeProcessDocument делаем подмену росписи директора росписью сотрудника.
|
|||||
|
|
12.12.2020 21:43:39
Антон Горбылев,
Пощупал своими руками, что такое создать документ docx без генератора документов. Действовал по следующему алгоритму: 1. Установил композер 2. Установил библиотеку phpword 3. Подключил файл autoload.php, который расположен в папке vendor, в файле init.php 4. Собственно по подготовленному раннее шаблону разобрал документ на текст и переменные и данные зависимые от переменных 5. В генераторе бизнес-процессов запустил действие "Произвольный PHP-код". Получил текущий бизнес-процесс. 6. Преобразовал данные и записал их в документ docx, после чего сохранил на сервере. Итого вышло 220 строк кода на 1-1,5 страницы документа в зависимости от количества данных(гиперссылок и строк, генерируемых в генераторе бп) и примерно 15 часов работы. Я не профессиональный разработчик php, поэтому код скорее всего можно оптимизировать, но сэкономить получится не более 30-40 строк кода. Вывод: генератор документов мощнейший инструмент, который позволяет создавать документы со скоростью в 10-15 раз быстрее, чем писать ручками, притом еще нужно понимать, что писать. Поэтому автору большой респект. ![]() И все-таки функционал гиперссылок очень нужен, хочется верить, что в ближайших релизах появится, а пока пользуемся PHPWord кому нужны, как и мне, гиперссылки из генератора бизнес-процессов. |
|
|
|
21.12.2020 14:54:08
Антон Горбылев, очень нужна помощь
Делаю по мануалу Если проделываю пример со строкой - работает Если добавляю массив вида
Хочу получить селектор в форме редактирования документа(генерирую договор в сделке) Здесь
Задача вообще такая, в сделку добавлены мультиполя(сайт, телефон, почта) как в лиде. И сайт нужно вывести в генерации документа. |
|||||||
|
|
22.12.2020 10:32:15
Евгений Ющенков,кидайте код вашего провайдера, посмотрю
|
|
|
|
22.12.2020 11:15:20
Антон Горбылев,
Код провайдера сделки.
Вопрос задавал по полю "WEB_SITES" Из метода getWebSites возвращается массив, но в поля редактирования документа это значение не попадает. Ну это я описывал в сообщении выше |
|||||
|
|
26.01.2021 14:14:07
Антон Горбылев,
встала такая задача: при генерации документа, чтоб файл имел определенную определенное название, составленное из переменных. Типа:
1) ловить на событии генерации файла, 2) получить ID сгенерированного файла 3) изменять название файла ? |
|||
|
|
28.01.2021 09:55:55
Ахат Баязи, название файла = переменная {DocumentTitle} + .docx
Пишите в это поле что надо, файл так и будет называться. Если в теле документа должен быть другой заголовок, то добавьте какое-нибудь другое поле для него |
|
|
|
28.01.2021 18:39:42
ни в настройках генератора ни в БП е нашел. ))) может не там смотрю? |
|||
|
|
02.02.2021 09:57:47
Ахат Баязи,у каждого документа есть поле {DocumentTitle}. Это поле есть, даже если оно не вставлено в шаблон.
Значение этого поля = название файла. Запишите в это поле что хотите и получите такое же название файла. Можете записать через БП, можете через рест, как угодно |
|
|
|
08.02.2021 19:21:22
|
|||
|
|
28.04.2021 08:51:55
Антон Горбылев,
Поступила новая задача: Необходимо разработать генератор оф. писем. При этом по условиям ТЗ, пользователь может вставлять (копипастить) в тело письма такие объекты, как: 1. таблицы 2. картинки. ================================== ПРЕДВАРИТЕЛЬНОЕ РЕШЕНИЕ: ----------------------------------------------------------- хочу реализовать на основе штатного генератора документа, но столкнулся с рядом проблем: Реализация: 1. на универсальных списках создаем поле (тип: HTML/текст) которое позволяет хранить и копипастить в него содержимое офисного документа. 2. при генерации достаем это поле и подставляем в шаблон генератора. Проблема: т.к. поле содержит HTML-верстку, то генератор ломает таблицу. все поля таблицы отображаются в виде строк. Есть способы преобразовать HTML-таблицу, чтобы можно было подсунуть в генератор документов? |
|
|
|